@scriptc/compiler 0.0.36 → 0.1.1

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 (113) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +7 -5
  2. package/ambient/scriptc-overrides.d.ts +0 -7
  3. package/ambient/scriptc.d.ts +1 -1
  4. package/dist/backend/c/exprs.js +69 -45
  5. package/dist/backend/c/exprs.js.map +1 -1
  6. package/dist/backend/c/may-throw.js +3 -0
  7. package/dist/backend/c/may-throw.js.map +1 -1
  8. package/dist/backend/c/stmts.js +18 -0
  9. package/dist/backend/c/stmts.js.map +1 -1
  10. package/dist/backend/c/walkers.js +5 -7
  11. package/dist/backend/c/walkers.js.map +1 -1
  12. package/dist/backend/external-c.d.ts +33 -0
  13. package/dist/backend/external-c.js +51 -0
  14. package/dist/backend/external-c.js.map +1 -0
  15. package/dist/backend/link-plan.d.ts +37 -0
  16. package/dist/backend/link-plan.js +26 -0
  17. package/dist/backend/link-plan.js.map +1 -0
  18. package/dist/backend/linker.d.ts +22 -0
  19. package/dist/backend/linker.js +236 -0
  20. package/dist/backend/linker.js.map +1 -0
  21. package/dist/backend/llvm/dyn.js +7 -0
  22. package/dist/backend/llvm/dyn.js.map +1 -1
  23. package/dist/backend/llvm/emitter.js +34 -7
  24. package/dist/backend/llvm/emitter.js.map +1 -1
  25. package/dist/backend/llvm/expr-containers.js +68 -54
  26. package/dist/backend/llvm/expr-containers.js.map +1 -1
  27. package/dist/backend/llvm/expr-context.d.ts +1 -1
  28. package/dist/backend/llvm/expr-dispatch.js +2 -0
  29. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  30. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  31. package/dist/backend/llvm/expr-primitives.js +20 -0
  32. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  33. package/dist/backend/llvm/expr-stream-bridges.js +25 -1
  34. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  35. package/dist/backend/llvm/walkers.js +31 -4
  36. package/dist/backend/llvm/walkers.js.map +1 -1
  37. package/dist/backend/native-codegen.d.ts +8 -1
  38. package/dist/backend/native-codegen.js +39 -25
  39. package/dist/backend/native-codegen.js.map +1 -1
  40. package/dist/backend/native-codegen.test.js +42 -4
  41. package/dist/backend/native-codegen.test.js.map +1 -1
  42. package/dist/backend/native-link-info.d.ts +1 -1
  43. package/dist/backend/native-link-info.js +42 -18
  44. package/dist/backend/native-link-info.js.map +1 -1
  45. package/dist/backend/native-toolchain.test.js +5 -1
  46. package/dist/backend/native-toolchain.test.js.map +1 -1
  47. package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
  48. package/dist/backend/runtime-pack-targets.test.js +54 -0
  49. package/dist/backend/runtime-pack-targets.test.js.map +1 -0
  50. package/dist/backend/runtime-pack.d.ts +5 -30
  51. package/dist/backend/runtime-pack.js +46 -141
  52. package/dist/backend/runtime-pack.js.map +1 -1
  53. package/dist/backend/runtime-pack.test.js +49 -14
  54. package/dist/backend/runtime-pack.test.js.map +1 -1
  55. package/dist/backend/targets.d.ts +63 -17
  56. package/dist/backend/targets.js +251 -35
  57. package/dist/backend/targets.js.map +1 -1
  58. package/dist/backend/targets.test.js +27 -5
  59. package/dist/backend/targets.test.js.map +1 -1
  60. package/dist/coverage/surface-manifest.js +10 -1
  61. package/dist/coverage/surface-manifest.js.map +1 -1
  62. package/dist/frontend/lowering/array-values.d.ts +8 -0
  63. package/dist/frontend/lowering/array-values.js +60 -0
  64. package/dist/frontend/lowering/array-values.js.map +1 -0
  65. package/dist/frontend/lowering/lower-array-sort.d.ts +22 -0
  66. package/dist/frontend/lowering/lower-array-sort.js +619 -0
  67. package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
  68. package/dist/frontend/lowering/lower-builtins.js +164 -4
  69. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  70. package/dist/frontend/lowering/lower-calls.d.ts +3 -4
  71. package/dist/frontend/lowering/lower-calls.js +260 -27
  72. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  73. package/dist/frontend/lowering/lower-containers.js +728 -655
  74. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  75. package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
  76. package/dist/frontend/lowering/lower-exprs.js +769 -101
  77. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  78. package/dist/frontend/lowering/lower-inspect.js +47 -33
  79. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  80. package/dist/frontend/lowering/lower-island.d.ts +5 -4
  81. package/dist/frontend/lowering/lower-island.js +10 -5
  82. package/dist/frontend/lowering/lower-island.js.map +1 -1
  83. package/dist/frontend/lowering/lower-server.js +10 -0
  84. package/dist/frontend/lowering/lower-server.js.map +1 -1
  85. package/dist/frontend/lowering/lower-stmts.js +302 -71
  86. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  87. package/dist/frontend/lowering/lowerer.d.ts +74 -2
  88. package/dist/frontend/lowering/lowerer.js +1087 -10
  89. package/dist/frontend/lowering/lowerer.js.map +1 -1
  90. package/dist/frontend/lowering/surfaces.d.ts +10 -2
  91. package/dist/frontend/lowering/surfaces.js +15 -3
  92. package/dist/frontend/lowering/surfaces.js.map +1 -1
  93. package/dist/index.d.ts +3 -3
  94. package/dist/index.js +45 -23
  95. package/dist/index.js.map +1 -1
  96. package/dist/ir/ir.d.ts +65 -11
  97. package/dist/ir/ir.js +25 -25
  98. package/dist/ir/ir.js.map +1 -1
  99. package/dist/ir/serialize.d.ts +1 -1
  100. package/dist/ir/serialize.js +1 -1
  101. package/dist/ir/validate.js +84 -37
  102. package/dist/ir/validate.js.map +1 -1
  103. package/dist/library/fence-eval.js +3 -1
  104. package/dist/library/fence-eval.js.map +1 -1
  105. package/dist/library/int-infer.js +12 -1
  106. package/dist/library/int-infer.js.map +1 -1
  107. package/dist/library/int-infer.test.js +4 -4
  108. package/dist/library/library-cache.test.js +5 -5
  109. package/dist/startup-cache.d.ts +9 -1
  110. package/dist/startup-cache.js +16 -1
  111. package/dist/startup-cache.js.map +1 -1
  112. package/package.json +17 -4
  113. package/surface-manifest.json +5 -5
@@ -360,10 +360,9 @@ export declare function lowerFfiCall(lowerer: Lowerer, expr: ts.CallExpression):
360
360
  export declare function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr;
361
361
  export declare function lowerDynDispatchMethodCall(lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, recv: IrExpr, arrayReceiver: boolean): IrExpr | null;
362
362
  /** Tagged templates `tag\`a${x}b\`` — ES's call: tag(strings, ...values).
363
- * The strings object is the per-SITE interned cooked array (the
364
- * templateStrings node: one immortal string[] per occurrence, so the
365
- * spec's identity contract holds — the same site evaluated twice hands
366
- * the tag the SAME array; two sites never share). TemplateStringsArray
363
+ * The strings object is a per-SITE lazily initialized hidden array global,
364
+ * so the spec's identity contract holds — the same site evaluated twice
365
+ * hands the tag the SAME array; two sites never share. TemplateStringsArray
367
366
  * maps to string[] (type-mapper.ts), so the array rides the ordinary
368
367
  * slot-directed coercion into whatever the tag's first parameter wants
369
368
  * — string[] exactly, an `any` slot through the dyn boundary, a rest
@@ -101,7 +101,7 @@ export function paramShape(lowerer, param) {
101
101
  }
102
102
  return { type: abi, mode: "omittable", bodyType };
103
103
  }
104
- return { type: lowerer.irTypeOf(param.name), mode: "required" };
104
+ return { type: lowerer.runtimeOptionalBindingType(param.name, lowerer.irTypeOf(param.name)), mode: "required" };
105
105
  }
106
106
  if (param.dotDotDotToken) {
107
107
  // A JS rest param with no static element type (`(...args)` — any[]):
@@ -124,7 +124,7 @@ export function paramShape(lowerer, param) {
124
124
  return { type: DYN, mode: "dynRest" };
125
125
  }
126
126
  }
127
- const type = lowerer.irTypeOf(param.name);
127
+ const type = lowerer.runtimeOptionalBindingType(param.name, lowerer.irTypeOf(param.name));
128
128
  // Tuple-typed rest params don't map to an array; generic rest is the
129
129
  // generic path's business. Anything non-array here is unmappable.
130
130
  if (type.kind !== "array")
@@ -167,7 +167,7 @@ export function paramShape(lowerer, param) {
167
167
  }
168
168
  return { type: abi, mode: "omittable", bodyType };
169
169
  }
170
- const type = lowerer.irTypeOf(param.name);
170
+ const type = lowerer.runtimeOptionalBindingType(param.name, lowerer.irTypeOf(param.name));
171
171
  if (param.questionToken && !lowerer.bareUndefinedArmedUnion(type) && type.kind !== "dyn" && type.kind !== "jsval") {
172
172
  // `x?: unknown` where unknown came from an annotation: undefined is
173
173
  // absorbed into the hole type, so no undefined ARM exists — but a
@@ -195,7 +195,7 @@ export function paramShapes(lowerer, params, signature, blameOf) {
195
195
  return params.map((declParam, i) => {
196
196
  const symbol = signature.getParameters()[i];
197
197
  const tsType = symbol ? lowerer.checker.getTypeOfSymbol(symbol) : lowerer.typeOf(declParam.name);
198
- const mapped = lowerer.mapTypeOf(tsType);
198
+ const mapped = lowerer.runtimeOptionalBindingType(declParam.name, lowerer.mapTypeOf(tsType) ?? lowerer.irTypeOf(declParam.name));
199
199
  if (!mapped || mapped.kind === "void")
200
200
  lowerer.badType(blameOf?.(declParam, i) ?? declParam.name, tsType);
201
201
  if (declParam.dotDotDotToken) {
@@ -1437,6 +1437,9 @@ export function lowerGenericFnValue(lowerer, ref, info) {
1437
1437
  fenceUnpinned();
1438
1438
  const bindings = new Map();
1439
1439
  unifySignatureBindings(lowerer, info, target, bindings);
1440
+ const hofBinding = runtimeOptionalHofGenericBinding(lowerer, ref, info);
1441
+ if (hofBinding)
1442
+ bindings.set(hofBinding.typeParam, hofBinding.type);
1440
1443
  bindDefaultTypeParams(lowerer, info.typeParams, info.decl.typeParameters, bindings);
1441
1444
  // A pinning signature that itself keeps type parameters (`let g: <T>(x:
1442
1445
  // T) => T = identity` — storing the generic signature as such) binds
@@ -1457,6 +1460,31 @@ export function lowerGenericFnValue(lowerer, ref, info) {
1457
1460
  lowerer.noteEdge(inst.name);
1458
1461
  return { kind: "closure", fnName: inst.name, captures: [], type: funcType, loc };
1459
1462
  }
1463
+ function runtimeOptionalHofGenericBinding(lowerer, ref, info) {
1464
+ let argument = ref;
1465
+ while (argument.parent && ts.isParenthesizedExpression(argument.parent))
1466
+ argument = argument.parent;
1467
+ const call = argument.parent;
1468
+ if (!ts.isCallExpression(call) || call.arguments[0] !== argument)
1469
+ return null;
1470
+ if (!ts.isPropertyAccessExpression(call.expression) || call.expression.name.text !== "map")
1471
+ return null;
1472
+ if (!lowerer.isStdlibMember(call.expression))
1473
+ return null;
1474
+ const receiver = lowerer.mapTypeOf(lowerer.typeOf(call.expression.expression));
1475
+ if (receiver?.kind !== "array")
1476
+ return null;
1477
+ const parameter = info.decl.parameters[0];
1478
+ if (!parameter || !ts.isIdentifier(parameter.name))
1479
+ return null;
1480
+ const parameterType = lowerer.typeOf(parameter.name);
1481
+ if ((parameterType.flags & ts.TypeFlags.TypeParameter) === 0)
1482
+ return null;
1483
+ const typeParam = parameterType.getSymbol();
1484
+ if (!typeParam || !info.typeParams.includes(typeParam))
1485
+ return null;
1486
+ return { typeParam, type: lowerer.runtimeOptionalType(receiver.elem) };
1487
+ }
1460
1488
  /** The instance a pinned VALUE reference names: the declaration's modes
1461
1489
  * over the DECLARED types mapped under the bindings (mapType's resolver
1462
1490
  * substitutes — the instance-body trick), which is the same result the
@@ -3044,6 +3072,9 @@ export function lowerCall(lowerer, expr) {
3044
3072
  if (arg.type.kind === "string") {
3045
3073
  return { kind: "libCall", fn: "num.fromString", args: [arg], type: F64, loc };
3046
3074
  }
3075
+ const optionalNumber = lowerOptionalStringNumber(lowerer, arg, loc);
3076
+ if (optionalNumber)
3077
+ return optionalNumber;
3047
3078
  lowerer.noLowering(`Number of ${lowerer.fmt(arg.type)} values`, argNode, arg.type.kind === "union"
3048
3079
  ? "numbers, booleans, and strings lower (the full ToNumber string grammar included) — narrow the union first"
3049
3080
  : undefined);
@@ -3335,7 +3366,7 @@ export function lowerCall(lowerer, expr) {
3335
3366
  const radix = expr.arguments[1]
3336
3367
  ? lowerer.lowerExprExpecting(expr.arguments[1], F64)
3337
3368
  : { kind: "numLit", value: 0, type: F64, loc };
3338
- const optional = lowerAbsenceProbe(lowerer, expr.arguments[0]);
3369
+ const optional = optionalCallValue(lowerer, expr.arguments[0]);
3339
3370
  if (optional?.type.kind === "union") {
3340
3371
  const def = lowerer.unions.get(optional.type.unionId);
3341
3372
  const stringTag = def?.arms.findIndex((arm) => arm.kind === "string") ?? -1;
@@ -3403,6 +3434,14 @@ export function lowerCall(lowerer, expr) {
3403
3434
  expr.arguments.length === 1 &&
3404
3435
  lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(expr.expression) ?? undefined)) {
3405
3436
  const name = expr.expression.text;
3437
+ if (name === "parseFloat") {
3438
+ const optional = optionalCallValue(lowerer, expr.arguments[0]);
3439
+ if (optional) {
3440
+ const parsed = lowerOptionalStringNumber(lowerer, optional, loc, "num.parseFloat", "parseFloat");
3441
+ if (parsed)
3442
+ return parsed;
3443
+ }
3444
+ }
3406
3445
  const probed = probeLower(lowerer, expr.arguments[0]);
3407
3446
  if (name === "parseFloat" && probed?.type.kind === "string") {
3408
3447
  return { kind: "libCall", fn: "num.parseFloat", args: [probed], type: F64, loc };
@@ -3433,13 +3472,12 @@ export function lowerCall(lowerer, expr) {
3433
3472
  const loc = locOf(expr);
3434
3473
  const argNode = expr.arguments[0];
3435
3474
  if (name === "decodeURIComponent") {
3436
- const d = lowerer.lowerExpr(argNode);
3437
- if (d.type.kind !== "string") {
3438
- lowerer.noLowering(`${name} with a '${lowerer.fmt(d.type)}' argument`, argNode, "only string arguments lower (Node would ToString the value — convert it explicitly)");
3439
- }
3440
- return { kind: "libCall", fn: "str.decodeUriComponent", args: [d], type: STRING, loc };
3475
+ const d = optionalCallValue(lowerer, argNode) ?? lowerer.lowerExpr(argNode);
3476
+ const s = lowerer.ensureString(d, argNode);
3477
+ return { kind: "libCall", fn: "str.decodeUriComponent", args: [s], type: STRING, loc };
3441
3478
  }
3442
- const s = lowerer.ensureString(lowerer.lowerExpr(argNode), argNode);
3479
+ const value = optionalCallValue(lowerer, argNode) ?? lowerer.lowerExpr(argNode);
3480
+ const s = lowerer.ensureString(value, argNode);
3443
3481
  return {
3444
3482
  kind: "libCall",
3445
3483
  fn: name === "encodeURIComponent" ? "str.encodeUriComponent" : "str.encodeUri",
@@ -3750,7 +3788,7 @@ export function lowerCall(lowerer, expr) {
3750
3788
  lowerPromiseRejectCall(lowerer, expr, expr.expression) ??
3751
3789
  // Before the island path: regex-argument replace/replaceAll/split
3752
3790
  // lower STATICALLY; only the string-pattern overloads are island.
3753
- lowerer.lowerRegexMethodCall(expr, expr.expression) ??
3791
+ lowerRegexMethodCallWithOptionalArg(lowerer, expr, expr.expression) ??
3754
3792
  lowerer.lowerUrlMethodCall(expr, expr.expression) ??
3755
3793
  lowerer.lowerSearchParamsMethodCall(expr, expr.expression) ??
3756
3794
  lowerer.lowerStatsMethodCall(expr, expr.expression) ??
@@ -3789,11 +3827,11 @@ export function lowerCall(lowerer, expr) {
3789
3827
  // TestContext surface (t.test/t.skip/t.diagnostic), t.assert.*.
3790
3828
  lowerer.lowerTestMethodCall(expr, expr.expression) ??
3791
3829
  lowerer.lowerTimeoutMethodCall(expr, expr.expression) ??
3792
- lowerer.lowerStringMethodCall(expr, expr.expression) ??
3830
+ lowerStringMethodCallWithOptionalArgs(lowerer, expr, expr.expression) ??
3793
3831
  // Typed-array/Buffer receivers and the Buffer statics — before the
3794
3832
  // island path (bytes never cross the boundary).
3795
3833
  lowerer.lowerBytesMethodCall(expr, expr.expression) ??
3796
- lowerer.lowerBufferStaticCall(expr, expr.expression) ??
3834
+ lowerBufferStaticCallWithOptionalArg(lowerer, expr, expr.expression) ??
3797
3835
  // URL.revokeObjectURL's zero-argument contract (the one-argument
3798
3836
  // form keeps the fence — createObjectURL does too).
3799
3837
  lowerUrlStaticCall(lowerer, expr, expr.expression) ??
@@ -4057,6 +4095,160 @@ export function lowerCall(lowerer, expr) {
4057
4095
  }
4058
4096
  return { kind: "callValue", callee, args, type: callee.type.ret, loc };
4059
4097
  }
4098
+ function lowerRegexMethodCallWithOptionalArg(lowerer, call, access) {
4099
+ const lowered = lowerer.lowerRegexMethodCall(call, access);
4100
+ if (lowered?.kind !== "regexIntrinsic" || lowered.method !== "test" || call.arguments.length !== 1)
4101
+ return lowered;
4102
+ const subject = lowered.args[0];
4103
+ if (subject?.type.kind !== "union" || lowerer.armTag(subject.type.unionId, UNDEFINED_T) < 0)
4104
+ return lowered;
4105
+ if (lowerer.mapTypeOf(lowerer.typeOf(call.arguments[0]))?.kind !== "string")
4106
+ return lowered;
4107
+ const present = lowerer.stripUndefinedArm(subject.type);
4108
+ if (present.kind !== "string")
4109
+ return lowered;
4110
+ return {
4111
+ ...lowered,
4112
+ args: [lowerer.ensureString(subject, call.arguments[0])],
4113
+ };
4114
+ }
4115
+ function optionalCallValue(lowerer, node) {
4116
+ return lowerer.runtimeOptionalIdentifierValue(node)?.value ?? lowerAbsenceProbe(lowerer, node);
4117
+ }
4118
+ function lowerBufferStaticCallWithOptionalArg(lowerer, call, access) {
4119
+ const lowered = lowerer.lowerBufferStaticCall(call, access);
4120
+ if (lowered?.kind !== "bytesNew" || !lowered.source || lowered.source.type.kind !== "union")
4121
+ return lowered;
4122
+ const present = lowerer.stripUndefinedArm(lowered.source.type);
4123
+ const validSource = (present.kind === "array" && present.elem.kind === "f64") ||
4124
+ (present.kind === "bytes" && present.elem === "u8");
4125
+ if (!validSource)
4126
+ return lowered;
4127
+ const helper = lowerer.narrowedArmHelper(lowered.source.type.unionId, present, lowered.source.loc);
4128
+ return helper
4129
+ ? {
4130
+ ...lowered,
4131
+ source: { kind: "call", callee: helper, args: [lowered.source], type: present, loc: lowered.source.loc },
4132
+ }
4133
+ : lowered;
4134
+ }
4135
+ function lowerOptionalNumberDefault(lowerer, value, dflt, loc) {
4136
+ const widened = lowerer.runtimeOptionalWidening(value.type, F64);
4137
+ if (!widened || widened.kind !== "union")
4138
+ return null;
4139
+ const numberTag = lowerer.armTag(widened.unionId, F64);
4140
+ const undefinedTag = lowerer.armTag(widened.unionId, UNDEFINED_T);
4141
+ if (numberTag < 0 || undefinedTag < 0)
4142
+ return null;
4143
+ const key = `number.optionalDefault:${widened.unionId}:${dflt}`;
4144
+ let helper = lowerer.widthHelpers.get(key);
4145
+ if (!helper) {
4146
+ helper = `%number.optionalDefault.${lowerer.widthHelpers.size}`;
4147
+ lowerer.widthHelpers.set(key, helper);
4148
+ const input = varRef("value.0", widened, loc);
4149
+ lowerer.liftedFns.push({
4150
+ name: helper,
4151
+ params: [{ localId: "value.0", name: "value", type: widened }],
4152
+ returnType: F64,
4153
+ locals: [{ id: "value.0", name: "value", type: widened, mutable: false }],
4154
+ body: [
4155
+ {
4156
+ kind: "if",
4157
+ cond: { kind: "unionIsTag", unionId: widened.unionId, tag: undefinedTag, negated: false, value: input, type: BOOL, loc },
4158
+ then: [{ kind: "return", value: { kind: "numLit", value: dflt, type: F64, loc }, loc }],
4159
+ else_: null,
4160
+ loc,
4161
+ },
4162
+ {
4163
+ kind: "return",
4164
+ value: { kind: "unionNarrow", unionId: widened.unionId, tag: numberTag, value: input, type: F64, loc },
4165
+ loc,
4166
+ },
4167
+ ],
4168
+ loc,
4169
+ });
4170
+ }
4171
+ return { kind: "call", callee: helper, args: [value], type: F64, loc };
4172
+ }
4173
+ function lowerStringMethodCallWithOptionalArgs(lowerer, call, access) {
4174
+ const lowered = lowerer.lowerStringMethodCall(call, access);
4175
+ if (lowered?.kind !== "strIntrinsic")
4176
+ return lowered;
4177
+ const args = [...lowered.args];
4178
+ let changed = false;
4179
+ const zeroDefaultArgs = lowered.method === "charCodeAt" || lowered.method === "charAt" ||
4180
+ lowered.method === "repeat" || lowered.method === "padStart" || lowered.method === "padEnd"
4181
+ ? [0]
4182
+ : lowered.method === "slice" || lowered.method === "substring"
4183
+ ? [0]
4184
+ : lowered.method === "indexOf" || lowered.method === "includes"
4185
+ ? [1]
4186
+ : [];
4187
+ for (const index of zeroDefaultArgs) {
4188
+ const value = args[index];
4189
+ if (!value)
4190
+ continue;
4191
+ const coerced = lowerOptionalNumberDefault(lowerer, value, 0, value.loc);
4192
+ if (coerced) {
4193
+ args[index] = coerced;
4194
+ changed = true;
4195
+ }
4196
+ }
4197
+ if ((lowered.method === "indexOf" || lowered.method === "includes" ||
4198
+ lowered.method === "startsWith" || lowered.method === "endsWith") &&
4199
+ args[0]?.type.kind === "union" &&
4200
+ lowerer.runtimeOptionalWidening(args[0].type, STRING) !== null) {
4201
+ args[0] = lowerer.ensureString(args[0], call.arguments[0]);
4202
+ changed = true;
4203
+ }
4204
+ return changed ? { ...lowered, args } : lowered;
4205
+ }
4206
+ function lowerOptionalStringNumber(lowerer, arg, loc, fn = "num.fromString", label = "number") {
4207
+ if (arg.type.kind !== "union")
4208
+ return null;
4209
+ const def = lowerer.unions.get(arg.type.unionId);
4210
+ if (!def || def.arms.length !== 2)
4211
+ return null;
4212
+ const stringTag = lowerer.armTag(arg.type.unionId, STRING);
4213
+ const undefinedTag = lowerer.armTag(arg.type.unionId, UNDEFINED_T);
4214
+ if (stringTag < 0 || undefinedTag < 0)
4215
+ return null;
4216
+ const key = `${label}.optionalString:${arg.type.unionId}`;
4217
+ let helper = lowerer.widthHelpers.get(key);
4218
+ if (!helper) {
4219
+ helper = `%${label}.optionalString.${lowerer.widthHelpers.size}`;
4220
+ lowerer.widthHelpers.set(key, helper);
4221
+ const value = varRef("value.0", arg.type, loc);
4222
+ lowerer.liftedFns.push({
4223
+ name: helper,
4224
+ params: [{ localId: "value.0", name: "value", type: arg.type }],
4225
+ returnType: F64,
4226
+ locals: [{ id: "value.0", name: "value", type: arg.type, mutable: false }],
4227
+ body: [
4228
+ {
4229
+ kind: "if",
4230
+ cond: { kind: "unionIsTag", unionId: arg.type.unionId, tag: undefinedTag, negated: false, value, type: BOOL, loc },
4231
+ then: [{ kind: "return", value: { kind: "bin", op: "/", left: { kind: "numLit", value: 0, type: F64, loc }, right: { kind: "numLit", value: 0, type: F64, loc }, type: F64, loc }, loc }],
4232
+ else_: null,
4233
+ loc,
4234
+ },
4235
+ {
4236
+ kind: "return",
4237
+ value: {
4238
+ kind: "libCall",
4239
+ fn,
4240
+ args: [{ kind: "unionNarrow", unionId: arg.type.unionId, tag: stringTag, value, type: STRING, loc }],
4241
+ type: F64,
4242
+ loc,
4243
+ },
4244
+ loc,
4245
+ },
4246
+ ],
4247
+ loc,
4248
+ });
4249
+ }
4250
+ return { kind: "call", callee: helper, args: [arg], type: F64, loc };
4251
+ }
4060
4252
  /** The RUNTIME-ARITY spread call — `f(...args)`, the rest-forwarding idiom
4061
4253
  * (`const f = (...args) => from(...args)`): a spread whose length is a
4062
4254
  * runtime fact has no home in the compile-time completion, so the call
@@ -4920,10 +5112,9 @@ function templateEscapesValid(raw) {
4920
5112
  return true;
4921
5113
  }
4922
5114
  /** Tagged templates `tag\`a${x}b\`` — ES's call: tag(strings, ...values).
4923
- * The strings object is the per-SITE interned cooked array (the
4924
- * templateStrings node: one immortal string[] per occurrence, so the
4925
- * spec's identity contract holds — the same site evaluated twice hands
4926
- * the tag the SAME array; two sites never share). TemplateStringsArray
5115
+ * The strings object is a per-SITE lazily initialized hidden array global,
5116
+ * so the spec's identity contract holds — the same site evaluated twice
5117
+ * hands the tag the SAME array; two sites never share. TemplateStringsArray
4927
5118
  * maps to string[] (type-mapper.ts), so the array rides the ordinary
4928
5119
  * slot-directed coercion into whatever the tag's first parameter wants
4929
5120
  * — string[] exactly, an `any` slot through the dyn boundary, a rest
@@ -4949,13 +5140,7 @@ export function lowerTaggedTemplate(lowerer, expr) {
4949
5140
  lowerer.unsupported("SC1090", p, "tagged templates with invalid escape sequences (the span cooks to undefined, which the strings array cannot carry)");
4950
5141
  }
4951
5142
  }
4952
- const strings = {
4953
- kind: "templateStrings",
4954
- key: `${loc.file}:${expr.template.getStart()}`,
4955
- cooked: pieces.map((p) => p.text),
4956
- type: arrayOf(STRING),
4957
- loc,
4958
- };
5143
+ const strings = () => loweredTemplateStrings(lowerer, expr, pieces.map((piece) => piece.text), loc);
4959
5144
  const values = ts.isNoSubstitutionTemplateLiteral(expr.template)
4960
5145
  ? []
4961
5146
  : expr.template.templateSpans.map((s) => s.expression);
@@ -5007,7 +5192,7 @@ export function lowerTaggedTemplate(lowerer, expr) {
5007
5192
  const sig = lowerer.fnSigOf(expr.tag);
5008
5193
  if (sig) {
5009
5194
  lowerer.noteEdge(sig.name);
5010
- const args = completeArgs(lowerer, values, sig.params, loc, expr, [strings]);
5195
+ const args = completeArgs(lowerer, values, sig.params, loc, expr, [strings()]);
5011
5196
  return reconcileOverloadReturn(lowerer, expr, { kind: "call", callee: sig.name, args, type: sig.returnType, loc });
5012
5197
  }
5013
5198
  // An ambient `declare function` nothing defines: Node throws
@@ -5034,7 +5219,7 @@ export function lowerTaggedTemplate(lowerer, expr) {
5034
5219
  const callee = lowerer.lowerExpr(expr.tag);
5035
5220
  if (callee.type.kind === "dyn") {
5036
5221
  const args = [
5037
- lowerer.coerceInto(expr.template, strings, DYN),
5222
+ lowerer.coerceInto(expr.template, strings(), DYN),
5038
5223
  ...values.map((a) => lowerer.lowerExprExpecting(a, DYN)),
5039
5224
  ];
5040
5225
  const calleeName = ts.isPropertyAccessExpression(expr.tag) || ts.isElementAccessExpression(expr.tag)
@@ -5046,6 +5231,53 @@ export function lowerTaggedTemplate(lowerer, expr) {
5046
5231
  }
5047
5232
  lowerer.unsupported("SC1090", expr, "tagged templates with this tag form (top-level functions and dynamic values tag; call the function directly otherwise)");
5048
5233
  }
5234
+ function loweredTemplateStrings(lowerer, expr, cooked, loc) {
5235
+ // Normal runtime construction keeps this array aligned with the current
5236
+ // ScrArr layout in both backends. The old static-header IR node encoded the
5237
+ // pre-sparse layout in LLVM and crashed as soon as a tag read its strings.
5238
+ const type = arrayOf(STRING);
5239
+ const key = `templateStringsGlobal:${loc.file}:${expr.template.getStart()}`;
5240
+ let getter = lowerer.widthHelpers.get(key);
5241
+ if (!getter) {
5242
+ const site = lowerer.globalsList.length;
5243
+ const globalId = `%g.templateStrings.${site}`;
5244
+ const readyId = `%g.templateStringsReady.${site}`;
5245
+ getter = `%templateStrings.get.${lowerer.widthHelpers.size}`;
5246
+ lowerer.widthHelpers.set(key, getter);
5247
+ lowerer.globalsList.push({ id: globalId, name: "%templateStrings", type, mutable: true }, { id: readyId, name: "%templateStringsReady", type: BOOL, mutable: true });
5248
+ lowerer.liftedFns.push({
5249
+ name: getter,
5250
+ params: [],
5251
+ returnType: type,
5252
+ locals: [],
5253
+ body: [
5254
+ {
5255
+ kind: "if",
5256
+ cond: { kind: "unary", op: "!", operand: varRef(readyId, BOOL, loc), type: BOOL, loc },
5257
+ then: [
5258
+ {
5259
+ kind: "assign",
5260
+ localId: globalId,
5261
+ value: {
5262
+ kind: "arrayLit",
5263
+ elems: cooked.map((value) => ({ kind: "strLit", value, type: STRING, loc })),
5264
+ type,
5265
+ loc,
5266
+ },
5267
+ loc,
5268
+ },
5269
+ { kind: "assign", localId: readyId, value: { kind: "boolLit", value: true, type: BOOL, loc }, loc },
5270
+ ],
5271
+ else_: null,
5272
+ loc,
5273
+ },
5274
+ { kind: "return", value: varRef(globalId, type, loc), loc },
5275
+ ],
5276
+ loc,
5277
+ });
5278
+ }
5279
+ return { kind: "call", callee: getter, args: [], type, loc };
5280
+ }
5049
5281
  /** True when the identifier resolves (through import aliases) to a
5050
5282
  * top-level function declaration of ANY program file (not merely a
5051
5283
  * same-named local shadowing one). Functions declared directly in a
@@ -5107,6 +5339,7 @@ export function lambdaSignature(lowerer, node) {
5107
5339
  let ret = ts.isArrowFunction(node) && !ts.isBlock(node.body) && isStreamUndefCallExpr(lowerer, node.body)
5108
5340
  ? VOID
5109
5341
  : lowerer.declaredReturnType(node, node);
5342
+ ret = lowerer.runtimeOptionalFunctionReturnType(node, ret);
5110
5343
  // A contextually-typed arrow/function EXPRESSION whose slot signature
5111
5344
  // returns a UNION the inferred return doesn't spell adopts the slot's
5112
5345
  // return as its ABI: `(n) => work()` (inferring Promise<void>) against