@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
@@ -1,15 +1,119 @@
1
1
  import * as ts from "../../ts7/adapter.js";
2
- import { BOOL, F64, STRING, arrayOf, isUnitType, typeEquals } from "../../../ir/ir.js";
3
- import { numLit, varRef } from "../../../ir/build.js";
2
+ import { BOOL, DYN, F64, STRING, UNDEFINED_T, arrayOf, isUnitType, typeEquals, typeKey } from "../../../ir/ir.js";
3
+ import { numLit, strLit, varRef } from "../../../ir/build.js";
4
4
  import { locOf } from "../../program.js";
5
- import { own } from "../lowerer.js";
5
+ import { nodeThrowExpr, own } from "../lowerer.js";
6
6
  import { isRequireMainFilename } from "../expressions/optional-chains.js";
7
- import { STR_METHODS } from "../surfaces.js";
8
- import { lowerOptionalArgument } from "../optional-arguments.js";
7
+ import { STRING_INDEX_METHODS, STR_METHODS } from "../surfaces.js";
8
+ import { coerceStringSearchValue, defaultAfterUndefined, lowerOptionalArgument, lowerPositionArgument, lowerStaticallyUndefinedArgument, lowerStringSearchArgument, positionNumber } from "../optional-arguments.js";
9
9
  function lowerSplitLimitArg(lowerer, node, loc) {
10
10
  const defaultValue = { kind: "numLit", value: 4294967295, type: F64, loc };
11
11
  return node ? lowerOptionalArgument(lowerer, node, F64, defaultValue) : defaultValue;
12
12
  }
13
+ /** Split evaluates its arguments before converting the receiver, limit, and
14
+ * separator, in that order. An undefined separator returns the whole string
15
+ * unless ToUint32(limit) is zero; the string-separator intrinsic handles the
16
+ * remaining split loop. */
17
+ export function lowerStringSplitCall(lowerer, call, receiver, receiverNode, argumentNodes) {
18
+ const loc = locOf(call);
19
+ if (argumentNodes.length > 2 || argumentNodes.some(ts.isSpreadElement)) {
20
+ return lowerer.noLowering(`.split with ${argumentNodes.length} arguments`, call);
21
+ }
22
+ const separatorNode = argumentNodes[0];
23
+ const undefinedSeparator = separatorNode ? lowerStaticallyUndefinedArgument(lowerer, separatorNode) : null;
24
+ const absent = !separatorNode || undefinedSeparator !== null;
25
+ let separator = absent
26
+ ? undefinedSeparator
27
+ ? defaultAfterUndefined(undefinedSeparator, strLit("undefined", loc))
28
+ : strLit("undefined", loc)
29
+ : lowerer.lowerExpr(separatorNode);
30
+ if (separator.type.kind === "nullT")
31
+ separator = defaultAfterUndefined(separator, strLit("null", loc));
32
+ const scalar = separator.type.kind === "string" || separator.type.kind === "f64" ||
33
+ separator.type.kind === "bool" || separator.type.kind === "bigint" ||
34
+ (separator.type.kind === "union" && (lowerer.unions.get(separator.type.unionId)?.arms.every((arm) => arm.kind === "string" || arm.kind === "f64" || arm.kind === "bool" ||
35
+ arm.kind === "bigint" || arm.kind === "nullT" || arm.kind === "undefinedT") ?? false));
36
+ if (!scalar) {
37
+ lowerer.unsupported("SC1090", separatorNode ?? call, `'.split()' on a '${lowerer.fmt(separator.type)}' separator (pass a string, or a regex literal)`);
38
+ }
39
+ const defaultLimit = numLit(4294967295, loc);
40
+ const suppliedLimit = lowerPositionArgument(lowerer, argumentNodes[1], defaultLimit);
41
+ const limit = suppliedLimit.type.kind === "record" && argumentNodes[1]
42
+ ? lowerer.coerceInto(argumentNodes[1], suppliedLimit, DYN) : suppliedLimit;
43
+ const resultType = arrayOf(STRING);
44
+ if (receiver.type.kind === "string" && separator.type.kind === "string" && limit.type.kind === "f64" && !absent) {
45
+ return { kind: "strIntrinsic", method: "split", receiver, args: [separator, limit], type: resultType, loc };
46
+ }
47
+ const key = `str.split:${receiver.type.kind === "union" ? typeKey(receiver.type) : receiver.type.kind}:${typeKey(separator.type)}:${typeKey(limit.type)}:${absent}`;
48
+ let helper = lowerer.widthHelpers.get(key);
49
+ if (!helper) {
50
+ helper = `%str.split.${lowerer.widthHelpers.size}`;
51
+ const values = [receiver, separator, limit];
52
+ const params = values.map((value, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: value.type }));
53
+ const rawReceiver = varRef("arg.0", receiver.type, loc);
54
+ const rawSeparator = varRef("arg.1", separator.type, loc);
55
+ const rawLimit = varRef("arg.2", limit.type, loc);
56
+ const stringReceiver = varRef("receiver.0", STRING, loc);
57
+ const numericLimit = varRef("limit.0", F64, loc);
58
+ const stringSeparator = varRef("separator.0", STRING, loc);
59
+ const undefinedTag = separator.type.kind === "union"
60
+ ? lowerer.unions.get(separator.type.unionId).arms.findIndex((arm) => arm.kind === "undefinedT") : -1;
61
+ const separatorIsUndefined = absent
62
+ ? { kind: "boolLit", value: true, type: BOOL, loc }
63
+ : undefinedTag >= 0 && separator.type.kind === "union"
64
+ ? { kind: "unionIsTag", unionId: separator.type.unionId, tag: undefinedTag, value: rawSeparator, negated: false, type: BOOL, loc }
65
+ : { kind: "boolLit", value: false, type: BOOL, loc };
66
+ const split = {
67
+ kind: "strIntrinsic", method: "split", receiver: stringReceiver,
68
+ args: [stringSeparator, numericLimit], type: resultType, loc,
69
+ };
70
+ const result = {
71
+ kind: "ternary",
72
+ cond: {
73
+ kind: "bin", op: "===",
74
+ left: { kind: "bin", op: ">>>", left: numericLimit, right: numLit(0, loc), type: F64, loc },
75
+ right: numLit(0, loc), type: BOOL, loc,
76
+ },
77
+ then: { kind: "arrayLit", elems: [], type: resultType, loc },
78
+ else_: {
79
+ kind: "ternary", cond: separatorIsUndefined,
80
+ then: { kind: "arrayLit", elems: [stringReceiver], type: resultType, loc },
81
+ else_: split, type: resultType, loc,
82
+ },
83
+ type: resultType, loc,
84
+ };
85
+ lowerer.widthHelpers.set(key, helper);
86
+ lowerer.liftedFns.push({
87
+ name: helper, params, returnType: resultType,
88
+ locals: [
89
+ ...params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false })),
90
+ { id: "receiver.0", name: "receiver", type: STRING, mutable: false },
91
+ { id: "limit.0", name: "limit", type: F64, mutable: false },
92
+ { id: "separator.0", name: "separator", type: STRING, mutable: false },
93
+ ],
94
+ body: [
95
+ { kind: "varDecl", localId: "receiver.0", init: lowerer.ensureString(rawReceiver, receiverNode), loc },
96
+ { kind: "varDecl", localId: "limit.0", init: positionNumber(lowerer, rawLimit, defaultLimit, argumentNodes[1] ?? call, "string split limit"), loc },
97
+ { kind: "varDecl", localId: "separator.0", init: lowerer.ensureString(rawSeparator, separatorNode ?? call), loc },
98
+ { kind: "return", value: result, loc },
99
+ ],
100
+ loc,
101
+ });
102
+ }
103
+ return { kind: "call", callee: helper, args: [receiver, separator, limit], type: resultType, loc };
104
+ }
105
+ function lowerRegexSubject(lowerer, node, loc) {
106
+ const absent = { kind: "strLit", value: "undefined", type: STRING, loc };
107
+ if (!node)
108
+ return absent;
109
+ const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, node);
110
+ if (undefinedArg)
111
+ return defaultAfterUndefined(undefinedArg, absent);
112
+ const value = lowerer.lowerExpr(node);
113
+ if (value.kind === "unitLit")
114
+ return { kind: "strLit", value: value.unit, type: STRING, loc };
115
+ return lowerer.ensureString(value, node);
116
+ }
13
117
  /** Extract the checker-proven receiver arm from a runtime-optional property
14
118
  * read. The receiver is stabilized once before its tag is tested, and a
15
119
  * missing value throws the same property-read TypeError Node throws before
@@ -23,6 +127,192 @@ function lowerMethodReceiver(lowerer, node, expected, member) {
23
127
  return lowered;
24
128
  return lowerer.coerceInto(node, lowered, expected);
25
129
  }
130
+ /** Relative string indexing and code-point decoding over the UTF-16 intrinsics. */
131
+ export function lowerStringIndexCall(lowerer, call, method, receiver, receiverNode, argumentNodes) {
132
+ const loc = locOf(call);
133
+ if (argumentNodes.length > 1 || argumentNodes.some(ts.isSpreadElement)) {
134
+ return lowerer.noLowering(`String.prototype.${method} with ${argumentNodes.length} arguments`, call);
135
+ }
136
+ const indexNode = argumentNodes[0];
137
+ let index = lowerPositionArgument(lowerer, indexNode, numLit(0, loc));
138
+ if (indexNode && (index.type.kind === "record" || index.type.kind === "array" || index.type.kind === "func")) {
139
+ index = lowerer.coerceInto(indexNode, index, DYN);
140
+ }
141
+ const valueType = method === "at" ? STRING : F64;
142
+ const resultType = lowerer.withUndefinedArmOf(valueType);
143
+ if (!resultType || resultType.kind !== "union")
144
+ return lowerer.noLowering(`String.prototype.${method} result`, call);
145
+ const undefinedTag = lowerer.armTag(resultType.unionId, UNDEFINED_T);
146
+ const valueTag = lowerer.armTag(resultType.unionId, valueType);
147
+ const key = `str.index:${method}:${typeKey(receiver.type)}:${typeKey(index.type)}`;
148
+ let helper = lowerer.widthHelpers.get(key);
149
+ if (!helper) {
150
+ helper = `%str.index.${lowerer.widthHelpers.size}`;
151
+ lowerer.widthHelpers.set(key, helper);
152
+ const rawReceiver = varRef("arg.0", receiver.type, loc);
153
+ const rawIndex = varRef("arg.1", index.type, loc);
154
+ const stringReceiver = varRef("receiver.0", STRING, loc);
155
+ const length = varRef("length.0", F64, loc);
156
+ const position = varRef("position.0", F64, loc);
157
+ const first = varRef("first.0", F64, loc);
158
+ const second = varRef("second.0", F64, loc);
159
+ const bin = (op, left, right) => ({ kind: "bin", op, left, right, type: op === "<" || op === ">=" || op === ">" ? BOOL : F64, loc });
160
+ const either = (left, right) => ({ kind: "logical", op: "||", left, right, type: BOOL, loc });
161
+ const wrap = (value) => ({ kind: "unionWrap", unionId: resultType.unionId, tag: valueTag, value, type: resultType, loc });
162
+ const miss = {
163
+ kind: "unionWrap", unionId: resultType.unionId, tag: undefinedTag,
164
+ value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, type: resultType, loc,
165
+ };
166
+ const numeric = positionNumber(lowerer, rawIndex, numLit(0, loc), indexNode ?? call, "string index");
167
+ const numericValue = varRef("numeric.0", F64, loc);
168
+ const body = [
169
+ { kind: "varDecl", localId: "receiver.0", init: lowerer.ensureString(rawReceiver, receiverNode), loc },
170
+ { kind: "varDecl", localId: "length.0", init: { kind: "strIntrinsic", method: "length", receiver: stringReceiver, args: [], type: F64, loc }, loc },
171
+ { kind: "varDecl", localId: "numeric.0", init: numeric, loc },
172
+ { kind: "varDecl", localId: "position.0", init: {
173
+ kind: "ternary", cond: { kind: "libCall", fn: "num.isNaN", args: [numericValue], type: BOOL, loc },
174
+ then: numLit(0, loc), else_: { kind: "libCall", fn: "math.trunc", args: [numericValue], type: F64, loc }, type: F64, loc,
175
+ }, loc },
176
+ ];
177
+ if (method === "at") {
178
+ body.push({ kind: "if", cond: bin("<", position, numLit(0, loc)),
179
+ then: [{ kind: "assign", localId: "position.0", value: bin("+", position, length), loc }], else_: null, loc });
180
+ }
181
+ body.push({ kind: "if", cond: either(bin("<", position, numLit(0, loc)), bin(">=", position, length)),
182
+ then: [{ kind: "return", value: miss, loc }], else_: null, loc });
183
+ if (method === "at") {
184
+ body.push({ kind: "return", value: wrap({ kind: "strIntrinsic", method: "charAt", receiver: stringReceiver, args: [position], type: STRING, loc }), loc });
185
+ }
186
+ else {
187
+ body.push({ kind: "varDecl", localId: "first.0", init: { kind: "strIntrinsic", method: "charCodeAt", receiver: stringReceiver, args: [position], type: F64, loc }, loc });
188
+ body.push({ kind: "if", cond: either(either(bin("<", first, numLit(0xd800, loc)), bin(">", first, numLit(0xdbff, loc))), bin(">=", bin("+", position, numLit(1, loc)), length)),
189
+ then: [{ kind: "return", value: wrap(first), loc }], else_: null, loc });
190
+ body.push({ kind: "varDecl", localId: "second.0", init: { kind: "strIntrinsic", method: "charCodeAt", receiver: stringReceiver, args: [bin("+", position, numLit(1, loc))], type: F64, loc }, loc });
191
+ body.push({ kind: "if", cond: either(bin("<", second, numLit(0xdc00, loc)), bin(">", second, numLit(0xdfff, loc))),
192
+ then: [{ kind: "return", value: wrap(first), loc }], else_: null, loc });
193
+ body.push({ kind: "return", value: wrap(bin("+", bin("+", bin("*", bin("-", first, numLit(0xd800, loc)), numLit(1024, loc)), bin("-", second, numLit(0xdc00, loc))), numLit(0x10000, loc))), loc });
194
+ }
195
+ lowerer.liftedFns.push({
196
+ name: helper,
197
+ params: [{ localId: "arg.0", name: "receiver", type: receiver.type }, { localId: "arg.1", name: "index", type: index.type }],
198
+ returnType: resultType,
199
+ locals: [
200
+ { id: "arg.0", name: "receiver", type: receiver.type, mutable: false },
201
+ { id: "arg.1", name: "index", type: index.type, mutable: false },
202
+ { id: "receiver.0", name: "stringReceiver", type: STRING, mutable: false },
203
+ { id: "length.0", name: "length", type: F64, mutable: false },
204
+ { id: "numeric.0", name: "numeric", type: F64, mutable: false },
205
+ { id: "position.0", name: "position", type: F64, mutable: method === "at" },
206
+ ...(method === "codePointAt" ? [
207
+ { id: "first.0", name: "first", type: F64, mutable: false },
208
+ { id: "second.0", name: "second", type: F64, mutable: false },
209
+ ] : []),
210
+ ],
211
+ body, loc,
212
+ });
213
+ }
214
+ return { kind: "call", callee: helper, args: [receiver, index], type: resultType, loc };
215
+ }
216
+ function paddingFillString(lowerer, value, node) {
217
+ const loc = value.loc;
218
+ if (value.type.kind === "undefinedT" || value.type.kind === "void")
219
+ return strLit(" ", loc);
220
+ if (value.type.kind === "nullT")
221
+ return strLit("null", loc);
222
+ if (value.type.kind === "dyn") {
223
+ return {
224
+ kind: "ternary",
225
+ cond: { kind: "dynTest", test: "undefined", value, type: BOOL, loc },
226
+ then: strLit(" ", loc),
227
+ else_: { kind: "libCall", fn: "dyn.toStringCoerce", args: [value], type: STRING, loc },
228
+ type: STRING, loc,
229
+ };
230
+ }
231
+ if (value.type.kind === "union") {
232
+ const unionId = value.type.unionId;
233
+ const arms = lowerer.unions.get(unionId).arms;
234
+ let result = strLit(" ", loc);
235
+ for (let tag = arms.length - 1; tag >= 0; tag--) {
236
+ const narrowed = { kind: "unionNarrow", unionId, tag, value, type: arms[tag], loc };
237
+ const converted = paddingFillString(lowerer, narrowed, node);
238
+ result = tag === arms.length - 1 ? converted : {
239
+ kind: "ternary",
240
+ cond: { kind: "unionIsTag", unionId, tag, value, negated: false, type: BOOL, loc },
241
+ then: converted, else_: result, type: STRING, loc,
242
+ };
243
+ }
244
+ return result;
245
+ }
246
+ return lowerer.ensureString(value, node);
247
+ }
248
+ /** Pad arguments are evaluated before receiver/length coercion, while fill
249
+ * conversion runs only if padding is needed. One helper keeps that order for
250
+ * member calls and direct String.prototype calls alike. */
251
+ export function lowerStringPaddingCall(lowerer, call, method, receiver, receiverNode, argumentNodes) {
252
+ if (argumentNodes.length > 2) {
253
+ return lowerer.noLowering(`.${method} with ${argumentNodes.length} arguments`, call);
254
+ }
255
+ const loc = locOf(call);
256
+ const maxLength = lowerPositionArgument(lowerer, argumentNodes[0], numLit(0, loc));
257
+ const fillNode = argumentNodes[1];
258
+ const undefinedFill = fillNode ? lowerStaticallyUndefinedArgument(lowerer, fillNode) : null;
259
+ const rawFill = !fillNode ? strLit(" ", loc) : undefinedFill
260
+ ? defaultAfterUndefined(undefinedFill, strLit(" ", loc))
261
+ : lowerer.lowerExpr(fillNode);
262
+ const fill = rawFill.type.kind === "nullT"
263
+ ? defaultAfterUndefined(rawFill, strLit("null", loc)) : rawFill;
264
+ const values = [receiver, maxLength, fill];
265
+ const key = `str.pad:${method}:${values.map(value => typeKey(value.type)).join(":")}`;
266
+ let helper = lowerer.widthHelpers.get(key);
267
+ if (!helper) {
268
+ helper = `%str.pad.${lowerer.widthHelpers.size}`;
269
+ const params = values.map((value, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: value.type }));
270
+ const rawReceiver = varRef("arg.0", receiver.type, loc);
271
+ const convertedReceiver = receiver.type.kind === "dyn" ? {
272
+ kind: "ternary",
273
+ cond: { kind: "dynTest", test: "nullish", value: rawReceiver, type: BOOL, loc },
274
+ then: nodeThrowExpr(1, "", `String.prototype.${method} called on null or undefined`, STRING, loc),
275
+ else_: { kind: "libCall", fn: "dyn.toStringCoerce", args: [rawReceiver], type: STRING, loc },
276
+ type: STRING, loc,
277
+ } : lowerer.ensureString(rawReceiver, receiverNode);
278
+ const stringReceiver = varRef("receiver.0", STRING, loc);
279
+ const length = varRef("length.0", F64, loc);
280
+ const fillValue = varRef("arg.2", fill.type, loc);
281
+ const padded = {
282
+ kind: "strIntrinsic", method, receiver: stringReceiver,
283
+ args: [length, paddingFillString(lowerer, fillValue, fillNode ?? call)], type: STRING, loc,
284
+ };
285
+ const result = {
286
+ kind: "ternary",
287
+ cond: {
288
+ kind: "bin", op: ">=", left: length,
289
+ right: {
290
+ kind: "bin", op: "+",
291
+ left: { kind: "strIntrinsic", method: "length", receiver: stringReceiver, args: [], type: F64, loc },
292
+ right: numLit(1, loc), type: F64, loc,
293
+ },
294
+ type: BOOL, loc,
295
+ },
296
+ then: padded, else_: stringReceiver, type: STRING, loc,
297
+ };
298
+ const body = [
299
+ { kind: "varDecl", localId: "receiver.0", init: convertedReceiver, loc },
300
+ { kind: "varDecl", localId: "length.0", init: positionNumber(lowerer, varRef("arg.1", maxLength.type, loc), numLit(0, loc), argumentNodes[0] ?? call, "string padding length"), loc },
301
+ { kind: "return", value: result, loc },
302
+ ];
303
+ lowerer.widthHelpers.set(key, helper);
304
+ lowerer.liftedFns.push({
305
+ name: helper, params, returnType: STRING,
306
+ locals: [
307
+ ...params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false })),
308
+ { id: "receiver.0", name: "receiver", type: STRING, mutable: false },
309
+ { id: "length.0", name: "length", type: F64, mutable: false },
310
+ ],
311
+ body, loc,
312
+ });
313
+ }
314
+ return { kind: "call", callee: helper, args: values, type: STRING, loc };
315
+ }
26
316
  /** Regex method calls, both directions: `re.test(s)` on a regex receiver,
27
317
  * and `s.replace(re, tpl)` / `s.replaceAll(re, tpl)` / `s.split(re)` on a
28
318
  * string receiver whose FIRST ARGUMENT is a regex (the string-pattern
@@ -52,6 +342,9 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
52
342
  };
53
343
  const loc = locOf(call);
54
344
  if (receiverKind === "regex" && name === "test") {
345
+ if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
346
+ lowerer.noLowering("RegExp.prototype.test with surplus or spread arguments", call);
347
+ }
55
348
  // The statefulness fence, at compile time where the flags are
56
349
  // visible: a literal receiver (possibly parenthesized). Values that
57
350
  // flow through variables hit the same fence at runtime.
@@ -65,7 +358,7 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
65
358
  }
66
359
  }
67
360
  const receiver = lowerReceiver();
68
- const args = call.arguments.map((a) => lowerer.ensureString(lowerer.lowerExpr(a), a));
361
+ const args = [lowerRegexSubject(lowerer, call.arguments[0], loc)];
69
362
  return { kind: "regexIntrinsic", method: "test", receiver, args, type: BOOL, loc };
70
363
  }
71
364
  // `re.exec(s)` for non-g/y regexes: spec-identical to `s.match(re)`
@@ -76,8 +369,8 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
76
369
  // compile time on literal receivers, exactly test()'s stance; values
77
370
  // reaching the runtime with those flags abort there.
78
371
  if (receiverKind === "regex" && name === "exec") {
79
- if (call.arguments.length !== 1)
80
- return null; // exec takes exactly the subject
372
+ if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement))
373
+ return null;
81
374
  let recv = access.expression;
82
375
  while (ts.isParenthesizedExpression(recv))
83
376
  recv = recv.expression;
@@ -88,9 +381,13 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
88
381
  }
89
382
  }
90
383
  const re = lowerReceiver();
91
- const subject = lowerer.lowerExprExpecting(call.arguments[0], STRING);
384
+ const subject = lowerRegexSubject(lowerer, call.arguments[0], loc);
92
385
  const resultT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
93
- return { kind: "regexIntrinsic", method: "match", receiver: subject, args: [re], type: resultT, loc };
386
+ // The shared match intrinsic takes the string first; preserve exec's
387
+ // receiver-before-subject evaluation order before swapping operands.
388
+ const saved = lowerer.declareHiddenLocal("%execReceiver", re.type);
389
+ const result = { kind: "regexIntrinsic", method: "match", receiver: subject, args: [varRef(saved.id, re.type, loc)], type: resultT, loc };
390
+ return { kind: "seqExpr", stmts: [{ kind: "varDecl", localId: saved.id, init: re, loc }], result, type: resultT, loc };
94
391
  }
95
392
  // `s.match(re)` for non-g/y regexes: Node's exec-shaped result reduced
96
393
  // to the honest slice — the `string[] | null` union holding
@@ -212,18 +509,16 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
212
509
  return null;
213
510
  }
214
511
  /** `s.slice(1, 4)` and friends → strIntrinsic. Null when this isn't an
215
- * ambient string method call (caller keeps its generic rejection). Missing
216
- * optional arguments are omitted from `args` — the backend fills the
217
- * documented defaults; the IR never encodes them (Infinity isn't
218
- * JSON-safe). tsc has already checked arity and argument types against
219
- * ambient/scriptc.d.ts. */
220
- export function lowerStringMethodCall(lowerer, call, access, dynReceiver) {
512
+ * ambient string method call (caller keeps its generic rejection).
513
+ * Complete position defaults and conversions here for both backends. */
514
+ export function lowerStringMethodCall(lowerer, call, access, dynReceiver, argumentNodes = call.arguments) {
221
515
  if (lowerer.chainBlocked(access, call))
222
516
  return null;
223
517
  if (dynReceiver === undefined && access.name.text === "localeCompare")
224
518
  return lowerLocaleCompareCall(lowerer, call, access);
225
519
  const entry = own(STR_METHODS, access.name.text);
226
- if (!entry)
520
+ const indexMethod = STRING_INDEX_METHODS.has(access.name.text) ? access.name.text : null;
521
+ if (!entry && !indexMethod)
227
522
  return null;
228
523
  // A validated dyn receiver (`pkg.name.replace(...)` on a JSON.parse
229
524
  // value) arrives pre-extracted through `dynReceiver`; its checker type
@@ -244,29 +539,120 @@ export function lowerStringMethodCall(lowerer, call, access, dynReceiver) {
244
539
  if (!lowerer.isStdlibMember(access))
245
540
  return null;
246
541
  }
247
- // The lib declares optional parameters beyond the lowered forms
248
- // (includes/startsWith/endsWith take a position); fence the unlowered
249
- // arities instead of passing arguments the runtime doesn't take.
250
- if (call.arguments.length < entry.minArgs || call.arguments.length > entry.maxArgs) {
251
- lowerer.noLowering(`.${access.name.text} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"} on strings`, call);
542
+ // The lib declares optional parameters beyond some lowered forms; fence
543
+ // those arities instead of passing arguments the runtime doesn't take.
544
+ if (argumentNodes.length < (entry?.minArgs ?? 0) || argumentNodes.length > (entry?.maxArgs ?? 1)) {
545
+ lowerer.noLowering(`.${access.name.text} with ${argumentNodes.length} argument${argumentNodes.length === 1 ? "" : "s"} on strings`, call);
252
546
  }
253
547
  const receiver = dynReceiver
254
548
  ? dynReceiver()
255
549
  : lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
256
- const args = entry.method === "split"
257
- ? [lowerer.lowerExpr(call.arguments[0]), lowerSplitLimitArg(lowerer, call.arguments[1], locOf(call))]
258
- : call.arguments.map((a) => lowerer.lowerExpr(a));
259
- // split's separator must BE a string here (a regex argument was
260
- // claimed by lowerRegexMethodCall before this path) — the lib's
261
- // `string | RegExp` union has no lowering as a VALUE.
262
- if (entry.method === "split" && args[0].type.kind !== "string") {
263
- lowerer.unsupported("SC1090", call.arguments[0], `'.split()' on a '${lowerer.fmt(args[0].type)}' separator (pass a string, or a regex literal)`);
550
+ const loc = locOf(call);
551
+ if (indexMethod)
552
+ return lowerStringIndexCall(lowerer, call, indexMethod, receiver, access.expression, argumentNodes);
553
+ if (!entry)
554
+ return null;
555
+ if (entry.method === "split")
556
+ return lowerStringSplitCall(lowerer, call, receiver, access.expression, argumentNodes);
557
+ if (entry.method === "padStart" || entry.method === "padEnd") {
558
+ return lowerStringPaddingCall(lowerer, call, entry.method, receiver, access.expression, argumentNodes);
264
559
  }
265
- // padStart/padEnd with the fill omitted: Node pads with " " — the
266
- // same call with the default made explicit.
267
- if ((entry.method === "padStart" || entry.method === "padEnd") && args.length === 1) {
268
- args.push({ kind: "strLit", value: " ", type: STRING, loc: locOf(call) });
560
+ const searchMethod = entry.method === "indexOf" || entry.method === "includes" ||
561
+ entry.method === "startsWith" || entry.method === "endsWith";
562
+ if (searchMethod && argumentNodes.length < 2) {
563
+ const needle = lowerStringSearchArgument(lowerer, argumentNodes[0], loc);
564
+ return { kind: "strIntrinsic", method: entry.method, receiver, args: [needle], type: entry.result, loc };
565
+ }
566
+ if (searchMethod && argumentNodes.length === 2) {
567
+ const needleNode = argumentNodes[0];
568
+ const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, needleNode);
569
+ let needle = undefinedArg
570
+ ? defaultAfterUndefined(undefinedArg, strLit("undefined", loc))
571
+ : lowerer.lowerExpr(needleNode);
572
+ if (isUnitType(needle.type))
573
+ needle = coerceStringSearchValue(lowerer, needle, needleNode, loc);
574
+ const scalarUnion = needle.type.kind === "union" &&
575
+ (lowerer.unions.get(needle.type.unionId)?.arms.every((arm) => arm.kind === "string" || arm.kind === "f64" || arm.kind === "bool" || arm.kind === "bigint" || isUnitType(arm)) ?? false);
576
+ const scalarNeedle = needle.type.kind === "f64" || needle.type.kind === "bool" || needle.type.kind === "bigint" || scalarUnion;
577
+ if (needle.type.kind !== "string" && needle.type.kind !== "dyn" && !scalarNeedle) {
578
+ lowerer.noLowering(`.${entry.method} with '${lowerer.fmt(needle.type)}' search values`, call);
579
+ }
580
+ const defaultPosition = entry.method === "endsWith"
581
+ ? { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }
582
+ : numLit(0, loc);
583
+ const position = lowerPositionArgument(lowerer, argumentNodes[1], defaultPosition);
584
+ if (needle.type.kind === "string" && (position.type.kind === "f64" || position.type.kind === "jsval")) {
585
+ return { kind: "strIntrinsic", method: entry.method, receiver, args: [needle, position], type: entry.result, loc };
586
+ }
587
+ const key = `str.positions:${entry.method}:${typeKey(needle.type)}:${typeKey(position.type)}`;
588
+ let helper = lowerer.widthHelpers.get(key);
589
+ if (!helper) {
590
+ helper = `%str.positions.${lowerer.widthHelpers.size}`;
591
+ const params = [receiver, needle, position].map((arg, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: arg.type }));
592
+ const coerceNeedle = needle.type.kind !== "string";
593
+ const search = coerceNeedle ? varRef("search.0", STRING, loc) : varRef("arg.1", STRING, loc);
594
+ const result = {
595
+ kind: "strIntrinsic", method: entry.method, receiver: varRef("arg.0", STRING, loc),
596
+ args: [
597
+ search,
598
+ positionNumber(lowerer, varRef("arg.2", position.type, loc), defaultPosition, argumentNodes[1], "string position"),
599
+ ],
600
+ type: entry.result, loc,
601
+ };
602
+ const locals = params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false }));
603
+ const body = [];
604
+ if (coerceNeedle) {
605
+ const value = varRef("arg.1", needle.type, loc);
606
+ const init = needle.type.kind === "dyn"
607
+ ? { kind: "libCall", fn: "dyn.toStringCoerce", args: [value], type: STRING, loc }
608
+ : coerceStringSearchValue(lowerer, value, needleNode, loc);
609
+ locals.push({ id: "search.0", name: "search", type: STRING, mutable: false });
610
+ body.push({ kind: "varDecl", localId: "search.0", init, loc });
611
+ }
612
+ body.push({ kind: "return", value: result, loc });
613
+ lowerer.widthHelpers.set(key, helper);
614
+ lowerer.liftedFns.push({
615
+ name: helper, params, returnType: entry.result,
616
+ locals, body, loc,
617
+ });
618
+ }
619
+ return { kind: "call", callee: helper, args: [receiver, needle, position], type: entry.result, loc };
620
+ }
621
+ if (entry.method === "charAt" || entry.method === "charCodeAt" || entry.method === "slice" || entry.method === "substring" || entry.method === "repeat") {
622
+ const defaults = [numLit(0, loc)];
623
+ if (entry.method === "slice" || entry.method === "substring") {
624
+ defaults.push({ kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc });
625
+ }
626
+ const args = defaults.map((value, index) => lowerPositionArgument(lowerer, argumentNodes[index], value));
627
+ const subject = entry.method === "repeat" ? "string repeat count" : "string position";
628
+ // Keep ordinary numeric calls on the direct intrinsic path, including the
629
+ // existing boundary validation for island values in numeric slots.
630
+ if (args.every(arg => arg.type.kind === "f64" || arg.type.kind === "jsval")) {
631
+ return { kind: "strIntrinsic", method: entry.method, receiver, args, type: entry.result, loc };
632
+ }
633
+ // A helper evaluates all arguments before conversions can invoke hooks or
634
+ // throw. Its parameters also give owned strings/unions a per-call lifetime
635
+ // when the call occurs in a loop condition or short-circuit expression.
636
+ const key = `str.positions:${entry.method}:${args.map(arg => typeKey(arg.type)).join(":")}`;
637
+ let helper = lowerer.widthHelpers.get(key);
638
+ if (!helper) {
639
+ helper = `%str.positions.${lowerer.widthHelpers.size}`;
640
+ const params = [receiver, ...args].map((arg, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: arg.type }));
641
+ const result = {
642
+ kind: "strIntrinsic", method: entry.method, receiver: varRef("arg.0", STRING, loc),
643
+ args: args.map((arg, index) => positionNumber(lowerer, varRef(`arg.${index + 1}`, arg.type, loc), defaults[index], argumentNodes[index] ?? call, subject)),
644
+ type: entry.result, loc,
645
+ };
646
+ lowerer.widthHelpers.set(key, helper);
647
+ lowerer.liftedFns.push({
648
+ name: helper, params, returnType: entry.result,
649
+ locals: params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false })),
650
+ body: [{ kind: "return", value: result, loc }], loc,
651
+ });
652
+ }
653
+ return { kind: "call", callee: helper, args: [receiver, ...args], type: entry.result, loc };
269
654
  }
655
+ const args = argumentNodes.map((a) => lowerer.lowerExpr(a));
270
656
  return {
271
657
  kind: "strIntrinsic",
272
658
  method: entry.method,