@scriptc/compiler 0.1.5 → 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 (45) hide show
  1. package/dist/backend/c/exprs.js +36 -8
  2. package/dist/backend/c/exprs.js.map +1 -1
  3. package/dist/backend/llvm/expr-containers.js +28 -2
  4. package/dist/backend/llvm/expr-containers.js.map +1 -1
  5. package/dist/backend/llvm/lib-filesystem.js +8 -0
  6. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  7. package/dist/backend/llvm/lib-process.js +4 -3
  8. package/dist/backend/llvm/lib-process.js.map +1 -1
  9. package/dist/backend/llvm/lib-shared.js +1 -0
  10. package/dist/backend/llvm/lib-shared.js.map +1 -1
  11. package/dist/coverage/surface-manifest.js +6 -1
  12. package/dist/coverage/surface-manifest.js.map +1 -1
  13. package/dist/frontend/lowering/array-indexed-mutation.d.ts +8 -0
  14. package/dist/frontend/lowering/array-indexed-mutation.js +176 -0
  15. package/dist/frontend/lowering/array-indexed-mutation.js.map +1 -0
  16. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +7 -0
  17. package/dist/frontend/lowering/containers/string-and-regexp.js +190 -13
  18. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  19. package/dist/frontend/lowering/indexed-comparison.d.ts +6 -0
  20. package/dist/frontend/lowering/indexed-comparison.js +30 -0
  21. package/dist/frontend/lowering/indexed-comparison.js.map +1 -0
  22. package/dist/frontend/lowering/lower-builtins.d.ts +1 -1
  23. package/dist/frontend/lowering/lower-builtins.js +6 -6
  24. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  25. package/dist/frontend/lowering/lower-calls.js +266 -10
  26. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  27. package/dist/frontend/lowering/lower-classes.js +3 -27
  28. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  29. package/dist/frontend/lowering/lower-containers.d.ts +8 -2
  30. package/dist/frontend/lowering/lower-containers.js +454 -104
  31. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  32. package/dist/frontend/lowering/lower-exprs.js +18 -2
  33. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  34. package/dist/frontend/lowering/surfaces.d.ts +2 -0
  35. package/dist/frontend/lowering/surfaces.js +13 -1
  36. package/dist/frontend/lowering/surfaces.js.map +1 -1
  37. package/dist/ir/ir.d.ts +15 -2
  38. package/dist/ir/ir.js +2 -0
  39. package/dist/ir/ir.js.map +1 -1
  40. package/dist/ir/validate.js +43 -26
  41. package/dist/ir/validate.js.map +1 -1
  42. package/dist/ir/validate.test.js +17 -0
  43. package/dist/ir/validate.test.js.map +1 -1
  44. package/package.json +17 -17
  45. package/surface-manifest.json +36 -4
@@ -10,12 +10,12 @@ import { isJsSourceFile, isNodeEsmFile, locOf } from "../program.js";
10
10
  import { genResultRecord, isGenericCallableMemberType, typeKey } from "../type-mapper.js";
11
11
  import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx, nodeThrowExpr, staticImportNamespaceType } from "./lowerer.js";
12
12
  import { enforceLibBoundary } from "./lib-boundary.js";
13
- import { NARROW_FIRST, STR_METHODS, builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
13
+ import { NARROW_FIRST, STRING_INDEX_METHODS, STR_METHODS, builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
14
14
  import { ffiBindingDiag, ffiSignatureDiag, libCallbackDiag, requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
15
15
  import { mixinFnShapeOf } from "./lower-mixins.js";
16
- import { dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerTupleReadMethodCall } from "./lower-containers.js";
16
+ import { dynStringReceiver, lowerArrayConstructor, lowerArrayFromCall, lowerArrayOfCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerTupleReadMethodCall } from "./lower-containers.js";
17
17
  import { bufEncoding } from "./containers/bytes.js";
18
- import { lowerRegexMethodCall, lowerStringMethodCall, lowerStringPaddingCall } from "./containers/string-and-regexp.js";
18
+ import { lowerRegexMethodCall, lowerStringIndexCall, lowerStringMethodCall, lowerStringPaddingCall, lowerStringSplitCall } from "./containers/string-and-regexp.js";
19
19
  import { lowerChildStreamMethodCall, lowerChildWriterMethodCall, lowerCreateRequireCall, lowerCryptoHashMethodCall, lowerDirentMethodCall, lowerFileHandleMethodCall, lowerImportMetaResolveCall, lowerNodeModuleCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerRequireResolveCall, lowerWatcherMethodCall } from "./lower-builtins.js";
20
20
  import { lowerAbsenceProbe, lowerPromiseAllTupleCall, lowerPromiseRejectCall, stringWrapperToString, templateRawTextOf } from "./lower-exprs.js";
21
21
  import { isSafeToDiscard } from "./expressions/evaluation-safety.js";
@@ -3581,6 +3581,9 @@ export function lowerCall(lowerer, expr) {
3581
3581
  // supported form is fenced here per site. Members with no lowering at
3582
3582
  // all (fs.watch, os.cpus, ...) fence with the module-qualified name.
3583
3583
  if (ts.isIdentifier(expr.expression)) {
3584
+ if (expr.expression.text === "Array" && lowerer.isStdlibGlobal(expr.expression, "Array")) {
3585
+ return lowerArrayConstructor(lowerer, expr, expr.arguments);
3586
+ }
3584
3587
  // A call through a compile-time util.promisify projection. execFile
3585
3588
  // keeps its custom result helper; ordinary builtins route through
3586
3589
  // their existing promise-module lowering.
@@ -4188,6 +4191,8 @@ export function lowerCall(lowerer, expr) {
4188
4191
  // TestContext surface (t.test/t.skip/t.diagnostic), t.assert.*.
4189
4192
  lowerer.lowerTestMethodCall(expr, expr.expression) ??
4190
4193
  lowerer.lowerTimeoutMethodCall(expr, expr.expression) ??
4194
+ lowerObjectOwnPrototypeCall(lowerer, expr, expr.expression) ??
4195
+ lowerObjectPrototypeCall(lowerer, expr, expr.expression) ??
4191
4196
  lowerStringPrototypeCall(lowerer, expr, expr.expression) ??
4192
4197
  lowerStringMethodCallWithOptionalArgs(lowerer, expr, expr.expression) ??
4193
4198
  // Typed-array/Buffer receivers and the Buffer statics — before the
@@ -4217,6 +4222,7 @@ export function lowerCall(lowerer, expr) {
4217
4222
  // hasOwnProperty on a program class CONSTRUCTOR — own statics are
4218
4223
  // compile-time-known, so a literal key folds to a constant.
4219
4224
  lowerClassHasOwnPropertyCall(lowerer, expr, expr.expression) ??
4225
+ lowerObjectOwnMethodCall(lowerer, expr, expr.expression) ??
4220
4226
  // Response constructor-object operations are unsupported in both
4221
4227
  // tiers. Keep their SC2020 inventory contract ahead of the island
4222
4228
  // and generic-call fallbacks (Response.json otherwise reports the
@@ -4257,6 +4263,7 @@ export function lowerCall(lowerer, expr) {
4257
4263
  lowerObjectStaticCall(lowerer, expr, expr.expression) ??
4258
4264
  lowerObjectFromEntriesCall(lowerer, expr, expr.expression) ??
4259
4265
  lowerArrayFromCall(lowerer, expr, expr.expression) ??
4266
+ lowerArrayOfCall(lowerer, expr, expr.expression) ??
4260
4267
  lowerer.lowerArrayMethodCall(expr, expr.expression) ??
4261
4268
  // Read-only array methods (slice/map) on TUPLE receivers — the
4262
4269
  // positions snapshot into a fresh array (the for-of stance).
@@ -4640,6 +4647,243 @@ function plainObjectCoercionReceiver(lowerer, node) {
4640
4647
  return false;
4641
4648
  return bindingNeverReassigned(lowerer, symbol, decl);
4642
4649
  }
4650
+ function objectToStringTag(lowerer, type) {
4651
+ switch (type.kind) {
4652
+ case "undefinedT":
4653
+ case "void": return "Undefined";
4654
+ case "nullT": return "Null";
4655
+ case "bool": return "Boolean";
4656
+ case "f64": return "Number";
4657
+ case "string": return "String";
4658
+ case "bigint": return "BigInt";
4659
+ case "symbol": return "Symbol";
4660
+ case "array": return "Array";
4661
+ case "record": return "Object";
4662
+ case "func":
4663
+ case "classval": return "Function";
4664
+ case "date": return "Date";
4665
+ case "regex": return "RegExp";
4666
+ case "map": return "Map";
4667
+ case "set": return "Set";
4668
+ case "promise": return "Promise";
4669
+ case "bytes":
4670
+ return ({ u8: "Uint8Array", u32: "Uint32Array", i32: "Int32Array", f32: "Float32Array", f64: "Float64Array" })[type.elem];
4671
+ case "object":
4672
+ return type.className === "%Error" || lowerer.isSubclassOf(type.className, "%Error") ? "Error" : null;
4673
+ default: return null;
4674
+ }
4675
+ }
4676
+ function immediateObjectTagReceiver(lowerer, node) {
4677
+ let value = node;
4678
+ while (ts.isParenthesizedExpression(value))
4679
+ value = value.expression;
4680
+ const loc = locOf(value);
4681
+ if (ts.isArrayLiteralExpression(value))
4682
+ return { tag: "Array", effect: lowerer.lowerExpr(value) };
4683
+ if ((ts.isCallExpression(value) || ts.isNewExpression(value)) && ts.isIdentifier(value.expression) &&
4684
+ (value.expression.text === "Array" || value.expression.text === "Error") &&
4685
+ (value.arguments?.length ?? 0) === 0 && lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(value.expression) ?? undefined)) {
4686
+ return { tag: value.expression.text, effect: { kind: "numLit", value: 0, type: F64, loc } };
4687
+ }
4688
+ if (ts.isNewExpression(value) && ts.isIdentifier(value.expression) &&
4689
+ lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(value.expression) ?? undefined)) {
4690
+ const name = value.expression.text;
4691
+ const args = value.arguments ?? [];
4692
+ if (args.length <= 1 && !args.some(ts.isSpreadElement)) {
4693
+ if (name === "String") {
4694
+ const effect = stringWrapperToString(lowerer, value);
4695
+ if (effect)
4696
+ return { tag: "String", effect };
4697
+ }
4698
+ if (name === "Number")
4699
+ return {
4700
+ tag: "Number",
4701
+ effect: args.length ? lowerNumberConstructorValue(lowerer, args[0], loc) : { kind: "numLit", value: 0, type: F64, loc },
4702
+ };
4703
+ if (name === "Boolean")
4704
+ return {
4705
+ tag: "Boolean",
4706
+ effect: args.length ? lowerer.lowerCondition(args[0]) : { kind: "boolLit", value: false, type: BOOL, loc },
4707
+ };
4708
+ }
4709
+ }
4710
+ if ((ts.isCallExpression(value) || ts.isNewExpression(value)) &&
4711
+ ts.isIdentifier(value.expression) && value.expression.text === "Object" &&
4712
+ lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(value.expression) ?? undefined)) {
4713
+ const args = value.arguments ?? [];
4714
+ if (args.some(ts.isSpreadElement))
4715
+ return null;
4716
+ const first = args[0];
4717
+ const inner = first && (immediateObjectTagReceiver(lowerer, first) ?? (() => {
4718
+ const effect = lowerer.lowerExpr(first);
4719
+ const tag = isUnitType(effect.type) || effect.type.kind === "void" ? "Object" : objectToStringTag(lowerer, effect.type);
4720
+ return tag === null ? null : { tag, effect };
4721
+ })());
4722
+ if (first && !inner)
4723
+ return null;
4724
+ const stmts = [];
4725
+ if (inner && !isSafeToDiscard(inner.effect))
4726
+ stmts.push({ kind: "exprStmt", expr: inner.effect, loc: inner.effect.loc });
4727
+ for (const extra of args.slice(1)) {
4728
+ const effect = lowerer.lowerExpr(extra);
4729
+ if (!isSafeToDiscard(effect))
4730
+ stmts.push({ kind: "exprStmt", expr: effect, loc: locOf(extra) });
4731
+ }
4732
+ return {
4733
+ tag: inner?.tag ?? "Object",
4734
+ effect: { kind: "seqExpr", stmts, result: { kind: "numLit", value: 0, type: F64, loc }, type: F64, loc },
4735
+ };
4736
+ }
4737
+ return null;
4738
+ }
4739
+ function lowerObjectOwnPrototypeCall(lowerer, call, access) {
4740
+ if (lowerer.dynamic || access.name.text !== "call" || !ts.isPropertyAccessExpression(access.expression))
4741
+ return null;
4742
+ const method = access.expression;
4743
+ const enumerable = method.name.text === "propertyIsEnumerable";
4744
+ if (method.name.text !== "hasOwnProperty" && !enumerable)
4745
+ return null;
4746
+ const prototype = method.expression;
4747
+ if (!ts.isPropertyAccessExpression(prototype) || prototype.name.text !== "prototype" ||
4748
+ !lowerer.isStdlibGlobal(prototype.expression, "Object") || !lowerer.isStdlibMember(method) ||
4749
+ call.arguments.some(ts.isSpreadElement))
4750
+ return null;
4751
+ return lowerObjectOwnCall(lowerer, call, call.arguments[0], call.arguments[1], call.arguments.slice(2), method.name.text);
4752
+ }
4753
+ function lowerObjectOwnMethodCall(lowerer, call, access) {
4754
+ const method = access.name.text;
4755
+ if (lowerer.dynamic || call.questionDotToken || access.questionDotToken ||
4756
+ (method !== "hasOwnProperty" && method !== "propertyIsEnumerable") ||
4757
+ !lowerer.isStdlibMember(access) || call.arguments.some(ts.isSpreadElement))
4758
+ return null;
4759
+ return lowerObjectOwnCall(lowerer, call, access.expression, call.arguments[0], call.arguments.slice(1), method);
4760
+ }
4761
+ function lowerObjectOwnCall(lowerer, call, receiverNode, keyNode, extraNodes, method) {
4762
+ const enumerable = method === "propertyIsEnumerable";
4763
+ const loc = locOf(call);
4764
+ const stmts = [];
4765
+ const receiver = receiverNode ? lowerer.lowerExpr(receiverNode) : { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc };
4766
+ let receiverRef = receiver;
4767
+ if (isUnitType(receiver.type) || receiver.type.kind === "void") {
4768
+ if (!isSafeToDiscard(receiver))
4769
+ stmts.push({ kind: "exprStmt", expr: receiver, loc });
4770
+ }
4771
+ else {
4772
+ const local = lowerer.declareHiddenLocal("%ownReceiver", receiver.type);
4773
+ stmts.push({ kind: "varDecl", localId: local.id, init: receiver, loc });
4774
+ receiverRef = varRef(local.id, receiver.type, loc);
4775
+ }
4776
+ const rawKey = keyNode ? lowerer.lowerExpr(keyNode) : null;
4777
+ let keyRef = rawKey;
4778
+ if (rawKey && (isUnitType(rawKey.type) || rawKey.type.kind === "void")) {
4779
+ if (!isSafeToDiscard(rawKey))
4780
+ stmts.push({ kind: "exprStmt", expr: rawKey, loc: rawKey.loc });
4781
+ }
4782
+ else if (rawKey) {
4783
+ const local = lowerer.declareHiddenLocal("%ownKey", rawKey.type);
4784
+ stmts.push({ kind: "varDecl", localId: local.id, init: rawKey, loc: rawKey.loc });
4785
+ keyRef = varRef(local.id, rawKey.type, loc);
4786
+ }
4787
+ for (const extra of extraNodes) {
4788
+ const effect = lowerer.lowerExpr(extra);
4789
+ if (!isSafeToDiscard(effect))
4790
+ stmts.push({ kind: "exprStmt", expr: effect, loc: locOf(extra) });
4791
+ }
4792
+ let key = keyRef;
4793
+ if (!keyNode || rawKey?.type.kind === "undefinedT" || rawKey?.type.kind === "void") {
4794
+ key = { kind: "strLit", value: "undefined", type: STRING, loc };
4795
+ }
4796
+ else if (rawKey?.type.kind === "nullT") {
4797
+ key = { kind: "strLit", value: "null", type: STRING, loc };
4798
+ }
4799
+ else if (key && (key.type.kind === "f64" || key.type.kind === "bool" || key.type.kind === "bigint")) {
4800
+ key = { kind: "toString", operand: key, type: STRING, loc };
4801
+ }
4802
+ if (!key || key.type.kind !== "string") {
4803
+ lowerer.noLowering("Object.prototype." + method + " with this property key", keyNode ?? call);
4804
+ }
4805
+ let result;
4806
+ const type = receiver.type;
4807
+ if (isUnitType(type) || type.kind === "void") {
4808
+ result = nodeThrowExpr(1, "", "Cannot convert undefined or null to object", BOOL, loc);
4809
+ }
4810
+ else if (type.kind === "record") {
4811
+ const shape = lowerer.shapes.get(type.shapeId);
4812
+ if (!shape || shape.tuple || shape.indexValue || shapeHasAccessorSlots(shape)) {
4813
+ lowerer.noLowering("Object.prototype." + method + " over this record shape", receiverNode ?? call);
4814
+ }
4815
+ result = { kind: "call", callee: recordHasOwnHelper(lowerer, type.shapeId, loc), args: [receiverRef, key], type: BOOL, loc };
4816
+ }
4817
+ else if (type.kind === "dyn" && !enumerable) {
4818
+ result = { kind: "libCall", fn: "dyn.hasOwn", args: [receiverRef, key], type: BOOL, loc };
4819
+ }
4820
+ else if (type.kind === "string" || type.kind === "array") {
4821
+ const needleLocal = lowerer.declareHiddenLocal("%ownIndex", F64);
4822
+ stmts.push({ kind: "varDecl", localId: needleLocal.id, init: { kind: "libCall", fn: "num.fromString", args: [key], type: F64, loc }, loc });
4823
+ const needle = varRef(needleLocal.id, F64, loc);
4824
+ const length = type.kind === "string"
4825
+ ? { kind: "strIntrinsic", method: "length", receiver: receiverRef, args: [], type: F64, loc }
4826
+ : { kind: "arrIntrinsic", method: "length", receiver: receiverRef, args: [], type: F64, loc };
4827
+ const inRange = { kind: "bin", op: "<", left: needle, right: length, type: BOOL, loc };
4828
+ const nonNegative = { kind: "bin", op: ">=", left: needle, right: { kind: "numLit", value: 0, type: F64, loc }, type: BOOL, loc };
4829
+ const integer = { kind: "bin", op: "===", left: needle,
4830
+ right: { kind: "libCall", fn: "math.floor", args: [needle], type: F64, loc }, type: BOOL, loc };
4831
+ const canonical = { kind: "strEq", negated: false, left: key, right: { kind: "toString", operand: needle, type: STRING, loc }, type: BOOL, loc };
4832
+ const present = type.kind === "string" ? { kind: "boolLit", value: true, type: BOOL, loc } :
4833
+ { kind: "bin", op: "===", left: { kind: "arrIntrinsic", method: "nextPresent", receiver: receiverRef, args: [needle], type: F64, loc }, right: needle, type: BOOL, loc };
4834
+ const no = { kind: "boolLit", value: false, type: BOOL, loc };
4835
+ const indexOwn = [inRange, integer, canonical, present].reduce((left, right) => ({ kind: "logical", op: "&&", left, right, type: BOOL, loc }), nonNegative);
4836
+ const lengthOwn = !enumerable
4837
+ ? { kind: "strEq", negated: false, left: key, right: { kind: "strLit", value: "length", type: STRING, loc }, type: BOOL, loc }
4838
+ : no;
4839
+ result = { kind: "logical", op: "||", left: lengthOwn, right: indexOwn, type: BOOL, loc };
4840
+ }
4841
+ else if (type.kind === "f64" || type.kind === "bool" || type.kind === "bigint" || type.kind === "symbol") {
4842
+ result = { kind: "boolLit", value: false, type: BOOL, loc };
4843
+ }
4844
+ else {
4845
+ lowerer.noLowering("Object.prototype." + method + " over a " + lowerer.fmt(type) + " receiver", receiverNode ?? call);
4846
+ }
4847
+ return { kind: "seqExpr", stmts, result, type: BOOL, loc };
4848
+ }
4849
+ function lowerObjectPrototypeCall(lowerer, call, access) {
4850
+ if (lowerer.dynamic || access.name.text !== "call" || !ts.isPropertyAccessExpression(access.expression))
4851
+ return null;
4852
+ const method = access.expression;
4853
+ const prototype = method.expression;
4854
+ if (method.name.text !== "toString" || !ts.isPropertyAccessExpression(prototype) ||
4855
+ prototype.name.text !== "prototype" || !lowerer.isStdlibGlobal(prototype.expression, "Object") ||
4856
+ !lowerer.isStdlibMember(method) || call.arguments.some(ts.isSpreadElement))
4857
+ return null;
4858
+ const loc = locOf(call);
4859
+ if (call.arguments.length === 0)
4860
+ return { kind: "strLit", value: "[object Undefined]", type: STRING, loc };
4861
+ const receiverNode = call.arguments[0];
4862
+ const immediate = immediateObjectTagReceiver(lowerer, receiverNode);
4863
+ const receiver = immediate?.effect ?? lowerer.lowerExpr(receiverNode);
4864
+ const tag = immediate?.tag ?? objectToStringTag(lowerer, receiver.type);
4865
+ if (tag === null && receiver.type.kind !== "dyn") {
4866
+ lowerer.noLowering("Object.prototype.toString.call over a " + lowerer.fmt(receiver.type) + " receiver", receiverNode);
4867
+ }
4868
+ const stmts = [];
4869
+ let ref = receiver;
4870
+ if (isUnitType(receiver.type) || receiver.type.kind === "void") {
4871
+ if (!isSafeToDiscard(receiver))
4872
+ stmts.push({ kind: "exprStmt", expr: receiver, loc });
4873
+ }
4874
+ else {
4875
+ const local = lowerer.declareHiddenLocal("%objectTagReceiver", receiver.type);
4876
+ stmts.push({ kind: "varDecl", localId: local.id, init: receiver, loc });
4877
+ ref = varRef(local.id, receiver.type, loc);
4878
+ }
4879
+ for (const extra of call.arguments.slice(1)) {
4880
+ stmts.push({ kind: "exprStmt", expr: lowerer.lowerExpr(extra), loc: locOf(extra) });
4881
+ }
4882
+ const result = tag === null
4883
+ ? { kind: "libCall", fn: "dyn.objectTag", args: [ref], type: STRING, loc }
4884
+ : { kind: "strLit", value: "[object " + tag + "]", type: STRING, loc };
4885
+ return { kind: "seqExpr", stmts, result, type: STRING, loc };
4886
+ }
4643
4887
  function lowerStringPrototypeCall(lowerer, call, access) {
4644
4888
  if (access.name.text !== "call" || !ts.isPropertyAccessExpression(access.expression))
4645
4889
  return null;
@@ -4647,21 +4891,26 @@ function lowerStringPrototypeCall(lowerer, call, access) {
4647
4891
  const prototypeAccess = methodAccess.expression;
4648
4892
  if (!ts.isPropertyAccessExpression(prototypeAccess) || prototypeAccess.name.text !== "prototype" ||
4649
4893
  !ts.isIdentifier(prototypeAccess.expression) || prototypeAccess.expression.text !== "String" ||
4650
- !Object.hasOwn(STR_METHODS, methodAccess.name.text) || !lowerer.isStdlibMember(methodAccess))
4894
+ (!Object.hasOwn(STR_METHODS, methodAccess.name.text) && !STRING_INDEX_METHODS.has(methodAccess.name.text)) ||
4895
+ !lowerer.isStdlibMember(methodAccess))
4651
4896
  return null;
4652
4897
  if (call.arguments.some(ts.isSpreadElement))
4653
4898
  return null;
4654
4899
  const entry = STR_METHODS[methodAccess.name.text];
4900
+ const indexMethod = STRING_INDEX_METHODS.has(methodAccess.name.text) ? methodAccess.name.text : null;
4901
+ const resultType = indexMethod ? lowerer.withUndefinedArmOf(indexMethod === "at" ? STRING : F64) : entry?.result;
4902
+ if (!resultType)
4903
+ return null;
4655
4904
  const method = methodAccess.name.text === "trimStart" ? "trimLeft" :
4656
4905
  methodAccess.name.text === "trimEnd" ? "trimRight" : methodAccess.name.text;
4657
4906
  const loc = locOf(call);
4658
- const nullishError = nodeThrowExpr(1, "", `String.prototype.${method} called on null or undefined`, entry.result, loc);
4907
+ const nullishError = nodeThrowExpr(1, "", `String.prototype.${method} called on null or undefined`, resultType, loc);
4659
4908
  const receiverNode = call.arguments[0];
4660
4909
  if (!receiverNode)
4661
4910
  return nullishError;
4662
4911
  // Receiver coercion runs after .call has evaluated its arguments; keep
4663
4912
  // contextual wrapper lowering on forms without later method arguments.
4664
- const immediateWrapper = entry.maxArgs === 0 && call.arguments.length === 1
4913
+ const immediateWrapper = entry?.maxArgs === 0 && call.arguments.length === 1
4665
4914
  ? immediatePrimitiveWrapperToString(lowerer, receiverNode) : null;
4666
4915
  const receiverValue = immediateWrapper ?? lowerer.lowerExpr(receiverNode);
4667
4916
  const receiverType = receiverValue.type;
@@ -4673,26 +4922,33 @@ function lowerStringPrototypeCall(lowerer, call, access) {
4673
4922
  kind: "seqExpr",
4674
4923
  stmts: values.filter((value) => !isSafeToDiscard(value)).map((value) => ({ kind: "exprStmt", expr: value, loc: value.loc })),
4675
4924
  result: nullishError,
4676
- type: entry.result,
4925
+ type: resultType,
4677
4926
  loc,
4678
4927
  };
4679
4928
  }
4680
- const padding = entry.method === "padStart" || entry.method === "padEnd";
4929
+ const padding = entry?.method === "padStart" || entry?.method === "padEnd";
4681
4930
  const scalar = receiverType.kind === "string" || receiverType.kind === "f64" ||
4682
4931
  receiverType.kind === "bool" || receiverType.kind === "bigint" ||
4683
4932
  (receiverType.kind === "union" && (lowerer.unions.get(receiverType.unionId)?.arms.every((arm) => arm.kind === "string" || arm.kind === "f64" || arm.kind === "bool" || arm.kind === "bigint") ?? false));
4684
4933
  // Other methods convert object receivers before lowering their arguments;
4685
4934
  // padding uses a helper that delays conversion until every argument is ready.
4686
- const objectWithoutMethodArgs = entry.maxArgs === 0 && call.arguments.length === 1 &&
4935
+ const objectWithoutMethodArgs = entry?.maxArgs === 0 && call.arguments.length === 1 &&
4687
4936
  (receiverType.kind === "record" || receiverType.kind === "array" || receiverType.kind === "object");
4688
- const dynObjectWithoutMethodArgs = entry.maxArgs === 0 && call.arguments.length === 1 &&
4937
+ const dynObjectWithoutMethodArgs = entry?.maxArgs === 0 && call.arguments.length === 1 &&
4689
4938
  receiverType.kind === "dyn" && plainObjectCoercionReceiver(lowerer, receiverNode);
4690
4939
  if (!scalar && !objectWithoutMethodArgs && !dynObjectWithoutMethodArgs && !(padding && receiverType.kind === "dyn")) {
4691
4940
  return lowerer.noLowering(`String.prototype.${methodAccess.name.text}.call with ${lowerer.fmt(receiverType)} receivers`, call);
4692
4941
  }
4942
+ if (indexMethod)
4943
+ return lowerStringIndexCall(lowerer, call, indexMethod, receiverValue, receiverNode, call.arguments.slice(1));
4944
+ if (!entry)
4945
+ return null;
4693
4946
  if (padding) {
4694
4947
  return lowerStringPaddingCall(lowerer, call, entry.method, receiverValue, receiverNode, call.arguments.slice(1));
4695
4948
  }
4949
+ if (entry.method === "split") {
4950
+ return lowerStringSplitCall(lowerer, call, receiverValue, receiverNode, call.arguments.slice(1));
4951
+ }
4696
4952
  const receiver = dynObjectWithoutMethodArgs
4697
4953
  ? { kind: "libCall", fn: "dyn.toStringCoerce", args: [receiverValue], type: STRING, loc }
4698
4954
  : lowerer.ensureString(receiverValue, receiverNode);