@scriptc/compiler 0.2.1 → 0.2.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 (193) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +59 -4
  2. package/dist/backend/llvm/dyn.d.ts +3 -0
  3. package/dist/backend/llvm/dyn.js +84 -12
  4. package/dist/backend/llvm/dyn.js.map +1 -1
  5. package/dist/backend/llvm/emitter.d.ts +4 -1
  6. package/dist/backend/llvm/emitter.js +20 -5
  7. package/dist/backend/llvm/emitter.js.map +1 -1
  8. package/dist/backend/llvm/emitter.test.js +9 -1
  9. package/dist/backend/llvm/emitter.test.js.map +1 -1
  10. package/dist/backend/llvm/expr-containers.js +5 -0
  11. package/dist/backend/llvm/expr-containers.js.map +1 -1
  12. package/dist/backend/llvm/expr-dynamic.js +30 -5
  13. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  14. package/dist/backend/llvm/expr-stream-bridges.js +70 -9
  15. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  16. package/dist/backend/llvm/expr-stream-callbacks.d.ts +2 -2
  17. package/dist/backend/llvm/expr-stream-callbacks.js +7 -57
  18. package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
  19. package/dist/backend/llvm/lib-dispatch.js +41 -3
  20. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  21. package/dist/backend/llvm/lib-filesystem.js +12 -9
  22. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  23. package/dist/backend/llvm/lib-shared.js +35 -1
  24. package/dist/backend/llvm/lib-shared.js.map +1 -1
  25. package/dist/backend/may-throw.js +4 -0
  26. package/dist/backend/may-throw.js.map +1 -1
  27. package/dist/backend/native-codegen.d.ts +1 -1
  28. package/dist/backend/native-codegen.js +4 -3
  29. package/dist/backend/native-codegen.js.map +1 -1
  30. package/dist/backend/native-codegen.test.js +11 -0
  31. package/dist/backend/native-codegen.test.js.map +1 -1
  32. package/dist/backend/target-diagnostics.d.ts +5 -0
  33. package/dist/backend/target-diagnostics.js +72 -1
  34. package/dist/backend/target-diagnostics.js.map +1 -1
  35. package/dist/backend/target-diagnostics.test.d.ts +1 -0
  36. package/dist/backend/target-diagnostics.test.js +68 -0
  37. package/dist/backend/target-diagnostics.test.js.map +1 -0
  38. package/dist/coverage/surface-manifest.js +5 -3
  39. package/dist/coverage/surface-manifest.js.map +1 -1
  40. package/dist/executable/executable-cache.d.ts +7 -3
  41. package/dist/executable/executable-cache.js +47 -11
  42. package/dist/executable/executable-cache.js.map +1 -1
  43. package/dist/executable/executable-cache.test.js +109 -2
  44. package/dist/executable/executable-cache.test.js.map +1 -1
  45. package/dist/executable/prepare.js +2 -1
  46. package/dist/executable/prepare.js.map +1 -1
  47. package/dist/frontend/builtin-modules.d.ts +1 -1
  48. package/dist/frontend/builtin-modules.js +1 -1
  49. package/dist/frontend/builtin-modules.js.map +1 -1
  50. package/dist/frontend/function-completion.d.ts +6 -0
  51. package/dist/frontend/function-completion.js +50 -0
  52. package/dist/frontend/function-completion.js.map +1 -0
  53. package/dist/frontend/lowering/abstract-equality.js +5 -1
  54. package/dist/frontend/lowering/abstract-equality.js.map +1 -1
  55. package/dist/frontend/lowering/checked-iterable-spread.d.ts +4 -0
  56. package/dist/frontend/lowering/checked-iterable-spread.js +30 -0
  57. package/dist/frontend/lowering/checked-iterable-spread.js.map +1 -0
  58. package/dist/frontend/lowering/checked-promise-all.d.ts +5 -0
  59. package/dist/frontend/lowering/checked-promise-all.js +31 -0
  60. package/dist/frontend/lowering/checked-promise-all.js.map +1 -0
  61. package/dist/frontend/lowering/class-callbacks.d.ts +1 -1
  62. package/dist/frontend/lowering/class-callbacks.js +7 -6
  63. package/dist/frontend/lowering/class-callbacks.js.map +1 -1
  64. package/dist/frontend/lowering/class-construction.d.ts +2 -0
  65. package/dist/frontend/lowering/class-construction.js +34 -5
  66. package/dist/frontend/lowering/class-construction.js.map +1 -1
  67. package/dist/frontend/lowering/class-descriptors.d.ts +1 -1
  68. package/dist/frontend/lowering/class-descriptors.js +12 -5
  69. package/dist/frontend/lowering/class-descriptors.js.map +1 -1
  70. package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +25 -0
  71. package/dist/frontend/lowering/class-dynamic-dispatch.js +597 -78
  72. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
  73. package/dist/frontend/lowering/class-dynamic-dispatch.test.js +64 -2
  74. package/dist/frontend/lowering/class-dynamic-dispatch.test.js.map +1 -1
  75. package/dist/frontend/lowering/class-instance-constructor.d.ts +1 -1
  76. package/dist/frontend/lowering/class-instance-constructor.js +11 -41
  77. package/dist/frontend/lowering/class-instance-constructor.js.map +1 -1
  78. package/dist/frontend/lowering/class-method-values.d.ts +16 -0
  79. package/dist/frontend/lowering/class-method-values.js +60 -29
  80. package/dist/frontend/lowering/class-method-values.js.map +1 -1
  81. package/dist/frontend/lowering/class-prototypes.d.ts +5 -1
  82. package/dist/frontend/lowering/class-prototypes.js +69 -10
  83. package/dist/frontend/lowering/class-prototypes.js.map +1 -1
  84. package/dist/frontend/lowering/class-runtime-statics.d.ts +4 -4
  85. package/dist/frontend/lowering/class-runtime-statics.js +26 -8
  86. package/dist/frontend/lowering/class-runtime-statics.js.map +1 -1
  87. package/dist/frontend/lowering/class-static-data.d.ts +1 -0
  88. package/dist/frontend/lowering/class-static-data.js +4 -2
  89. package/dist/frontend/lowering/class-static-data.js.map +1 -1
  90. package/dist/frontend/lowering/containers/bytes.js +17 -4
  91. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  92. package/dist/frontend/lowering/containers/string-and-regexp.js +6 -1
  93. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  94. package/dist/frontend/lowering/deferred-module-initializers.d.ts +13 -0
  95. package/dist/frontend/lowering/deferred-module-initializers.js +410 -0
  96. package/dist/frontend/lowering/deferred-module-initializers.js.map +1 -0
  97. package/dist/frontend/lowering/deferred-module-initializers.test.d.ts +1 -0
  98. package/dist/frontend/lowering/deferred-module-initializers.test.js +92 -0
  99. package/dist/frontend/lowering/deferred-module-initializers.test.js.map +1 -0
  100. package/dist/frontend/lowering/expressions/object-literals.js +31 -1
  101. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  102. package/dist/frontend/lowering/expressions/optional-chains.js +3 -3
  103. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
  104. package/dist/frontend/lowering/lower-builtins.d.ts +4 -9
  105. package/dist/frontend/lowering/lower-builtins.js +291 -51
  106. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  107. package/dist/frontend/lowering/lower-calls.js +283 -73
  108. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  109. package/dist/frontend/lowering/lower-classes.d.ts +5 -0
  110. package/dist/frontend/lowering/lower-classes.js +439 -25
  111. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  112. package/dist/frontend/lowering/lower-containers.d.ts +1 -1
  113. package/dist/frontend/lowering/lower-containers.js +39 -17
  114. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  115. package/dist/frontend/lowering/lower-exprs.d.ts +10 -6
  116. package/dist/frontend/lowering/lower-exprs.js +294 -129
  117. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  118. package/dist/frontend/lowering/lower-generators.js +5 -1
  119. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  120. package/dist/frontend/lowering/lower-inspect.js +15 -1
  121. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  122. package/dist/frontend/lowering/lower-island.js +4 -0
  123. package/dist/frontend/lowering/lower-island.js.map +1 -1
  124. package/dist/frontend/lowering/lower-island.test.d.ts +1 -0
  125. package/dist/frontend/lowering/lower-island.test.js +51 -0
  126. package/dist/frontend/lowering/lower-island.test.js.map +1 -0
  127. package/dist/frontend/lowering/lower-modules.d.ts +1 -3
  128. package/dist/frontend/lowering/lower-modules.js +186 -82
  129. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  130. package/dist/frontend/lowering/lower-modules.test.js +69 -1
  131. package/dist/frontend/lowering/lower-modules.test.js.map +1 -1
  132. package/dist/frontend/lowering/lower-stmts.js +100 -35
  133. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  134. package/dist/frontend/lowering/lower-stmts.test.d.ts +1 -0
  135. package/dist/frontend/lowering/lower-stmts.test.js +77 -0
  136. package/dist/frontend/lowering/lower-stmts.test.js.map +1 -0
  137. package/dist/frontend/lowering/lower-stream.js +2 -0
  138. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  139. package/dist/frontend/lowering/lower-url.d.ts +2 -1
  140. package/dist/frontend/lowering/lower-url.js +32 -9
  141. package/dist/frontend/lowering/lower-url.js.map +1 -1
  142. package/dist/frontend/lowering/lowerer.d.ts +26 -9
  143. package/dist/frontend/lowering/lowerer.js +218 -27
  144. package/dist/frontend/lowering/lowerer.js.map +1 -1
  145. package/dist/frontend/lowering/lowerer.test.js +38 -0
  146. package/dist/frontend/lowering/lowerer.test.js.map +1 -1
  147. package/dist/frontend/lowering/module-namespace-elements.d.ts +7 -0
  148. package/dist/frontend/lowering/module-namespace-elements.js +54 -0
  149. package/dist/frontend/lowering/module-namespace-elements.js.map +1 -0
  150. package/dist/frontend/lowering/object-factory-new.d.ts +4 -3
  151. package/dist/frontend/lowering/object-factory-new.js +27 -14
  152. package/dist/frontend/lowering/object-factory-new.js.map +1 -1
  153. package/dist/frontend/lowering/surfaces.d.ts +1 -0
  154. package/dist/frontend/lowering/surfaces.js +24 -12
  155. package/dist/frontend/lowering/surfaces.js.map +1 -1
  156. package/dist/frontend/lowering/text-decoder-encoding.js +3 -0
  157. package/dist/frontend/lowering/text-decoder-encoding.js.map +1 -1
  158. package/dist/frontend/lowering/text-decoder-encoding.test.js +2 -1
  159. package/dist/frontend/lowering/text-decoder-encoding.test.js.map +1 -1
  160. package/dist/frontend/program.js +4 -2
  161. package/dist/frontend/program.js.map +1 -1
  162. package/dist/frontend/type-mapper.js +83 -14
  163. package/dist/frontend/type-mapper.js.map +1 -1
  164. package/dist/frontend/union-discriminants.js +22 -7
  165. package/dist/frontend/union-discriminants.js.map +1 -1
  166. package/dist/index.js +16 -10
  167. package/dist/index.js.map +1 -1
  168. package/dist/ir/ir.d.ts +27 -13
  169. package/dist/ir/ir.js +26 -2
  170. package/dist/ir/ir.js.map +1 -1
  171. package/dist/ir/serialize.js +21 -1
  172. package/dist/ir/serialize.js.map +1 -1
  173. package/dist/ir/serialize.test.js +21 -0
  174. package/dist/ir/serialize.test.js.map +1 -1
  175. package/dist/ir/traverse.d.ts +0 -3
  176. package/dist/ir/traverse.js +33 -20
  177. package/dist/ir/traverse.js.map +1 -1
  178. package/dist/ir/validate.js +56 -5
  179. package/dist/ir/validate.js.map +1 -1
  180. package/dist/ir/validate.test.js +17 -1
  181. package/dist/ir/validate.test.js.map +1 -1
  182. package/dist/library/compiler-self-identity.js +1 -1
  183. package/dist/library/compiler-self-identity.test.js +2 -2
  184. package/dist/library/compiler-self-identity.test.js.map +1 -1
  185. package/dist/library/int-infer.d.ts +2 -2
  186. package/dist/library/int-infer.js +10 -7
  187. package/dist/library/int-infer.js.map +1 -1
  188. package/dist/library/int-infer.test.js +44 -0
  189. package/dist/library/int-infer.test.js.map +1 -1
  190. package/dist/library/prepare.js +2 -1
  191. package/dist/library/prepare.js.map +1 -1
  192. package/package.json +20 -20
  193. package/surface-manifest.json +115 -13
@@ -1,4 +1,5 @@
1
1
  import { InternalCompilerError } from "../../errors.js";
2
+ import { SYMBOL_T } from "../../ir/ir.js";
2
3
  import { literalValues } from "../literal-values.js";
3
4
  import { literalUnionArm } from "../union-discriminants.js";
4
5
  /* Expression lowering: the expression dispatch (lowerExpr), literals
@@ -53,13 +54,14 @@ import { classSymbolKeyOf } from "./symbol-fields.js";
53
54
  import { lowerClassMethodValue } from "./class-method-values.js";
54
55
  import { classInstanceOf } from "./class-dynamic-dispatch.js";
55
56
  import { lowerGlobalValue } from "./lower-global-value.js";
56
- import { lowerClassPrototypeData } from "./class-prototypes.js";
57
+ import { lowerClassPrototypeData, lowerClassPrototypeComputedAssignment } from "./class-prototypes.js";
57
58
  import { isClassCallback } from "./class-callbacks.js";
58
59
  import { builtinPrototypeMethod, lowerArrayIsArrayValue, lowerCheckedPredicateValue, lowerNumberParserValue, lowerObjectAssignValue, lowerStringCodesValue } from "./lower-builtin-values.js";
59
60
  import { lowerArrayFromValue } from "./lower-containers.js";
60
61
  import { lowerPerfHooksTypeof } from "./lower-builtins.js";
61
62
  import { jsBindingHasOpenWrites } from "./lower-stmts.js";
62
63
  import { checkedClassInstanceOf } from "./class-construction.js";
64
+ import { lowerModuleNamespaceElement } from "./module-namespace-elements.js";
63
65
  /** A template piece's RAW text (String.raw's contract: escapes stay
64
66
  * characters). 7's client AST ships no rawText at runtime (the typing
65
67
  * declares it; the serialized node data omits it), so the raw span comes
@@ -586,6 +588,8 @@ function lowerExprInner(lowerer, expr) {
586
588
  if (ts.isTypeOfExpression(expr)) {
587
589
  if (lowerer.isStdlibGlobal(expr.expression, "process"))
588
590
  return { kind: "strLit", value: "object", type: STRING, loc };
591
+ if (lowerer.isStdlibGlobal(expr.expression, "fetch"))
592
+ return { kind: "strLit", value: "function", type: STRING, loc };
589
593
  const performance = lowerPerfHooksTypeof(lowerer, expr.expression);
590
594
  if (performance)
591
595
  return performance;
@@ -777,7 +781,7 @@ function lowerExprInner(lowerer, expr) {
777
781
  if (use?.kind === "element") {
778
782
  const key = runtimeOptionalElementKey(use.access.argumentExpression);
779
783
  if (key === null) {
780
- lowerer.unsupported("SC1090", use.access, "a computed read from a runtime-optional capture (use a literal key)");
784
+ return varRef(local.id, local.type, loc);
781
785
  }
782
786
  return runtimeOptionalReceiverRead(lowerer, expr, local, key, loc);
783
787
  }
@@ -1067,6 +1071,8 @@ function lowerExprInner(lowerer, expr) {
1067
1071
  }
1068
1072
  if (lowerer.isStdlibGlobal(expr, "ArrayBuffer"))
1069
1073
  return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc };
1074
+ if (isJsSourceFile(expr.getSourceFile()) && lowerer.isStdlibGlobal(expr, "Array"))
1075
+ return { kind: "libCall", fn: "dyn.arrayConstructor", args: [], type: DYN, loc };
1070
1076
  if (Object.values(BYTES_ELEMENT_NAME).includes(expr.text) && lowerer.isStdlibSymbol(lowerer.checker.getSymbolAtLocation(expr))) {
1071
1077
  return { kind: "libCall", fn: "bytes.constructor", args: [{ kind: "strLit", value: expr.text, type: STRING, loc }], type: DYN, loc };
1072
1078
  }
@@ -1941,6 +1947,13 @@ function lowerExprInner(lowerer, expr) {
1941
1947
  : { kind: "strIntrinsic", method: "length", receiver: recv, args: [], type: F64, loc };
1942
1948
  }
1943
1949
  }
1950
+ if (isJsSourceFile(expr.getSourceFile()) && expr.name.text === "constructor") {
1951
+ const receiver = tryLowerExpression(lowerer, expr.expression);
1952
+ if (receiver?.type.kind === "dyn" || receiver?.type.kind === "object" && !lowerer.classes.get(receiver.type.className)?.fields.has("constructor")) {
1953
+ return { kind: "dynKeyGet", value: lowerer.coerceToExpected(receiver, DYN),
1954
+ key: { kind: "strLit", value: "constructor", type: STRING, loc }, type: DYN, loc };
1955
+ }
1956
+ }
1944
1957
  // `f.name` / `f.length` / own properties on a FUNCTION-typed JS
1945
1958
  // value (the mustCall wrapper's function-instance members): read
1946
1959
  // through the dyn box — the closure's own-property table answers
@@ -2091,7 +2104,8 @@ function lowerExprInner(lowerer, expr) {
2091
2104
  // chain. Scalar-narrowed occurrences bridge via maybeNarrow's
2092
2105
  // validated dynCheck as usual.
2093
2106
  if (recvLowered.type.kind === "dyn" || isJsSourceFile(expr.getSourceFile()) &&
2094
- ["f64", "bool", "bigint", "string", "symbol"].includes(recvLowered.type.kind)) {
2107
+ (["f64", "bool", "bigint", "string", "symbol"].includes(recvLowered.type.kind) ||
2108
+ recvLowered.type.kind === "union" && lowerer.dynConvertible(recvLowered.type))) {
2095
2109
  const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
2096
2110
  const opt = hasOptionalChainGuard(expr.expression);
2097
2111
  return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: lowerer.coerceToExpected(recvLowered, DYN), type: DYN, loc }, expr);
@@ -2540,8 +2554,52 @@ export function maybeNarrow(lowerer, expr, node) {
2540
2554
  // instead of misreading the payload (the dyn boundary's usual stance).
2541
2555
  // Object/array narrowings stay dyn-typed and keep their fences.
2542
2556
  if (expr.type.kind === "dyn") {
2557
+ // An inferred JS member's storage may be deliberately wider than its
2558
+ // declaration (prototype-selected buffers, null-initialized scratch
2559
+ // objects). Extract at the consuming operation, not at the read.
2560
+ if (isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node)) {
2561
+ const receiver = lowerer.mapTypeOf(lowerer.typeOf(node.expression));
2562
+ if (receiver?.kind === "object") {
2563
+ const info = lowerer.classes.get(receiver.className);
2564
+ if (info?.fields.get(node.name.text)?.kind === "dyn" || lowerer.findMethodOn(info ?? null, `get:${node.name.text}`)?.sig.ret.kind === "dyn")
2565
+ return expr;
2566
+ }
2567
+ if (receiver?.kind === "classval") {
2568
+ const info = lowerer.classes.get(receiver.className);
2569
+ if (info && hasRuntimeStatics(info))
2570
+ return expr;
2571
+ }
2572
+ }
2573
+ if (isJsSourceFile(node.getSourceFile()) &&
2574
+ (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node))) {
2575
+ let receiver = node.expression;
2576
+ while (ts.isPropertyAccessExpression(receiver) || ts.isElementAccessExpression(receiver) || ts.isParenthesizedExpression(receiver)) {
2577
+ if (ts.isPropertyAccessExpression(receiver)) {
2578
+ const owner = lowerer.mapTypeOf(lowerer.typeOf(receiver.expression));
2579
+ if (owner?.kind === "object" && lowerer.classes.get(owner.className)?.fields.get(receiver.name.text)?.kind === "dyn")
2580
+ return expr;
2581
+ }
2582
+ receiver = receiver.expression;
2583
+ }
2584
+ if (receiver.kind === ts.SyntaxKind.ThisKeyword && lowerer.resolveThis()?.type.kind === "dyn")
2585
+ return expr;
2586
+ if (ts.isIdentifier(receiver)) {
2587
+ const stored = lowerer.peekLocal(receiver)?.type ?? lowerer.globalOf(receiver)?.type;
2588
+ if (stored?.kind === "dyn")
2589
+ return expr;
2590
+ const symbol = lowerer.resolveValueSymbol(receiver);
2591
+ const bound = symbol && lowerer.implicitParamTypes?.get(symbol);
2592
+ if (bound && lowerer.mapTypeOf(bound)?.kind === "dyn")
2593
+ return expr;
2594
+ }
2595
+ }
2543
2596
  if (isJsSourceFile(node.getSourceFile()) && ts.isIdentifier(node)) {
2544
2597
  const symbol = lowerer.resolveValueSymbol(node);
2598
+ if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isParameter(decl) &&
2599
+ ts.isMethodDeclaration(decl.parent) && lowerer.checkedVirtualJsMethods.has(decl.parent)))
2600
+ return expr;
2601
+ if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isParameter(decl) && ts.isConstructorDeclaration(decl.parent)))
2602
+ return expr;
2545
2603
  if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isVariableDeclaration(decl) && jsBindingHasOpenWrites(lowerer, decl)))
2546
2604
  return expr;
2547
2605
  if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isParameter(decl) && decl.initializer && !decl.type &&
@@ -2605,6 +2663,19 @@ export function maybeNarrow(lowerer, expr, node) {
2605
2663
  }
2606
2664
  if (expr.type.kind !== "union")
2607
2665
  return expr;
2666
+ // Represented fields and index values can include undefined even when
2667
+ // checker inference or lib.d.ts declares strings (RegExp captures).
2668
+ // Only a declaration that represents the optional arm can provide a
2669
+ // trustworthy flow narrowing; otherwise consumers must check it.
2670
+ if ((expr.kind === "recordGet" || expr.kind === "recordKeyGet") &&
2671
+ (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node))) {
2672
+ const receiver = lowerer.mapTypeOf(lowerer.typeOf(node.expression));
2673
+ const shape = receiver?.kind === "record" ? lowerer.shapes.get(receiver.shapeId) : undefined;
2674
+ const declared = expr.kind === "recordGet" ? shape?.fields.find((field) => field.name === expr.field)?.type : shape?.indexValue;
2675
+ if (lowerer.armTag(expr.type.unionId, UNDEFINED_T) >= 0 &&
2676
+ declared && !(declared.kind === "union" && lowerer.armTag(declared.unionId, UNDEFINED_T) >= 0))
2677
+ return expr;
2678
+ }
2608
2679
  if (ts.isIdentifier(node)) {
2609
2680
  const symbol = lowerer.resolveValueSymbol(node);
2610
2681
  if (symbol && lowerer.unstableSwitchBindings.has(symbol))
@@ -3559,14 +3630,16 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3559
3630
  if (receiver.type.kind === "dyn")
3560
3631
  return { kind: "dynKeyGet", value: receiver,
3561
3632
  key: { kind: "strLit", value: name, type: STRING, loc: locOf(expr) }, type: DYN, loc: locOf(expr) };
3562
- // Ordinary arrays keep the existing Array builtin identity token.
3563
- const result = { kind: "strLit", value: "[builtin Array]", type: STRING, loc: locOf(expr) };
3564
- return { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: receiver, loc: locOf(expr) }], result, type: STRING, loc: locOf(expr) };
3633
+ const result = { kind: "libCall", fn: "dyn.arrayConstructor", args: [], type: DYN, loc: locOf(expr) };
3634
+ return { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: receiver, loc: locOf(expr) }], result, type: DYN, loc: locOf(expr) };
3565
3635
  }
3566
3636
  const strictReceiver = (expected) => {
3567
3637
  const value = lowerer.lowerExpr(expr.expression);
3568
- if (value.type.kind === "dyn")
3638
+ if (value.type.kind === "dyn") {
3639
+ if (isJsSourceFile(expr.getSourceFile()) && name === "length")
3640
+ return value;
3569
3641
  return lowerer.coerceInto(expr.expression, value, expected);
3642
+ }
3570
3643
  return lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, expected, name) ?? value;
3571
3644
  };
3572
3645
  if (kind === "child") {
@@ -3670,7 +3743,7 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3670
3743
  // (the child.stdout pattern); a checker-narrowed read extracts
3671
3744
  // through maybeNarrow like any union member.
3672
3745
  if (name === "description") {
3673
- const receiver = lowerer.lowerExpr(expr.expression);
3746
+ const receiver = lowerer.coerceToExpected(lowerer.lowerExpr(expr.expression), SYMBOL_T);
3674
3747
  const type = { kind: "union", unionId: lowerer.unions.intern([STRING, UNDEFINED_T]) };
3675
3748
  return { kind: "libCall", fn: "sym.desc", args: [receiver], type, loc };
3676
3749
  }
@@ -3771,6 +3844,9 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3771
3844
  let receiver = expected?.kind === "bytes"
3772
3845
  ? strictReceiver(expected)
3773
3846
  : lowerer.lowerExpr(expr.expression);
3847
+ if (receiver.type.kind === "dyn")
3848
+ return { kind: "dynCheck", value: { kind: "dynKeyGet", value: receiver,
3849
+ key: { kind: "strLit", value: name, type: STRING, loc: locOf(expr) }, type: DYN, loc: locOf(expr) }, type: F64, loc: locOf(expr) };
3774
3850
  if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0) {
3775
3851
  const present = lowerer.stripUndefinedArm(receiver.type);
3776
3852
  const helper = present.kind === "bytes"
@@ -4020,6 +4096,8 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
4020
4096
  if (arms?.length && arms.every((arm) => arm.kind === "classval"))
4021
4097
  mapped = DYN;
4022
4098
  }
4099
+ if (!expected && isJsSourceFile(expr.getSourceFile()) && expr.elements.some((element) => ts.isSpreadElement(element) && tryLowerExpression(lowerer, element.expression)?.type.kind === "dyn"))
4100
+ mapped = DYN;
4023
4101
  // An EMPTY literal under a CONST ASSERTION whose type queries panicked
4024
4102
  // (tsgo's readonly-[] TupleType conversion — the facade answers `any`,
4025
4103
  // which maps to nothing statically and to an island value under
@@ -4438,6 +4516,9 @@ export function lowerElementAccess(lowerer, expr) {
4438
4516
  const cachedModule = lowerRequireCacheElement(lowerer, expr);
4439
4517
  if (cachedModule)
4440
4518
  return cachedModule;
4519
+ const namespaceElement = lowerModuleNamespaceElement(lowerer, expr);
4520
+ if (namespaceElement)
4521
+ return namespaceElement;
4441
4522
  // Native Web handles use the same supported surface for bracket and dot
4442
4523
  // spellings. Fence a statically-known unsupported key before the generic
4443
4524
  // checked-dynamic element-read path can turn it into a runtime missing
@@ -4540,6 +4621,13 @@ export function lowerElementAccess(lowerer, expr) {
4540
4621
  const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN);
4541
4622
  return lowerer.maybeNarrow({ kind: "dynKeyGet", key, value: boxed, type: DYN, loc }, expr);
4542
4623
  }
4624
+ if (receiver?.type.kind === "array" || receiver?.type.kind === "bytes")
4625
+ receiverIr = receiver.type;
4626
+ if (receiver?.type.kind === "union") {
4627
+ const present = lowerer.stripUndefinedArm(receiver.type);
4628
+ if (present.kind === "array" || present.kind === "bytes")
4629
+ receiverIr = present;
4630
+ }
4543
4631
  }
4544
4632
  // A readonly tuple union under Array.isArray is checker-typed as an
4545
4633
  // intersection with any[], whose structural mapping is not the tuple
@@ -4774,6 +4862,11 @@ export function lowerElementAccess(lowerer, expr) {
4774
4862
  if (receiverIr?.kind !== "array") {
4775
4863
  {
4776
4864
  const receiver = tryLowerExpression(lowerer, expr.expression);
4865
+ if (receiver?.type.kind === "dyn" || receiver && isJsSourceFile(expr.getSourceFile()) &&
4866
+ (receiver.type.kind === "union" || receiver.type.kind === "bytes") && lowerer.dynConvertible(receiver.type)) {
4867
+ const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(expr.argumentExpression), expr.argumentExpression);
4868
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", value: lowerer.coerceInto(expr.expression, receiver, DYN), key, type: DYN, loc: locOf(expr) }, expr);
4869
+ }
4777
4870
  if (receiver?.type.kind === "func" &&
4778
4871
  canBoxFuncIntoDyn(receiver.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
4779
4872
  const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(expr.argumentExpression), expr.argumentExpression);
@@ -5353,7 +5446,9 @@ export function lowerElementCompound(lowerer, expr, op) {
5353
5446
  const oldLocal = lowerer.declareHiddenLocal("%compoundOld", oldValue.type);
5354
5447
  const oldRef = () => varRef(oldLocal.id, oldValue.type, loc);
5355
5448
  const rhs = lowerer.lowerExpr(expr.right);
5356
- const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
5449
+ const numericRhs = rhs.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()) && receiver.type.kind === "bytes"
5450
+ ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [rhs], type: F64, loc }
5451
+ : lowerOptionalNumber(lowerer, rhs, loc);
5357
5452
  const elementType = receiver.type.kind === "array" ? receiver.type.elem : F64;
5358
5453
  const valueType = elementType.kind === "union" && lowerer.armTag(elementType.unionId, UNDEFINED_T) >= 0
5359
5454
  ? lowerer.stripUndefinedArm(elementType)
@@ -5432,6 +5527,18 @@ export function lowerNativeFunctionAssignment(lowerer, expr) {
5432
5527
  (!ts.isPropertyAccessExpression(target) && !ts.isElementAccessExpression(target)) ||
5433
5528
  target.questionDotToken)
5434
5529
  return null;
5530
+ // Checked constructors share the callable's live property descriptors.
5531
+ const classType = storedClassValueType(lowerer, target.expression);
5532
+ const staticClass = classType?.kind === "classval" ? lowerer.classes.get(classType.className) : undefined;
5533
+ if (staticClass && hasRuntimeStatics(staticClass)) {
5534
+ return lowerDynMemberAssignment(lowerer, expr, lowerer.lowerExprExpecting(target.expression, DYN));
5535
+ }
5536
+ // Other class statics and prototype tables have their own paths.
5537
+ // Probing them as ordinary functions could materialize unused methods.
5538
+ if (lowerer.exactClassOfReceiver(target.expression) ||
5539
+ ts.isPropertyAccessExpression(target.expression) && target.expression.name.text === "prototype" &&
5540
+ lowerer.exactClassOfReceiver(target.expression.expression))
5541
+ return null;
5435
5542
  const receiver = tryLowerExpression(lowerer, target.expression);
5436
5543
  if (receiver?.type.kind !== "func" ||
5437
5544
  !canBoxFuncIntoDyn(receiver.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id)))
@@ -5445,6 +5552,9 @@ export function lowerNativeFunctionAssignment(lowerer, expr) {
5445
5552
  return lowerDynMemberAssignment(lowerer, expr, boxed);
5446
5553
  }
5447
5554
  export function lowerElementWrite(lowerer, expr) {
5555
+ const prototype = lowerClassPrototypeComputedAssignment(lowerer, expr);
5556
+ if (prototype)
5557
+ return { kind: "exprStmt", expr: prototype, loc: locOf(expr) };
5448
5558
  const target = expr.left;
5449
5559
  fenceNodeModuleMutation(lowerer, target, "assignment");
5450
5560
  const globalKey = globalSymbolKey(lowerer, target.expression, target.argumentExpression);
@@ -5502,6 +5612,12 @@ export function lowerElementWrite(lowerer, expr) {
5502
5612
  }
5503
5613
  }
5504
5614
  let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(target.expression));
5615
+ if (isJsSourceFile(expr.getSourceFile()) && checkedReceiver) {
5616
+ const represented = checkedReceiver.type.kind === "union"
5617
+ ? lowerer.stripUndefinedArm(checkedReceiver.type) : checkedReceiver.type;
5618
+ if (represented.kind === "array" || represented.kind === "bytes")
5619
+ receiverIr = represented;
5620
+ }
5505
5621
  if (receiverIr === null || receiverIr.kind === "dyn") {
5506
5622
  const receiver = tryLowerExpression(lowerer, target.expression);
5507
5623
  if (receiver?.type.kind === "array")
@@ -6278,78 +6394,86 @@ export function lowerPrefixUnary(lowerer, expr) {
6278
6394
  }
6279
6395
  lowerer.unsupported("SC1090", expr, `syntax '${ts.syntaxKindName(expr.kind)}'`);
6280
6396
  }
6281
- /** `x++`/`x--`/`++x`/`--x` in EXPRESSION position — the incDec node:
6282
- * read, write ±1, yield old (postfix) or new (prefix) value. Receivers
6283
- * are f64 LOCALS or module globals (captured/boxed included — the
6284
- * backend reads/writes through the box); record fields and array
6285
- * elements keep the fence in value position (statement-position `obj.f++`
6286
- * still desugars through the compound-field path). */
6287
- function lowerIncDec(lowerer, expr, prefix) {
6397
+ /** Member updates evaluate the reference once, convert its old value with
6398
+ * ToNumeric, store the new value, then yield the old or new numeric value. */
6399
+ export function lowerIncDec(lowerer, expr, prefix) {
6288
6400
  const loc = locOf(expr);
6289
6401
  const op = expr.operator === ts.SyntaxKind.PlusPlusToken ? "+" : "-";
6290
6402
  if (!ts.isIdentifier(expr.operand)) {
6291
- if (ts.isPropertyAccessExpression(expr.operand)) {
6292
- const target = staticFieldWriteTarget(lowerer, expr.operand);
6403
+ const access = expr.operand;
6404
+ if (!ts.isPropertyAccessExpression(access) && !ts.isElementAccessExpression(access) || access.questionDotToken) {
6405
+ lowerer.unsupported("SC1045", expr, "increment/decrement of this target");
6406
+ }
6407
+ fenceNodeModuleMutation(lowerer, access, "assignment");
6408
+ if (ts.isPropertyAccessExpression(access)) {
6409
+ const target = staticFieldWriteTarget(lowerer, access);
6293
6410
  if (target) {
6294
6411
  if (target.type.kind !== "f64")
6295
6412
  lowerer.unsupported("SC1043", expr);
6296
6413
  return { kind: "incDec", op, prefix, localId: target.id, type: F64, loc };
6297
6414
  }
6298
6415
  }
6299
- // CLASS-FIELD receivers (`if (--this.limit === 0)` — countdown.js's
6300
- // dec(); `--this[kLimit]` — its symbol-keyed spelling): a single-
6301
- // evaluation read-modify-write over the instance, yielding old/new
6302
- // like the local form. f64 fields are JS-exact; CHECKED-DYNAMIC
6303
- // fields (implicit-any ctor assignments in JS) take the dyn
6304
- // arithmetic stance — the number validates out (dynCheck's
6305
- // catchable TypeError, never a silent ToNumber; SEMANTICS.md),
6306
- // computes, and boxes back. Array elements, record fields, and
6307
- // accessor-backed names keep the fence.
6308
- if ((ts.isPropertyAccessExpression(expr.operand) || ts.isElementAccessExpression(expr.operand)) &&
6309
- !expr.operand.questionDotToken) {
6310
- if (ts.isPropertyAccessExpression(expr.operand) && isJsSourceFile(expr.getSourceFile())) {
6311
- const receiver = lowerer.lowerExpr(expr.operand.expression);
6312
- if (receiver.type.kind === "dyn") {
6313
- const local = lowerer.declareHiddenLocal("%incrementReceiver", DYN);
6314
- const old = lowerer.declareHiddenLocal("%incrementOld", DYN);
6315
- const next = lowerer.declareHiddenLocal("%incrementNext", DYN);
6316
- const value = varRef(local.id, DYN, loc);
6317
- const key = { kind: "strLit", value: expr.operand.name.text, type: STRING, loc };
6318
- return { kind: "seqExpr", stmts: [
6319
- { kind: "varDecl", localId: local.id, init: receiver, loc },
6320
- { kind: "varDecl", localId: old.id, init: { kind: "libCall", fn: "dyn.toNumeric", args: [
6321
- { kind: "dynKeyGet", value, key, type: DYN, loc },
6322
- ], type: DYN, loc }, loc },
6323
- { kind: "varDecl", localId: next.id, init: { kind: "libCall", fn: "dyn.increment", args: [varRef(old.id, DYN, loc),
6324
- { kind: "boolLit", value: op === "+", type: BOOL, loc }], type: DYN, loc }, loc },
6325
- { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [value, key, varRef(next.id, DYN, loc)], type: VOID, loc }, loc },
6326
- ], result: varRef(prefix ? next.id : old.id, DYN, loc), type: DYN, loc };
6327
- }
6328
- }
6329
- const target = ts.isPropertyAccessExpression(expr.operand)
6330
- ? lowerer.fieldTarget(expr.operand)
6331
- : symbolFieldTarget(lowerer, expr.operand);
6332
- if (target?.container === "class" &&
6333
- !RUNTIME_ERROR_CLASSES.has(target.className) &&
6334
- (target.fieldType.kind === "f64" ||
6335
- (target.fieldType.kind === "dyn" && isJsSourceFile(expr.getSourceFile())))) {
6336
- return {
6337
- kind: "fieldIncDec",
6338
- op,
6339
- prefix,
6340
- obj: target.obj,
6341
- className: target.className,
6342
- field: target.field,
6343
- fieldDyn: target.fieldType.kind === "dyn",
6344
- type: F64,
6345
- loc,
6346
- };
6347
- }
6348
- }
6349
- // Other field/element receivers need a read-modify-write over a
6350
- // computed receiver — statement position has that desugar; value
6351
- // position does not yet. The message names the working forms.
6352
- lowerer.unsupported("SC1045", expr, "increment/decrement of record fields or elements in expression position");
6416
+ const stmts = [];
6417
+ const save = (value, name) => {
6418
+ const local = lowerer.declareHiddenLocal(name, value.type);
6419
+ stmts.push({ kind: "varDecl", localId: local.id, init: value, loc });
6420
+ return varRef(local.id, value.type, loc);
6421
+ };
6422
+ const finish = (read, write) => {
6423
+ const old = save(read.type.kind === "f64" ? read : {
6424
+ kind: "libCall", fn: "dyn.toNumeric", args: [lowerer.coerceInto(access, read, DYN)], type: DYN, loc,
6425
+ }, "%incrementOld");
6426
+ const next = save(old.type.kind === "f64" ? {
6427
+ kind: "bin", op, left: old, right: { kind: "numLit", value: 1, type: F64, loc }, type: F64, loc,
6428
+ } : {
6429
+ kind: "libCall", fn: "dyn.increment", args: [old, { kind: "boolLit", value: op === "+", type: BOOL, loc }], type: DYN, loc,
6430
+ }, "%incrementNext");
6431
+ stmts.push(write(next));
6432
+ const result = prefix ? next : old;
6433
+ return { kind: "seqExpr", stmts, result, type: result.type, loc };
6434
+ };
6435
+ const raw = lowerer.lowerExpr(access.expression);
6436
+ // Keep native array/byte storage: boxing a native array can create a
6437
+ // checked view, which is not a substitute for updating its slot.
6438
+ if (ts.isElementAccessExpression(access) && (raw.type.kind === "array" || raw.type.kind === "bytes")) {
6439
+ const receiver = save(raw, "%incrementArray");
6440
+ const rawIndex = lowerer.lowerExpr(access.argumentExpression);
6441
+ const index = save(rawIndex.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())
6442
+ ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [rawIndex], type: F64, loc } : rawIndex, "%incrementIndex");
6443
+ if (index.type.kind !== "f64")
6444
+ lowerer.unsupported("SC1090", access, "incrementing non-number array keys");
6445
+ const type = raw.type;
6446
+ const read = type.kind === "array"
6447
+ ? arrayValueRead(lowerer, receiver, index, type.elem, loc)
6448
+ : { kind: "bytesIntrinsic", method: "get", receiver, args: [index], type: F64, loc };
6449
+ return finish(read, (value) => type.kind === "array"
6450
+ ? arrayValueStore(lowerer, receiver, index, lowerer.coerceInto(access, value, type.elem), type.elem, loc)
6451
+ : { kind: "bytesSet", arr: receiver, index, value: lowerer.coerceInto(access, value, F64), loc });
6452
+ }
6453
+ if (isJsSourceFile(expr.getSourceFile()) && raw.type.kind === "dyn") {
6454
+ const receiver = save(raw, "%incrementReceiver");
6455
+ const key = save(ts.isElementAccessExpression(access)
6456
+ ? lowerer.lowerExprExpecting(access.argumentExpression, DYN)
6457
+ : lowerer.coerceInto(access, { kind: "strLit", value: access.name.text, type: STRING, loc }, DYN), "%incrementKey");
6458
+ return finish({ kind: "dynKeyGet", value: receiver, key, type: DYN, loc }, (value) => ({
6459
+ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySetComputed", args: [receiver, key, lowerer.coerceInto(access, value, DYN)], type: VOID, loc }, loc,
6460
+ }));
6461
+ }
6462
+ const target = ts.isPropertyAccessExpression(access) ? lowerer.fieldTarget(access) : symbolFieldTarget(lowerer, access);
6463
+ if (target && lowerer.dynConvertible(target.fieldType)) {
6464
+ target.obj = save(target.obj, "%incrementReceiver");
6465
+ return finish(lowerer.fieldGetExpr(target, loc, access), (value) => lowerer.fieldSetStmt(target, lowerer.coerceInto(access, value, target.fieldType), loc, access));
6466
+ }
6467
+ if (isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(raw.type)) {
6468
+ const receiver = save(lowerer.coerceInto(access.expression, raw, DYN), "%incrementReceiver");
6469
+ const key = save(ts.isElementAccessExpression(access)
6470
+ ? lowerer.lowerExprExpecting(access.argumentExpression, DYN)
6471
+ : lowerer.coerceInto(access, { kind: "strLit", value: access.name.text, type: STRING, loc }, DYN), "%incrementKey");
6472
+ return finish({ kind: "dynKeyGet", value: receiver, key, type: DYN, loc }, (value) => ({
6473
+ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySetComputed", args: [receiver, key, lowerer.coerceInto(access, value, DYN)], type: VOID, loc }, loc,
6474
+ }));
6475
+ }
6476
+ lowerer.unsupported("SC1045", expr, "increment/decrement of this member target");
6353
6477
  }
6354
6478
  const target = lowerer.resolveWritable(expr.operand);
6355
6479
  if (!target) {
@@ -6429,6 +6553,7 @@ export function lowerCompoundValueToTarget(lowerer, expr, compound, target) {
6429
6553
  const loc = locOf(expr);
6430
6554
  const read = { kind: "varRef", localId: target.id, type: target.type, loc: locOf(expr.left) };
6431
6555
  const rhs = lowerer.lowerExpr(expr.right);
6556
+ const numericRead = lowerOptionalNumber(lowerer, read, loc);
6432
6557
  const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
6433
6558
  if (target.type.kind === "jsval" || rhs.type.kind === "jsval") {
6434
6559
  const JS_COMPOUND = { "+": "add", "-": "sub", "*": "mul", "/": "div", "%": "mod", "**": "pow" };
@@ -6445,8 +6570,8 @@ export function lowerCompoundValueToTarget(lowerer, expr, compound, target) {
6445
6570
  if (compound === "+" && target.type.kind === "string") {
6446
6571
  return { kind: "strConcat", left: read, right: lowerer.ensureString(rhs, expr.right), type: STRING, loc };
6447
6572
  }
6448
- if (target.type.kind === "f64" && numericRhs.type.kind === "f64") {
6449
- return { kind: "bin", op: compound, left: read, right: numericRhs, type: F64, loc };
6573
+ if (numericRead.type.kind === "f64" && numericRhs.type.kind === "f64") {
6574
+ return lowerer.coerceInto(expr, { kind: "bin", op: compound, left: numericRead, right: numericRhs, type: F64, loc }, target.type);
6450
6575
  }
6451
6576
  if ((target.type.kind === "dyn" || rhs.type.kind === "dyn") &&
6452
6577
  isJsSourceFile(expr.getSourceFile())) {
@@ -6467,6 +6592,9 @@ export function lowerBinary(lowerer, expr) {
6467
6592
  return cacheHas;
6468
6593
  if (op === ts.SyntaxKind.EqualsToken || (op >= ts.SyntaxKind.FirstCompoundAssignment && op <= ts.SyntaxKind.LastCompoundAssignment)) {
6469
6594
  if (op === ts.SyntaxKind.EqualsToken) {
6595
+ const prototypeWrite = lowerClassPrototypeComputedAssignment(lowerer, expr);
6596
+ if (prototypeWrite)
6597
+ return prototypeWrite;
6470
6598
  const callableWrite = lowerNativeFunctionAssignment(lowerer, expr);
6471
6599
  if (callableWrite)
6472
6600
  return callableWrite;
@@ -6731,8 +6859,14 @@ export function lowerBinary(lowerer, expr) {
6731
6859
  }
6732
6860
  const strictEquality = op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
6733
6861
  op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6734
- const left = strictEquality ? lowerAbsenceProbe(lowerer, expr.left) ?? lowerer.lowerExpr(expr.left) : lowerer.lowerExpr(expr.left);
6735
- const right = strictEquality ? lowerAbsenceProbe(lowerer, expr.right) ?? lowerer.lowerExpr(expr.right) : lowerer.lowerExpr(expr.right);
6862
+ let left = strictEquality ? lowerAbsenceProbe(lowerer, expr.left) ?? lowerer.lowerExpr(expr.left) : lowerer.lowerExpr(expr.left);
6863
+ let right = strictEquality ? lowerAbsenceProbe(lowerer, expr.right) ?? lowerer.lowerExpr(expr.right) : lowerer.lowerExpr(expr.right);
6864
+ if (strictEquality && (left.type.kind === "caught" || right.type.kind === "caught")) {
6865
+ if (left.type.kind === "caught")
6866
+ left = { kind: "caughtToDyn", value: left, type: DYN, loc: left.loc };
6867
+ if (right.type.kind === "caught")
6868
+ right = { kind: "caughtToDyn", value: right, type: DYN, loc: right.loc };
6869
+ }
6736
6870
  if (left.type.kind === "dyn" || right.type.kind === "dyn") {
6737
6871
  // The narrowing unit comparisons ARE answerable on unknown: `v ===
6738
6872
  // undefined` / `v !== null` test the dyn node's kind directly, and
@@ -6760,7 +6894,7 @@ export function lowerBinary(lowerer, expr) {
6760
6894
  const dynSide = left.type.kind === "dyn" ? left : right;
6761
6895
  const scalarSide = dynSide === left ? right : left;
6762
6896
  const reference = scalarSide.type;
6763
- if ((isDynTypedRefType(reference) || reference.kind === "record" || reference.kind === "array" ||
6897
+ if ((reference.kind === "object" || reference.kind === "record" || reference.kind === "array" ||
6764
6898
  reference.kind === "bytes" || reference.kind === "func" || reference.kind === "classval" || reference.kind === "regex" || reference.kind === "url" ||
6765
6899
  reference.kind === "bigint" || reference.kind === "symbol" || reference.kind === "map" || reference.kind === "set" || reference.kind === "union" || DYN_HANDLE_KINDS.has(reference.kind)) &&
6766
6900
  lowerer.dynConvertible(reference)) {
@@ -7976,6 +8110,8 @@ function lowerDynTypeofTest(lowerer, expr, loc) {
7976
8110
  for (const [a, b] of operandPairs(expr)) {
7977
8111
  if (!ts.isTypeOfExpression(a))
7978
8112
  continue;
8113
+ if (lowerer.isStdlibGlobal(a.expression, "fetch"))
8114
+ continue;
7979
8115
  // Non-literal comparisons use the ordinary typeof string value,
7980
8116
  // preserving both operands' evaluation order without a narrowing test.
7981
8117
  if (!ts.isStringLiteral(b))
@@ -9338,11 +9474,10 @@ function matchAllRegexOf(lowerer, e) {
9338
9474
  * member fence);
9339
9475
  * - a nullable receiver (the JS `s.match(re).groups` idiom) throws
9340
9476
  * Node's exact TypeError on the unit arms;
9341
- * - every declared name is a key (nonparticipating groups hold "" —
9342
- * divergence 51's rule, exactly the slice elements they project);
9477
+ * - every declared name is a key (nonparticipating groups hold undefined,
9478
+ * exactly the slice elements they project);
9343
9479
  * - ES2025 duplicate names (distinct alternatives) project the first
9344
- * participating occurrence: at most one participates, and a
9345
- * participating-empty "" falls through to slots that are "" anyway;
9480
+ * participating occurrence, including a participating empty string;
9346
9481
  * - key order is group declaration order (declaredOrder), Node's own.
9347
9482
  * The record has no prototype — Node's groups object is null-prototype,
9348
9483
  * observable only through util.inspect's "[Object: null prototype]"
@@ -9419,12 +9554,8 @@ function matchSliceUnionArms(lowerer, unionId) {
9419
9554
  }
9420
9555
  return { arrTag, units };
9421
9556
  }
9422
- /** The groups-record projection call: the result is the CHECKER'S OWN
9423
- * shape — `{ [key: string]: string }`, the hybrid index-signature
9424
- * record with no declared fields — holding one OVERFLOW entry per
9425
- * group name in declaration order, so every downstream consumer
9426
- * (recordKeyGet reads, Object.keys, JSON, the `| undefined` union
9427
- * wrap) meets exactly the type tsc reported. One lifted helper per
9557
+ /** The groups-record projection holds string | undefined values, including
9558
+ * an own overflow entry for every nonparticipating capture. One lifted helper per
9428
9559
  * (index list, receiver representation) builds it; see
9429
9560
  * lowerMatchGroupsRead for the semantics the body encodes. */
9430
9561
  export function lowerGroupsProjection(lowerer, recv, groups, loc) {
@@ -9442,7 +9573,8 @@ export function lowerGroupsProjection(lowerer, recv, groups, loc) {
9442
9573
  order.push(g.name);
9443
9574
  }
9444
9575
  }
9445
- const shapeId = lowerer.shapes.intern([], false, STRING);
9576
+ const valueType = arrayValueType(lowerer, STRING);
9577
+ const shapeId = lowerer.shapes.intern([], false, valueType);
9446
9578
  const recordT = { kind: "record", shapeId };
9447
9579
  const recvKey = recv.type.kind === "union" ? recv.type.unionId : "arr";
9448
9580
  const key = `regexgroups:${shapeId}:${groups.map((g) => `${g.name}=${g.index}`).join(",")}:${recvKey}`;
@@ -9484,7 +9616,7 @@ export function lowerGroupsProjection(lowerer, recv, groups, loc) {
9484
9616
  body.push({ kind: "varDecl", localId: "arr.0", init: narrowed, loc });
9485
9617
  mRef = { kind: "varRef", localId: "arr.0", type: arrayOf(STRING), loc };
9486
9618
  }
9487
- const elem = (index) => ({ kind: "arrayGet", arr: mRef, index: numLit(index, loc), type: STRING, loc });
9619
+ const elem = (index) => arrayValueRead(lowerer, mRef, numLit(index, loc), STRING, loc);
9488
9620
  const values = new Map();
9489
9621
  order.forEach((name, i) => {
9490
9622
  const idxs = indicesByName.get(name);
@@ -9492,16 +9624,16 @@ export function lowerGroupsProjection(lowerer, recv, groups, loc) {
9492
9624
  values.set(name, elem(idxs[0]));
9493
9625
  return;
9494
9626
  }
9495
- // Duplicates: the first non-"" occurrence (at most one
9496
- // participates; all-"" answers "" like any nonparticipating slot).
9627
+ // Duplicates: the first participating occurrence. Empty strings
9628
+ // participate; undefined means this alternative did not match.
9497
9629
  const id = `g${i}.0`;
9498
- locals.push({ id, name: `g${i}`, type: STRING, mutable: true });
9630
+ locals.push({ id, name: `g${i}`, type: valueType, mutable: true });
9499
9631
  body.push({ kind: "varDecl", localId: id, init: elem(idxs[0]), loc });
9500
- const gRef = { kind: "varRef", localId: id, type: STRING, loc };
9632
+ const gRef = { kind: "varRef", localId: id, type: valueType, loc };
9501
9633
  for (const idx of idxs.slice(1)) {
9502
9634
  body.push({
9503
9635
  kind: "if",
9504
- cond: { kind: "strEq", negated: false, left: gRef, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc },
9636
+ cond: { kind: "unionIsTag", unionId: valueType.kind === "union" ? valueType.unionId : "", tag: valueType.kind === "union" ? lowerer.armTag(valueType.unionId, UNDEFINED_T) : -1, negated: false, value: gRef, type: BOOL, loc },
9505
9637
  then: [{ kind: "assign", localId: id, value: elem(idx), loc }],
9506
9638
  else_: null,
9507
9639
  loc,
@@ -9595,6 +9727,15 @@ export function lowerFieldRead(lowerer, expr) {
9595
9727
  if (receiverIr?.kind === "record") {
9596
9728
  const shape = lowerer.shapes.get(receiverIr.shapeId);
9597
9729
  if (shape?.indexValue && !shape.fields.some((f) => f.name === expr.name.text)) {
9730
+ const actual = tryLowerExpression(lowerer, expr.expression);
9731
+ if (actual?.type.kind === "dyn")
9732
+ return { kind: "dynKeyGet", value: actual,
9733
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) }, type: DYN, loc: locOf(expr) };
9734
+ if (actual?.type.kind === "record") {
9735
+ const field = lowerer.shapes.get(actual.type.shapeId)?.fields.find((field) => field.name === expr.name.text);
9736
+ if (field)
9737
+ return { kind: "recordGet", obj: actual, shapeId: actual.type.shapeId, field: field.name, type: field.type, loc: locOf(expr) };
9738
+ }
9598
9739
  lowerer.unsupported("SC1090", expr, `dot access to index-signature keys (spell it r["${expr.name.text}"] — brackets are the index-signature form)`);
9599
9740
  }
9600
9741
  }
@@ -9875,6 +10016,10 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
9875
10016
  * or accessor dispatch. Resolve the property from the represented class;
9876
10017
  * the optional checker view must not turn its getter into a record read. */
9877
10018
  function classFieldTarget(lowerer, receiverNode, receiverType, fieldName, saved) {
10019
+ const represented = saved ?? lowerer.lowerExpr(receiverNode);
10020
+ if (represented.type.kind === "dyn" && isJsSourceFile(receiverNode.getSourceFile()) && !fieldName.startsWith("#")) {
10021
+ return { container: "dynamic", obj: represented, field: fieldName, fieldType: DYN };
10022
+ }
9878
10023
  const info = lowerer.classes.get(receiverType.className);
9879
10024
  if (!info) {
9880
10025
  // A receiver typed as a class whose collection deferred: the
@@ -9884,14 +10029,14 @@ function classFieldTarget(lowerer, receiverNode, receiverType, fieldName, saved)
9884
10029
  }
9885
10030
  const fieldType = info.fields.get(fieldName);
9886
10031
  const lowerObjectReceiver = () => {
9887
- let obj = saved ?? lowerer.lowerExpr(receiverNode);
10032
+ let obj = represented;
9888
10033
  if (obj.type.kind === "union") {
9889
10034
  const helper = lowerer.narrowedArmHelper(obj.type.unionId, receiverType, locOf(receiverNode));
9890
10035
  obj = helper
9891
10036
  ? { kind: "call", callee: helper, args: [obj], type: receiverType, loc: locOf(receiverNode) }
9892
10037
  : lowerer.maybeNarrow(obj, receiverNode);
9893
10038
  }
9894
- return obj.type.kind === "dyn" ? lowerer.coerceInto(receiverNode, obj, receiverType) : obj;
10039
+ return lowerer.coerceInto(receiverNode, obj, receiverType);
9895
10040
  };
9896
10041
  if (fieldName === "cause" && receiverType.className !== "%DOMException") {
9897
10042
  let root = info;
@@ -9903,6 +10048,11 @@ function classFieldTarget(lowerer, receiverNode, receiverType, fieldName, saved)
9903
10048
  }
9904
10049
  if (fieldType) {
9905
10050
  const obj = lowerObjectReceiver();
10051
+ if (isJsSourceFile(receiverNode.getSourceFile()) && !fieldName.startsWith("#") &&
10052
+ [...lowerer.classes.values()].some((candidate) => lowerer.isSubclassOf(candidate.def.name, receiverType.className) &&
10053
+ (candidate.methods.has(`get:${fieldName}`) || candidate.methods.has(`set:${fieldName}`)))) {
10054
+ return { container: "dynamic", obj: lowerer.coerceToExpected(obj, DYN), field: fieldName, fieldType: DYN };
10055
+ }
9906
10056
  return { container: "class", obj, className: receiverType.className, field: fieldName, fieldType };
9907
10057
  }
9908
10058
  // Accessor property: either half declared anywhere on the chain
@@ -9920,6 +10070,12 @@ function classFieldTarget(lowerer, receiverNode, receiverType, fieldName, saved)
9920
10070
  fieldType: getF ? getF.sig.ret : setF.sig.params[0].type,
9921
10071
  };
9922
10072
  }
10073
+ if (info.decl && isJsSourceFile(info.decl.getSourceFile())) {
10074
+ const property = lowerer.checker.getPropertyOfType(lowerer.checker.getTypeAtLocation(receiverNode), fieldName);
10075
+ if (property && lowerer.checker.declarationsOf(property).some((declaration) => ts.isPropertyAccessExpression(declaration) || ts.isBinaryExpression(declaration) || ts.isElementAccessExpression(declaration))) {
10076
+ return { container: "dynamic", obj: lowerer.coerceToExpected(lowerObjectReceiver(), DYN), field: fieldName, fieldType: DYN };
10077
+ }
10078
+ }
9923
10079
  return null;
9924
10080
  }
9925
10081
  /** A structural assertion can leave the value in its original class
@@ -9945,6 +10101,8 @@ function representedClassFieldTarget(lowerer, receiverNode, fieldName, value) {
9945
10101
  * receivers) a declared accessor property. Returns the pieces of a
9946
10102
  * fieldSet/recordSet/accessor-call (minus value/kind) or null. */
9947
10103
  export function fieldTarget(lowerer, access) {
10104
+ if (lowerer.cjsLocalModuleBindingOf(access.expression))
10105
+ return null;
9948
10106
  if (lowerer.chainBlocked(access))
9949
10107
  return null;
9950
10108
  if (access.name.text === "stackTraceLimit" && lowerer.isStdlibGlobal(access.expression, "Error")) {
@@ -10059,8 +10217,17 @@ export function fieldTarget(lowerer, access) {
10059
10217
  return declaredArms.every((a) => ivArms.some((b) => typeEquals(a, b)));
10060
10218
  };
10061
10219
  if (!nameSym || nameSym.name === ts.InternalSymbolName.Index || canonicalized()) {
10062
- const obj = lowerer.lowerExpr(access.expression);
10063
- return { container: "recordOvf", obj, shapeId: receiverIr.shapeId, field: access.name.text, fieldType: shape.indexValue };
10220
+ let obj = lowerer.lowerExpr(access.expression);
10221
+ if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
10222
+ const present = lowerer.stripUndefinedArm(obj.type);
10223
+ obj = present.kind === "record" ? lowerer.runtimeOptionalPropertyReceiver(access.expression, obj, present, access.name.text) ?? obj : lowerer.maybeNarrow(obj, access.expression);
10224
+ }
10225
+ if (obj.type.kind !== "record")
10226
+ return null;
10227
+ const actualShape = lowerer.shapes.get(obj.type.shapeId);
10228
+ if (!actualShape?.indexValue)
10229
+ return null;
10230
+ return { container: "recordOvf", obj, shapeId: obj.type.shapeId, field: access.name.text, fieldType: actualShape.indexValue };
10064
10231
  }
10065
10232
  }
10066
10233
  return null;
@@ -10158,6 +10325,11 @@ function symbolFieldTarget(lowerer, expr) {
10158
10325
  * tsc-clean (the property types as the setter's param), but Node yields
10159
10326
  * undefined, which these property types cannot represent. */
10160
10327
  export function fieldGetExpr(lowerer, target, loc, blame) {
10328
+ if (target.container === "dynamic")
10329
+ return {
10330
+ kind: "dynKeyGet", value: target.obj,
10331
+ key: { kind: "strLit", value: target.field, type: STRING, loc }, type: DYN, loc,
10332
+ };
10161
10333
  if (target.container === "class" && (target.field === "message" || target.field === "name") && (target.className === "%Error" || lowerer.isSubclassOf(target.className, "%Error")))
10162
10334
  return errorPropertyRead(lowerer, target.obj, target.field);
10163
10335
  if (target.container === "errorStackLimit") {
@@ -10267,6 +10439,11 @@ export function fieldGetExpr(lowerer, target, loc, blame) {
10267
10439
  * call). A write to a getter-only property never gets here in a clean
10268
10440
  * program (tsc's TS2540 is the fence); the rejection is the backstop. */
10269
10441
  export function fieldSetStmt(lowerer, target, value, loc, blame) {
10442
+ if (target.container === "dynamic")
10443
+ return {
10444
+ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet",
10445
+ args: [target.obj, { kind: "strLit", value: target.field, type: STRING, loc }, lowerer.coerceToExpected(value, DYN)], type: VOID, loc }, loc,
10446
+ };
10270
10447
  if (target.container === "class" && (target.field === "message" || target.field === "name") && (target.className === "%Error" || lowerer.isSubclassOf(target.className, "%Error")))
10271
10448
  return errorPropertyWrite(lowerer, target.obj, value, target.field);
10272
10449
  if (target.container === "errorStackLimit") {
@@ -10434,7 +10611,7 @@ export function lowerPromiseAllTupleCall(lowerer, call, access) {
10434
10611
  const loc = locOf(call);
10435
10612
  const inner = first.inner;
10436
10613
  const entryT = { kind: "promise", inner };
10437
- const elems = argNode.elements.map((el) => lowerer.lowerExpr(el));
10614
+ const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, entryT));
10438
10615
  const entries = { kind: "arrayLit", elems, type: arrayOf(entryT), loc };
10439
10616
  const type = inner.kind === "void"
10440
10617
  ? { kind: "promise", inner: VOID }
@@ -10466,7 +10643,10 @@ function lowerFieldCompoundValue(lowerer, access, op, rhsNode, loc) {
10466
10643
  // NaN — SEMANTICS.md), combine, write back (dyn.keySet). The
10467
10644
  // receiver and member read are saved before evaluating the RHS.
10468
10645
  if (ts.isPropertyAccessExpression(access)) {
10469
- const probed = tryLowerExpression(lowerer, access.expression);
10646
+ let probed = tryLowerExpression(lowerer, access.expression);
10647
+ const staticClass = probed?.type.kind === "classval" ? lowerer.classes.get(probed.type.className) : undefined;
10648
+ if (probed && staticClass && hasRuntimeStatics(staticClass))
10649
+ probed = lowerer.coerceToExpected(probed, DYN);
10470
10650
  if (probed?.type.kind === "dyn") {
10471
10651
  const receiver = save(probed, "%compoundReceiver");
10472
10652
  const key = { kind: "strLit", value: access.name.text, type: STRING, loc: locOf(access.name) };
@@ -10475,6 +10655,14 @@ function lowerFieldCompoundValue(lowerer, access, op, rhsNode, loc) {
10475
10655
  const rhs = save(rhsNode
10476
10656
  ? lowerer.lowerExpr(rhsNode)
10477
10657
  : { kind: "numLit", value: 1, type: F64, loc }, "%compoundRhs");
10658
+ if (isJsSourceFile(access.getSourceFile()) && lowerer.dynConvertible(rhs.type)) {
10659
+ const numeric = ["-", "*", "/", "%", "**"].includes(op);
10660
+ const right = lowerer.coerceToExpected(rhs, DYN);
10661
+ const value = save({ kind: "libCall", fn: op === "+" ? "dyn.add" : numeric ? "dyn.arithmetic" : "dyn.bitwise",
10662
+ args: op === "+" ? [read, right] : [read, right, { kind: "strLit", value: op, type: STRING, loc }], type: DYN, loc }, "%compoundResult");
10663
+ body.push({ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [receiver, key, value], type: VOID, loc }, loc });
10664
+ return { kind: "seqExpr", stmts: body, result: value, type: DYN, loc };
10665
+ }
10478
10666
  const numericRhs = rhs.type.kind === "dyn"
10479
10667
  ? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc }
10480
10668
  : lowerOptionalNumber(lowerer, rhs, loc);
@@ -10512,35 +10700,12 @@ function lowerFieldCompoundValue(lowerer, access, op, rhsNode, loc) {
10512
10700
  value = { kind: "bin", op, left: read, right: numericRhs, type: F64, loc };
10513
10701
  }
10514
10702
  else if (target.fieldType.kind === "dyn" &&
10515
- isJsSourceFile(access.getSourceFile()) &&
10516
- (op === "+" || op === "-" || op === "*" || op === "/" || op === "%" || op === "**")) {
10517
- // CHECKED-DYNAMIC fields (implicit-any ctor assignments —
10518
- // countdown.js's `this[kLimit]` shape with a plain name) follow the
10519
- // JS dyn-operand binary stance: arithmetic CHECKS the dyn side to
10520
- // number (dynCheck — the catchable TypeError, never a silent
10521
- // ToNumber; SEMANTICS.md) and computes natively; the result boxes
10522
- // back into the field's dyn slot. `+=` with a string RHS is the
10523
- // string-context read: check to string, concat, box back.
10524
- const checkNum = (e) => e.type.kind === "dyn" ? { kind: "dynCheck", value: e, type: F64, loc: e.loc } : e;
10525
- if (op === "+" && rhs.type.kind === "string") {
10526
- const concat = {
10527
- kind: "strConcat",
10528
- // String-context read: JS's String(unknown) over the field —
10529
- // the JS-exact dyn walker, never a checked cast.
10530
- left: { kind: "toString", operand: read, type: STRING, loc },
10531
- right: rhs,
10532
- type: STRING,
10533
- loc,
10534
- };
10535
- value = { kind: "dynFrom", value: concat, type: DYN, loc };
10536
- }
10537
- else if (rhs.type.kind === "f64" || rhs.type.kind === "dyn") {
10538
- const bin = { kind: "bin", op, left: checkNum(read), right: checkNum(rhs), type: F64, loc };
10539
- value = { kind: "dynFrom", value: bin, type: DYN, loc };
10540
- }
10541
- else {
10542
- lowerer.unsupported("SC1043", access);
10543
- }
10703
+ isJsSourceFile(access.getSourceFile())) {
10704
+ const numeric = ["-", "*", "/", "%", "**"].includes(op);
10705
+ const left = lowerer.coerceToExpected(read, DYN);
10706
+ const right = lowerer.coerceToExpected(rhs, DYN);
10707
+ value = { kind: "libCall", fn: op === "+" ? "dyn.add" : numeric ? "dyn.arithmetic" : "dyn.bitwise",
10708
+ args: op === "+" ? [left, right] : [left, right, { kind: "strLit", value: op, type: STRING, loc }], type: DYN, loc };
10544
10709
  }
10545
10710
  else {
10546
10711
  lowerer.unsupported("SC1043", access);