@scriptc/compiler 0.0.6 → 0.0.8

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 (75) hide show
  1. package/dist/backend/emission/emit-exprs.js +47 -1
  2. package/dist/backend/emission/emit-exprs.js.map +1 -1
  3. package/dist/backend/emission/emit-island.js +9 -1
  4. package/dist/backend/emission/emit-island.js.map +1 -1
  5. package/dist/backend/emission/emit-stmts.js +3 -1
  6. package/dist/backend/emission/emit-stmts.js.map +1 -1
  7. package/dist/backend/emission/emit-walkers.js +22 -8
  8. package/dist/backend/emission/emit-walkers.js.map +1 -1
  9. package/dist/backend/emission/emitter.js +12 -1
  10. package/dist/backend/emission/emitter.js.map +1 -1
  11. package/dist/backend/emission/may-throw.js +4 -3
  12. package/dist/backend/emission/may-throw.js.map +1 -1
  13. package/dist/backend/llvm/emitter.js +105 -9
  14. package/dist/backend/llvm/emitter.js.map +1 -1
  15. package/dist/diagnostics/diagnostic.d.ts +49 -1
  16. package/dist/diagnostics/diagnostic.js +119 -5
  17. package/dist/diagnostics/diagnostic.js.map +1 -1
  18. package/dist/frontend/lib-contract.d.ts +114 -0
  19. package/dist/frontend/lib-contract.js +372 -0
  20. package/dist/frontend/lib-contract.js.map +1 -0
  21. package/dist/frontend/lowering/lower-builtins.js +16 -1
  22. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  23. package/dist/frontend/lowering/lower-calls.d.ts +47 -7
  24. package/dist/frontend/lowering/lower-calls.js +560 -36
  25. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  26. package/dist/frontend/lowering/lower-classes.d.ts +14 -1
  27. package/dist/frontend/lowering/lower-classes.js +85 -15
  28. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  29. package/dist/frontend/lowering/lower-containers.js +43 -1
  30. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  31. package/dist/frontend/lowering/lower-emitter.js +1 -1
  32. package/dist/frontend/lowering/lower-emitter.js.map +1 -1
  33. package/dist/frontend/lowering/lower-exprs.d.ts +26 -3
  34. package/dist/frontend/lowering/lower-exprs.js +474 -44
  35. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  36. package/dist/frontend/lowering/lower-island.d.ts +7 -0
  37. package/dist/frontend/lowering/lower-island.js +71 -10
  38. package/dist/frontend/lowering/lower-island.js.map +1 -1
  39. package/dist/frontend/lowering/lower-mixins.d.ts +1 -1
  40. package/dist/frontend/lowering/lower-mixins.js +1 -1
  41. package/dist/frontend/lowering/lower-modules.js +95 -4
  42. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  43. package/dist/frontend/lowering/lower-stmts.js +430 -44
  44. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  45. package/dist/frontend/lowering/lowerer.d.ts +19 -3
  46. package/dist/frontend/lowering/lowerer.js +188 -11
  47. package/dist/frontend/lowering/lowerer.js.map +1 -1
  48. package/dist/frontend/types.d.ts +28 -1
  49. package/dist/frontend/types.js +300 -8
  50. package/dist/frontend/types.js.map +1 -1
  51. package/dist/index.d.ts +11 -2
  52. package/dist/index.js +96 -7
  53. package/dist/index.js.map +1 -1
  54. package/dist/ir/nodes.d.ts +48 -2
  55. package/dist/ir/nodes.js +103 -3
  56. package/dist/ir/nodes.js.map +1 -1
  57. package/dist/ir/validate.js +35 -6
  58. package/dist/ir/validate.js.map +1 -1
  59. package/dist/library/profile.d.ts +53 -3
  60. package/dist/library/profile.js +143 -12
  61. package/dist/library/profile.js.map +1 -1
  62. package/dist/library/sidecar-validate.d.ts +4 -0
  63. package/dist/library/sidecar-validate.js +506 -0
  64. package/dist/library/sidecar-validate.js.map +1 -0
  65. package/dist/library/sidecar.d.ts +191 -0
  66. package/dist/library/sidecar.js +687 -0
  67. package/dist/library/sidecar.js.map +1 -0
  68. package/dist/library/trap-teaching.d.ts +11 -0
  69. package/dist/library/trap-teaching.js +49 -0
  70. package/dist/library/trap-teaching.js.map +1 -0
  71. package/dist/library/wyhash.d.ts +12 -0
  72. package/dist/library/wyhash.js +116 -0
  73. package/dist/library/wyhash.js.map +1 -0
  74. package/package.json +2 -2
  75. package/surface-manifest.json +37 -2
@@ -16,7 +16,7 @@ import { bufEncoding, dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilter
16
16
  import { lowerChildStreamMethodCall, lowerDirentMethodCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerWatcherMethodCall } from "./lower-builtins.js";
17
17
  import { lowerPromiseAllTupleCall, lowerPromiseRejectCall, probeLower, templateRawTextOf } from "./lower-exprs.js";
18
18
  import { httpClientFnBindingOf, isStreamUndefCallExpr, lowerHttpClientFnCall } from "./lower-server.js";
19
- import { EMITTER_API_MEMBERS, findGenericMethodOn, lowerClassGenericMethodCall, lowerStaticMethodCall } from "./lower-classes.js";
19
+ import { EMITTER_API_MEMBERS, exactInstanceClassOf, findGenericMethodOn, lowerClassGenericMethodCall, lowerStaticMethodCall } from "./lower-classes.js";
20
20
  import { emitterRooted, lowerEmitterMethodCall } from "./lower-emitter.js";
21
21
  import { lowerConsoleInspectArg, lowerFormatCall } from "./lower-inspect.js";
22
22
  import { STREAM_API_MEMBERS, lowerStreamMethodCall, lowerStreamModuleCall, lowerStreamStaticCall, streamSidesOf } from "./lower-stream.js";
@@ -107,9 +107,20 @@ export function paramShape(L, param) {
107
107
  // ARRAY param the dyn call thunk fills with the call's surplus
108
108
  // arguments; the binding is that array (dynRest — funcType marks
109
109
  // `rest`, and the value only ever calls through the boxed thunk).
110
- if (isJsSourceFile(param.getSourceFile()) &&
111
- L.mapTypeOf(L.typeOf(param.name))?.kind !== "array") {
112
- return { type: DYN, mode: "dynRest" };
110
+ if (isJsSourceFile(param.getSourceFile())) {
111
+ const restMapped = L.mapTypeOf(L.typeOf(param.name));
112
+ // `any[]` under --dynamic maps to an island-element array — that
113
+ // is inference residue, not element information; the binding is
114
+ // the ENGINE's own arguments array (an island handle) and the
115
+ // value crosses as a REST host function (the withPlugins
116
+ // `async (...args) =>` shape). Static builds keep the variadic
117
+ // dyn form for every unmappable JS rest.
118
+ if (restMapped?.kind === "array" && restMapped.elem.kind === "jsval" && L.dynamic) {
119
+ return { type: JSVAL, mode: "islandRest" };
120
+ }
121
+ if (restMapped?.kind !== "array") {
122
+ return { type: DYN, mode: "dynRest" };
123
+ }
113
124
  }
114
125
  const type = L.irTypeOf(param.name);
115
126
  // Tuple-typed rest params don't map to an array; generic rest is the
@@ -213,7 +224,7 @@ export function completeArgs(L, argNodes, shapes, loc, blame,
213
224
  leading) {
214
225
  const isIr = (s) => s !== undefined && !("kind" in s);
215
226
  const sources = leading && leading.length > 0 ? [...leading.map((ir) => ({ ir })), ...argNodes] : argNodes;
216
- const restAt = shapes.findIndex((s) => s.mode === "rest" || s.mode === "dynRest");
227
+ const restAt = shapes.findIndex((s) => s.mode === "rest" || s.mode === "dynRest" || s.mode === "islandRest");
217
228
  const positional = restAt >= 0 ? shapes.slice(0, restAt) : [...shapes];
218
229
  const out = positional.map((shape, i) => {
219
230
  const src = sources[i];
@@ -243,7 +254,21 @@ leading) {
243
254
  }
244
255
  return L.undefinedArgFor(shape.type, loc, blame);
245
256
  });
246
- if (restAt >= 0 && shapes[restAt].mode === "dynRest") {
257
+ if (restAt >= 0 && shapes[restAt].mode === "islandRest") {
258
+ // The ISLAND variadic pack: surplus arguments marshal into one
259
+ // fresh ENGINE array — exactly what the REST host-call adapter
260
+ // hands the closure for indirect calls.
261
+ const elems = sources.slice(restAt).map((a) => {
262
+ if (isIr(a))
263
+ return L.coerceInto(blame, a.ir, JSVAL);
264
+ if (ts.isSpreadElement(a)) {
265
+ L.unsupported("SC1090", a, "spread arguments into an island rest parameter");
266
+ }
267
+ return L.lowerExprExpecting(a, JSVAL);
268
+ });
269
+ out.push({ kind: "jsOp", op: "arrLit", args: elems, type: JSVAL, loc });
270
+ }
271
+ else if (restAt >= 0 && shapes[restAt].mode === "dynRest") {
247
272
  // The VARIADIC dyn pack (a JS `...args` with no static element
248
273
  // type, or the synthetic `arguments` slot): surplus arguments
249
274
  // convert through the dyn boundary into one fresh DOM array —
@@ -405,6 +430,7 @@ export function requireExactArityValue(L, blame, contextual, shapes, funcType) {
405
430
  // tier's undefined (omittedArgFor).
406
431
  if (shapes.every((s) => s.mode === "required" ||
407
432
  s.mode === "dynRest" ||
433
+ s.mode === "islandRest" ||
408
434
  (s.mode === "omittable" && (s.type.kind === "dyn" || s.type.kind === "jsval")))) {
409
435
  return;
410
436
  }
@@ -831,24 +857,61 @@ export function genericCallInstance(L, expr, info) {
831
857
  fenceGenericSignatureResult(L, expr, retTs);
832
858
  L.badType(expr, retTs);
833
859
  }
834
- return internGenericInstance(L, expr, info, params, returnType, () => L.inferTypeParamBindings(expr, info, rsig));
860
+ // keyof-constrained type parameters (`K extends keyof T`): the bound
861
+ // LITERAL is semantic — the instance body reads the named field — so
862
+ // the bindings compute EAGERLY (the key needs them) and each literal
863
+ // keys its own instance (`pick(o, "a")` and `pick(o, "b")` map to the
864
+ // same IR signature when the fields agree, but their bodies read
865
+ // different fields). Non-literal bindings (a key union, plain string)
866
+ // share one runtime-keyed instance per IR signature, exactly the
867
+ // widened discipline.
868
+ const keyofTps = keyofConstrainedTypeParams(info);
869
+ if (keyofTps.size > 0) {
870
+ const tsBindings = new Map();
871
+ const bindings = L.inferTypeParamBindings(expr, info, rsig, tsBindings);
872
+ const litKey = info.typeParams
873
+ .filter((tp) => keyofTps.has(tp))
874
+ .map((tp) => {
875
+ const bound = tsBindings.get(tp);
876
+ return bound?.isStringLiteralType() ? JSON.stringify(bound.value)
877
+ : bound?.isNumberLiteralType() ? String(bound.value)
878
+ : "*";
879
+ })
880
+ .join(",");
881
+ return internGenericInstance(L, expr, info, params, returnType, () => bindings, {
882
+ extraKey: `@${litKey}`,
883
+ tsBindings,
884
+ });
885
+ }
886
+ return internGenericInstance(L, expr, info, params, returnType, (tsBindings) => L.inferTypeParamBindings(expr, info, rsig, tsBindings));
835
887
  }
836
888
  /** The one instance table both instantiation routes share: key identity IS
837
889
  * signature identity, so a call (`identity(1)`) and a pinned VALUE
838
890
  * (`const f: (x: number) => number = identity`) reuse one compiled
839
891
  * instance. `makeBindings` runs only for a NEW key (binding inference
840
892
  * costs checker walks). */
841
- export function internGenericInstance(L, blame, info, params, returnType, makeBindings) {
842
- const key = `${params.map((s) => typeKey(s.type)).join(",")}=>${typeKey(returnType)}`;
893
+ export function internGenericInstance(L, blame, info, params, returnType, makeBindings, opts) {
894
+ // keyof-constrained instantiations append their literal keys
895
+ // (extraKey): the IR signature alone under-discriminates there — two
896
+ // literals can map to one IR signature while their bodies read
897
+ // different fields.
898
+ const key = `${params.map((s) => typeKey(s.type)).join(",")}=>${typeKey(returnType)}${opts?.extraKey ?? ""}`;
843
899
  let inst = info.instances.get(key);
844
900
  if (!inst) {
845
901
  if (info.instances.size >= MAX_GENERIC_INSTANCES) {
846
902
  L.unsupported("SC1090", blame, `unbounded generic instantiation ('${info.baseName}' exceeded ` +
847
903
  `${MAX_GENERIC_INSTANCES} instances — polymorphic recursion?)`);
848
904
  }
849
- const bindings = makeBindings();
905
+ const tsBindings = opts?.tsBindings ?? new Map();
906
+ const bindings = makeBindings(tsBindings);
850
907
  const rendered = info.typeParams
851
908
  .map((tp) => {
909
+ // A literal-bound keyof parameter renders its literal — the
910
+ // instance is per-literal, and '<…, string>' would misname it.
911
+ const tsBound = tsBindings.get(tp);
912
+ if (info.keyofTps?.has(tp) && tsBound?.isStringLiteralType()) {
913
+ return JSON.stringify(tsBound.value);
914
+ }
852
915
  const bound = bindings.get(tp);
853
916
  return bound ? L.fmt(bound) : tp.name;
854
917
  })
@@ -861,6 +924,7 @@ export function internGenericInstance(L, blame, info, params, returnType, makeBi
861
924
  params,
862
925
  returnType,
863
926
  bindings,
927
+ tsBindings,
864
928
  typeArgsText,
865
929
  };
866
930
  info.instances.set(key, inst);
@@ -868,40 +932,65 @@ export function internGenericInstance(L, blame, info, params, returnType, makeBi
868
932
  }
869
933
  return inst;
870
934
  }
935
+ /** The type parameters of `info` declared with a `keyof` CONSTRAINT
936
+ * (`K extends keyof T`) — the parameters whose bound literal is semantic
937
+ * (the body's `o[k]` reads the named field), computed once per info from
938
+ * the declaration's syntax. */
939
+ export function keyofConstrainedTypeParams(info) {
940
+ if (info.keyofTps)
941
+ return info.keyofTps;
942
+ const out = new Set();
943
+ info.decl.typeParameters?.forEach((tpDecl, i) => {
944
+ const sym = info.typeParams[i];
945
+ if (!sym || tpDecl.constraint === undefined)
946
+ return;
947
+ if (ts.isTypeOperatorNode(tpDecl.constraint) && tpDecl.constraint.operator === ts.SyntaxKind.KeyOfKeyword) {
948
+ out.add(sym);
949
+ }
950
+ });
951
+ info.keyofTps = out;
952
+ return out;
953
+ }
871
954
  /** Type-parameter symbol → concrete IR type for one instantiation.
872
955
  * Explicit type arguments bind directly; the rest come from structurally
873
956
  * matching each DECLARED param/return type (which mentions the type
874
957
  * parameters) against the checker's INSTANTIATED one — the latter is the
875
958
  * former with the substitution applied, so the shapes are parallel by
876
959
  * construction. A type parameter left unbound only matters if the body
877
- * mentions it, where mapType fails with SC2001 (carrying the
878
- * instantiation context). */
879
- export function inferTypeParamBindings(L, expr, info, rsig) {
960
+ * mentions it, where mapType fails and badType names the shape
961
+ * (carrying the instantiation context). */
962
+ export function inferTypeParamBindings(L, expr, info, rsig, tsBindings) {
880
963
  const bindings = new Map();
881
964
  expr.typeArguments?.forEach((ta, i) => {
882
965
  const tp = info.typeParams[i];
883
966
  if (!tp)
884
967
  return;
885
- const mapped = L.mapTypeOf(L.checker.getTypeFromTypeNode(ta));
886
- if (mapped && mapped.kind !== "void")
968
+ const taT = L.checker.getTypeFromTypeNode(ta);
969
+ const mapped = L.mapTypeOf(taT);
970
+ if (mapped && mapped.kind !== "void") {
887
971
  bindings.set(tp, mapped);
972
+ tsBindings?.set(tp, taT);
973
+ }
888
974
  });
889
- unifySignatureBindings(L, info, rsig, bindings);
890
- bindDefaultTypeParams(L, info.typeParams, info.decl.typeParameters, bindings);
975
+ unifySignatureBindings(L, info, rsig, bindings, tsBindings);
976
+ bindDefaultTypeParams(L, info.typeParams, info.decl.typeParameters, bindings, tsBindings);
891
977
  return bindings;
892
978
  }
893
979
  /** Type parameters still unbound after unification take their declared
894
980
  * DEFAULT (`<T = number>`), mapped — the checker already substituted the
895
981
  * default into every resolved signature, so this only fills the bindings
896
982
  * an instance body's mapType consults. */
897
- export function bindDefaultTypeParams(L, typeParams, typeParamDecls, bindings) {
983
+ export function bindDefaultTypeParams(L, typeParams, typeParamDecls, bindings, tsBindings) {
898
984
  typeParamDecls?.forEach((tpDecl, i) => {
899
985
  const tp = typeParams[i];
900
986
  if (!tp || bindings.has(tp) || !tpDecl.defaultType)
901
987
  return;
902
- const mapped = L.mapTypeOf(L.checker.getTypeFromTypeNode(tpDecl.defaultType));
903
- if (mapped && mapped.kind !== "void")
988
+ const defT = L.checker.getTypeFromTypeNode(tpDecl.defaultType);
989
+ const mapped = L.mapTypeOf(defT);
990
+ if (mapped && mapped.kind !== "void") {
904
991
  bindings.set(tp, mapped);
992
+ tsBindings?.set(tp, defT);
993
+ }
905
994
  });
906
995
  }
907
996
  /** The structural half of binding inference: unify the DECLARED signature
@@ -910,7 +999,7 @@ export function bindDefaultTypeParams(L, typeParams, typeParamDecls, bindings) {
910
999
  * completed signature a VALUE reference is pinned to (the contextual
911
1000
  * type's one call signature). Mutates `bindings`; already-bound
912
1001
  * parameters (explicit type arguments) win. */
913
- export function unifySignatureBindings(L, info, rsig, bindings) {
1002
+ export function unifySignatureBindings(L, info, rsig, bindings, tsBindings) {
914
1003
  const tpSet = new Set(info.typeParams);
915
1004
  const seen = new Set(); // recursive declared types must not loop
916
1005
  // The identity-keyed set cannot catch LAZILY INFINITE anonymous types
@@ -927,10 +1016,22 @@ export function unifySignatureBindings(L, info, rsig, bindings) {
927
1016
  return;
928
1017
  if (declared.flags & ts.TypeFlags.TypeParameter) {
929
1018
  const sym = declared.getSymbol();
930
- if (sym && tpSet.has(sym) && !bindings.has(sym)) {
931
- const mapped = L.mapTypeOf(inst);
932
- if (mapped && mapped.kind !== "void")
933
- bindings.set(sym, mapped);
1019
+ if (sym && tpSet.has(sym)) {
1020
+ // The checker type records even when an explicit type argument
1021
+ // already bound the IrType: the raw type is the SAME binding
1022
+ // pre-widening, and first-hit-wins keeps the two maps parallel.
1023
+ // A generic body FORWARDING its own parameter (`pluck`'s
1024
+ // `pick(it, key)` binds pick's K to pluck's K) resolves through
1025
+ // the enclosing instantiation's ts bindings first — the literal
1026
+ // carries through the chain.
1027
+ if (tsBindings && !tsBindings.has(sym)) {
1028
+ tsBindings.set(sym, L.typeParamTsResolver(inst) ?? inst);
1029
+ }
1030
+ if (!bindings.has(sym)) {
1031
+ const mapped = L.mapTypeOf(inst);
1032
+ if (mapped && mapped.kind !== "void")
1033
+ bindings.set(sym, mapped);
1034
+ }
934
1035
  }
935
1036
  return;
936
1037
  }
@@ -1049,6 +1150,13 @@ export function lowerGenericInstance(L, info, inst) {
1049
1150
  L.typeParamBindings = clsBindings
1050
1151
  ? new Map([...clsBindings, ...inst.bindings])
1051
1152
  : inst.bindings;
1153
+ // The ts-level bindings ride along: `T[K]` and literal-keyed `o[k]`
1154
+ // reads inside the body resolve through the bound CHECKER types
1155
+ // (typeParamTsResolver). Generic-class instantiations carry no
1156
+ // ts-level bindings (their type arguments widened at the reference),
1157
+ // so only the method instantiation's own map installs.
1158
+ const prevTsBindings = L.typeParamTsBindings;
1159
+ L.typeParamTsBindings = inst.tsBindings ?? null;
1052
1160
  // Implicit-any instances thread their param bindings through typeOf
1053
1161
  // (the checker reports `any` inside the body — there is no T for
1054
1162
  // mapType to substitute); see the implicit-monomorphization section.
@@ -1186,6 +1294,7 @@ export function lowerGenericInstance(L, info, inst) {
1186
1294
  L.fnStack.pop();
1187
1295
  L.currentClass = prevClass;
1188
1296
  L.typeParamBindings = prevBindings;
1297
+ L.typeParamTsBindings = prevTsBindings;
1189
1298
  L.implicitParamTypes = prevImplicit;
1190
1299
  L.instantiationContext = prevContext;
1191
1300
  L.suppressStats = prevSuppress;
@@ -2591,6 +2700,37 @@ export function lowerCall(L, expr) {
2591
2700
  : "value";
2592
2701
  return { kind: "dynCall", callee, calleeName, args, type: DYN, loc };
2593
2702
  }
2703
+ // `fn(...args)` — a TRAILING spread into an island call: the
2704
+ // engine's own apply (`fn.apply(undefined, argsArray)`); leading
2705
+ // plain arguments prepend through `[l1, l2].concat(argsArray)`
2706
+ // (concat flattens the array argument one level — exactly the
2707
+ // spread). Other spread shapes keep the syntax fence.
2708
+ if (expr.arguments.length > 0 &&
2709
+ ts.isSpreadElement(expr.arguments[expr.arguments.length - 1]) &&
2710
+ expr.arguments.slice(0, -1).every((a) => !ts.isSpreadElement(a))) {
2711
+ const spread = expr.arguments[expr.arguments.length - 1];
2712
+ const spreadV = L.jsvalIn(L.lowerExpr(spread.expression), spread.expression);
2713
+ const leading = expr.arguments.slice(0, -1).map((a) => L.jsvalIn(L.lowerExpr(a), a));
2714
+ const argsArr = leading.length === 0
2715
+ ? spreadV
2716
+ : {
2717
+ kind: "jsOp",
2718
+ op: "callMethod",
2719
+ name: "concat",
2720
+ args: [{ kind: "jsOp", op: "arrLit", args: leading, type: JSVAL, loc }, spreadV],
2721
+ type: JSVAL,
2722
+ loc,
2723
+ };
2724
+ const result = {
2725
+ kind: "jsOp",
2726
+ op: "callMethod",
2727
+ name: "apply",
2728
+ args: [callee, { kind: "jsOp", op: "undefLit", args: [], type: JSVAL, loc }, argsArr],
2729
+ type: JSVAL,
2730
+ loc,
2731
+ };
2732
+ return islandPrimitiveExit(L, expr, result);
2733
+ }
2594
2734
  const args = expr.arguments.map((a) => L.jsvalIn(L.lowerExpr(a), a));
2595
2735
  const result = { kind: "jsOp", op: "callFn", args: [callee, ...args], type: JSVAL, loc };
2596
2736
  return islandPrimitiveExit(L, expr, result);
@@ -4044,11 +4184,26 @@ export function lowerTaggedTemplate(L, expr) {
4044
4184
  : expr.template.templateSpans.map((s) => s.expression);
4045
4185
  // Island tags (--dynamic): the engine call forms, mirroring lowerCall's
4046
4186
  // island paths — a property-access tag is a method call (this = the
4047
- // receiver, JS-exact), any other island tag a function call.
4187
+ // receiver, JS-exact), any other island tag a function call. The
4188
+ // strings argument builds ENGINE-NATIVE with its `.raw` property (the
4189
+ // tplStrings op): a JSON marshal would drop `.raw`, and tags dispatch
4190
+ // on it (the outdent idiom treats a raw-less argument as its OPTIONS
4191
+ // form and answers a function). A fresh array per evaluation — tags
4192
+ // caching by strings identity re-compute per call (SEMANTICS.md).
4193
+ const islandStrings = () => ({
4194
+ kind: "jsOp",
4195
+ op: "tplStrings",
4196
+ args: [
4197
+ ...pieces.map((p) => ({ kind: "jsMarshal", value: { kind: "strLit", value: p.text, type: STRING, loc }, type: JSVAL, loc })),
4198
+ ...pieces.map((p) => ({ kind: "jsMarshal", value: { kind: "strLit", value: templateRawTextOf(p), type: STRING, loc }, type: JSVAL, loc })),
4199
+ ],
4200
+ type: JSVAL,
4201
+ loc,
4202
+ });
4048
4203
  if (ts.isPropertyAccessExpression(expr.tag) && L.isIslandExpr(expr.tag.expression)) {
4049
4204
  const receiver = L.lowerExpr(expr.tag.expression);
4050
4205
  const args = [
4051
- L.jsvalIn(strings, expr.template),
4206
+ islandStrings(),
4052
4207
  ...values.map((a) => L.jsvalIn(L.lowerExpr(a), a)),
4053
4208
  ];
4054
4209
  return {
@@ -4059,7 +4214,7 @@ export function lowerTaggedTemplate(L, expr) {
4059
4214
  if (L.isIslandExpr(expr.tag)) {
4060
4215
  const callee = L.lowerExpr(expr.tag);
4061
4216
  const args = [
4062
- L.jsvalIn(strings, expr.template),
4217
+ islandStrings(),
4063
4218
  ...values.map((a) => L.jsvalIn(L.lowerExpr(a), a)),
4064
4219
  ];
4065
4220
  return { kind: "jsOp", op: "callFn", args: [callee, ...args], type: JSVAL, loc };
@@ -4247,6 +4402,7 @@ export function lambdaSignature(L, node) {
4247
4402
  // use a rest parameter. Arrows never claim it (JS: an arrow's
4248
4403
  // `arguments` is the enclosing function's).
4249
4404
  const hasDynRest = shapes.some((s) => s.mode === "dynRest");
4405
+ const hasIslandRest = shapes.some((s) => s.mode === "islandRest");
4250
4406
  const usesArguments = !hasDynRest &&
4251
4407
  !ts.isArrowFunction(node) &&
4252
4408
  isJsSourceFile(node.getSourceFile()) &&
@@ -4258,9 +4414,14 @@ export function lambdaSignature(L, node) {
4258
4414
  shapes,
4259
4415
  funcType: {
4260
4416
  kind: "func",
4417
+ // dynRest is EXCLUDED (the boxed thunk fills the trailing DOM
4418
+ // array — no spelled slot); islandRest is INCLUDED (the trailing
4419
+ // jsval param IS the engine arguments array, the REST host-call
4420
+ // adapter's one uniform shape).
4261
4421
  params: shapes.filter((s) => s.mode !== "dynRest").map((s) => s.type),
4262
4422
  ret,
4263
- ...(hasDynRest || usesArguments ? { rest: true } : {}),
4423
+ ...(hasDynRest || usesArguments || hasIslandRest ? { rest: true } : {}),
4424
+ ...(hasIslandRest ? { restAbi: "jsval" } : {}),
4264
4425
  },
4265
4426
  };
4266
4427
  }
@@ -4331,13 +4492,14 @@ export function lowerLambda(L, node) {
4331
4492
  if (isGenerator && funcType.ret.kind === "generator") {
4332
4493
  fnCtx.generator = { yieldT: funcType.ret.yieldT, nextT: funcType.ret.nextT };
4333
4494
  }
4495
+ const diagsBefore = L.diags.length;
4334
4496
  L.fnStack.push(fnCtx);
4335
4497
  try {
4336
4498
  const { params, prologue } = L.declareParams(node.parameters, shapes);
4337
4499
  // The VARIADIC `arguments` form (rest-marked with no declared rest
4338
4500
  // param): a synthetic trailing DOM-array param carries the call's
4339
4501
  // arguments; `arguments` reads resolve to it (identifier lowering).
4340
- if (funcType.rest && !shapes.some((s) => s.mode === "dynRest")) {
4502
+ if (funcType.rest && !shapes.some((s) => s.mode === "dynRest" || s.mode === "islandRest")) {
4341
4503
  const argsLocal = L.declareHiddenLocal("%arguments", DYN);
4342
4504
  params.push({ localId: argsLocal.id, name: "%arguments", type: DYN });
4343
4505
  fnCtx.argumentsLocal = argsLocal;
@@ -4416,6 +4578,53 @@ export function lowerLambda(L, node) {
4416
4578
  L.liftedFns.push(lifted);
4417
4579
  return { kind: "closure", fnName, captures: ctx.captureSources, type: funcType, loc };
4418
4580
  }
4581
+ catch (e) {
4582
+ // JS sources defer LAMBDA poisons like function declarations
4583
+ // (lowerFunction's catch, lambda form — entry the function-level
4584
+ // deferral): a fenced concise body (`(list) => new Intl.ListFormat
4585
+ // (...).format(list)` — the error-message list-join idiom) would
4586
+ // otherwise poison the ENCLOSING statement, stopping module init
4587
+ // where Node only stops when the lambda is CALLED. The value
4588
+ // compiles as a capture-free closure over a runtimeFence body —
4589
+ // calling throws the first captured diagnostic at its source
4590
+ // position. ICEs (SC9001) stay compile errors, exactly like
4591
+ // lowerStmts; probe mode (diagSink) keeps the poison.
4592
+ if (!(e instanceof PoisonError))
4593
+ throw e;
4594
+ if (!isJsSourceFile(node.getSourceFile()) ||
4595
+ L.diagSink !== null ||
4596
+ L.diags.length <= diagsBefore ||
4597
+ L.diags.slice(diagsBefore).some((d) => d.code === "SC9001")) {
4598
+ throw e;
4599
+ }
4600
+ const captured = L.diags.splice(diagsBefore);
4601
+ L.runtimeFences.push(...captured);
4602
+ const first = captured[0];
4603
+ const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? node.getSourceFile(), first.loc.start);
4604
+ const params = funcType.params.map((t, i) => ({ localId: `%pf${i}`, name: `%pf${i}`, type: t }));
4605
+ const lifted = {
4606
+ name: fnName,
4607
+ params,
4608
+ returnType: bodyReturn,
4609
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
4610
+ captures: [],
4611
+ body: [
4612
+ {
4613
+ kind: "runtimeFence",
4614
+ code: first.code,
4615
+ message: `${first.message} [${first.code} at ${first.loc.file}:${pos.line + 1}]`,
4616
+ loc,
4617
+ },
4618
+ ],
4619
+ loc,
4620
+ };
4621
+ if (isAsync)
4622
+ lifted.async = true;
4623
+ if (fnCtx.generator)
4624
+ lifted.generator = fnCtx.generator;
4625
+ L.liftedFns.push(lifted);
4626
+ return { kind: "closure", fnName, captures: [], type: funcType, loc };
4627
+ }
4419
4628
  finally {
4420
4629
  L.fnStack.pop();
4421
4630
  }
@@ -5304,8 +5513,8 @@ function filterNarrowHelper(L, test, elem, outElem, tag, loc) {
5304
5513
  * (Symbol.iterator, Symbol.asyncIterator, Symbol.toStringTag, ...) —
5305
5514
  * language-level protocol uses (for-of, template literals) already
5306
5515
  * compile through their constructs without reifying the symbol, so the
5307
- * VALUE forms fence with a named message rather than the generic
5308
- * SymbolConstructor SC2001. */
5516
+ * VALUE forms fence with a named message rather than a blanket
5517
+ * SymbolConstructor type fence. */
5309
5518
  function lowerSymbolStaticCall(L, call, access) {
5310
5519
  if (call.questionDotToken || access.questionDotToken)
5311
5520
  return null;
@@ -5435,6 +5644,110 @@ export function recordKeysArrayCall(L, receiver, argIr, shape, loc) {
5435
5644
  }
5436
5645
  return { kind: "call", callee: helper, args: [receiver], type: resultT, loc };
5437
5646
  }
5647
+ /** Interned `%obj.hasOwn.<n>(r, k)` — Object.hasOwn's membership walk
5648
+ * over a signature-free record shape: the key compares against each
5649
+ * declared field name, undefined-armed fields answering by their tag
5650
+ * (a key is own exactly when Object.keys would list it — the two share
5651
+ * the guard), everything else true, no match false. */
5652
+ function recordHasOwnHelper(L, shapeId, loc) {
5653
+ const key = `obj.hasOwn:${shapeId}`;
5654
+ const existing = L.arrHofHelpers.get(key);
5655
+ if (existing)
5656
+ return existing;
5657
+ const helper = `%obj.hasOwn.${L.arrHofHelpers.size}`;
5658
+ L.arrHofHelpers.set(key, helper);
5659
+ const shape = L.shapes.get(shapeId);
5660
+ const recT = { kind: "record", shapeId };
5661
+ const rRef = { kind: "varRef", localId: "r.0", type: recT, loc };
5662
+ const kRef = { kind: "varRef", localId: "k.0", type: STRING, loc };
5663
+ const body = [];
5664
+ for (const f of shape.fields) {
5665
+ const utag = f.type.kind === "union" ? L.armTag(f.type.unionId, UNDEFINED_T) : -1;
5666
+ const answer = utag >= 0 && f.type.kind === "union"
5667
+ ? {
5668
+ kind: "unionIsTag",
5669
+ unionId: f.type.unionId,
5670
+ tag: utag,
5671
+ negated: true,
5672
+ value: { kind: "recordGet", obj: rRef, shapeId, field: f.name, type: f.type, loc },
5673
+ type: BOOL,
5674
+ loc,
5675
+ }
5676
+ : { kind: "boolLit", value: true, type: BOOL, loc };
5677
+ body.push({
5678
+ kind: "if",
5679
+ cond: { kind: "strEq", negated: false, left: kRef, right: { kind: "strLit", value: f.name, type: STRING, loc }, type: BOOL, loc },
5680
+ then: [{ kind: "return", value: answer, loc }],
5681
+ else_: null,
5682
+ loc,
5683
+ });
5684
+ }
5685
+ body.push({ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc });
5686
+ L.liftedFns.push({
5687
+ name: helper,
5688
+ params: [
5689
+ { localId: "r.0", name: "r", type: recT },
5690
+ { localId: "k.0", name: "k", type: STRING },
5691
+ ],
5692
+ returnType: BOOL,
5693
+ locals: [
5694
+ { id: "r.0", name: "r", type: recT, mutable: true },
5695
+ { id: "k.0", name: "k", type: STRING, mutable: false },
5696
+ ],
5697
+ body,
5698
+ loc,
5699
+ });
5700
+ return helper;
5701
+ }
5702
+ /** Interned `%obj.assign.<n>(t, s)` — Object.assign's per-field copy
5703
+ * over signature-free records (every source field lands on a same-named,
5704
+ * same-typed target field — the caller's gate): undefined-armed source
5705
+ * fields copy behind the not-undefined guard, everything else straight,
5706
+ * and the TARGET returns (JS's aliasing). */
5707
+ function recordAssignHelper(L, targetShapeId, srcShapeId, loc) {
5708
+ const key = `obj.assign:${targetShapeId}:${srcShapeId}`;
5709
+ const existing = L.arrHofHelpers.get(key);
5710
+ if (existing)
5711
+ return existing;
5712
+ const helper = `%obj.assign.${L.arrHofHelpers.size}`;
5713
+ L.arrHofHelpers.set(key, helper);
5714
+ const sShape = L.shapes.get(srcShapeId);
5715
+ const tT = { kind: "record", shapeId: targetShapeId };
5716
+ const sT = { kind: "record", shapeId: srcShapeId };
5717
+ const tRef = { kind: "varRef", localId: "t.0", type: tT, loc };
5718
+ const sRef = { kind: "varRef", localId: "s.0", type: sT, loc };
5719
+ const body = [];
5720
+ for (const f of sShape.fields) {
5721
+ const get = { kind: "recordGet", obj: sRef, shapeId: srcShapeId, field: f.name, type: f.type, loc };
5722
+ const set = { kind: "recordSet", obj: tRef, shapeId: targetShapeId, field: f.name, value: get, loc };
5723
+ const utag = f.type.kind === "union" ? L.armTag(f.type.unionId, UNDEFINED_T) : -1;
5724
+ body.push(utag >= 0 && f.type.kind === "union"
5725
+ ? {
5726
+ kind: "if",
5727
+ cond: { kind: "unionIsTag", unionId: f.type.unionId, tag: utag, negated: true, value: get, type: BOOL, loc },
5728
+ then: [set],
5729
+ else_: null,
5730
+ loc,
5731
+ }
5732
+ : set);
5733
+ }
5734
+ body.push({ kind: "return", value: tRef, loc });
5735
+ L.liftedFns.push({
5736
+ name: helper,
5737
+ params: [
5738
+ { localId: "t.0", name: "t", type: tT },
5739
+ { localId: "s.0", name: "s", type: sT },
5740
+ ],
5741
+ returnType: tT,
5742
+ locals: [
5743
+ { id: "t.0", name: "t", type: tT, mutable: true },
5744
+ { id: "s.0", name: "s", type: sT, mutable: true },
5745
+ ],
5746
+ body,
5747
+ loc,
5748
+ });
5749
+ return helper;
5750
+ }
5438
5751
  /** The `Iterator` global's statics (ES2025 — Iterator.from, and the
5439
5752
  * abstract constructor as a value): no first-class iterator objects
5440
5753
  * exist here, so every member fences with the working spelling named
@@ -5504,6 +5817,53 @@ function lowerObjectStaticCall(L, call, access) {
5504
5817
  const merged = lowerObjectAssignIndexShape(L, call);
5505
5818
  if (merged)
5506
5819
  return merged;
5820
+ // `Object.assign(target, source)` over signature-free RECORDS whose
5821
+ // source fields all land on same-named, same-typed target fields
5822
+ // (the mockable-clock restore: `Object.assign(mocked,
5823
+ // implementations)` over one shape): the per-field copy helper,
5824
+ // returning the TARGET — JS's aliasing, the target mutates in
5825
+ // place. Undefined-armed source fields copy behind the
5826
+ // not-undefined guard (an omitted optional field holds the
5827
+ // undefined arm and must not erase the target's value — Node
5828
+ // copies own keys only; an EXPLICIT `k: undefined` source diverges,
5829
+ // the explicit-undefined-is-absent stance). Everything else keeps
5830
+ // the spread hint.
5831
+ if (call.arguments.length === 2 && !call.arguments.some((a) => ts.isSpreadElement(a))) {
5832
+ const tProbe = probeLower(L, call.arguments[0]);
5833
+ const sProbe = probeLower(L, call.arguments[1]);
5834
+ // CHECKED-DYNAMIC target and source (the JS file-scope
5835
+ // object-literal identity story): the runtime DOM copy — own
5836
+ // members of the source land on the target, which returns.
5837
+ if (tProbe?.type.kind === "dyn") {
5838
+ const loc = locOf(call);
5839
+ const target = L.lowerExpr(call.arguments[0]);
5840
+ const source = L.coerceToExpected(L.lowerExpr(call.arguments[1]), DYN);
5841
+ if (target.type.kind === "dyn" && source.type.kind === "dyn") {
5842
+ return { kind: "libCall", fn: "dyn.assign", args: [target, source], type: DYN, loc };
5843
+ }
5844
+ }
5845
+ if (tProbe?.type.kind === "record" && sProbe?.type.kind === "record") {
5846
+ const tShape = L.shapes.get(tProbe.type.shapeId);
5847
+ const sShape = L.shapes.get(sProbe.type.shapeId);
5848
+ const ok = tShape && sShape &&
5849
+ !tShape.tuple && !sShape.tuple &&
5850
+ !tShape.indexValue && !sShape.indexValue &&
5851
+ !shapeHasAccessorSlots(tShape) && !shapeHasAccessorSlots(sShape) &&
5852
+ sShape.fields.every((sf) => {
5853
+ const tf = tShape.fields.find((x) => x.name === sf.name);
5854
+ return tf !== undefined && typeEquals(tf.type, sf.type);
5855
+ });
5856
+ if (ok) {
5857
+ const loc = locOf(call);
5858
+ const target = L.lowerExpr(call.arguments[0]);
5859
+ const source = L.lowerExpr(call.arguments[1]);
5860
+ if (target.type.kind === "record" && source.type.kind === "record") {
5861
+ const helper = recordAssignHelper(L, target.type.shapeId, source.type.shapeId, loc);
5862
+ return { kind: "call", callee: helper, args: [target, source], type: target.type, loc };
5863
+ }
5864
+ }
5865
+ }
5866
+ }
5507
5867
  return null;
5508
5868
  }
5509
5869
  // Object.defineProperties over a CHECKED-DYNAMIC target (test/common's
@@ -5557,6 +5917,54 @@ function lowerObjectStaticCall(L, call, access) {
5557
5917
  }
5558
5918
  L.noLowering("Object.freeze of a possibly-aliased value", call, "freeze of a FRESH object/array literal (and of primitives) compiles — frozen-ness is unobservable there; an aliased target's later writes would need the runtime frozen bit");
5559
5919
  }
5920
+ // `Object.hasOwn(r, k)` over a RECORD receiver: a record's own-key set
5921
+ // is its declared field list, so membership is a compare chain against
5922
+ // the field names (interned per shape). Undefined-armed (optional)
5923
+ // fields answer by their runtime tag — the explicit-undefined-is-absent
5924
+ // stance: an omitted optional field holds the undefined arm and reads
5925
+ // as NOT own, exactly Node's absent key (an EXPLICIT `k: undefined`
5926
+ // diverges — documented next to the child-env/JSON rule). Tuple,
5927
+ // index-signature (overflow membership lives in the runtime map), and
5928
+ // accessor-carrying shapes keep the SC2020 fence; non-record receivers
5929
+ // do too.
5930
+ if (member === "hasOwn" && call.arguments.length === 2 && !call.arguments.some((a) => ts.isSpreadElement(a))) {
5931
+ const recvNode = call.arguments[0];
5932
+ const keyNode = call.arguments[1];
5933
+ const probed = probeLower(L, recvNode);
5934
+ // A CHECKED-DYNAMIC receiver (the JS file-scope object-literal
5935
+ // identity story): the runtime DOM probe — OBJ member presence, ARR
5936
+ // index bounds, Node's ToObject TypeError on nullish.
5937
+ if (probed?.type.kind === "dyn") {
5938
+ const loc = locOf(call);
5939
+ const receiver = L.lowerExpr(recvNode);
5940
+ let key = L.lowerExpr(keyNode);
5941
+ if (key.type.kind === "f64" || key.type.kind === "bool" || key.type.kind === "dyn") {
5942
+ key = { kind: "toString", operand: key, type: STRING, loc: locOf(keyNode) };
5943
+ }
5944
+ if (key.type.kind !== "string")
5945
+ return null;
5946
+ return { kind: "libCall", fn: "dyn.hasOwn", args: [receiver, key], type: BOOL, loc };
5947
+ }
5948
+ if (probed?.type.kind !== "record")
5949
+ return null;
5950
+ const shape = L.shapes.get(probed.type.shapeId);
5951
+ if (!shape || shape.tuple || shape.indexValue || shapeHasAccessorSlots(shape))
5952
+ return null;
5953
+ const loc = locOf(call);
5954
+ const receiver = L.lowerExpr(recvNode);
5955
+ if (receiver.type.kind !== "record")
5956
+ return null; // probe/lower drift: keep the fence
5957
+ let key = L.lowerExpr(keyNode);
5958
+ // Number/boolean/dyn keys stringify — ToPropertyKey, the keyed-write
5959
+ // path's rule; symbol and composite keys keep the fence.
5960
+ if (key.type.kind === "f64" || key.type.kind === "bool" || key.type.kind === "dyn") {
5961
+ key = { kind: "toString", operand: key, type: STRING, loc: locOf(keyNode) };
5962
+ }
5963
+ if (key.type.kind !== "string")
5964
+ return null;
5965
+ const helper = recordHasOwnHelper(L, receiver.type.shapeId, loc);
5966
+ return { kind: "call", callee: helper, args: [receiver, key], type: BOOL, loc };
5967
+ }
5560
5968
  if (member !== "keys" && member !== "values" && member !== "entries")
5561
5969
  return null;
5562
5970
  if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0]))
@@ -6109,6 +6517,29 @@ export function bindingGenericFnNodeOf(decl) {
6109
6517
  }
6110
6518
  return null;
6111
6519
  }
6520
+ /** The CONTEXTUAL twin of bindingGenericFnNodeOf: `const g: Mapper = (x)
6521
+ * => x` where `type Mapper = <T>(x: T) => T` — the initializer declares
6522
+ * no type parameters of its own, but the ANNOTATION's one call signature
6523
+ * does, and the checker types the arrow's parameters by those (`x: T`).
6524
+ * Such a binding monomorphizes exactly like `const g = <T>(x: T) => x`;
6525
+ * bindingGenericFnInfoOf reads the type parameters off the annotation's
6526
+ * signature. Null when the shape doesn't match (a concrete annotation, a
6527
+ * generic arrow — the syntactic probe's case, an overloaded alias). */
6528
+ export function bindingContextualGenericFnNodeOf(L, decl) {
6529
+ if (!ts.isIdentifier(decl.name) || decl.initializer === undefined || decl.type === undefined)
6530
+ return null;
6531
+ let init = decl.initializer;
6532
+ while (ts.isParenthesizedExpression(init))
6533
+ init = init.expression;
6534
+ if (!(ts.isArrowFunction(init) || ts.isFunctionExpression(init)) ||
6535
+ init.typeParameters !== undefined || init.body === undefined) {
6536
+ return null;
6537
+ }
6538
+ const sigs = L.checker.getCallSignatures(L.typeOf(decl.name));
6539
+ if (sigs.length !== 1 || sigs[0].getTypeParameters().length === 0)
6540
+ return null;
6541
+ return init;
6542
+ }
6112
6543
  /** The interned GenericFnInfo for one generic arrow/function-expression
6113
6544
  * binding initializer, with the supportability fences applied ONCE per
6114
6545
  * declaration: the binding must sit at module scope (the compiled
@@ -6150,11 +6581,29 @@ export function bindingGenericFnInfoOf(L, decl, fnNode) {
6150
6581
  L.unsupported("SC1090", decl.name, `generic function values in reassigned bindings (calls of '${name}' resolve statically against this initializer, so the binding must provably hold it — a const, or a let/var nothing in its declaring file writes)`);
6151
6582
  }
6152
6583
  const typeParams = [];
6153
- for (const tp of fnNode.typeParameters) {
6154
- const tpSym = L.checker.getSymbolAtLocation(tp.name);
6155
- if (!tpSym)
6584
+ if (fnNode.typeParameters !== undefined) {
6585
+ for (const tp of fnNode.typeParameters) {
6586
+ const tpSym = L.checker.getSymbolAtLocation(tp.name);
6587
+ if (!tpSym)
6588
+ L.unsupported("SC1090", fnNode, "this function form");
6589
+ typeParams.push(tpSym);
6590
+ }
6591
+ }
6592
+ else {
6593
+ // The CONTEXTUAL shape (bindingContextualGenericFnNodeOf): the type
6594
+ // parameters live on the annotation's one call signature, and the
6595
+ // checker types the initializer's parameters by them — the same
6596
+ // symbols the instance bodies resolve through.
6597
+ const sigs = L.checker.getCallSignatures(L.typeOf(decl.name));
6598
+ const tps = sigs.length === 1 ? sigs[0].getTypeParameters() : [];
6599
+ if (tps.length === 0)
6156
6600
  L.unsupported("SC1090", fnNode, "this function form");
6157
- typeParams.push(tpSym);
6601
+ for (const tp of tps) {
6602
+ const tpSym = tp.getSymbol();
6603
+ if (!tpSym)
6604
+ L.unsupported("SC1090", fnNode, "this function form");
6605
+ typeParams.push(tpSym);
6606
+ }
6158
6607
  }
6159
6608
  // Only NAME syntax is checkable here; optional/default/rest shapes are
6160
6609
  // computed per instantiation from the resolved signature — exactly
@@ -6181,6 +6630,53 @@ export function bindingGenericFnInfoOf(L, decl, fnNode) {
6181
6630
  }
6182
6631
  return info;
6183
6632
  }
6633
+ /** `const h = id` — a binding ALIASING a generic function (a top-level
6634
+ * declaration, a registered generic binding, or another alias — resolved
6635
+ * left to right in declaration order). The alias registers the SAME info
6636
+ * under its own symbol, so calls (`h(3)`) and pinned values (`take(h)`)
6637
+ * resolve exactly like the target's own name, and the binding itself has
6638
+ * no runtime value (a generic function value cannot materialize). Claims
6639
+ * only bindings whose OWN type still keeps type parameters — a
6640
+ * concrete-annotated alias (`const h: (x: number) => number = id`) is a
6641
+ * pinned VALUE, the existing lowerGenericFnValue story. Null when the
6642
+ * shape doesn't match or the target isn't a registered generic; fences
6643
+ * (reassignment, var redeclaration) report by name inside. */
6644
+ export function bindingGenericFnAliasInfoOf(L, decl) {
6645
+ if (!ts.isIdentifier(decl.name) || decl.initializer === undefined)
6646
+ return null;
6647
+ let init = decl.initializer;
6648
+ while (ts.isParenthesizedExpression(init))
6649
+ init = init.expression;
6650
+ if (!ts.isIdentifier(init))
6651
+ return null;
6652
+ const target = genericFnOf(L, init);
6653
+ if (!target)
6654
+ return null;
6655
+ // A concrete annotation pins one signature — that value story
6656
+ // (lowerGenericFnValue at the reference) stays untouched.
6657
+ const ownSigs = L.checker.getCallSignatures(L.typeOf(decl.name));
6658
+ if (ownSigs.length === 0 || !ownSigs.every((s) => s.getTypeParameters().length > 0))
6659
+ return null;
6660
+ const sym = L.checker.getSymbolAtLocation(decl.name);
6661
+ if (!sym)
6662
+ return null;
6663
+ const existing = L.genericFnsBySymbol.get(sym);
6664
+ if (existing)
6665
+ return existing;
6666
+ const name = decl.name.text;
6667
+ const isConst = (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0;
6668
+ // The same holds-it-forever discipline as generic arrow bindings:
6669
+ // calls through the alias resolve statically against the target, so
6670
+ // nothing may ever rebind it (merged `var` redeclarations included).
6671
+ const redeclared = L.checker
6672
+ .declarationsOf(sym)
6673
+ .some((d) => d !== decl && ts.isVariableDeclaration(d) && d.initializer !== undefined);
6674
+ if (!isConst && (redeclared || !bindingNeverReassigned(L, sym, decl))) {
6675
+ L.unsupported("SC1090", decl.name, `generic function values in reassigned bindings (calls of '${name}' resolve statically against this initializer, so the binding must provably hold it — a const, or a let/var nothing in its declaring file writes)`);
6676
+ }
6677
+ L.genericFnsBySymbol.set(sym, target);
6678
+ return target;
6679
+ }
6184
6680
  /** Static resolution stands in for the receiver's runtime value, so an
6185
6681
  * object-literal generic-method receiver must provably HOLD the defining
6186
6682
  * literal: a direct read of a binding whose initializer IS that literal
@@ -6241,8 +6737,36 @@ export function lowerObjLitGenericMethodCall(L, call, access) {
6241
6737
  if (L.checker.declarationsOf(propSym).some((d) => d.parent !== undefined && (ts.isClassDeclaration(d.parent) || ts.isClassExpression(d.parent)))) {
6242
6738
  return null;
6243
6739
  }
6740
+ // An INTERFACE-typed receiver over a class instance (`const r: Repo =
6741
+ // new MemRepo(); r.get(...)` — the declaration is signature-only, but
6742
+ // the receiver's exact class is statically proven and the binding
6743
+ // kept the class representation, genericIfaceBindingKeepsClass): the
6744
+ // call is a class generic-method call on that exact class. The
6745
+ // receiver must LOWER as the class — a record-held value (a `let`, a
6746
+ // produced value, a parameter) has already dropped it, and keeps the
6747
+ // named fence below.
6748
+ {
6749
+ const exact = exactInstanceClassOf(L, access.expression);
6750
+ const gfound = exact ? findGenericMethodOn(L, exact, name) : null;
6751
+ if (gfound && ts.isIdentifier(access.expression)) {
6752
+ const recv = L.lowerExpr(access.expression); // identifier reads are pure — no double evaluation
6753
+ if (recv.type.kind === "object") {
6754
+ return lowerClassGenericMethodCall(L, call, access, exact, gfound, recv);
6755
+ }
6756
+ }
6757
+ }
6244
6758
  const found = objLitGenericFnNodeOf(L, propSym);
6245
6759
  if (!found) {
6760
+ // Interface-declared generic methods dispatch statically, so the
6761
+ // receiver's runtime class must be provable — name that discipline
6762
+ // instead of the object-literal wording when the method lives on an
6763
+ // interface.
6764
+ const onInterface = L.checker
6765
+ .declarationsOf(propSym)
6766
+ .some((d) => d.parent !== undefined && ts.isInterfaceDeclaration(d.parent));
6767
+ if (onInterface) {
6768
+ L.unsupported("SC1090", call, `calls of the generic method '${name}' through this receiver (the interface declaration is signature-only and generic methods dispatch statically, so the receiver's runtime class must be provable — bind the receiver to a const initialized with its 'new' expression, e.g. 'const r: ${L.checker.typeToString(recvT)} = new C(...)')`);
6769
+ }
6246
6770
  L.unsupported("SC1090", call, `calls of the generic method '${name}' with no defining object literal (the declaration is signature-only — only methods declared with a body in an object literal monomorphize)`);
6247
6771
  }
6248
6772
  requireObjLitGenericReceiver(L, call, access.expression, found.literal, name);