@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.
- package/dist/backend/emission/emit-exprs.js +47 -1
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-island.js +9 -1
- package/dist/backend/emission/emit-island.js.map +1 -1
- package/dist/backend/emission/emit-stmts.js +3 -1
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-walkers.js +22 -8
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.js +12 -1
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +4 -3
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/llvm/emitter.js +105 -9
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +49 -1
- package/dist/diagnostics/diagnostic.js +119 -5
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lib-contract.d.ts +114 -0
- package/dist/frontend/lib-contract.js +372 -0
- package/dist/frontend/lib-contract.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +16 -1
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +47 -7
- package/dist/frontend/lowering/lower-calls.js +560 -36
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +14 -1
- package/dist/frontend/lowering/lower-classes.js +85 -15
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +43 -1
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +1 -1
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +26 -3
- package/dist/frontend/lowering/lower-exprs.js +474 -44
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +7 -0
- package/dist/frontend/lowering/lower-island.js +71 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-mixins.d.ts +1 -1
- package/dist/frontend/lowering/lower-mixins.js +1 -1
- package/dist/frontend/lowering/lower-modules.js +95 -4
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +430 -44
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +19 -3
- package/dist/frontend/lowering/lowerer.js +188 -11
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/types.d.ts +28 -1
- package/dist/frontend/types.js +300 -8
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +96 -7
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +48 -2
- package/dist/ir/nodes.js +103 -3
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +35 -6
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +53 -3
- package/dist/library/profile.js +143 -12
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar-validate.d.ts +4 -0
- package/dist/library/sidecar-validate.js +506 -0
- package/dist/library/sidecar-validate.js.map +1 -0
- package/dist/library/sidecar.d.ts +191 -0
- package/dist/library/sidecar.js +687 -0
- package/dist/library/sidecar.js.map +1 -0
- package/dist/library/trap-teaching.d.ts +11 -0
- package/dist/library/trap-teaching.js +49 -0
- package/dist/library/trap-teaching.js.map +1 -0
- package/dist/library/wyhash.d.ts +12 -0
- package/dist/library/wyhash.js +116 -0
- package/dist/library/wyhash.js.map +1 -0
- package/package.json +2 -2
- 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))
|
|
112
|
-
|
|
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 === "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
886
|
-
|
|
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
|
|
903
|
-
|
|
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)
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5308
|
-
* SymbolConstructor
|
|
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
|
-
|
|
6154
|
-
const
|
|
6155
|
-
|
|
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
|
-
|
|
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);
|