@scriptc/compiler 0.1.4 → 0.1.6

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 (158) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +75 -11
  2. package/dist/backend/c/c-emitter.d.ts +5 -1
  3. package/dist/backend/c/c-emitter.js +22 -9
  4. package/dist/backend/c/c-emitter.js.map +1 -1
  5. package/dist/backend/c/exprs.d.ts +2 -2
  6. package/dist/backend/c/exprs.js +298 -27
  7. package/dist/backend/c/exprs.js.map +1 -1
  8. package/dist/backend/c/stmts.js +5 -3
  9. package/dist/backend/c/stmts.js.map +1 -1
  10. package/dist/backend/c/types.d.ts +2 -3
  11. package/dist/backend/c/types.js +8 -1
  12. package/dist/backend/c/types.js.map +1 -1
  13. package/dist/backend/c/walkers.js +1 -1
  14. package/dist/backend/c/walkers.js.map +1 -1
  15. package/dist/backend/link-plan.d.ts +1 -0
  16. package/dist/backend/link-plan.js +2 -1
  17. package/dist/backend/link-plan.js.map +1 -1
  18. package/dist/backend/llvm/dyn.js +1 -1
  19. package/dist/backend/llvm/dyn.js.map +1 -1
  20. package/dist/backend/llvm/emitter.js +25 -9
  21. package/dist/backend/llvm/emitter.js.map +1 -1
  22. package/dist/backend/llvm/expr-bytes.d.ts +1 -2
  23. package/dist/backend/llvm/expr-bytes.js +22 -17
  24. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  25. package/dist/backend/llvm/expr-containers.d.ts +1 -0
  26. package/dist/backend/llvm/expr-containers.js +58 -4
  27. package/dist/backend/llvm/expr-containers.js.map +1 -1
  28. package/dist/backend/llvm/expr-context.d.ts +6 -2
  29. package/dist/backend/llvm/expr-primitives.js +22 -7
  30. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  31. package/dist/backend/llvm/lib-filesystem.js +20 -0
  32. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  33. package/dist/backend/llvm/lib-network.js +53 -10
  34. package/dist/backend/llvm/lib-network.js.map +1 -1
  35. package/dist/backend/llvm/lib-process.js +4 -3
  36. package/dist/backend/llvm/lib-process.js.map +1 -1
  37. package/dist/backend/llvm/lib-shared.js +65 -0
  38. package/dist/backend/llvm/lib-shared.js.map +1 -1
  39. package/dist/backend/native-toolchain.d.ts +2 -0
  40. package/dist/backend/native-toolchain.js +2 -1
  41. package/dist/backend/native-toolchain.js.map +1 -1
  42. package/dist/backend/targets.d.ts +3 -0
  43. package/dist/backend/targets.js +13 -0
  44. package/dist/backend/targets.js.map +1 -1
  45. package/dist/coverage/surface-manifest.js +12 -5
  46. package/dist/coverage/surface-manifest.js.map +1 -1
  47. package/dist/executable/executable-cache.d.ts +2 -0
  48. package/dist/executable/executable-cache.js +3 -1
  49. package/dist/executable/executable-cache.js.map +1 -1
  50. package/dist/executable/executable-cache.test.js +3 -3
  51. package/dist/executable/executable-cache.test.js.map +1 -1
  52. package/dist/frontend/lowering/array-indexed-mutation.d.ts +8 -0
  53. package/dist/frontend/lowering/array-indexed-mutation.js +176 -0
  54. package/dist/frontend/lowering/array-indexed-mutation.js.map +1 -0
  55. package/dist/frontend/lowering/containers/bytes.d.ts +1 -1
  56. package/dist/frontend/lowering/containers/bytes.js +6 -5
  57. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  58. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +14 -6
  59. package/dist/frontend/lowering/containers/string-and-regexp.js +420 -34
  60. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  61. package/dist/frontend/lowering/expressions/object-literals.js +8 -0
  62. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  63. package/dist/frontend/lowering/indexed-comparison.d.ts +6 -0
  64. package/dist/frontend/lowering/indexed-comparison.js +30 -0
  65. package/dist/frontend/lowering/indexed-comparison.js.map +1 -0
  66. package/dist/frontend/lowering/lower-builtins.d.ts +5 -6
  67. package/dist/frontend/lowering/lower-builtins.js +24 -16
  68. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  69. package/dist/frontend/lowering/lower-calls.d.ts +3 -2
  70. package/dist/frontend/lowering/lower-calls.js +522 -89
  71. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  72. package/dist/frontend/lowering/lower-classes.js +5 -29
  73. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  74. package/dist/frontend/lowering/lower-containers.d.ts +15 -11
  75. package/dist/frontend/lowering/lower-containers.js +588 -146
  76. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  77. package/dist/frontend/lowering/lower-exprs.d.ts +12 -0
  78. package/dist/frontend/lowering/lower-exprs.js +145 -11
  79. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  80. package/dist/frontend/lowering/lower-island.js +17 -6
  81. package/dist/frontend/lowering/lower-island.js.map +1 -1
  82. package/dist/frontend/lowering/lower-server.d.ts +2 -4
  83. package/dist/frontend/lowering/lower-server.js +288 -32
  84. package/dist/frontend/lowering/lower-server.js.map +1 -1
  85. package/dist/frontend/lowering/lower-stmts.js +4 -4
  86. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  87. package/dist/frontend/lowering/lowerer.js +37 -2
  88. package/dist/frontend/lowering/lowerer.js.map +1 -1
  89. package/dist/frontend/lowering/optional-arguments.d.ts +9 -1
  90. package/dist/frontend/lowering/optional-arguments.js +67 -1
  91. package/dist/frontend/lowering/optional-arguments.js.map +1 -1
  92. package/dist/frontend/lowering/pairs-snapshot.d.ts +3 -1
  93. package/dist/frontend/lowering/pairs-snapshot.js +12 -4
  94. package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
  95. package/dist/frontend/lowering/surfaces.d.ts +10 -14
  96. package/dist/frontend/lowering/surfaces.js +50 -28
  97. package/dist/frontend/lowering/surfaces.js.map +1 -1
  98. package/dist/frontend/npm-static-prune.d.ts +6 -0
  99. package/dist/frontend/npm-static-prune.js +203 -0
  100. package/dist/frontend/npm-static-prune.js.map +1 -0
  101. package/dist/frontend/npm-static-prune.test.d.ts +1 -0
  102. package/dist/frontend/npm-static-prune.test.js +48 -0
  103. package/dist/frontend/npm-static-prune.test.js.map +1 -0
  104. package/dist/frontend/npm.js +7 -6
  105. package/dist/frontend/npm.js.map +1 -1
  106. package/dist/frontend/program.js +15 -4
  107. package/dist/frontend/program.js.map +1 -1
  108. package/dist/frontend/provenance.js +8 -3
  109. package/dist/frontend/provenance.js.map +1 -1
  110. package/dist/frontend/type-mapper.js +16 -5
  111. package/dist/frontend/type-mapper.js.map +1 -1
  112. package/dist/index.d.ts +3 -3
  113. package/dist/index.js +13 -41
  114. package/dist/index.js.map +1 -1
  115. package/dist/ir/analysis.d.ts +5 -3
  116. package/dist/ir/analysis.js +25 -11
  117. package/dist/ir/analysis.js.map +1 -1
  118. package/dist/ir/analysis.test.js +50 -2
  119. package/dist/ir/analysis.test.js.map +1 -1
  120. package/dist/ir/constant-tables.d.ts +17 -0
  121. package/dist/ir/constant-tables.js +99 -0
  122. package/dist/ir/constant-tables.js.map +1 -0
  123. package/dist/ir/constant-tables.test.d.ts +1 -0
  124. package/dist/ir/constant-tables.test.js +78 -0
  125. package/dist/ir/constant-tables.test.js.map +1 -0
  126. package/dist/ir/integer-ranges.d.ts +13 -0
  127. package/dist/ir/integer-ranges.js +122 -0
  128. package/dist/ir/integer-ranges.js.map +1 -0
  129. package/dist/ir/integer-ranges.test.d.ts +1 -0
  130. package/dist/ir/integer-ranges.test.js +63 -0
  131. package/dist/ir/integer-ranges.test.js.map +1 -0
  132. package/dist/ir/ir.d.ts +56 -35
  133. package/dist/ir/ir.js +9 -1
  134. package/dist/ir/ir.js.map +1 -1
  135. package/dist/ir/ir.test.js +6 -0
  136. package/dist/ir/ir.test.js.map +1 -1
  137. package/dist/ir/scalar-records.d.ts +8 -0
  138. package/dist/ir/scalar-records.js +216 -0
  139. package/dist/ir/scalar-records.js.map +1 -0
  140. package/dist/ir/scalar-records.test.d.ts +1 -0
  141. package/dist/ir/scalar-records.test.js +142 -0
  142. package/dist/ir/scalar-records.test.js.map +1 -0
  143. package/dist/ir/validate.js +135 -34
  144. package/dist/ir/validate.js.map +1 -1
  145. package/dist/ir/validate.test.d.ts +1 -0
  146. package/dist/ir/validate.test.js +48 -0
  147. package/dist/ir/validate.test.js.map +1 -0
  148. package/dist/library/cache-primitives.d.ts +1 -1
  149. package/dist/library/cache-primitives.js +3 -3
  150. package/dist/library/cache-primitives.js.map +1 -1
  151. package/dist/library/fence-eval.js +11 -4
  152. package/dist/library/fence-eval.js.map +1 -1
  153. package/dist/library/library-cache.js +0 -1
  154. package/dist/library/library-cache.js.map +1 -1
  155. package/dist/library/library-cache.test.js +3 -3
  156. package/dist/library/library-cache.test.js.map +1 -1
  157. package/package.json +17 -17
  158. package/surface-manifest.json +90 -45
@@ -164,10 +164,22 @@ export declare function lowerRecordKeyRead(lowerer: Lowerer, expr: ts.ElementAcc
164
164
  * around the read are erased here because this context handles the missing
165
165
  * arm instead of consuming it as the asserted type. */
166
166
  export declare function lowerAbsenceProbe(lowerer: Lowerer, node: ts.Expression): IrExpr | null;
167
+ /** `a[i] op= rhs` over native arrays and byte views. A compound assignment
168
+ * evaluates the receiver and index, reads the old element, evaluates rhs,
169
+ * then writes. Capture each stage so a call in the index or rhs cannot
170
+ * change the target or the value used by the operator. The expression yields
171
+ * the computed value before a typed array or Buffer coerces it for storage. */
172
+ export declare function lowerElementCompound(lowerer: Lowerer, expr: ts.BinaryExpression, op: CompoundOp): IrExpr;
167
173
  /** `a[i] = v` in statement position → arraySet (element writes, like local
168
174
  * assignment, produce no value in our subset). */
169
175
  export declare function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): IrStmt;
170
176
  export declare function ensureString(lowerer: Lowerer, e: IrExpr, node: ts.Node): IrExpr;
177
+ /** `new String(x)` (stdlib provenance, ≤1 argument) in a ToString
178
+ * position: the wrapper's only distinguishers are typeof and identity —
179
+ * neither is observable where the value immediately stringifies — so the
180
+ * span lowers as the argument's own ToString (`new String()` is "").
181
+ * Every other position keeps the wrapper-object constructor fence. */
182
+ export declare function stringWrapperToString(lowerer: Lowerer, node: ts.Expression): IrExpr | null;
171
183
  export declare function lowerTemplate(lowerer: Lowerer, expr: ts.TemplateExpression): IrExpr;
172
184
  /** `x as T`. Non-dyn inner values ordinarily erase; a record assertion
173
185
  * changing monomorphic shape rebuilds through the established width-copy
@@ -11,11 +11,12 @@ import { pathToFileURL } from "node:url";
11
11
  import { wasiGuestPath } from "../../wasi-paths.js";
12
12
  import { BIGINT_T, BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, funcOf, isDynTypedRefType, isJsonSafeType, isSupportedArrayElem, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionFuncSetArmsOk } from "../../ir/ir.js";
13
13
  import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
14
- import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
14
+ import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, COMPOUND_ASSIGN_OPS, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STRING_INDEX_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
15
15
  import { blockedBindingUseDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
16
16
  import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
17
- import { lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall } from "./lower-containers.js";
18
- import { arrayValueStore } from "./array-values.js";
17
+ import { lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall, tryLowerNumericIndexRead } from "./lower-containers.js";
18
+ import { arrayValueRead, arrayValueStore } from "./array-values.js";
19
+ import { tryLowerIndexedComparison } from "./indexed-comparison.js";
19
20
  import { npmStaticPackageOfPath } from "../npm-static.js";
20
21
  import { unsupportedModuleFeatureOf } from "../builtin-modules.js";
21
22
  import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
@@ -35,6 +36,7 @@ import { conditionalSpreadOf, foldedStringKeyOf } from "./expressions/object-lit
35
36
  import { tryLowerExpression } from "./expressions/try-lower-expression.js";
36
37
  import { fenceNodeModuleMutation, isNodeModuleValue, lowerNodeModuleIdentifier, lowerNodeModuleProperty, lowerRequireCacheElement, lowerRequireCacheHas, lowerRequireMainProperty } from "./lower-node-module.js";
37
38
  import { lowerAbstractEquality } from "./abstract-equality.js";
39
+ import { coerceStringSearchValue, defaultAfterUndefined, lowerStaticallyUndefinedArgument } from "./optional-arguments.js";
38
40
  /** A template piece's RAW text (String.raw's contract: escapes stay
39
41
  * characters). 7's client AST ships no rawText at runtime (the typing
40
42
  * declares it; the serialized node data omits it), so the raw span comes
@@ -553,6 +555,18 @@ function lowerExprInner(lowerer, expr) {
553
555
  return { kind: "strLit", value: "object", type: STRING, loc };
554
556
  }
555
557
  if (ts.isPropertyAccessExpression(expr.expression)) {
558
+ const member = expr.expression;
559
+ const prototype = member.expression;
560
+ if (STRING_INDEX_METHODS.has(member.name.text) && ts.isPropertyAccessExpression(prototype) &&
561
+ prototype.name.text === "prototype" && ts.isIdentifier(prototype.expression) &&
562
+ lowerer.isStdlibGlobal(prototype.expression, "String") && lowerer.isStdlibMember(member)) {
563
+ return { kind: "strLit", value: "function", type: STRING, loc };
564
+ }
565
+ if (member.name.text === "at" && ts.isPropertyAccessExpression(prototype) &&
566
+ prototype.name.text === "prototype" && ts.isIdentifier(prototype.expression) &&
567
+ lowerer.isStdlibGlobal(prototype.expression, "Array") && lowerer.isStdlibMember(member)) {
568
+ return { kind: "strLit", value: "function", type: STRING, loc };
569
+ }
556
570
  const presence = lowerPromiseThenPresence(lowerer, expr.expression, { kind: "strLit", value: "function", type: STRING, loc });
557
571
  if (presence !== null)
558
572
  return presence;
@@ -871,6 +885,12 @@ function lowerExprInner(lowerer, expr) {
871
885
  if (bi.module === "module" && bi.member === "builtinModules") {
872
886
  return builtinModulesArrayLit(loc);
873
887
  }
888
+ if (bi.module === "http" && bi.member === "METHODS") {
889
+ return { kind: "libCall", fn: "http.methods", args: [], type: arrayOf(STRING), loc };
890
+ }
891
+ if (bi.module === "http" && bi.member === "STATUS_CODES") {
892
+ return { kind: "libCall", fn: "http.statusCodes", args: [], type: DYN, loc };
893
+ }
874
894
  // tls.rootCertificates: a runtime-valued module constant (the
875
895
  // cached bundled-CA array) — the one member read that lowers
876
896
  // to a libCall instead of a baked literal.
@@ -3392,7 +3412,7 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3392
3412
  : { kind: "arrIntrinsic", method: "length", receiver, args: [], type: F64, loc: locOf(expr) };
3393
3413
  }
3394
3414
  if (kind === "string"
3395
- ? own(STR_METHODS, name) !== undefined || own(ISLAND_SURFACE.string, name) !== undefined
3415
+ ? own(STR_METHODS, name) !== undefined || STRING_INDEX_METHODS.has(name) || own(ISLAND_SURFACE.string, name) !== undefined
3396
3416
  : ARRAY_METHODS.has(name)) {
3397
3417
  lowerer.unsupported("SC1090", expr, `${kind} methods as values (call '${name}' directly)`);
3398
3418
  }
@@ -3804,6 +3824,11 @@ export function lowerOptionalNumber(lowerer, operand, loc, narrowedNode) {
3804
3824
  if (operand.type.kind !== "union" || lowerer.armTag(operand.type.unionId, UNDEFINED_T) < 0)
3805
3825
  return operand;
3806
3826
  const directNumber = lowerer.stripUndefinedArm(operand.type).kind === "f64";
3827
+ if (directNumber) {
3828
+ const scalarRead = tryLowerNumericIndexRead(lowerer, operand, loc);
3829
+ if (scalarRead)
3830
+ return scalarRead;
3831
+ }
3807
3832
  const checkerNumber = narrowedNode !== undefined && lowerer.mapTypeOf(lowerer.typeOf(narrowedNode))?.kind === "f64";
3808
3833
  if (!directNumber && (!checkerNumber || lowerer.armTag(operand.type.unionId, F64) < 0))
3809
3834
  return operand;
@@ -4582,6 +4607,78 @@ function tupleLiteralIndex(node) {
4582
4607
  const n = Number(e.text);
4583
4608
  return Number.isInteger(n) && n >= 0 ? n : null;
4584
4609
  }
4610
+ /** `a[i] op= rhs` over native arrays and byte views. A compound assignment
4611
+ * evaluates the receiver and index, reads the old element, evaluates rhs,
4612
+ * then writes. Capture each stage so a call in the index or rhs cannot
4613
+ * change the target or the value used by the operator. The expression yields
4614
+ * the computed value before a typed array or Buffer coerces it for storage. */
4615
+ export function lowerElementCompound(lowerer, expr, op) {
4616
+ const target = expr.left;
4617
+ fenceNodeModuleMutation(lowerer, target, "assignment");
4618
+ const loc = locOf(expr);
4619
+ const receiverType = lowerer.mapTypeOf(lowerer.typeOf(target.expression));
4620
+ if (receiverType?.kind !== "array" && receiverType?.kind !== "bytes") {
4621
+ lowerer.unsupported("SC1090", target, "compound assignment to non-array elements");
4622
+ }
4623
+ let receiver = lowerer.lowerExpr(target.expression);
4624
+ if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0) {
4625
+ const present = lowerer.stripUndefinedArm(receiver.type);
4626
+ const helper = present.kind === receiverType.kind
4627
+ ? lowerer.narrowedArmHelper(receiver.type.unionId, present, locOf(target.expression))
4628
+ : null;
4629
+ if (helper)
4630
+ receiver = { kind: "call", callee: helper, args: [receiver], type: present, loc: locOf(target.expression) };
4631
+ }
4632
+ if (receiver.type.kind !== receiverType.kind) {
4633
+ lowerer.unsupported("SC1090", target.expression, "compound assignment through a non-native array or byte view");
4634
+ }
4635
+ const index = lowerer.lowerExpr(target.argumentExpression);
4636
+ if (index.type.kind !== "f64")
4637
+ lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-number keys");
4638
+ const receiverLocal = lowerer.declareHiddenLocal("%compoundArray", receiver.type);
4639
+ const indexLocal = lowerer.declareHiddenLocal("%compoundIndex", F64);
4640
+ const receiverRef = () => varRef(receiverLocal.id, receiver.type, loc);
4641
+ const indexRef = () => varRef(indexLocal.id, F64, loc);
4642
+ const oldValue = receiver.type.kind === "array"
4643
+ ? arrayValueRead(lowerer, receiverRef(), indexRef(), receiver.type.elem, locOf(target))
4644
+ : { kind: "bytesIntrinsic", method: "get", receiver: receiverRef(), args: [indexRef()], type: F64, loc: locOf(target) };
4645
+ const oldLocal = lowerer.declareHiddenLocal("%compoundOld", oldValue.type);
4646
+ const oldRef = () => varRef(oldLocal.id, oldValue.type, loc);
4647
+ const rhs = lowerer.lowerExpr(expr.right);
4648
+ const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
4649
+ const elementType = receiver.type.kind === "array" ? receiver.type.elem : F64;
4650
+ const valueType = elementType.kind === "union" && lowerer.armTag(elementType.unionId, UNDEFINED_T) >= 0
4651
+ ? lowerer.stripUndefinedArm(elementType)
4652
+ : elementType;
4653
+ let computed;
4654
+ if (op === "+" && valueType.kind === "string") {
4655
+ computed = { kind: "strConcat", left: lowerer.ensureString(oldRef(), target), right: lowerer.ensureString(rhs, expr.right), type: STRING, loc };
4656
+ }
4657
+ else if (valueType.kind === "f64" && numericRhs.type.kind === "f64") {
4658
+ computed = { kind: "bin", op, left: lowerOptionalNumber(lowerer, oldRef(), loc), right: numericRhs, type: F64, loc };
4659
+ }
4660
+ else {
4661
+ lowerer.unsupported("SC1043", expr);
4662
+ }
4663
+ const resultLocal = lowerer.declareHiddenLocal("%compoundResult", computed.type);
4664
+ const resultRef = () => varRef(resultLocal.id, computed.type, loc);
4665
+ const write = receiver.type.kind === "array"
4666
+ ? arrayValueStore(lowerer, receiverRef(), indexRef(), resultRef(), receiver.type.elem, loc)
4667
+ : { kind: "bytesSet", arr: receiverRef(), index: indexRef(), value: resultRef(), loc };
4668
+ return {
4669
+ kind: "seqExpr",
4670
+ stmts: [
4671
+ { kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
4672
+ { kind: "varDecl", localId: indexLocal.id, init: index, loc },
4673
+ { kind: "varDecl", localId: oldLocal.id, init: oldValue, loc },
4674
+ { kind: "varDecl", localId: resultLocal.id, init: computed, loc },
4675
+ write,
4676
+ ],
4677
+ result: resultRef(),
4678
+ type: computed.type,
4679
+ loc,
4680
+ };
4681
+ }
4585
4682
  /** `a[i] = v` in statement position → arraySet (element writes, like local
4586
4683
  * assignment, produce no value in our subset). */
4587
4684
  export function lowerElementWrite(lowerer, expr) {
@@ -4953,6 +5050,18 @@ export function ensureString(lowerer, e, node) {
4953
5050
  if (stringable) {
4954
5051
  return { kind: "toString", operand: e, type: STRING, loc: e.loc };
4955
5052
  }
5053
+ // Runtime-optional locals can retain their stored union after the
5054
+ // checker has narrowed this use to a primitive arm. Validate that
5055
+ // arm before converting it; an unguarded object arm stays fenced.
5056
+ const narrowed = lowerer.mapTypeOf(lowerer.typeOf(node));
5057
+ if (narrowed &&
5058
+ (narrowed.kind === "string" || narrowed.kind === "f64" ||
5059
+ narrowed.kind === "bool" || narrowed.kind === "bigint")) {
5060
+ const helper = lowerer.narrowedArmHelper(e.type.unionId, narrowed, e.loc);
5061
+ if (helper) {
5062
+ return ensureString(lowerer, { kind: "call", callee: helper, args: [e], type: narrowed, loc: e.loc }, node);
5063
+ }
5064
+ }
4956
5065
  lowerer.unsupported("SC1090", node, `string conversions of unions with object arms (${NARROW_FIRST})`);
4957
5066
  }
4958
5067
  if (e.type.kind === "symbol") {
@@ -5004,7 +5113,7 @@ export function ensureString(lowerer, e, node) {
5004
5113
  * neither is observable where the value immediately stringifies — so the
5005
5114
  * span lowers as the argument's own ToString (`new String()` is "").
5006
5115
  * Every other position keeps the wrapper-object constructor fence. */
5007
- function stringWrapperToString(lowerer, node) {
5116
+ export function stringWrapperToString(lowerer, node) {
5008
5117
  let e = node;
5009
5118
  while (ts.isParenthesizedExpression(e))
5010
5119
  e = e.expression;
@@ -5018,7 +5127,11 @@ function stringWrapperToString(lowerer, node) {
5018
5127
  return null;
5019
5128
  if (args.length === 0)
5020
5129
  return { kind: "strLit", value: "", type: STRING, loc: locOf(e) };
5021
- return lowerer.caughtToString(args[0]) ?? lowerer.ensureString(lowerer.lowerExpr(args[0]), args[0]);
5130
+ const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, args[0]);
5131
+ if (undefinedArg)
5132
+ return defaultAfterUndefined(undefinedArg, { kind: "strLit", value: "undefined", type: STRING, loc: locOf(e) });
5133
+ return lowerer.caughtToString(args[0]) ??
5134
+ coerceStringSearchValue(lowerer, lowerer.lowerExpr(args[0]), args[0], locOf(e));
5022
5135
  }
5023
5136
  export function lowerTemplate(lowerer, expr) {
5024
5137
  const loc = locOf(expr);
@@ -5410,8 +5523,9 @@ export function lowerBinary(lowerer, expr) {
5410
5523
  // globals, captured/boxed included); the RHS coerces into the binding's
5411
5524
  // type exactly like statement position, and the expression's value is
5412
5525
  // the coerced binding-typed value (representation change only — never
5413
- // observably different from JS's raw-RHS yield). Compound operators,
5414
- // property/element targets, and destructuring targets stay fenced.
5526
+ // observably different from JS's raw-RHS yield). Indexed compounds
5527
+ // use a sequenced read/modify/write; other member targets and
5528
+ // destructuring targets stay fenced.
5415
5529
  if (op === ts.SyntaxKind.EqualsToken && ts.isIdentifier(expr.left)) {
5416
5530
  const target = lowerer.resolveWritable(expr.left);
5417
5531
  if (!target) {
@@ -5423,6 +5537,10 @@ export function lowerBinary(lowerer, expr) {
5423
5537
  lowerer.runtimeOptionalLocals.add(runtimeOptionalRoot);
5424
5538
  return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
5425
5539
  }
5540
+ const indexedCompound = COMPOUND_ASSIGN_OPS[op];
5541
+ if (indexedCompound !== undefined && ts.isElementAccessExpression(expr.left)) {
5542
+ return lowerElementCompound(lowerer, expr, indexedCompound);
5543
+ }
5426
5544
  // `events.defaultMaxListeners = v` — the module-property write
5427
5545
  // Node validates (validateNumber(n, 'defaultMaxListeners', 0)):
5428
5546
  // the value crosses into the checked-dynamic tree and the runtime ladder throws
@@ -6171,6 +6289,9 @@ export function lowerBinary(lowerer, expr) {
6171
6289
  case ts.SyntaxKind.EqualsEqualsEqualsToken:
6172
6290
  case ts.SyntaxKind.ExclamationEqualsEqualsToken: {
6173
6291
  const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6292
+ const indexed = tryLowerIndexedComparison(lowerer, left, right, negated, loc);
6293
+ if (indexed)
6294
+ return indexed;
6174
6295
  if (plainBothNum)
6175
6296
  return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc };
6176
6297
  if (bothStr)
@@ -8739,7 +8860,20 @@ export function fieldGetExpr(lowerer, target, loc, blame) {
8739
8860
  // construction), typed as the index value armed with undefined under
8740
8861
  // noUncheckedIndexedAccess (mirroring lowerRecordKeyRead).
8741
8862
  if (target.container === "recordOvf") {
8742
- let t = target.fieldType;
8863
+ let obj = target.obj;
8864
+ if (obj.type.kind === "union" && ts.isPropertyAccessExpression(blame)) {
8865
+ const present = lowerer.stripUndefinedArm(obj.type);
8866
+ if (present.kind === "record") {
8867
+ obj = lowerer.runtimeOptionalPropertyReceiver(blame.expression, obj, present, target.field) ?? obj;
8868
+ }
8869
+ }
8870
+ if (obj.type.kind !== "record") {
8871
+ lowerer.unsupported("SC1090", blame, `reading '${target.field}' on a non-record receiver (narrow first)`);
8872
+ }
8873
+ const shape = lowerer.shapes.get(obj.type.shapeId);
8874
+ if (!shape?.indexValue)
8875
+ lowerer.unsupported("SC1090", blame, `reading '${target.field}' on a record without an index signature`);
8876
+ let t = shape.indexValue;
8743
8877
  if (lowerer.program.getCompilerOptions().noUncheckedIndexedAccess) {
8744
8878
  const armed = lowerer.withUndefinedArmOf(t);
8745
8879
  if (!armed)
@@ -8748,8 +8882,8 @@ export function fieldGetExpr(lowerer, target, loc, blame) {
8748
8882
  }
8749
8883
  return {
8750
8884
  kind: "recordKeyGet",
8751
- obj: target.obj,
8752
- shapeId: target.shapeId,
8885
+ obj,
8886
+ shapeId: obj.type.shapeId,
8753
8887
  key: { kind: "strLit", value: target.field, type: STRING, loc },
8754
8888
  overflowOnly: true,
8755
8889
  type: t,