@scriptc/compiler 0.1.1 → 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 (209) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +45 -17
  2. package/ambient/scriptc.d.ts +2 -1
  3. package/dist/backend/build-cache.js.map +1 -1
  4. package/dist/backend/c/async.js +24 -2
  5. package/dist/backend/c/async.js.map +1 -1
  6. package/dist/backend/c/c-emitter.d.ts +22 -10
  7. package/dist/backend/c/c-emitter.js +60 -15
  8. package/dist/backend/c/c-emitter.js.map +1 -1
  9. package/dist/backend/c/exprs.js +220 -10
  10. package/dist/backend/c/exprs.js.map +1 -1
  11. package/dist/backend/c/island.js.map +1 -1
  12. package/dist/backend/c/may-throw.js.map +1 -1
  13. package/dist/backend/c/stmts.d.ts +3 -5
  14. package/dist/backend/c/stmts.js +58 -17
  15. package/dist/backend/c/stmts.js.map +1 -1
  16. package/dist/backend/c/types.js.map +1 -1
  17. package/dist/backend/c/walkers.js +31 -5
  18. package/dist/backend/c/walkers.js.map +1 -1
  19. package/dist/backend/external-c.js.map +1 -1
  20. package/dist/backend/linker.js.map +1 -1
  21. package/dist/backend/llvm/blocks.js.map +1 -1
  22. package/dist/backend/llvm/dyn.js +56 -3
  23. package/dist/backend/llvm/dyn.js.map +1 -1
  24. package/dist/backend/llvm/emitter.js +98 -19
  25. package/dist/backend/llvm/emitter.js.map +1 -1
  26. package/dist/backend/llvm/expr-async.js +85 -3
  27. package/dist/backend/llvm/expr-async.js.map +1 -1
  28. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  29. package/dist/backend/llvm/expr-calls.js.map +1 -1
  30. package/dist/backend/llvm/expr-containers.js.map +1 -1
  31. package/dist/backend/llvm/expr-context.d.ts +1 -0
  32. package/dist/backend/llvm/expr-control.js.map +1 -1
  33. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  34. package/dist/backend/llvm/expr-dynamic.js +6 -3
  35. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  36. package/dist/backend/llvm/expr-island.js.map +1 -1
  37. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  38. package/dist/backend/llvm/expr-records.js.map +1 -1
  39. package/dist/backend/llvm/expr-stream-bridges.js +220 -9
  40. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  41. package/dist/backend/llvm/lib-dispatch.js +2 -0
  42. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  43. package/dist/backend/llvm/lib-shared.js +18 -0
  44. package/dist/backend/llvm/lib-shared.js.map +1 -1
  45. package/dist/backend/llvm/lib-stream.js +6 -3
  46. package/dist/backend/llvm/lib-stream.js.map +1 -1
  47. package/dist/backend/llvm/split.js.map +1 -1
  48. package/dist/backend/llvm/unsupported.js.map +1 -1
  49. package/dist/backend/llvm/walkers.js.map +1 -1
  50. package/dist/backend/native-codegen.js.map +1 -1
  51. package/dist/backend/native-codegen.test.js +2 -0
  52. package/dist/backend/native-codegen.test.js.map +1 -1
  53. package/dist/backend/native-toolchain.js.map +1 -1
  54. package/dist/backend/native-toolchain.test.js.map +1 -1
  55. package/dist/backend/runtime-pack.js.map +1 -1
  56. package/dist/backend/targets.js.map +1 -1
  57. package/dist/backend/vendor-archives.js.map +1 -1
  58. package/dist/compat/fetch-profile.js +4 -2
  59. package/dist/compat/fetch-profile.js.map +1 -1
  60. package/dist/coverage/report.js.map +1 -1
  61. package/dist/coverage/surface-manifest.js +12 -0
  62. package/dist/coverage/surface-manifest.js.map +1 -1
  63. package/dist/diagnostics/diagnostic.d.ts +6 -5
  64. package/dist/diagnostics/diagnostic.js +7 -6
  65. package/dist/diagnostics/diagnostic.js.map +1 -1
  66. package/dist/diagnostics/render.js.map +1 -1
  67. package/dist/ffi/ffi-manifest.js.map +1 -1
  68. package/dist/frontend/cjs-lexer.js.map +1 -1
  69. package/dist/frontend/dts-paths.js.map +1 -1
  70. package/dist/frontend/input-tracker.d.ts +10 -0
  71. package/dist/frontend/input-tracker.js +26 -3
  72. package/dist/frontend/input-tracker.js.map +1 -1
  73. package/dist/frontend/input-tracker.test.js +8 -1
  74. package/dist/frontend/input-tracker.test.js.map +1 -1
  75. package/dist/frontend/lib-contract.js.map +1 -1
  76. package/dist/frontend/lowering/array-values.d.ts +4 -0
  77. package/dist/frontend/lowering/array-values.js +18 -2
  78. package/dist/frontend/lowering/array-values.js.map +1 -1
  79. package/dist/frontend/lowering/callback-arg.js.map +1 -1
  80. package/dist/frontend/lowering/containers/bytes.d.ts +35 -0
  81. package/dist/frontend/lowering/containers/bytes.js +1021 -0
  82. package/dist/frontend/lowering/containers/bytes.js.map +1 -0
  83. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +17 -0
  84. package/dist/frontend/lowering/containers/string-and-regexp.js +345 -0
  85. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -0
  86. package/dist/frontend/lowering/expressions/evaluation-safety.d.ts +13 -0
  87. package/dist/frontend/lowering/expressions/evaluation-safety.js +76 -0
  88. package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -0
  89. package/dist/frontend/lowering/expressions/object-literals.d.ts +44 -0
  90. package/dist/frontend/lowering/expressions/object-literals.js +1815 -0
  91. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -0
  92. package/dist/frontend/lowering/expressions/optional-chains.d.ts +31 -0
  93. package/dist/frontend/lowering/expressions/optional-chains.js +382 -0
  94. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -0
  95. package/dist/frontend/lowering/expressions/try-lower-expression.d.ts +7 -0
  96. package/dist/frontend/lowering/expressions/try-lower-expression.js +26 -0
  97. package/dist/frontend/lowering/expressions/try-lower-expression.js.map +1 -0
  98. package/dist/frontend/lowering/lib-boundary.js +7 -2
  99. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  100. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  101. package/dist/frontend/lowering/lower-async-iteration.d.ts +7 -0
  102. package/dist/frontend/lowering/lower-async-iteration.js +657 -0
  103. package/dist/frontend/lowering/lower-async-iteration.js.map +1 -0
  104. package/dist/frontend/lowering/lower-builtins.d.ts +18 -0
  105. package/dist/frontend/lowering/lower-builtins.js +219 -48
  106. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  107. package/dist/frontend/lowering/lower-calls.d.ts +13 -4
  108. package/dist/frontend/lowering/lower-calls.js +407 -64
  109. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  110. package/dist/frontend/lowering/lower-classes.d.ts +7 -13
  111. package/dist/frontend/lowering/lower-classes.js +128 -35
  112. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  113. package/dist/frontend/lowering/lower-comptime.js.map +1 -1
  114. package/dist/frontend/lowering/lower-containers.d.ts +1 -47
  115. package/dist/frontend/lowering/lower-containers.js +135 -1467
  116. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  117. package/dist/frontend/lowering/lower-event-emitter.js +51 -5
  118. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  119. package/dist/frontend/lowering/lower-exprs.d.ts +0 -78
  120. package/dist/frontend/lowering/lower-exprs.js +402 -2351
  121. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  122. package/dist/frontend/lowering/lower-generators.d.ts +18 -3
  123. package/dist/frontend/lowering/lower-generators.js +145 -9
  124. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  125. package/dist/frontend/lowering/lower-inspect.js +3 -3
  126. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  127. package/dist/frontend/lowering/lower-island.d.ts +6 -0
  128. package/dist/frontend/lowering/lower-island.js +37 -5
  129. package/dist/frontend/lowering/lower-island.js.map +1 -1
  130. package/dist/frontend/lowering/lower-modules.js +52 -15
  131. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  132. package/dist/frontend/lowering/lower-node-module.d.ts +18 -0
  133. package/dist/frontend/lowering/lower-node-module.js +302 -0
  134. package/dist/frontend/lowering/lower-node-module.js.map +1 -0
  135. package/dist/frontend/lowering/lower-server.js +1 -1
  136. package/dist/frontend/lowering/lower-server.js.map +1 -1
  137. package/dist/frontend/lowering/lower-stmts.d.ts +5 -24
  138. package/dist/frontend/lowering/lower-stmts.js +519 -253
  139. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  140. package/dist/frontend/lowering/lower-stream.js +4 -4
  141. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  142. package/dist/frontend/lowering/lowerer.d.ts +37 -34
  143. package/dist/frontend/lowering/lowerer.js +143 -57
  144. package/dist/frontend/lowering/lowerer.js.map +1 -1
  145. package/dist/frontend/lowering/optional-arguments.d.ts +9 -0
  146. package/dist/frontend/lowering/optional-arguments.js +57 -0
  147. package/dist/frontend/lowering/optional-arguments.js.map +1 -0
  148. package/dist/frontend/npm-static-declarations.d.ts +35 -0
  149. package/dist/frontend/npm-static-declarations.js +506 -0
  150. package/dist/frontend/npm-static-declarations.js.map +1 -0
  151. package/dist/frontend/npm-static-declarations.test.d.ts +1 -0
  152. package/dist/frontend/npm-static-declarations.test.js +122 -0
  153. package/dist/frontend/npm-static-declarations.test.js.map +1 -0
  154. package/dist/frontend/npm-static.d.ts +2 -0
  155. package/dist/frontend/npm-static.js +26 -10
  156. package/dist/frontend/npm-static.js.map +1 -1
  157. package/dist/frontend/npm.d.ts +60 -0
  158. package/dist/frontend/npm.js +38 -4
  159. package/dist/frontend/npm.js.map +1 -1
  160. package/dist/frontend/npm.test.d.ts +1 -0
  161. package/dist/frontend/npm.test.js +50 -0
  162. package/dist/frontend/npm.test.js.map +1 -0
  163. package/dist/frontend/program.d.ts +3 -2
  164. package/dist/frontend/program.js +495 -68
  165. package/dist/frontend/program.js.map +1 -1
  166. package/dist/frontend/resolve.d.ts +10 -2
  167. package/dist/frontend/resolve.js +90 -14
  168. package/dist/frontend/resolve.js.map +1 -1
  169. package/dist/frontend/resolve.test.js +4 -1
  170. package/dist/frontend/resolve.test.js.map +1 -1
  171. package/dist/frontend/runtime-resolve.d.ts +22 -0
  172. package/dist/frontend/runtime-resolve.js +101 -0
  173. package/dist/frontend/runtime-resolve.js.map +1 -0
  174. package/dist/frontend/runtime-resolve.test.d.ts +1 -0
  175. package/dist/frontend/runtime-resolve.test.js +18 -0
  176. package/dist/frontend/runtime-resolve.test.js.map +1 -0
  177. package/dist/frontend/ts7/adapter.js +12 -13
  178. package/dist/frontend/ts7/adapter.js.map +1 -1
  179. package/dist/frontend/ts7/ast.js.map +1 -1
  180. package/dist/frontend/ts7/census-check.js +3 -3
  181. package/dist/frontend/ts7/census-check.js.map +1 -1
  182. package/dist/frontend/ts7/checker.d.ts +3 -0
  183. package/dist/frontend/ts7/checker.js +12 -3
  184. package/dist/frontend/ts7/checker.js.map +1 -1
  185. package/dist/frontend/ts7/enums.js.map +1 -1
  186. package/dist/frontend/ts7/program-adapter.js +42 -3
  187. package/dist/frontend/ts7/program-adapter.js.map +1 -1
  188. package/dist/frontend/type-mapper.js +120 -7
  189. package/dist/frontend/type-mapper.js.map +1 -1
  190. package/dist/index.js +5 -5
  191. package/dist/index.js.map +1 -1
  192. package/dist/ir/analysis.js.map +1 -1
  193. package/dist/ir/ir.d.ts +60 -30
  194. package/dist/ir/ir.js +57 -6
  195. package/dist/ir/ir.js.map +1 -1
  196. package/dist/ir/validate.js +44 -59
  197. package/dist/ir/validate.js.map +1 -1
  198. package/dist/library/cache-primitives.js.map +1 -1
  199. package/dist/library/cache-reset.test.js.map +1 -1
  200. package/dist/library/compiler-self-identity.js.map +1 -1
  201. package/dist/library/int-infer.js.map +1 -1
  202. package/dist/library/int-infer.test.js.map +1 -1
  203. package/dist/library/library-profile.js.map +1 -1
  204. package/dist/library/sidecar-validate.js.map +1 -1
  205. package/dist/library/sidecar.js.map +1 -1
  206. package/dist/startup-cache.js.map +1 -1
  207. package/dist/wasi-paths.js.map +1 -1
  208. package/package.json +18 -18
  209. package/surface-manifest.json +60 -4
@@ -1,11 +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
7
  import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
8
- import { lowerForOfGenerator, lowerYieldStarStatement } from "./lower-generators.js";
8
+ import { lowerForAwaitGenerator, lowerForOfGenerator, lowerYieldStarStatement } from "./lower-generators.js";
9
+ import { lowerForAwaitBuiltin } from "./lower-async-iteration.js";
9
10
  import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/ir.js";
10
11
  import { PoisonError, boundIdentifiersOf, dynFallbackType, dynUndefinedExpr, importCallHandleType, neverTaintedJsType, stmtUsesIsland, uncheckedOverloadHandleCall } from "./lowerer.js";
11
12
  import { enforceLibBoundary } from "./lib-boundary.js";
@@ -15,20 +16,262 @@ import { isProvenanceSourceFile } from "../provenance-registry.js";
15
16
  import { ambientUndefVarRootOf, lowerImportEquals, nsUndefRead, nsWritableTarget, trapDeclRootOf } from "./lower-namespaces.js";
16
17
  import { expandoWritableTarget, lowerExpandoAssignStmt } from "./lower-expando.js";
17
18
  import { lowerForOfArrayIter, lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, lowerSafeIndexRead, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
18
- import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, nullishExprUnitOf, nullishGenericBindingUnitOf, recordKeysArrayCall } from "./lower-calls.js";
19
+ import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, implicitMethodCallInfersReturn, nullishExprUnitOf, nullishGenericBindingUnitOf, recordKeysArrayCall } from "./lower-calls.js";
19
20
  import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
20
21
  import { genericIfaceBindingKeepsClass } from "./lower-classes.js";
21
- import { lowerStreamUnderscoreAssign, streamClassAliasDecl, streamSidesOf } from "./lower-stream.js";
22
+ import { lowerStreamUnderscoreAssign, streamClassAliasDecl } from "./lower-stream.js";
22
23
  import { lowerHttpResPropertyAssignment, lowerHttpServerTimeoutAssignment, lowerServerCloseOverrideAssignment } from "./lower-server.js";
23
24
  import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, createRequireBindingDecl, createRequireCalleeFileOf, createRequireNamespaceDecl, textCodecBindingDecl } from "./lower-builtins.js";
24
25
  import { lowerEnumDeclaration } from "./lower-enums.js";
25
- import { abstractPropertyDeclOf, aliasTypeofNarrows, isMatchSliceType, lowerAbsenceProbe, lowerGroupsProjection, lowerOptionalNumber, 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";
26
29
  import { checkerPanicDiag, isCheckerPanic, requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
27
30
  import { isParseArgsDynTypeName, isUnitOnlyTsType, unitOnlyUnion } from "../type-mapper.js";
28
31
  import { canonicalBuiltinModule } from "../builtin-modules.js";
29
32
  import { isRelativeSpecifier } from "../workspace-registry.js";
30
33
  import { probeNodeRequireRefusal } from "../npm.js";
31
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
+ }
32
275
  /** `const X = /* @__PURE__ *\/ makeX()` at a module's top level: every
33
276
  * declarator initialized by a call (or `new`) carrying the bundler PURE
34
277
  * annotation in its leading trivia — the shape --provenance-sources may
@@ -113,6 +356,7 @@ export function provenanceElidedConstDecl(lowerer, decl) {
113
356
  * `let a = 1, b = a + 1;`) but is counted once. */
114
357
  export function lowerStmts(lowerer, stmts) {
115
358
  const out = [];
359
+ const usingMarkers = [];
116
360
  // JAVASCRIPT sources defer their compile fences to runtime (the
117
361
  // JS-input design: no annotations exist to change what lowers, so a
118
362
  // statement whose construct has no static lowering compiles to a
@@ -144,7 +388,25 @@ export function lowerStmts(lowerer, stmts) {
144
388
  }
145
389
  const diagsBefore = lowerer.diags.length;
146
390
  try {
147
- 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
+ }
148
410
  // The lib-boundary chokepoint (lib-boundary.ts): checked-dynamic
149
411
  // arguments that reached builtin-call slots without the checked
150
412
  // coercion get their dynCheck here, and the uncoercible ones
@@ -153,10 +415,12 @@ export function lowerStmts(lowerer, stmts) {
153
415
  // deferred rejection.
154
416
  if (lowered)
155
417
  enforceLibBoundary(lowerer, lowered);
156
- if (Array.isArray(lowered))
157
- out.push(...lowered);
158
- else if (lowered)
159
- out.push(lowered);
418
+ if (!appended) {
419
+ if (Array.isArray(lowered))
420
+ out.push(...lowered);
421
+ else if (lowered)
422
+ out.push(lowered);
423
+ }
160
424
  // Island accounting (--dynamic coverage): a statement that lowered
161
425
  // but carries island constructs compiles DYNAMICALLY — its work
162
426
  // runs in the embedded engine, and the report says so instead of
@@ -215,6 +479,23 @@ export function lowerStmts(lowerer, stmts) {
215
479
  }
216
480
  }
217
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
+ }
218
499
  }
219
500
  finally {
220
501
  lowerer.activeStmtLists.pop();
@@ -589,44 +870,6 @@ export function lowerScopedBlock(lowerer, stmt) {
589
870
  lowerer.scopes.pop();
590
871
  }
591
872
  }
592
- /** The finally fences. `return` crossing OUT of a try/catch body guarded
593
- * by a finally is SUPPORTED now (the backend's pending-return path runs
594
- * every crossed finally inner-to-outer before the function returns, the
595
- * value snapshotted first — Node-exact); what stays rejected is (a) any
596
- * jump out of a finally BLOCK itself (a return there would REPLACE a
597
- * pending completion — clearing a still-propagating exception or
598
- * abandoning a pending return, a model the emitter doesn't implement),
599
- * and (b) break/continue crossing a try-with-finally (their targets bind
600
- * inside the function; the pending-action plumbing exists only for
601
- * return, the shape real code needs). Rejected, not miscompiled. Plain
602
- * try/catch is transparent to jumps (it never appears in ctl). A LABELED
603
- * jump walks outward past every construct (labeled targets can be
604
- * arbitrarily far out) until the entry carrying its label, applying the
605
- * same finally fences to everything crossed on the way. */
606
- export function rejectJumpCrossingFinally(lowerer, kw, stmt, label) {
607
- const ctl = lowerer.ctx.ctl;
608
- for (let i = ctl.length - 1; i >= 0; i--) {
609
- const c = ctl[i];
610
- if (c.kind === "finallyBlock") {
611
- lowerer.unsupported("SC1090", stmt, `'${kw}' out of a 'finally' block (it would replace the pending completion — restructure so the finally only cleans up)`);
612
- }
613
- if (c.kind === "tryFinally") {
614
- if (kw !== "return") {
615
- lowerer.unsupported("SC1090", stmt, `'${kw}' crossing a 'finally' block`);
616
- }
617
- continue; // return runs the finally on the way out — supported
618
- }
619
- if (kw === "return")
620
- continue; // return crosses every construct
621
- if (label !== undefined) {
622
- if (c.labels?.includes(label))
623
- return; // the labeled target — binds here
624
- continue; // an inner construct the labeled jump exits
625
- }
626
- if (c.kind === "loop" || (kw === "break" && c.kind === "switch"))
627
- return; // binds inside the region
628
- }
629
- }
630
873
  /** `lbl: stmt` — labeled statements. A label chain collapses to one name
631
874
  * list (`a: b: while ...` — both names target the same loop, JS-exact).
632
875
  * Loops and switches take the labels DIRECTLY (a labeled continue needs
@@ -720,7 +963,6 @@ export function lowerStmt(lowerer, stmt) {
720
963
  if (ts.isLabeledStatement(stmt))
721
964
  return lowerLabeled(lowerer, stmt);
722
965
  if (ts.isReturnStatement(stmt)) {
723
- lowerer.rejectJumpCrossingFinally("return", stmt);
724
966
  // Implicit-any instance RETURN INFERENCE (lower-calls'
725
967
  // resolveInferredReturn): the value lowers BARE and the statement
726
968
  // records itself — the post-pass settles the instance's return type
@@ -784,10 +1026,8 @@ export function lowerStmt(lowerer, stmt) {
784
1026
  if (!target || (kw === "continue" && target.kind !== "loop")) {
785
1027
  lowerer.unsupported("SC1050", stmt, `'${kw} ${label}' targeting this statement form (the labeled statement lowers through a desugar that has no label point)`);
786
1028
  }
787
- lowerer.rejectJumpCrossingFinally(kw, stmt, label);
788
1029
  return { kind: kw, label, loc: locOf(stmt) };
789
1030
  }
790
- lowerer.rejectJumpCrossingFinally(kw, stmt);
791
1031
  // tsc rejects break outside loops/switches and continue outside loops,
792
1032
  // so the context is always valid here (the validator re-checks).
793
1033
  return { kind: kw, loc: locOf(stmt) };
@@ -2904,6 +3144,95 @@ export function lowerVarDeclList(lowerer, list) {
2904
3144
  throw new InternalCompilerError("lowerer bug: for-init declarator resolved to a global");
2905
3145
  return lowered;
2906
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
+ }
2907
3236
  export function lowerVarDecl(lowerer, decl, isLet) {
2908
3237
  // --provenance-sources: an elided pure-annotated dead const emits
2909
3238
  // nothing (collectGlobals registered no global by the same test —
@@ -3163,6 +3492,9 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3163
3492
  // immediately (the type covers the unassigned state), so they
3164
3493
  // initialize to the interned undefined arm like an omitted optional.
3165
3494
  let type = lowerer.irTypeOf(decl.name);
3495
+ if (type.kind === "dyn" && isLet && isJsSourceFile(decl.getSourceFile())) {
3496
+ type = inferredEvolvingLetType(lowerer, decl) ?? type;
3497
+ }
3166
3498
  // `var v: void;` / `let x: undefined;` — a unit-only binding rides
3167
3499
  // the unit-only union (its undefined arm is the unassigned state,
3168
3500
  // which is also the only state).
@@ -3233,6 +3565,12 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3233
3565
  (lowerer.dynamic && init.type.kind === "dyn" && jsvalFlavoredType(lowerer.mapTypeOf(lowerer.typeOf(decl.name)) ?? DYN) ? DYN : null) ??
3234
3566
  (bindingTainted ? null : lowerer.mapTypeOf(lowerer.typeOf(decl.name))) ??
3235
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
+ }
3236
3574
  // A JS `let x = {}`: TS's empty-object-literal type admits ANY later
3237
3575
  // non-nullish assignment (`envs = {}`, later `envs =
3238
3576
  // Object.fromEntries(...)` — tsc accepts every such write, since
@@ -3245,6 +3583,11 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3245
3583
  if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple)
3246
3584
  type = DYN;
3247
3585
  }
3586
+ const returnedEmptyArrayType = npmStaticReturnedEmptyArrayType(lowerer, decl);
3587
+ if (returnedEmptyArrayType !== null) {
3588
+ type = returnedEmptyArrayType;
3589
+ init = lowerer.lowerExprExpecting(decl.initializer, returnedEmptyArrayType);
3590
+ }
3248
3591
  // A JS `const leaked = [];` (the evolving-array idiom — test/common's
3249
3592
  // leak ledger): tsc types the binding by its LATER pushes, but this
3250
3593
  // frontend answers the declaration name with the uninhabited never[],
@@ -3254,7 +3597,9 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3254
3597
  // checked-dynamic instead (the `let x = {}` stance, and lower-modules'
3255
3598
  // file-scope evolving-array rule): pushes ride the dyn array, typed
3256
3599
  // exits dynCheck.
3257
- if ((type === null || type.kind === "array") && isJsSourceFile(decl.getSourceFile())) {
3600
+ if (returnedEmptyArrayType === null &&
3601
+ (type === null || type.kind === "array") &&
3602
+ isJsSourceFile(decl.getSourceFile())) {
3258
3603
  let initExpr = decl.initializer;
3259
3604
  while (ts.isParenthesizedExpression(initExpr))
3260
3605
  initExpr = initExpr.expression;
@@ -3316,14 +3661,15 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3316
3661
  // their tuple record likewise). Const only — an evolving-`any`
3317
3662
  // `let` may be reassigned a different shape later; genuine `any`
3318
3663
  // only — every other unmappable keeps its own diagnostic.
3319
- if (type === null && !isLet &&
3320
- ((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 ||
3321
3667
  (lowerer.checkerAnyArray(decl.name) && lowerer.isArrayValueType(init.type)) ||
3322
3668
  // JS declarations carry no annotations: an unmappable inferred
3323
3669
  // type (a union with a fence-folded arm — the typeof-'bigint'
3324
3670
  // dual-mode ternary) adopts the initializer's static type, the
3325
3671
  // same rule genuine `any` consts take.
3326
- isJsSourceFile(decl.getSourceFile())) &&
3672
+ (type === null && isJsSourceFile(decl.getSourceFile()))) &&
3327
3673
  init.type.kind !== "void" && init.type.kind !== "caught" && init.type.kind !== "jsval" &&
3328
3674
  !isUnitType(init.type)) {
3329
3675
  type = init.type;
@@ -3338,6 +3684,7 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3338
3684
  }
3339
3685
  if (!type)
3340
3686
  lowerer.badType(decl.name, lowerer.typeOf(decl.name));
3687
+ let settledType = type;
3341
3688
  const arithmeticType = decl.initializer ? lowerer.runtimeOptionalArithmeticTypes.get(decl.initializer) : undefined;
3342
3689
  const isStringArithmeticUnion = (t) => {
3343
3690
  if (t.kind !== "union")
@@ -3346,35 +3693,42 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3346
3693
  return !!arms && arms.length === 2 &&
3347
3694
  arms.some((a) => a.kind === "f64") && arms.some((a) => a.kind === "string");
3348
3695
  };
3349
- const runtimeStringArithmetic = type?.kind === "string" &&
3696
+ const runtimeStringArithmetic = settledType.kind === "string" &&
3350
3697
  ((arithmeticType && typeEquals(arithmeticType, init.type)) || isStringArithmeticUnion(init.type));
3351
3698
  if (runtimeStringArithmetic) {
3352
- type = init.type;
3699
+ settledType = init.type;
3353
3700
  }
3354
- type = lowerer.runtimeOptionalBindingType(decl.name, type);
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;
3355
3709
  // `const x: void = undefined` / `let y: undefined = undefined`: the
3356
3710
  // unit-only union — the initializer's unit literal wraps into its arm
3357
3711
  // like any optional completion. Non-literal void initializers (a
3358
3712
  // void CALL's result) keep their fence at the coercion below.
3359
- if (type.kind === "void" && isUnitOnlyTsType(lowerer.typeOf(decl.name))) {
3360
- type = unitOnlyUnion(lowerer.unions);
3713
+ if (settledType.kind === "void" && isUnitOnlyTsType(lowerer.typeOf(decl.name))) {
3714
+ settledType = unitOnlyUnion(lowerer.unions);
3361
3715
  }
3362
- if (type.kind === "void")
3716
+ if (settledType.kind === "void")
3363
3717
  lowerer.badType(decl.name, lowerer.typeOf(decl.name));
3364
3718
  // A PACKAGE promise stored in a local stays an island HANDLE — the
3365
3719
  // engine's promise has no static promise value to coerce into. Awaits
3366
3720
  // and .catch/.finally chains on the local bridge per use site (each
3367
3721
  // bridge is an independent observer of the same settlement).
3368
- if (init.type.kind === "jsval" && type.kind === "promise")
3369
- type = JSVAL;
3722
+ if (init.type.kind === "jsval" && settledType.kind === "promise")
3723
+ settledType = JSVAL;
3370
3724
  // An island value under an `any[]`-declared LOCAL spelling stays an
3371
3725
  // island HANDLE too (the runtime-world rule): the jsval-element-array
3372
3726
  // exit is a CALL-boundary conversion (the loadPlugins param ABI) —
3373
3727
  // converting a local would strand the engine's own prototypes
3374
3728
  // (join/map/... on a native handle-element array), where the handle
3375
3729
  // routes every use engine-side.
3376
- if (init.type.kind === "jsval" && type.kind === "array" && type.elem.kind === "jsval") {
3377
- type = JSVAL;
3730
+ if (init.type.kind === "jsval" && settledType.kind === "array" && settledType.elem.kind === "jsval") {
3731
+ settledType = JSVAL;
3378
3732
  }
3379
3733
  // A STATIC array of island HANDLES under an unannotated declared type
3380
3734
  // spelling evolved elements (`const kept = fns.filter(...)` over an
@@ -3387,8 +3741,8 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3387
3741
  // validated-boundary fence.
3388
3742
  if (!decl.type &&
3389
3743
  init.type.kind === "array" && init.type.elem.kind === "jsval" &&
3390
- type.kind === "array" && type.elem.kind !== "jsval") {
3391
- type = init.type;
3744
+ settledType.kind === "array" && settledType.elem.kind !== "jsval") {
3745
+ settledType = init.type;
3392
3746
  }
3393
3747
  // `const runner = options.runner || defaultRunner`: the checker types
3394
3748
  // the || as the union/merge of two structurally-compatible function
@@ -3398,13 +3752,13 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3398
3752
  // when the parameters agree and the declared return either contains
3399
3753
  // the init's return as a union arm or is the opaque spawnRes the
3400
3754
  // adapter converts (nothing coerces INTO either declared form).
3401
- 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)) {
3402
3756
  const initFn = init.type;
3403
- const paramsAgree = type.params.length === initFn.params.length &&
3404
- type.params.every((p, i) => typeEquals(p, initFn.params[i]));
3405
- const retUnionArm = type.ret.kind === "union" && lowerer.armTag(type.ret.unionId, initFn.ret) >= 0;
3406
- if (paramsAgree && (retUnionArm || lowerer.spawnResFnAdapterPlan(type, initFn) !== null)) {
3407
- 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;
3408
3762
  }
3409
3763
  }
3410
3764
  // The downstream twin: `const result = runner(cmd, args)` where the
@@ -3413,12 +3767,12 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3413
3767
  // can narrow (spawnRes has no discriminant). The VALUE is statically
3414
3768
  // the record arm, so the local adopts it; flows into the union slot
3415
3769
  // re-wrap via the ordinary arm coercion.
3416
- if (type.kind === "union" && init.type.kind === "record") {
3417
- 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 ?? [];
3418
3772
  if (arms.length === 2 &&
3419
3773
  arms.some((a) => a.kind === "spawnRes") &&
3420
3774
  arms.some((a) => typeEquals(a, init.type))) {
3421
- type = init.type;
3775
+ settledType = init.type;
3422
3776
  }
3423
3777
  }
3424
3778
  // `const r: Repo = new MemRepo()` over an all-generic-method
@@ -3426,8 +3780,8 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3426
3780
  // (the record shape maps empty and the width copy would drop the
3427
3781
  // class the generic-method calls monomorphize against) — see
3428
3782
  // genericIfaceBindingKeepsClass.
3429
- if (type.kind === "record" && init.type.kind === "object" && genericIfaceBindingKeepsClass(lowerer, decl, type)) {
3430
- type = init.type;
3783
+ if (settledType.kind === "record" && init.type.kind === "object" && genericIfaceBindingKeepsClass(lowerer, decl, settledType)) {
3784
+ settledType = init.type;
3431
3785
  }
3432
3786
  // A TDZ box minted DURING this very initializer (a callback inside it
3433
3787
  // captured this const — predeclareForwardCapture's current-statement
@@ -3442,8 +3796,8 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3442
3796
  }
3443
3797
  // Slot coercion: `const r: A | B = bValue;` wraps implicitly; width
3444
3798
  // subtyping (`const p: {a: number} = wider;`) is rejected, not coerced.
3445
- init = lowerer.coerceInto(decl.initializer, init, type);
3446
- 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);
3447
3801
  if (runtimeStringArithmetic && isStringArithmeticUnion(init.type)) {
3448
3802
  lowerer.runtimeOptionalArithmeticLocals.add(lowerer.runtimeOptionalRootOf(local));
3449
3803
  }
@@ -3545,9 +3899,8 @@ export function lowerSwitch(lowerer, stmt) {
3545
3899
  const labels = lowerer.takeLabels();
3546
3900
  const disc = lowerer.lowerExpr(stmt.expression);
3547
3901
  const dk = disc.type.kind;
3548
- if (dk === "dyn") {
3549
- lowerer.unsupported("SC1100", stmt.expression, "switch statements on 'unknown' values");
3550
- }
3902
+ if (dk === "dyn")
3903
+ return lowerDynSwitch(lowerer, stmt, disc);
3551
3904
  if (dk === "union")
3552
3905
  return lowerUnionSwitch(lowerer, stmt, disc);
3553
3906
  if (dk !== "f64" && dk !== "string" && dk !== "bool") {
@@ -3596,19 +3949,51 @@ export function lowerSwitch(lowerer, stmt) {
3596
3949
  * is last).
3597
3950
  * Case bodies share ONE lexical scope, exactly like the real switch. */
3598
3951
  function lowerUnionSwitch(lowerer, stmt, disc) {
3599
- const loc = locOf(stmt);
3600
3952
  if (disc.type.kind !== "union")
3601
3953
  throw new InternalCompilerError("lowerer bug: non-union disc");
3602
3954
  const unionType = disc.type;
3603
3955
  if (!lowerer.eqComparableUnion(unionType.unionId)) {
3604
3956
  lowerer.unsupported("SC1090", stmt.expression, "switch on a union whose arms do not have static equality");
3605
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);
3606
3991
  const prefix = [];
3607
3992
  let stableDisc = disc;
3608
- if (!pureReemittable(disc)) {
3609
- const temp = lowerer.declareHiddenLocal("%switch", unionType);
3993
+ if (!isSafeToRepeat(disc)) {
3994
+ const temp = lowerer.declareHiddenLocal("%switch", disc.type);
3610
3995
  prefix.push({ kind: "varDecl", localId: temp.id, init: disc, loc });
3611
- stableDisc = { kind: "varRef", localId: temp.id, type: unionType, loc };
3996
+ stableDisc = { kind: "varRef", localId: temp.id, type: disc.type, loc };
3612
3997
  }
3613
3998
  const clauses = stmt.caseBlock.clauses;
3614
3999
  // An unlabeled break at a clause's END exits the switch — the chain's
@@ -3667,7 +4052,7 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3667
4052
  // final else, so matching them changes nothing when pure).
3668
4053
  if (test.kind !== "strLit" && test.kind !== "numLit" &&
3669
4054
  test.kind !== "boolLit" && test.kind !== "unitLit" &&
3670
- !pureReemittable(test)) {
4055
+ !isSafeToRepeat(test)) {
3671
4056
  lowerer.unsupported("SC1090", clause.expression, "effectful case tests in a union-typed switch (bind the test value to a const first)");
3672
4057
  }
3673
4058
  // A unit-literal test takes the unit-comparison lowering: a tag
@@ -3675,19 +4060,7 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3675
4060
  // lacks it (`case null:` on a `number | undefined` — legal TS,
3676
4061
  // never matches; coercing the literal into the union would hit
3677
4062
  // the stranded-arm trap and throw where JS just skips the case).
3678
- const unitTest = test.kind === "unitLit"
3679
- ? lowerer.lowerUnitComparison(stableDisc, test, false, locOf(clause.expression))
3680
- : null;
3681
- pendingTests.push(unitTest ?? {
3682
- kind: "unionEq",
3683
- unionId: unionType.unionId,
3684
- negated: false,
3685
- sameValue: false,
3686
- left: stableDisc,
3687
- right: lowerer.coerceInto(clause.expression, test, unionType),
3688
- type: BOOL,
3689
- loc: locOf(clause.expression),
3690
- });
4063
+ pendingTests.push(compare(stableDisc, test, clause.expression));
3691
4064
  }
3692
4065
  const isDefault = ts.isDefaultClause(clause);
3693
4066
  if (clause.statements.length === 0 && !isDefault && i < clauses.length - 1) {
@@ -3730,7 +4103,7 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3730
4103
  lowerer.scopes.pop();
3731
4104
  }
3732
4105
  }
3733
- /** try/catch/finally. Supported subset:
4106
+ /** try/catch/finally:
3734
4107
  * - `catch { }` (bindingless) discards the thrown value on entry.
3735
4108
  * - `catch (e)` binds it as a CAUGHT local — a snapshot of the exception
3736
4109
  * cell, deliberately narrower than dyn: the supported uses are the
@@ -3741,23 +4114,14 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3741
4114
  * assignment are fenced (SC1063 and friends); destructuring patterns
3742
4115
  * are SC1062. The `: any` / `: unknown` annotations tsc admits both
3743
4116
  * lower the same way (tsc narrows either through the tests).
3744
- * - `finally` runs on normal completion, on exception paths, and on the
3745
- * way out of a `return` in the try/catch body (the backend's
3746
- * pending-return path — inner-to-outer through nested finallys, the
3747
- * value snapshotted before the finally runs, Node-exact).
3748
- * break/continue crossing a try-with-finally stay rejected, as does
3749
- * any jump out of the finally body itself (it would replace a pending
3750
- * completion — rejectJumpCrossingFinally). Plain try/catch places no
3751
- * 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.
3752
4121
  * Each block is its own lexical scope (the binding lives in a scope
3753
4122
  * WRAPPING the catch block, like the spec's catch environment). */
3754
4123
  export function lowerTry(lowerer, stmt) {
3755
- const hasFinally = stmt.finallyBlock !== undefined;
3756
- // try/catch bodies are jump-fenced only when a finally guards them.
3757
- const lowerGuarded = (block) => hasFinally
3758
- ? lowerer.inCtl("tryFinally", () => lowerer.lowerScopedBlock(block))
3759
- : lowerer.lowerScopedBlock(block);
3760
- const tryBody = lowerGuarded(stmt.tryBlock);
4124
+ const tryBody = lowerer.lowerScopedBlock(stmt.tryBlock);
3761
4125
  let catchBody = null;
3762
4126
  let catchLocalId = null;
3763
4127
  if (stmt.catchClause) {
@@ -3770,19 +4134,17 @@ export function lowerTry(lowerer, stmt) {
3770
4134
  try {
3771
4135
  const local = lowerer.declareLocal(vd.name, vd.name.text, CAUGHT, false);
3772
4136
  catchLocalId = local.id;
3773
- catchBody = lowerGuarded(stmt.catchClause.block);
4137
+ catchBody = lowerer.lowerScopedBlock(stmt.catchClause.block);
3774
4138
  }
3775
4139
  finally {
3776
4140
  lowerer.scopes.pop();
3777
4141
  }
3778
4142
  }
3779
4143
  else {
3780
- catchBody = lowerGuarded(stmt.catchClause.block);
4144
+ catchBody = lowerer.lowerScopedBlock(stmt.catchClause.block);
3781
4145
  }
3782
4146
  }
3783
- const finallyBody = stmt.finallyBlock
3784
- ? lowerer.inCtl("finallyBlock", () => lowerer.lowerScopedBlock(stmt.finallyBlock))
3785
- : null;
4147
+ const finallyBody = stmt.finallyBlock ? lowerer.lowerScopedBlock(stmt.finallyBlock) : null;
3786
4148
  return {
3787
4149
  kind: "tryCatch",
3788
4150
  tryBody,
@@ -3806,6 +4168,7 @@ function lowerDeleteStatement(lowerer, expr) {
3806
4168
  if (!ts.isElementAccessExpression(target) && !ts.isPropertyAccessExpression(target)) {
3807
4169
  lowerer.unsupported("SC1090", expr, "'delete' of non-property expressions");
3808
4170
  }
4171
+ fenceNodeModuleMutation(lowerer, target, "delete");
3809
4172
  const lowerKey = () => {
3810
4173
  if (ts.isPropertyAccessExpression(target)) {
3811
4174
  return { kind: "strLit", value: target.name.text, type: STRING, loc: locOf(target.name) };
@@ -4299,6 +4662,7 @@ export function lowerExprStatement(lowerer, expr) {
4299
4662
  if (ts.isElementAccessExpression(expr.left))
4300
4663
  return lowerer.lowerElementWrite(expr);
4301
4664
  if (ts.isPropertyAccessExpression(expr.left)) {
4665
+ fenceNodeModuleMutation(lowerer, expr.left, "assignment");
4302
4666
  // Native arrays have a writable length property. The runtime grows
4303
4667
  // by creating holes and truncates with RC-aware removal; keep this
4304
4668
  // as a statement operation so invalid lengths can use the normal
@@ -4451,10 +4815,10 @@ export function lowerExprStatement(lowerer, expr) {
4451
4815
  // chain): the dyn keyed write — the value converts INTO dyn
4452
4816
  // (closures box), the runtime sets the member on OBJ receivers
4453
4817
  // and throws Node's TypeErrors on the rest. The receiver is
4454
- // PROBED (probeLower) and claimed exactly when it lowers to
4818
+ // PROBED (tryLowerExpression) and claimed exactly when it lowers to
4455
4819
  // dyn; typed receivers keep their own lowerings and fences.
4456
4820
  if (!expr.left.questionDotToken) {
4457
- const recv = probeLower(lowerer, expr.left.expression);
4821
+ const recv = tryLowerExpression(lowerer, expr.left.expression);
4458
4822
  if (recv && recv.type.kind === "dyn") {
4459
4823
  const loc = locOf(expr);
4460
4824
  const key = { kind: "strLit", value: expr.left.name.text, type: STRING, loc: locOf(expr.left.name) };
@@ -5810,29 +6174,27 @@ export function lowerForOf(lowerer, stmt) {
5810
6174
  // desugars drop them — a labeled jump naming those loops fences at the
5811
6175
  // jump site (no label point exists in their desugared shape).
5812
6176
  const labels = lowerer.takeLabels();
5813
- // `for await` is ASYNC ITERATION, not the shipped async/await. ONE
5814
- // async iterable has a lowering: process.stdin (the piped-input
5815
- // pattern real CLIs use) — see lowerForAwaitStdin. Everything else is
5816
- // named specifically (the bare SC1070 text would claim async/await
5817
- // 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.
5818
6180
  if (stmt.awaitModifier) {
5819
- if (ts.isPropertyAccessExpression(stmt.expression) &&
5820
- lowerer.stdlibGlobalMember(stmt.expression, "process") === "stdin") {
5821
- return lowerForAwaitStdin(lowerer, stmt);
5822
- }
5823
- // Readable streams (the readableAsyncIterator surface): the mapped
5824
- // receiver class roots at a readable-sided stream class.
5825
6181
  {
5826
- const recvT = lowerer.mapTypeOf(lowerer.typeOf(stmt.expression));
5827
- if (recvT?.kind === "object") {
5828
- const info = lowerer.classes.get(recvT.className);
5829
- const sides = streamSidesOf(lowerer, info);
5830
- if (sides === "r" || sides === "rw") {
5831
- 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);
5832
6187
  }
5833
6188
  }
5834
6189
  }
5835
- 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)");
5836
6198
  }
5837
6199
  lowerer.fenceStaticHeadersIteration(stmt.expression);
5838
6200
  // A stored numeric value iterator declared in this function keeps its
@@ -6051,7 +6413,7 @@ export function lowerForOf(lowerer, stmt) {
6051
6413
  elemT = { kind: "union", unionId: lowerer.unions.intern(arms) };
6052
6414
  }
6053
6415
  }
6054
- if (elemT !== null && pureReemittable(iterable)) {
6416
+ if (elemT !== null && isSafeToRepeat(iterable)) {
6055
6417
  const loc = locOf(stmt.expression);
6056
6418
  const shapeId = iterable.type.shapeId;
6057
6419
  const snapshotElem = elemT;
@@ -6161,9 +6523,7 @@ export function lowerForOf(lowerer, stmt) {
6161
6523
  lowerer.unsupported("SC1090", stmt.initializer, "for-of over a pre-declared variable (declare the loop variable in the loop: for (const x of ...))");
6162
6524
  }
6163
6525
  const list = stmt.initializer;
6164
- if ((list.flags & ts.NodeFlags.Using) !== 0) {
6165
- lowerer.unsupported("SC1090", list, "'using' declarations (dispose-at-scope-exit semantics)");
6166
- }
6526
+ const resourceLoop = (list.flags & ts.NodeFlags.Using) !== 0;
6167
6527
  const isLet = (list.flags & ts.NodeFlags.Let) !== 0 || (list.flags & ts.NodeFlags.BlockScoped) === 0;
6168
6528
  const decl = list.declarations[0]; // the grammar allows exactly one
6169
6529
  // `for (const [k, v] of pairs)` — a destructuring loop variable: the
@@ -6216,7 +6576,19 @@ export function lowerForOf(lowerer, stmt) {
6216
6576
  lowerer.runtimeOptionalStorageLocals.add(root);
6217
6577
  }
6218
6578
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
6219
- return forValues(local.id, body);
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
+ }]);
6220
6592
  }
6221
6593
  finally {
6222
6594
  lowerer.scopes.pop();
@@ -7372,112 +7744,6 @@ function lowerForAwaitStdin(lowerer, stmt) {
7372
7744
  lowerer.scopes.pop();
7373
7745
  }
7374
7746
  }
7375
- /** `for await (const chunk of readable)` — the stream async iterator
7376
- * (the stdin desugar's sibling): every pass awaits the runtime's
7377
- * next-chunk promise — buffered content, or a REJECTION carrying the
7378
- * stream's error (the await rethrows it, Node's iterator contract) —
7379
- * and exits on the EOF sentinel. Chunks are Buffers in typed code
7380
- * (encoded streams fence at the runtime entry); in the JS lane the
7381
- * chunk is checked-dynamic and boxes by runtime tag (dyn strings once
7382
- * an encoding applies, dyn undefined as the sentinel — chunks are
7383
- * never undefined). Early exit leaves the stream alive (Node's
7384
- * iterator return() would destroy it — a documented divergence). */
7385
- function lowerForAwaitReadable(lowerer, stmt, recvT) {
7386
- if (!lowerer.ctx.isAsync) {
7387
- lowerer.unsupported("SC1090", stmt, "top-level 'for await' (await outside async functions)");
7388
- }
7389
- if (!ts.isVariableDeclarationList(stmt.initializer)) {
7390
- lowerer.unsupported("SC1090", stmt.initializer, "for-await over a pre-declared variable (declare the loop variable in the loop: for await (const chunk of ...))");
7391
- }
7392
- const list = stmt.initializer;
7393
- const isConst = (list.flags & ts.NodeFlags.Const) !== 0;
7394
- const isLet = (list.flags & ts.NodeFlags.Let) !== 0;
7395
- // `for await (var chunk of ...)`: the chunk binding is per-await
7396
- // machinery (a fresh value each pass, fiber-parked in between) —
7397
- // threading it through a shared hoisted slot has no user yet.
7398
- if (!isConst && !isLet)
7399
- lowerer.unsupported("SC1030", list, "'var' loop bindings in 'for await' (use const)");
7400
- const decl = list.declarations[0];
7401
- if (!ts.isIdentifier(decl.name))
7402
- lowerer.unsupported("SC1031", decl.name);
7403
- const loc = locOf(stmt);
7404
- // The chunk is CHECKED-DYNAMIC in both lanes (the shim types the
7405
- // iterator's element as any): the runtime boxes by tag — Buffers
7406
- // normally, strings once an encoding applies — and the dyn surface
7407
- // carries the common consumptions (.length, [i], toString, +=,
7408
- // typeof). A statically-typed chunk would have to pick one side of
7409
- // the encoding question at compile time.
7410
- const dynLane = true;
7411
- const chunkT = DYN;
7412
- const promiseT = { kind: "promise", inner: chunkT };
7413
- lowerer.scopes.push(new Map());
7414
- try {
7415
- // The receiver evaluates ONCE, before the loop.
7416
- const recvLocal = lowerer.declareHiddenLocal("%faStream", recvT);
7417
- const recvDecl = {
7418
- kind: "varDecl",
7419
- localId: recvLocal.id,
7420
- init: lowerer.lowerExpr(stmt.expression),
7421
- loc,
7422
- };
7423
- const p = lowerer.declareHiddenLocal("%streamNext", promiseT);
7424
- const chunk = lowerer.declareLocal(decl.name, decl.name.text, chunkT, isLet);
7425
- const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement));
7426
- const chunkRef = { kind: "varRef", localId: chunk.id, type: chunkT, loc };
7427
- const eofCond = dynLane
7428
- ? { kind: "dynTest", test: "undefined", value: chunkRef, type: BOOL, loc }
7429
- : {
7430
- kind: "bin",
7431
- op: "===",
7432
- left: { kind: "bytesIntrinsic", method: "length", receiver: chunkRef, args: [], type: F64, loc },
7433
- right: { kind: "numLit", value: 0, type: F64, loc },
7434
- type: BOOL,
7435
- loc,
7436
- };
7437
- const head = [
7438
- {
7439
- kind: "varDecl",
7440
- localId: p.id,
7441
- init: {
7442
- kind: "libCall",
7443
- fn: dynLane ? "readable.nextChunkDyn" : "readable.nextChunk",
7444
- args: [{ kind: "varRef", localId: recvLocal.id, type: recvT, loc }],
7445
- type: promiseT,
7446
- loc,
7447
- },
7448
- loc,
7449
- },
7450
- {
7451
- kind: "varDecl",
7452
- localId: chunk.id,
7453
- init: {
7454
- kind: "awaitExpr",
7455
- value: { kind: "varRef", localId: p.id, type: promiseT, loc },
7456
- type: chunkT,
7457
- loc,
7458
- },
7459
- loc,
7460
- },
7461
- { kind: "if", cond: eofCond, then: [{ kind: "break", loc }], else_: null, loc },
7462
- ];
7463
- return {
7464
- kind: "block",
7465
- body: [
7466
- recvDecl,
7467
- {
7468
- kind: "while",
7469
- cond: { kind: "boolLit", value: true, type: BOOL, loc },
7470
- body: [...head, ...body],
7471
- loc,
7472
- },
7473
- ],
7474
- loc,
7475
- };
7476
- }
7477
- finally {
7478
- lowerer.scopes.pop();
7479
- }
7480
- }
7481
7747
  export function lowerForStatement(lowerer, stmt) {
7482
7748
  const labels = lowerer.takeLabels();
7483
7749
  lowerer.scopes.push(new Map());