@scriptc/compiler 0.0.7 → 0.0.9
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/ambient/scriptc-node-fallback.d.ts +13 -0
- package/dist/backend/cc.js +14 -0
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +155 -6
- 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-types.d.ts +3 -0
- package/dist/backend/emission/emit-types.js +12 -0
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emit-walkers.d.ts +7 -5
- package/dist/backend/emission/emit-walkers.js +45 -18
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +1 -1
- package/dist/backend/emission/emitter.js +35 -10
- 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 +300 -18
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +98 -11
- package/dist/diagnostics/diagnostic.js +185 -25
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lib-contract.d.ts +24 -6
- package/dist/frontend/lib-contract.js +125 -44
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +16 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +11 -0
- package/dist/frontend/lowering/lower-builtins.js +127 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +77 -18
- package/dist/frontend/lowering/lower-calls.js +978 -44
- 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 +112 -16
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +4 -2
- package/dist/frontend/lowering/lower-containers.js +236 -44
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +55 -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 +507 -47
- 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 +866 -56
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +28 -2
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +85 -17
- package/dist/frontend/lowering/lowerer.js +459 -37
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +20 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.d.ts +7 -2
- package/dist/frontend/npm-static-rewrite.js +566 -138
- package/dist/frontend/npm-static-rewrite.js.map +1 -1
- package/dist/frontend/npm-static.js +11 -2
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/program.js +5 -5
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/shared.d.ts +6 -1
- package/dist/frontend/shared.js +46 -1
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/types.d.ts +28 -1
- package/dist/frontend/types.js +304 -10
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +181 -26
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +159 -9
- package/dist/ir/nodes.js +76 -6
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +93 -14
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +17 -3
- package/dist/library/profile.js +38 -9
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar.js +162 -43
- package/dist/library/sidecar.js.map +1 -1
- 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/package.json +2 -2
- package/surface-manifest.json +46 -4
|
@@ -7,16 +7,16 @@ import { lowerGenMethodCall } from "./lower-generators.js";
|
|
|
7
7
|
import { BOOL, CAUGHT, DYN, F64, JSVAL, STRING, SYMBOL_T, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, canMarshalTypedFuncIntoIsland, funcOf, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/nodes.js";
|
|
8
8
|
import { isJsSourceFile, locOf } from "../program.js";
|
|
9
9
|
import { isGenericCallableMemberType, typeKey } from "../types.js";
|
|
10
|
-
import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx } from "./lowerer.js";
|
|
10
|
+
import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx, nodeThrowExpr } from "./lowerer.js";
|
|
11
11
|
import { enforceLibBoundary } from "./lib-boundary.js";
|
|
12
|
-
import { builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
|
|
12
|
+
import { NARROW_FIRST, builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
|
|
13
13
|
import { requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
|
|
14
14
|
import { mixinFnShapeOf } from "./lower-mixins.js";
|
|
15
15
|
import { bufEncoding, dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerRegexMethodCall, lowerStringMethodCall, lowerTupleReadMethodCall } from "./lower-containers.js";
|
|
16
16
|
import { lowerChildStreamMethodCall, lowerDirentMethodCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerWatcherMethodCall } from "./lower-builtins.js";
|
|
17
|
-
import { lowerPromiseAllTupleCall, lowerPromiseRejectCall, probeLower, templateRawTextOf } from "./lower-exprs.js";
|
|
17
|
+
import { droppableStatic, 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);
|
|
@@ -2664,6 +2804,9 @@ export function lowerCall(L, expr) {
|
|
|
2664
2804
|
const streamServed = lowerStreamModuleCall(L, expr, bi, loc);
|
|
2665
2805
|
if (streamServed)
|
|
2666
2806
|
return streamServed;
|
|
2807
|
+
const fsTs = L.lowerFsToUnixTimestampCall(expr, bi, loc);
|
|
2808
|
+
if (fsTs)
|
|
2809
|
+
return fsTs;
|
|
2667
2810
|
const builtinFn = builtinModuleFnOf(L, bi.module, bi.member);
|
|
2668
2811
|
if (!builtinFn) {
|
|
2669
2812
|
// Typed by @types/node (the fallback declarations only declare
|
|
@@ -2893,15 +3036,24 @@ export function lowerCall(L, expr) {
|
|
|
2893
3036
|
// closure object, plain C call). Generic functions route through
|
|
2894
3037
|
// monomorphization (the call targets a per-instantiation instance).
|
|
2895
3038
|
if (ts.isIdentifier(expr.expression) && !L.isSelfReference(expr.expression)) {
|
|
3039
|
+
// A JS spread argument the compile-time completion cannot take (a
|
|
3040
|
+
// fixed position, a dynamic rest slot) sends the call down the
|
|
3041
|
+
// VALUE path — the runtime-arity lane (lowerSpreadArgsCall) boxes
|
|
3042
|
+
// the declaration's value and applies through a runtime-built
|
|
3043
|
+
// argument list. Typed .ts spreads keep completeArgs' rest packing.
|
|
3044
|
+
const jsSpreadArgs = expr.arguments.some((a) => ts.isSpreadElement(a)) && isJsSourceFile(expr.getSourceFile());
|
|
2896
3045
|
if (L.isTopLevelFnSymbol(expr.expression) && !L.peekLocal(expr.expression)) {
|
|
2897
3046
|
// `import g = N.f; g()` — the alias's own source-order guards
|
|
2898
3047
|
// (a no-op for every non-import= binding).
|
|
2899
3048
|
fenceEarlyAliasUse(L, expr.expression, expr);
|
|
2900
3049
|
const generic = L.genericFnOf(expr.expression);
|
|
2901
|
-
|
|
3050
|
+
// An implicit-any JS function spread-forwarded into: per-site
|
|
3051
|
+
// monomorphization has no slot for a runtime-length argument
|
|
3052
|
+
// list — the value path's boxed thunk delivers JS arity instead.
|
|
3053
|
+
if (generic && !(jsSpreadArgs && generic.implicitParams))
|
|
2902
3054
|
return L.lowerGenericCall(expr, generic);
|
|
2903
|
-
const sig = L.fnSigOf(expr.expression);
|
|
2904
|
-
if (sig) {
|
|
3055
|
+
const sig = generic ? null : L.fnSigOf(expr.expression);
|
|
3056
|
+
if (sig && !(jsSpreadArgs && spreadNeedsRuntimeArity(sig.params, expr.arguments))) {
|
|
2905
3057
|
L.noteEdge(sig.name);
|
|
2906
3058
|
const args = L.completeArgs(expr.arguments, sig.params, loc, expr);
|
|
2907
3059
|
return reconcileOverloadReturn(L, expr, { kind: "call", callee: sig.name, args, type: sig.returnType, loc });
|
|
@@ -2925,10 +3077,12 @@ export function lowerCall(L, expr) {
|
|
|
2925
3077
|
// the call monomorphizes against it exactly like a generic function
|
|
2926
3078
|
// declaration and the binding is never read. Symbol identity does
|
|
2927
3079
|
// the discrimination — a shadowing local has its own symbol, and
|
|
2928
|
-
// registered bindings never declare locals or globals.
|
|
3080
|
+
// registered bindings never declare locals or globals. Implicit-any
|
|
3081
|
+
// JS bindings spread-forwarded into skip to the value path (the
|
|
3082
|
+
// runtime-arity lane), like the declaration form above.
|
|
2929
3083
|
{
|
|
2930
3084
|
const generic = L.genericFnOf(expr.expression);
|
|
2931
|
-
if (generic)
|
|
3085
|
+
if (generic && !(jsSpreadArgs && generic.implicitParams))
|
|
2932
3086
|
return L.lowerGenericCall(expr, generic);
|
|
2933
3087
|
}
|
|
2934
3088
|
}
|
|
@@ -3137,6 +3291,9 @@ export function lowerCall(L, expr) {
|
|
|
3137
3291
|
// island path (bytes never cross the boundary).
|
|
3138
3292
|
L.lowerBytesMethodCall(expr, expr.expression) ??
|
|
3139
3293
|
L.lowerBufferStaticCall(expr, expr.expression) ??
|
|
3294
|
+
// URL.revokeObjectURL's zero-argument contract (the one-argument
|
|
3295
|
+
// form keeps the fence — createObjectURL does too).
|
|
3296
|
+
lowerUrlStaticCall(L, expr, expr.expression) ??
|
|
3140
3297
|
// Readable.from — the stream classes' one static (before the
|
|
3141
3298
|
// stdlib chokepoint claims the member).
|
|
3142
3299
|
lowerStreamStaticCall(L, expr, expr.expression) ??
|
|
@@ -3172,6 +3329,9 @@ export function lowerCall(L, expr) {
|
|
|
3172
3329
|
lowerSymbolStaticCall(L, expr, expr.expression) ??
|
|
3173
3330
|
lowerSymbolMethodCall(L, expr, expr.expression) ??
|
|
3174
3331
|
lowerRegExpStaticCall(L, expr, expr.expression) ??
|
|
3332
|
+
// The composed en-US Intl.NumberFormat form — before the member
|
|
3333
|
+
// fences (the receiver's Intl.NumberFormat type has no mapping).
|
|
3334
|
+
lowerIntlNumberFormatCall(L, expr, expr.expression) ??
|
|
3175
3335
|
lowerGroupByStaticCall(L, expr, expr.expression) ??
|
|
3176
3336
|
// Iterator-helper chains rooted at arr.values() — before the
|
|
3177
3337
|
// array method paths (the terminal names collide with array
|
|
@@ -3280,6 +3440,12 @@ export function lowerCall(L, expr) {
|
|
|
3280
3440
|
// any-origin value). Spread arguments keep their fence.
|
|
3281
3441
|
if (callee.type.kind === "dyn") {
|
|
3282
3442
|
if (expr.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
3443
|
+
// The runtime-arity lane: a dyn callee is already boxed — the
|
|
3444
|
+
// spread-marked dynCall applies through a fresh DOM argument
|
|
3445
|
+
// array (lowerSpreadArgsCall). Sources outside it keep the fence.
|
|
3446
|
+
const spreadServed = lowerSpreadArgsCall(L, expr, callee, loc);
|
|
3447
|
+
if (spreadServed)
|
|
3448
|
+
return spreadServed;
|
|
3283
3449
|
L.unsupported("SC1090", expr, "spread arguments in calls through 'unknown' values");
|
|
3284
3450
|
}
|
|
3285
3451
|
const args = expr.arguments.map((a) => L.lowerExprExpecting(a, DYN));
|
|
@@ -3293,6 +3459,42 @@ export function lowerCall(L, expr) {
|
|
|
3293
3459
|
if (callee.type.kind !== "func") {
|
|
3294
3460
|
L.badType(expr.expression, L.typeOf(expr.expression));
|
|
3295
3461
|
}
|
|
3462
|
+
// A SPREAD argument on a func-typed callee — the rest-forwarding
|
|
3463
|
+
// idiom (`(...args) => from(...args)`): the runtime-arity lane boxes
|
|
3464
|
+
// or marshals the callee and applies through a runtime-built argument
|
|
3465
|
+
// list (lowerSpreadArgsCall). Shapes outside its lanes fall through
|
|
3466
|
+
// to the historical fences.
|
|
3467
|
+
if (expr.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
3468
|
+
const spreadServed = lowerSpreadArgsCall(L, expr, callee, loc);
|
|
3469
|
+
if (spreadServed)
|
|
3470
|
+
return spreadServed;
|
|
3471
|
+
}
|
|
3472
|
+
// An ISLAND-REST func value called directly (`f(1, 2)` where f is the
|
|
3473
|
+
// --dynamic `(...args) =>` lambda): the type SPELLS its trailing
|
|
3474
|
+
// engine-array param — complete the call exactly like completeArgs'
|
|
3475
|
+
// island pack (fixed slots positionally, missing ones with the
|
|
3476
|
+
// engine's undefined, the surplus marshaled into one fresh engine
|
|
3477
|
+
// array). JS arity, no runtime machinery.
|
|
3478
|
+
if (callee.type.rest === true &&
|
|
3479
|
+
callee.type.restAbi === "jsval" &&
|
|
3480
|
+
callee.type.params.length >= 1 &&
|
|
3481
|
+
callee.type.params[callee.type.params.length - 1].kind === "jsval" &&
|
|
3482
|
+
!expr.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
3483
|
+
const fixed = callee.type.params.slice(0, -1);
|
|
3484
|
+
const args = fixed.map((p, i) => {
|
|
3485
|
+
const a = expr.arguments[i];
|
|
3486
|
+
if (a)
|
|
3487
|
+
return L.lowerExprExpecting(a, p);
|
|
3488
|
+
const absent = omittedArgFor(L, p, loc);
|
|
3489
|
+
if (!absent) {
|
|
3490
|
+
L.unsupported("SC1090", expr, "calls omitting a non-optional parameter of the callee's type");
|
|
3491
|
+
}
|
|
3492
|
+
return absent;
|
|
3493
|
+
});
|
|
3494
|
+
const restArgs = expr.arguments.slice(fixed.length).map((a) => L.lowerExprExpecting(a, JSVAL));
|
|
3495
|
+
args.push({ kind: "jsOp", op: "arrLit", args: restArgs, type: JSVAL, loc });
|
|
3496
|
+
return { kind: "callValue", callee, args, type: callee.type.ret, loc };
|
|
3497
|
+
}
|
|
3296
3498
|
// A JS call with MORE arguments than the callee's lowered signature
|
|
3297
3499
|
// (`cb(1, 'x')` where the mustCall wrapper's inferred type declared
|
|
3298
3500
|
// fewer params — tsc's JS world doesn't police arity): ride the
|
|
@@ -3330,6 +3532,101 @@ export function lowerCall(L, expr) {
|
|
|
3330
3532
|
}
|
|
3331
3533
|
return { kind: "callValue", callee, args, type: callee.type.ret, loc };
|
|
3332
3534
|
}
|
|
3535
|
+
/** The RUNTIME-ARITY spread call — `f(...args)`, the rest-forwarding idiom
|
|
3536
|
+
* (`const f = (...args) => from(...args)`): a spread whose length is a
|
|
3537
|
+
* runtime fact has no home in the compile-time completion, so the call
|
|
3538
|
+
* rides a dynamic boundary instead. Two lanes, picked by the spread
|
|
3539
|
+
* source's tier:
|
|
3540
|
+
*
|
|
3541
|
+
* - CHECKED-DYNAMIC (dyn spread sources — a JS rest binding, a dyn
|
|
3542
|
+
* value): box the callee (dynFrom; a dyn callee is already boxed),
|
|
3543
|
+
* convert every argument into dyn, and emit the spread-marked dynCall
|
|
3544
|
+
* — the emitters build one fresh DOM argument array (spreads flatten
|
|
3545
|
+
* left-to-right, non-iterables throw V8's TypeError) and apply through
|
|
3546
|
+
* it; the boxed thunk delivers JS arity exactly. Result dyn, checked
|
|
3547
|
+
* per use like every any-origin value.
|
|
3548
|
+
* - ISLAND (a jsval spread source — the --dynamic rest binding is the
|
|
3549
|
+
* engine's own arguments array): marshal the callee in and emit jsOp
|
|
3550
|
+
* callSpread — the prelude helper's REAL `f(...pre, ...s)`, so
|
|
3551
|
+
* iterator protocols and the not-iterable TypeError are the engine's
|
|
3552
|
+
* own. One trailing spread after the fixed arguments is the modeled
|
|
3553
|
+
* shape (exactly the forwarding idiom).
|
|
3554
|
+
*
|
|
3555
|
+
* Answers null when neither lane fits (typed .ts spreads keep
|
|
3556
|
+
* completeArgs' rest packing and its fences). JS sources only — the
|
|
3557
|
+
* same guard as the over-arity dynCall precedent. */
|
|
3558
|
+
export function lowerSpreadArgsCall(L, expr, callee, loc) {
|
|
3559
|
+
if (!isJsSourceFile(expr.getSourceFile()))
|
|
3560
|
+
return null;
|
|
3561
|
+
if (!expr.arguments.some((a) => ts.isSpreadElement(a)))
|
|
3562
|
+
return null;
|
|
3563
|
+
if (callee.type.kind !== "dyn" && callee.type.kind !== "func" && callee.type.kind !== "jsval")
|
|
3564
|
+
return null;
|
|
3565
|
+
const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression)
|
|
3566
|
+
? expr.expression.getText()
|
|
3567
|
+
: ts.isIdentifier(expr.expression)
|
|
3568
|
+
? expr.expression.text
|
|
3569
|
+
: "value";
|
|
3570
|
+
// Lower every argument ONCE, in source order (the IR nests them in
|
|
3571
|
+
// exactly this order, so runtime evaluation order is JS's).
|
|
3572
|
+
const parts = expr.arguments.map((a) => ts.isSpreadElement(a)
|
|
3573
|
+
? { spreadOf: a.expression, node: null, v: L.lowerExpr(a.expression) }
|
|
3574
|
+
: { spreadOf: null, node: a, v: L.lowerExpr(a) });
|
|
3575
|
+
const spreadParts = parts.filter((p) => p.spreadOf !== null);
|
|
3576
|
+
const getR = (id) => L.shapes.get(id);
|
|
3577
|
+
const getU = (id) => L.unions.get(id);
|
|
3578
|
+
const anyJsvalSpread = spreadParts.some((p) => p.v.type.kind === "jsval");
|
|
3579
|
+
if (!anyJsvalSpread &&
|
|
3580
|
+
(callee.type.kind === "dyn" || (callee.type.kind === "func" && canBoxFuncIntoDyn(callee.type, getR, getU))) &&
|
|
3581
|
+
spreadParts.every((p) => p.v.type.kind === "dyn" || canConvertToDyn(p.v.type, getR, getU))) {
|
|
3582
|
+
const args = [];
|
|
3583
|
+
const spreads = [];
|
|
3584
|
+
for (const p of parts) {
|
|
3585
|
+
if (p.spreadOf !== null) {
|
|
3586
|
+
// The spelling rides along for V8's nullish spread-call
|
|
3587
|
+
// TypeError ("v is not iterable (cannot read property ...)").
|
|
3588
|
+
spreads.push({ arg: args.length, what: p.spreadOf.getText() });
|
|
3589
|
+
args.push(L.coerceInto(p.spreadOf, p.v, DYN));
|
|
3590
|
+
}
|
|
3591
|
+
else {
|
|
3592
|
+
args.push(L.coerceInto(p.node, p.v, DYN));
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
const boxed = callee.type.kind === "dyn" ? callee : L.coerceInto(expr.expression, callee, DYN);
|
|
3596
|
+
return { kind: "dynCall", callee: boxed, calleeName, args, spreads, type: DYN, loc };
|
|
3597
|
+
}
|
|
3598
|
+
if (spreadParts.length > 0 &&
|
|
3599
|
+
spreadParts.every((p) => p.v.type.kind === "jsval") &&
|
|
3600
|
+
(callee.type.kind === "jsval" || callee.type.kind === "func")) {
|
|
3601
|
+
if (spreadParts.length !== 1 || parts[parts.length - 1].spreadOf === null) {
|
|
3602
|
+
L.unsupported("SC1090", expr, "spread arguments before positional arguments in island calls (one trailing spread after the fixed arguments is the supported form)");
|
|
3603
|
+
}
|
|
3604
|
+
const f = L.coerceInto(expr.expression, callee, JSVAL);
|
|
3605
|
+
const pre = parts
|
|
3606
|
+
.slice(0, -1)
|
|
3607
|
+
.map((p) => L.coerceInto(p.node, p.v, JSVAL));
|
|
3608
|
+
const preArr = { kind: "jsOp", op: "arrLit", args: pre, type: JSVAL, loc };
|
|
3609
|
+
const last = parts[parts.length - 1];
|
|
3610
|
+
// The spelling rides in `name` for V8's nullish spread-call
|
|
3611
|
+
// TypeError ("v is not iterable (cannot read property ...)").
|
|
3612
|
+
return { kind: "jsOp", op: "callSpread", name: last.spreadOf.getText(), args: [f, preArr, last.v], type: JSVAL, loc };
|
|
3613
|
+
}
|
|
3614
|
+
// No lane fits (a spread source outside both tiers, a callee neither
|
|
3615
|
+
// boxable nor marshalable, mixed dyn/jsval spreads): fence HERE — the
|
|
3616
|
+
// arguments are already lowered, and falling back to the historical
|
|
3617
|
+
// per-site fences would lower them a second time (duplicate lambda
|
|
3618
|
+
// lifts, duplicated diagnostics).
|
|
3619
|
+
L.unsupported("SC1090", expr, "spread arguments");
|
|
3620
|
+
}
|
|
3621
|
+
/** True when a spread argument lands where the compile-time completion
|
|
3622
|
+
* cannot take it — a FIXED parameter position, or a dynamic rest slot
|
|
3623
|
+
* (dynRest/islandRest, whose packs are built per-argument): the shapes
|
|
3624
|
+
* the runtime-arity lane (lowerSpreadArgsCall) serves. Typed `rest`
|
|
3625
|
+
* slots keep completeArgs' same-element spread packing. */
|
|
3626
|
+
export function spreadNeedsRuntimeArity(shapes, argNodes) {
|
|
3627
|
+
const restAt = shapes.findIndex((s) => s.mode === "rest" || s.mode === "dynRest" || s.mode === "islandRest");
|
|
3628
|
+
return argNodes.some((a, i) => ts.isSpreadElement(a) && (restAt < 0 || i < restAt || shapes[restAt].mode !== "rest"));
|
|
3629
|
+
}
|
|
3333
3630
|
/** METHOD calls on dyn receivers (`pkg.name.replace(...)`, `rawName.split`,
|
|
3334
3631
|
* `ws.packages.filter(...)` — JSON.parse-derived values): validate the
|
|
3335
3632
|
* receiver's DOM kind, extract, and ride the STATIC method machinery — the
|
|
@@ -3868,6 +4165,32 @@ export function lowerPrimitiveProtoCall(L, call, recv, name, memberSym) {
|
|
|
3868
4165
|
const fn = name === "toExponential" ? "num.toExponential" : "num.toFixed0";
|
|
3869
4166
|
return { kind: "libCall", fn, args: [operand], type: STRING, loc };
|
|
3870
4167
|
}
|
|
4168
|
+
// Number.prototype.toLocaleString("en-US") — the spec makes it
|
|
4169
|
+
// NumberFormat(locale).format(this), so the en-US embedded formatter
|
|
4170
|
+
// answers exactly. The unlowered forms fence by NAME: no locale (the
|
|
4171
|
+
// host environment's default, which a compiled binary cannot carry),
|
|
4172
|
+
// other locales (ICU data the binary does not embed), options bags.
|
|
4173
|
+
if (name === "toLocaleString" && recvKind === "f64") {
|
|
4174
|
+
if (call.arguments.length === 0) {
|
|
4175
|
+
L.noLowering("Number.prototype.toLocaleString without a locale", call, "the default locale is the host environment's, which a compiled binary cannot carry — " +
|
|
4176
|
+
'pass it explicitly: x.toLocaleString("en-US")');
|
|
4177
|
+
}
|
|
4178
|
+
if (call.arguments.length > 1) {
|
|
4179
|
+
L.noLowering("Number.prototype.toLocaleString with an options bag", call, "the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction " +
|
|
4180
|
+
'digits, grouping) — x.toLocaleString("en-US")');
|
|
4181
|
+
}
|
|
4182
|
+
const locNode = call.arguments[0];
|
|
4183
|
+
if (ts.isSpreadElement(locNode) || !ts.isStringLiteralLike(locNode) || locNode.text !== "en-US") {
|
|
4184
|
+
L.noLowering(!ts.isSpreadElement(locNode) && ts.isStringLiteralLike(locNode)
|
|
4185
|
+
? `Number.prototype.toLocaleString at locale "${locNode.text}"`
|
|
4186
|
+
: "Number.prototype.toLocaleString with a non-literal locale", locNode, '"en-US" (Node\'s default-build locale) is the one locale whose data the runtime embeds — ' +
|
|
4187
|
+
"everything else is ICU data the binary does not carry");
|
|
4188
|
+
}
|
|
4189
|
+
const operand = L.lowerExpr(recv);
|
|
4190
|
+
if (operand.type.kind !== "f64")
|
|
4191
|
+
return null;
|
|
4192
|
+
return { kind: "libCall", fn: "intl.numFormatEnUs", args: [operand], type: STRING, loc };
|
|
4193
|
+
}
|
|
3871
4194
|
if (name === "charAt" && recvKind === "string" && call.arguments.length === 1 &&
|
|
3872
4195
|
!ts.isSpreadElement(call.arguments[0])) {
|
|
3873
4196
|
const receiver = L.lowerExpr(recv);
|
|
@@ -4044,11 +4367,26 @@ export function lowerTaggedTemplate(L, expr) {
|
|
|
4044
4367
|
: expr.template.templateSpans.map((s) => s.expression);
|
|
4045
4368
|
// Island tags (--dynamic): the engine call forms, mirroring lowerCall's
|
|
4046
4369
|
// island paths — a property-access tag is a method call (this = the
|
|
4047
|
-
// receiver, JS-exact), any other island tag a function call.
|
|
4370
|
+
// receiver, JS-exact), any other island tag a function call. The
|
|
4371
|
+
// strings argument builds ENGINE-NATIVE with its `.raw` property (the
|
|
4372
|
+
// tplStrings op): a JSON marshal would drop `.raw`, and tags dispatch
|
|
4373
|
+
// on it (the outdent idiom treats a raw-less argument as its OPTIONS
|
|
4374
|
+
// form and answers a function). A fresh array per evaluation — tags
|
|
4375
|
+
// caching by strings identity re-compute per call (SEMANTICS.md).
|
|
4376
|
+
const islandStrings = () => ({
|
|
4377
|
+
kind: "jsOp",
|
|
4378
|
+
op: "tplStrings",
|
|
4379
|
+
args: [
|
|
4380
|
+
...pieces.map((p) => ({ kind: "jsMarshal", value: { kind: "strLit", value: p.text, type: STRING, loc }, type: JSVAL, loc })),
|
|
4381
|
+
...pieces.map((p) => ({ kind: "jsMarshal", value: { kind: "strLit", value: templateRawTextOf(p), type: STRING, loc }, type: JSVAL, loc })),
|
|
4382
|
+
],
|
|
4383
|
+
type: JSVAL,
|
|
4384
|
+
loc,
|
|
4385
|
+
});
|
|
4048
4386
|
if (ts.isPropertyAccessExpression(expr.tag) && L.isIslandExpr(expr.tag.expression)) {
|
|
4049
4387
|
const receiver = L.lowerExpr(expr.tag.expression);
|
|
4050
4388
|
const args = [
|
|
4051
|
-
|
|
4389
|
+
islandStrings(),
|
|
4052
4390
|
...values.map((a) => L.jsvalIn(L.lowerExpr(a), a)),
|
|
4053
4391
|
];
|
|
4054
4392
|
return {
|
|
@@ -4059,7 +4397,7 @@ export function lowerTaggedTemplate(L, expr) {
|
|
|
4059
4397
|
if (L.isIslandExpr(expr.tag)) {
|
|
4060
4398
|
const callee = L.lowerExpr(expr.tag);
|
|
4061
4399
|
const args = [
|
|
4062
|
-
|
|
4400
|
+
islandStrings(),
|
|
4063
4401
|
...values.map((a) => L.jsvalIn(L.lowerExpr(a), a)),
|
|
4064
4402
|
];
|
|
4065
4403
|
return { kind: "jsOp", op: "callFn", args: [callee, ...args], type: JSVAL, loc };
|
|
@@ -4247,6 +4585,7 @@ export function lambdaSignature(L, node) {
|
|
|
4247
4585
|
// use a rest parameter. Arrows never claim it (JS: an arrow's
|
|
4248
4586
|
// `arguments` is the enclosing function's).
|
|
4249
4587
|
const hasDynRest = shapes.some((s) => s.mode === "dynRest");
|
|
4588
|
+
const hasIslandRest = shapes.some((s) => s.mode === "islandRest");
|
|
4250
4589
|
const usesArguments = !hasDynRest &&
|
|
4251
4590
|
!ts.isArrowFunction(node) &&
|
|
4252
4591
|
isJsSourceFile(node.getSourceFile()) &&
|
|
@@ -4258,9 +4597,14 @@ export function lambdaSignature(L, node) {
|
|
|
4258
4597
|
shapes,
|
|
4259
4598
|
funcType: {
|
|
4260
4599
|
kind: "func",
|
|
4600
|
+
// dynRest is EXCLUDED (the boxed thunk fills the trailing DOM
|
|
4601
|
+
// array — no spelled slot); islandRest is INCLUDED (the trailing
|
|
4602
|
+
// jsval param IS the engine arguments array, the REST host-call
|
|
4603
|
+
// adapter's one uniform shape).
|
|
4261
4604
|
params: shapes.filter((s) => s.mode !== "dynRest").map((s) => s.type),
|
|
4262
4605
|
ret,
|
|
4263
|
-
...(hasDynRest || usesArguments ? { rest: true } : {}),
|
|
4606
|
+
...(hasDynRest || usesArguments || hasIslandRest ? { rest: true } : {}),
|
|
4607
|
+
...(hasIslandRest ? { restAbi: "jsval" } : {}),
|
|
4264
4608
|
},
|
|
4265
4609
|
};
|
|
4266
4610
|
}
|
|
@@ -4331,13 +4675,14 @@ export function lowerLambda(L, node) {
|
|
|
4331
4675
|
if (isGenerator && funcType.ret.kind === "generator") {
|
|
4332
4676
|
fnCtx.generator = { yieldT: funcType.ret.yieldT, nextT: funcType.ret.nextT };
|
|
4333
4677
|
}
|
|
4678
|
+
const diagsBefore = L.diags.length;
|
|
4334
4679
|
L.fnStack.push(fnCtx);
|
|
4335
4680
|
try {
|
|
4336
4681
|
const { params, prologue } = L.declareParams(node.parameters, shapes);
|
|
4337
4682
|
// The VARIADIC `arguments` form (rest-marked with no declared rest
|
|
4338
4683
|
// param): a synthetic trailing DOM-array param carries the call's
|
|
4339
4684
|
// arguments; `arguments` reads resolve to it (identifier lowering).
|
|
4340
|
-
if (funcType.rest && !shapes.some((s) => s.mode === "dynRest")) {
|
|
4685
|
+
if (funcType.rest && !shapes.some((s) => s.mode === "dynRest" || s.mode === "islandRest")) {
|
|
4341
4686
|
const argsLocal = L.declareHiddenLocal("%arguments", DYN);
|
|
4342
4687
|
params.push({ localId: argsLocal.id, name: "%arguments", type: DYN });
|
|
4343
4688
|
fnCtx.argumentsLocal = argsLocal;
|
|
@@ -4416,6 +4761,62 @@ export function lowerLambda(L, node) {
|
|
|
4416
4761
|
L.liftedFns.push(lifted);
|
|
4417
4762
|
return { kind: "closure", fnName, captures: ctx.captureSources, type: funcType, loc };
|
|
4418
4763
|
}
|
|
4764
|
+
catch (e) {
|
|
4765
|
+
// JS sources defer LAMBDA poisons like function declarations
|
|
4766
|
+
// (lowerFunction's catch, lambda form — entry the function-level
|
|
4767
|
+
// deferral): a fenced concise body (`(list) => new Intl.ListFormat
|
|
4768
|
+
// (...).format(list)` — the error-message list-join idiom) would
|
|
4769
|
+
// otherwise poison the ENCLOSING statement, stopping module init
|
|
4770
|
+
// where Node only stops when the lambda is CALLED. The value
|
|
4771
|
+
// compiles as a capture-free closure over a runtimeFence body —
|
|
4772
|
+
// calling throws the first captured diagnostic at its source
|
|
4773
|
+
// position. ICEs (SC9001) stay compile errors, exactly like
|
|
4774
|
+
// lowerStmts; probe mode (diagSink) keeps the poison.
|
|
4775
|
+
if (!(e instanceof PoisonError))
|
|
4776
|
+
throw e;
|
|
4777
|
+
if (!isJsSourceFile(node.getSourceFile()) ||
|
|
4778
|
+
L.diagSink !== null ||
|
|
4779
|
+
L.diags.length <= diagsBefore ||
|
|
4780
|
+
L.diags.slice(diagsBefore).some((d) => d.code === "SC9001")) {
|
|
4781
|
+
throw e;
|
|
4782
|
+
}
|
|
4783
|
+
const captured = L.diags.splice(diagsBefore);
|
|
4784
|
+
L.runtimeFences.push(...captured);
|
|
4785
|
+
const first = captured[0];
|
|
4786
|
+
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? node.getSourceFile(), first.loc.start);
|
|
4787
|
+
const params = funcType.params.map((t, i) => ({ localId: `%pf${i}`, name: `%pf${i}`, type: t }));
|
|
4788
|
+
// A REST-MARKED value type hides one synthetic trailing DOM-array
|
|
4789
|
+
// param in the lifted function (the boxed call thunk fills it) —
|
|
4790
|
+
// the fence lambda must spell that slot too or the validator's
|
|
4791
|
+
// closure-signature check trips (SC9001). Island rest types SPELL
|
|
4792
|
+
// their trailing engine-array param, so funcType.params already
|
|
4793
|
+
// covers those.
|
|
4794
|
+
if (funcType.rest === true && funcType.restAbi !== "jsval") {
|
|
4795
|
+
params.push({ localId: "%pfrest", name: "%pfrest", type: DYN });
|
|
4796
|
+
}
|
|
4797
|
+
const lifted = {
|
|
4798
|
+
name: fnName,
|
|
4799
|
+
params,
|
|
4800
|
+
returnType: bodyReturn,
|
|
4801
|
+
locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
|
|
4802
|
+
captures: [],
|
|
4803
|
+
body: [
|
|
4804
|
+
{
|
|
4805
|
+
kind: "runtimeFence",
|
|
4806
|
+
code: first.code,
|
|
4807
|
+
message: `${first.message} [${first.code} at ${first.loc.file}:${pos.line + 1}]`,
|
|
4808
|
+
loc,
|
|
4809
|
+
},
|
|
4810
|
+
],
|
|
4811
|
+
loc,
|
|
4812
|
+
};
|
|
4813
|
+
if (isAsync)
|
|
4814
|
+
lifted.async = true;
|
|
4815
|
+
if (fnCtx.generator)
|
|
4816
|
+
lifted.generator = fnCtx.generator;
|
|
4817
|
+
L.liftedFns.push(lifted);
|
|
4818
|
+
return { kind: "closure", fnName, captures: [], type: funcType, loc };
|
|
4819
|
+
}
|
|
4419
4820
|
finally {
|
|
4420
4821
|
L.fnStack.pop();
|
|
4421
4822
|
}
|
|
@@ -5304,8 +5705,8 @@ function filterNarrowHelper(L, test, elem, outElem, tag, loc) {
|
|
|
5304
5705
|
* (Symbol.iterator, Symbol.asyncIterator, Symbol.toStringTag, ...) —
|
|
5305
5706
|
* language-level protocol uses (for-of, template literals) already
|
|
5306
5707
|
* compile through their constructs without reifying the symbol, so the
|
|
5307
|
-
* VALUE forms fence with a named message rather than
|
|
5308
|
-
* SymbolConstructor
|
|
5708
|
+
* VALUE forms fence with a named message rather than a blanket
|
|
5709
|
+
* SymbolConstructor type fence. */
|
|
5309
5710
|
function lowerSymbolStaticCall(L, call, access) {
|
|
5310
5711
|
if (call.questionDotToken || access.questionDotToken)
|
|
5311
5712
|
return null;
|
|
@@ -5435,6 +5836,110 @@ export function recordKeysArrayCall(L, receiver, argIr, shape, loc) {
|
|
|
5435
5836
|
}
|
|
5436
5837
|
return { kind: "call", callee: helper, args: [receiver], type: resultT, loc };
|
|
5437
5838
|
}
|
|
5839
|
+
/** Interned `%obj.hasOwn.<n>(r, k)` — Object.hasOwn's membership walk
|
|
5840
|
+
* over a signature-free record shape: the key compares against each
|
|
5841
|
+
* declared field name, undefined-armed fields answering by their tag
|
|
5842
|
+
* (a key is own exactly when Object.keys would list it — the two share
|
|
5843
|
+
* the guard), everything else true, no match false. */
|
|
5844
|
+
function recordHasOwnHelper(L, shapeId, loc) {
|
|
5845
|
+
const key = `obj.hasOwn:${shapeId}`;
|
|
5846
|
+
const existing = L.arrHofHelpers.get(key);
|
|
5847
|
+
if (existing)
|
|
5848
|
+
return existing;
|
|
5849
|
+
const helper = `%obj.hasOwn.${L.arrHofHelpers.size}`;
|
|
5850
|
+
L.arrHofHelpers.set(key, helper);
|
|
5851
|
+
const shape = L.shapes.get(shapeId);
|
|
5852
|
+
const recT = { kind: "record", shapeId };
|
|
5853
|
+
const rRef = { kind: "varRef", localId: "r.0", type: recT, loc };
|
|
5854
|
+
const kRef = { kind: "varRef", localId: "k.0", type: STRING, loc };
|
|
5855
|
+
const body = [];
|
|
5856
|
+
for (const f of shape.fields) {
|
|
5857
|
+
const utag = f.type.kind === "union" ? L.armTag(f.type.unionId, UNDEFINED_T) : -1;
|
|
5858
|
+
const answer = utag >= 0 && f.type.kind === "union"
|
|
5859
|
+
? {
|
|
5860
|
+
kind: "unionIsTag",
|
|
5861
|
+
unionId: f.type.unionId,
|
|
5862
|
+
tag: utag,
|
|
5863
|
+
negated: true,
|
|
5864
|
+
value: { kind: "recordGet", obj: rRef, shapeId, field: f.name, type: f.type, loc },
|
|
5865
|
+
type: BOOL,
|
|
5866
|
+
loc,
|
|
5867
|
+
}
|
|
5868
|
+
: { kind: "boolLit", value: true, type: BOOL, loc };
|
|
5869
|
+
body.push({
|
|
5870
|
+
kind: "if",
|
|
5871
|
+
cond: { kind: "strEq", negated: false, left: kRef, right: { kind: "strLit", value: f.name, type: STRING, loc }, type: BOOL, loc },
|
|
5872
|
+
then: [{ kind: "return", value: answer, loc }],
|
|
5873
|
+
else_: null,
|
|
5874
|
+
loc,
|
|
5875
|
+
});
|
|
5876
|
+
}
|
|
5877
|
+
body.push({ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc });
|
|
5878
|
+
L.liftedFns.push({
|
|
5879
|
+
name: helper,
|
|
5880
|
+
params: [
|
|
5881
|
+
{ localId: "r.0", name: "r", type: recT },
|
|
5882
|
+
{ localId: "k.0", name: "k", type: STRING },
|
|
5883
|
+
],
|
|
5884
|
+
returnType: BOOL,
|
|
5885
|
+
locals: [
|
|
5886
|
+
{ id: "r.0", name: "r", type: recT, mutable: true },
|
|
5887
|
+
{ id: "k.0", name: "k", type: STRING, mutable: false },
|
|
5888
|
+
],
|
|
5889
|
+
body,
|
|
5890
|
+
loc,
|
|
5891
|
+
});
|
|
5892
|
+
return helper;
|
|
5893
|
+
}
|
|
5894
|
+
/** Interned `%obj.assign.<n>(t, s)` — Object.assign's per-field copy
|
|
5895
|
+
* over signature-free records (every source field lands on a same-named,
|
|
5896
|
+
* same-typed target field — the caller's gate): undefined-armed source
|
|
5897
|
+
* fields copy behind the not-undefined guard, everything else straight,
|
|
5898
|
+
* and the TARGET returns (JS's aliasing). */
|
|
5899
|
+
function recordAssignHelper(L, targetShapeId, srcShapeId, loc) {
|
|
5900
|
+
const key = `obj.assign:${targetShapeId}:${srcShapeId}`;
|
|
5901
|
+
const existing = L.arrHofHelpers.get(key);
|
|
5902
|
+
if (existing)
|
|
5903
|
+
return existing;
|
|
5904
|
+
const helper = `%obj.assign.${L.arrHofHelpers.size}`;
|
|
5905
|
+
L.arrHofHelpers.set(key, helper);
|
|
5906
|
+
const sShape = L.shapes.get(srcShapeId);
|
|
5907
|
+
const tT = { kind: "record", shapeId: targetShapeId };
|
|
5908
|
+
const sT = { kind: "record", shapeId: srcShapeId };
|
|
5909
|
+
const tRef = { kind: "varRef", localId: "t.0", type: tT, loc };
|
|
5910
|
+
const sRef = { kind: "varRef", localId: "s.0", type: sT, loc };
|
|
5911
|
+
const body = [];
|
|
5912
|
+
for (const f of sShape.fields) {
|
|
5913
|
+
const get = { kind: "recordGet", obj: sRef, shapeId: srcShapeId, field: f.name, type: f.type, loc };
|
|
5914
|
+
const set = { kind: "recordSet", obj: tRef, shapeId: targetShapeId, field: f.name, value: get, loc };
|
|
5915
|
+
const utag = f.type.kind === "union" ? L.armTag(f.type.unionId, UNDEFINED_T) : -1;
|
|
5916
|
+
body.push(utag >= 0 && f.type.kind === "union"
|
|
5917
|
+
? {
|
|
5918
|
+
kind: "if",
|
|
5919
|
+
cond: { kind: "unionIsTag", unionId: f.type.unionId, tag: utag, negated: true, value: get, type: BOOL, loc },
|
|
5920
|
+
then: [set],
|
|
5921
|
+
else_: null,
|
|
5922
|
+
loc,
|
|
5923
|
+
}
|
|
5924
|
+
: set);
|
|
5925
|
+
}
|
|
5926
|
+
body.push({ kind: "return", value: tRef, loc });
|
|
5927
|
+
L.liftedFns.push({
|
|
5928
|
+
name: helper,
|
|
5929
|
+
params: [
|
|
5930
|
+
{ localId: "t.0", name: "t", type: tT },
|
|
5931
|
+
{ localId: "s.0", name: "s", type: sT },
|
|
5932
|
+
],
|
|
5933
|
+
returnType: tT,
|
|
5934
|
+
locals: [
|
|
5935
|
+
{ id: "t.0", name: "t", type: tT, mutable: true },
|
|
5936
|
+
{ id: "s.0", name: "s", type: sT, mutable: true },
|
|
5937
|
+
],
|
|
5938
|
+
body,
|
|
5939
|
+
loc,
|
|
5940
|
+
});
|
|
5941
|
+
return helper;
|
|
5942
|
+
}
|
|
5438
5943
|
/** The `Iterator` global's statics (ES2025 — Iterator.from, and the
|
|
5439
5944
|
* abstract constructor as a value): no first-class iterator objects
|
|
5440
5945
|
* exist here, so every member fences with the working spelling named
|
|
@@ -5469,12 +5974,184 @@ function lowerRegExpStaticCall(L, call, access) {
|
|
|
5469
5974
|
L.badType(call.arguments[0], L.typeOf(call.arguments[0]));
|
|
5470
5975
|
return { kind: "libCall", fn: "regexp.escape", args: [arg], type: STRING, loc: locOf(call) };
|
|
5471
5976
|
}
|
|
5977
|
+
/** The composed en-US Intl.NumberFormat form: `new Intl.NumberFormat(
|
|
5978
|
+
* "en-US").format(x)` (and the callable spelling without `new` — the
|
|
5979
|
+
* spec makes them the same formatter). Only the COMPOSED form lowers —
|
|
5980
|
+
* formatter values have no representation — and only the one locale
|
|
5981
|
+
* whose data the runtime embeds, with default options: decimal
|
|
5982
|
+
* notation, 0–3 fraction digits rounded half-up on the shortest
|
|
5983
|
+
* round-tripping decimal (ICU's rounding input — format(1.0005) is
|
|
5984
|
+
* "1.001" though toFixed(3) answers "1.000"), "," grouping. The
|
|
5985
|
+
* unlowered forms fence by NAME (no locale — the host environment's
|
|
5986
|
+
* default, which a compiled binary cannot carry; other locales — ICU
|
|
5987
|
+
* data the binary does not embed; options bags; non-number arguments).
|
|
5988
|
+
* Null when the callee isn't a NumberFormat-construction .format. */
|
|
5989
|
+
function lowerIntlNumberFormatCall(L, call, access) {
|
|
5990
|
+
if (call.questionDotToken || access.questionDotToken)
|
|
5991
|
+
return null;
|
|
5992
|
+
if (access.name.text !== "format")
|
|
5993
|
+
return null;
|
|
5994
|
+
let recv = access.expression;
|
|
5995
|
+
while (ts.isParenthesizedExpression(recv))
|
|
5996
|
+
recv = recv.expression;
|
|
5997
|
+
let ctorArgs;
|
|
5998
|
+
if (ts.isNewExpression(recv) || (ts.isCallExpression(recv) && !recv.questionDotToken)) {
|
|
5999
|
+
const ctor = recv.expression;
|
|
6000
|
+
if (!ts.isPropertyAccessExpression(ctor) || ctor.questionDotToken ||
|
|
6001
|
+
ctor.name.text !== "NumberFormat" || !L.isStdlibGlobal(ctor.expression, "Intl")) {
|
|
6002
|
+
return null;
|
|
6003
|
+
}
|
|
6004
|
+
ctorArgs = recv.arguments ?? [];
|
|
6005
|
+
}
|
|
6006
|
+
else {
|
|
6007
|
+
return null;
|
|
6008
|
+
}
|
|
6009
|
+
const loc = locOf(call);
|
|
6010
|
+
if (ctorArgs.length === 0) {
|
|
6011
|
+
L.noLowering("Intl.NumberFormat without a locale", recv, "the default locale is the host environment's, which a compiled binary cannot carry — " +
|
|
6012
|
+
'pass it explicitly: new Intl.NumberFormat("en-US").format(x)');
|
|
6013
|
+
}
|
|
6014
|
+
if (ctorArgs.length > 1) {
|
|
6015
|
+
L.noLowering("Intl.NumberFormat with an options bag", ctorArgs[1], "the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction digits, " +
|
|
6016
|
+
'grouping) — new Intl.NumberFormat("en-US").format(x)');
|
|
6017
|
+
}
|
|
6018
|
+
const locArg = ctorArgs[0];
|
|
6019
|
+
if (ts.isSpreadElement(locArg) || !ts.isStringLiteralLike(locArg) || locArg.text !== "en-US") {
|
|
6020
|
+
L.noLowering(!ts.isSpreadElement(locArg) && ts.isStringLiteralLike(locArg)
|
|
6021
|
+
? `Intl.NumberFormat at locale "${locArg.text}"`
|
|
6022
|
+
: "Intl.NumberFormat with a non-literal locale", locArg, '"en-US" (Node\'s default-build locale) is the one locale whose data the runtime embeds — ' +
|
|
6023
|
+
"everything else is ICU data the binary does not carry");
|
|
6024
|
+
}
|
|
6025
|
+
if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0])) {
|
|
6026
|
+
L.noLowering(`Intl.NumberFormat("en-US").format with ${call.arguments.length} arguments`, call);
|
|
6027
|
+
}
|
|
6028
|
+
const argNode = call.arguments[0];
|
|
6029
|
+
if (L.mapTypeOf(L.typeOf(argNode))?.kind !== "f64") {
|
|
6030
|
+
L.noLowering(`Intl.NumberFormat("en-US").format over a '${L.checker.typeToString(L.typeOf(argNode))}'`, argNode, "a number argument is the lowered form (bigint and numeric-string inputs have no representation)");
|
|
6031
|
+
}
|
|
6032
|
+
const arg = L.lowerExprExpecting(argNode, F64);
|
|
6033
|
+
if (arg.type.kind !== "f64")
|
|
6034
|
+
L.badType(argNode, L.typeOf(argNode));
|
|
6035
|
+
return { kind: "libCall", fn: "intl.numFormatEnUs", args: [arg], type: STRING, loc };
|
|
6036
|
+
}
|
|
6037
|
+
/** Object.is over statically disjoint kinds: the constant false, with
|
|
6038
|
+
* both operands still evaluated for their effects (droppable statics
|
|
6039
|
+
* fold away — JS evaluates arguments, but nothing observes a pure one). */
|
|
6040
|
+
function objectIsDisjointFalse(left, right, loc) {
|
|
6041
|
+
const stmts = [];
|
|
6042
|
+
for (const e of [left, right]) {
|
|
6043
|
+
if (!droppableStatic(e))
|
|
6044
|
+
stmts.push({ kind: "exprStmt", expr: e, loc });
|
|
6045
|
+
}
|
|
6046
|
+
const answer = { kind: "boolLit", value: false, type: BOOL, loc };
|
|
6047
|
+
if (stmts.length === 0)
|
|
6048
|
+
return answer;
|
|
6049
|
+
return { kind: "seqExpr", stmts, result: answer, type: BOOL, loc };
|
|
6050
|
+
}
|
|
5472
6051
|
function lowerObjectStaticCall(L, call, access) {
|
|
5473
6052
|
if (call.questionDotToken || access.questionDotToken)
|
|
5474
6053
|
return null;
|
|
5475
6054
|
if (!L.isStdlibGlobal(access.expression, "Object"))
|
|
5476
6055
|
return null;
|
|
5477
6056
|
const member = access.name.text;
|
|
6057
|
+
// Object.is — the spec's SameValue over the static kinds. Number
|
|
6058
|
+
// pairs take the runtime SameValue (NaN equals NaN, +0 differs from
|
|
6059
|
+
// -0 — the two divergences from ===); every other supported pair
|
|
6060
|
+
// rides exactly the strict-equality machinery, whose answers
|
|
6061
|
+
// SameValue shares: strings by bytes, bools by value, unit literals
|
|
6062
|
+
// by tag, unions per arm (a number arm's payload compare upgrades to
|
|
6063
|
+
// SameValue via unionEq's flag), and the reference kinds by pointer
|
|
6064
|
+
// identity. Statically DISJOINT kind pairs answer the constant false
|
|
6065
|
+
// with the operands still evaluated (tsc admits any pair — Object.is
|
|
6066
|
+
// is (any, any) — and JS evaluates the arguments either way).
|
|
6067
|
+
// dyn/jsval operands keep strict equality's stance: validate first.
|
|
6068
|
+
if (member === "is") {
|
|
6069
|
+
if (call.arguments.length !== 2 || call.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
6070
|
+
L.noLowering(`Object.is with ${call.arguments.length} arguments`, call, "exactly two arguments are the lowered form (JS treats a missing one as undefined — pass it explicitly)");
|
|
6071
|
+
}
|
|
6072
|
+
const loc = locOf(call);
|
|
6073
|
+
const leftNode = call.arguments[0];
|
|
6074
|
+
const rightNode = call.arguments[1];
|
|
6075
|
+
const left = L.lowerExpr(leftNode);
|
|
6076
|
+
const right = L.lowerExpr(rightNode);
|
|
6077
|
+
const lk = left.type.kind;
|
|
6078
|
+
const rk = right.type.kind;
|
|
6079
|
+
if (lk === "f64" && rk === "f64") {
|
|
6080
|
+
return { kind: "libCall", fn: "num.sameValue", args: [left, right], type: BOOL, loc };
|
|
6081
|
+
}
|
|
6082
|
+
if (left.type.kind === "string" && right.type.kind === "string") {
|
|
6083
|
+
return { kind: "strEq", negated: false, left, right, type: BOOL, loc };
|
|
6084
|
+
}
|
|
6085
|
+
if (lk === "bool" && rk === "bool") {
|
|
6086
|
+
return { kind: "bin", op: "===", left, right, type: BOOL, loc };
|
|
6087
|
+
}
|
|
6088
|
+
const unitTest = L.lowerUnitComparison(left, right, false, loc);
|
|
6089
|
+
if (unitTest)
|
|
6090
|
+
return unitTest;
|
|
6091
|
+
if (lk === "dyn" || rk === "dyn" || lk === "jsval" || rk === "jsval") {
|
|
6092
|
+
L.noLowering("Object.is over a dynamic operand", call, "validate/narrow the value first (strict equality's rule) — SameValue only differs from === on numbers (NaN, ±0)");
|
|
6093
|
+
}
|
|
6094
|
+
if (left.type.kind === "union" || right.type.kind === "union") {
|
|
6095
|
+
const ut = left.type.kind === "union" ? left.type : right.type;
|
|
6096
|
+
const bothUnion = left.type.kind === "union" && right.type.kind === "union";
|
|
6097
|
+
const sameUnion = bothUnion && typeEquals(left.type, right.type);
|
|
6098
|
+
if ((sameUnion || !bothUnion) && L.eqComparableUnion(ut.unionId)) {
|
|
6099
|
+
const plain = left.type.kind === "union" ? right : left;
|
|
6100
|
+
const arms = L.unions.get(ut.unionId)?.arms ?? [];
|
|
6101
|
+
// The plain side wraps into the union exactly like === when the
|
|
6102
|
+
// union holds its type; a plain PRIMITIVE the union has no arm
|
|
6103
|
+
// for is the disjoint constant false (coercing it would strand).
|
|
6104
|
+
if (bothUnion || arms.some((a) => typeEquals(a, plain.type))) {
|
|
6105
|
+
const sameValue = arms.some((a) => a.kind === "f64");
|
|
6106
|
+
return {
|
|
6107
|
+
kind: "unionEq",
|
|
6108
|
+
unionId: ut.unionId,
|
|
6109
|
+
negated: false,
|
|
6110
|
+
sameValue,
|
|
6111
|
+
left: L.coerceInto(leftNode, left, ut),
|
|
6112
|
+
right: L.coerceInto(rightNode, right, ut),
|
|
6113
|
+
type: BOOL,
|
|
6114
|
+
loc,
|
|
6115
|
+
};
|
|
6116
|
+
}
|
|
6117
|
+
if (plain.type.kind === "f64" || plain.type.kind === "string" ||
|
|
6118
|
+
plain.type.kind === "bool" || isUnitType(plain.type)) {
|
|
6119
|
+
return objectIsDisjointFalse(left, right, loc);
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
L.noLowering("Object.is over these union operands", call, `union-typed comparisons need one comparable shape (${NARROW_FIRST})`);
|
|
6123
|
+
}
|
|
6124
|
+
// Reference kinds: pointer identity — exactly strict equality
|
|
6125
|
+
// (hierarchy-related classes widen the derived side first).
|
|
6126
|
+
let idLeft = left;
|
|
6127
|
+
let idRight = right;
|
|
6128
|
+
if (left.type.kind === "object" && right.type.kind === "object") {
|
|
6129
|
+
if (L.isSubclassOf(left.type.className, right.type.className)) {
|
|
6130
|
+
idLeft = L.upcastTo(left, right.type.className);
|
|
6131
|
+
}
|
|
6132
|
+
else if (L.isSubclassOf(right.type.className, left.type.className)) {
|
|
6133
|
+
idRight = L.upcastTo(right, left.type.className);
|
|
6134
|
+
}
|
|
6135
|
+
}
|
|
6136
|
+
if ((idLeft.type.kind === "func" && idRight.type.kind === "func") ||
|
|
6137
|
+
(idLeft.type.kind === "classval" && idRight.type.kind === "classval")) {
|
|
6138
|
+
return { kind: "bin", op: "===", left: idLeft, right: idRight, type: BOOL, loc };
|
|
6139
|
+
}
|
|
6140
|
+
if ((idLeft.type.kind === "array" || idLeft.type.kind === "map" ||
|
|
6141
|
+
idLeft.type.kind === "set" || idLeft.type.kind === "object" ||
|
|
6142
|
+
idLeft.type.kind === "record" || idLeft.type.kind === "symbol" ||
|
|
6143
|
+
idLeft.type.kind === "bytes" || idLeft.type.kind === "promise") &&
|
|
6144
|
+
typeEquals(idLeft.type, idRight.type)) {
|
|
6145
|
+
return { kind: "bin", op: "===", left: idLeft, right: idRight, type: BOOL, loc };
|
|
6146
|
+
}
|
|
6147
|
+
// Statically disjoint pairs with a primitive/unit side: SameValue
|
|
6148
|
+
// never crosses kinds, so the answer is the constant false.
|
|
6149
|
+
const disjoint = new Set(["f64", "string", "bool", "undefinedT", "nullT"]);
|
|
6150
|
+
if (lk !== rk && (disjoint.has(lk) || disjoint.has(rk))) {
|
|
6151
|
+
return objectIsDisjointFalse(left, right, loc);
|
|
6152
|
+
}
|
|
6153
|
+
L.noLowering(`Object.is over '${L.fmt(left.type)}' and '${L.fmt(right.type)}' operands`, call, "the operands must share one comparable kind (numbers, strings, booleans, units, one union shape, or one reference type)");
|
|
6154
|
+
}
|
|
5478
6155
|
// `Object.assign(fn, { props })` whose RESULT type maps to the hybrid
|
|
5479
6156
|
// (function-with-properties) record: the chalk-shape CONSTRUCTOR.
|
|
5480
6157
|
if (member === "assign") {
|
|
@@ -5504,6 +6181,53 @@ function lowerObjectStaticCall(L, call, access) {
|
|
|
5504
6181
|
const merged = lowerObjectAssignIndexShape(L, call);
|
|
5505
6182
|
if (merged)
|
|
5506
6183
|
return merged;
|
|
6184
|
+
// `Object.assign(target, source)` over signature-free RECORDS whose
|
|
6185
|
+
// source fields all land on same-named, same-typed target fields
|
|
6186
|
+
// (the mockable-clock restore: `Object.assign(mocked,
|
|
6187
|
+
// implementations)` over one shape): the per-field copy helper,
|
|
6188
|
+
// returning the TARGET — JS's aliasing, the target mutates in
|
|
6189
|
+
// place. Undefined-armed source fields copy behind the
|
|
6190
|
+
// not-undefined guard (an omitted optional field holds the
|
|
6191
|
+
// undefined arm and must not erase the target's value — Node
|
|
6192
|
+
// copies own keys only; an EXPLICIT `k: undefined` source diverges,
|
|
6193
|
+
// the explicit-undefined-is-absent stance). Everything else keeps
|
|
6194
|
+
// the spread hint.
|
|
6195
|
+
if (call.arguments.length === 2 && !call.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
6196
|
+
const tProbe = probeLower(L, call.arguments[0]);
|
|
6197
|
+
const sProbe = probeLower(L, call.arguments[1]);
|
|
6198
|
+
// CHECKED-DYNAMIC target and source (the JS file-scope
|
|
6199
|
+
// object-literal identity story): the runtime DOM copy — own
|
|
6200
|
+
// members of the source land on the target, which returns.
|
|
6201
|
+
if (tProbe?.type.kind === "dyn") {
|
|
6202
|
+
const loc = locOf(call);
|
|
6203
|
+
const target = L.lowerExpr(call.arguments[0]);
|
|
6204
|
+
const source = L.coerceToExpected(L.lowerExpr(call.arguments[1]), DYN);
|
|
6205
|
+
if (target.type.kind === "dyn" && source.type.kind === "dyn") {
|
|
6206
|
+
return { kind: "libCall", fn: "dyn.assign", args: [target, source], type: DYN, loc };
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
if (tProbe?.type.kind === "record" && sProbe?.type.kind === "record") {
|
|
6210
|
+
const tShape = L.shapes.get(tProbe.type.shapeId);
|
|
6211
|
+
const sShape = L.shapes.get(sProbe.type.shapeId);
|
|
6212
|
+
const ok = tShape && sShape &&
|
|
6213
|
+
!tShape.tuple && !sShape.tuple &&
|
|
6214
|
+
!tShape.indexValue && !sShape.indexValue &&
|
|
6215
|
+
!shapeHasAccessorSlots(tShape) && !shapeHasAccessorSlots(sShape) &&
|
|
6216
|
+
sShape.fields.every((sf) => {
|
|
6217
|
+
const tf = tShape.fields.find((x) => x.name === sf.name);
|
|
6218
|
+
return tf !== undefined && typeEquals(tf.type, sf.type);
|
|
6219
|
+
});
|
|
6220
|
+
if (ok) {
|
|
6221
|
+
const loc = locOf(call);
|
|
6222
|
+
const target = L.lowerExpr(call.arguments[0]);
|
|
6223
|
+
const source = L.lowerExpr(call.arguments[1]);
|
|
6224
|
+
if (target.type.kind === "record" && source.type.kind === "record") {
|
|
6225
|
+
const helper = recordAssignHelper(L, target.type.shapeId, source.type.shapeId, loc);
|
|
6226
|
+
return { kind: "call", callee: helper, args: [target, source], type: target.type, loc };
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
5507
6231
|
return null;
|
|
5508
6232
|
}
|
|
5509
6233
|
// Object.defineProperties over a CHECKED-DYNAMIC target (test/common's
|
|
@@ -5557,6 +6281,54 @@ function lowerObjectStaticCall(L, call, access) {
|
|
|
5557
6281
|
}
|
|
5558
6282
|
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
6283
|
}
|
|
6284
|
+
// `Object.hasOwn(r, k)` over a RECORD receiver: a record's own-key set
|
|
6285
|
+
// is its declared field list, so membership is a compare chain against
|
|
6286
|
+
// the field names (interned per shape). Undefined-armed (optional)
|
|
6287
|
+
// fields answer by their runtime tag — the explicit-undefined-is-absent
|
|
6288
|
+
// stance: an omitted optional field holds the undefined arm and reads
|
|
6289
|
+
// as NOT own, exactly Node's absent key (an EXPLICIT `k: undefined`
|
|
6290
|
+
// diverges — documented next to the child-env/JSON rule). Tuple,
|
|
6291
|
+
// index-signature (overflow membership lives in the runtime map), and
|
|
6292
|
+
// accessor-carrying shapes keep the SC2020 fence; non-record receivers
|
|
6293
|
+
// do too.
|
|
6294
|
+
if (member === "hasOwn" && call.arguments.length === 2 && !call.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
6295
|
+
const recvNode = call.arguments[0];
|
|
6296
|
+
const keyNode = call.arguments[1];
|
|
6297
|
+
const probed = probeLower(L, recvNode);
|
|
6298
|
+
// A CHECKED-DYNAMIC receiver (the JS file-scope object-literal
|
|
6299
|
+
// identity story): the runtime DOM probe — OBJ member presence, ARR
|
|
6300
|
+
// index bounds, Node's ToObject TypeError on nullish.
|
|
6301
|
+
if (probed?.type.kind === "dyn") {
|
|
6302
|
+
const loc = locOf(call);
|
|
6303
|
+
const receiver = L.lowerExpr(recvNode);
|
|
6304
|
+
let key = L.lowerExpr(keyNode);
|
|
6305
|
+
if (key.type.kind === "f64" || key.type.kind === "bool" || key.type.kind === "dyn") {
|
|
6306
|
+
key = { kind: "toString", operand: key, type: STRING, loc: locOf(keyNode) };
|
|
6307
|
+
}
|
|
6308
|
+
if (key.type.kind !== "string")
|
|
6309
|
+
return null;
|
|
6310
|
+
return { kind: "libCall", fn: "dyn.hasOwn", args: [receiver, key], type: BOOL, loc };
|
|
6311
|
+
}
|
|
6312
|
+
if (probed?.type.kind !== "record")
|
|
6313
|
+
return null;
|
|
6314
|
+
const shape = L.shapes.get(probed.type.shapeId);
|
|
6315
|
+
if (!shape || shape.tuple || shape.indexValue || shapeHasAccessorSlots(shape))
|
|
6316
|
+
return null;
|
|
6317
|
+
const loc = locOf(call);
|
|
6318
|
+
const receiver = L.lowerExpr(recvNode);
|
|
6319
|
+
if (receiver.type.kind !== "record")
|
|
6320
|
+
return null; // probe/lower drift: keep the fence
|
|
6321
|
+
let key = L.lowerExpr(keyNode);
|
|
6322
|
+
// Number/boolean/dyn keys stringify — ToPropertyKey, the keyed-write
|
|
6323
|
+
// path's rule; symbol and composite keys keep the fence.
|
|
6324
|
+
if (key.type.kind === "f64" || key.type.kind === "bool" || key.type.kind === "dyn") {
|
|
6325
|
+
key = { kind: "toString", operand: key, type: STRING, loc: locOf(keyNode) };
|
|
6326
|
+
}
|
|
6327
|
+
if (key.type.kind !== "string")
|
|
6328
|
+
return null;
|
|
6329
|
+
const helper = recordHasOwnHelper(L, receiver.type.shapeId, loc);
|
|
6330
|
+
return { kind: "call", callee: helper, args: [receiver, key], type: BOOL, loc };
|
|
6331
|
+
}
|
|
5560
6332
|
if (member !== "keys" && member !== "values" && member !== "entries")
|
|
5561
6333
|
return null;
|
|
5562
6334
|
if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0]))
|
|
@@ -6109,6 +6881,29 @@ export function bindingGenericFnNodeOf(decl) {
|
|
|
6109
6881
|
}
|
|
6110
6882
|
return null;
|
|
6111
6883
|
}
|
|
6884
|
+
/** The CONTEXTUAL twin of bindingGenericFnNodeOf: `const g: Mapper = (x)
|
|
6885
|
+
* => x` where `type Mapper = <T>(x: T) => T` — the initializer declares
|
|
6886
|
+
* no type parameters of its own, but the ANNOTATION's one call signature
|
|
6887
|
+
* does, and the checker types the arrow's parameters by those (`x: T`).
|
|
6888
|
+
* Such a binding monomorphizes exactly like `const g = <T>(x: T) => x`;
|
|
6889
|
+
* bindingGenericFnInfoOf reads the type parameters off the annotation's
|
|
6890
|
+
* signature. Null when the shape doesn't match (a concrete annotation, a
|
|
6891
|
+
* generic arrow — the syntactic probe's case, an overloaded alias). */
|
|
6892
|
+
export function bindingContextualGenericFnNodeOf(L, decl) {
|
|
6893
|
+
if (!ts.isIdentifier(decl.name) || decl.initializer === undefined || decl.type === undefined)
|
|
6894
|
+
return null;
|
|
6895
|
+
let init = decl.initializer;
|
|
6896
|
+
while (ts.isParenthesizedExpression(init))
|
|
6897
|
+
init = init.expression;
|
|
6898
|
+
if (!(ts.isArrowFunction(init) || ts.isFunctionExpression(init)) ||
|
|
6899
|
+
init.typeParameters !== undefined || init.body === undefined) {
|
|
6900
|
+
return null;
|
|
6901
|
+
}
|
|
6902
|
+
const sigs = L.checker.getCallSignatures(L.typeOf(decl.name));
|
|
6903
|
+
if (sigs.length !== 1 || sigs[0].getTypeParameters().length === 0)
|
|
6904
|
+
return null;
|
|
6905
|
+
return init;
|
|
6906
|
+
}
|
|
6112
6907
|
/** The interned GenericFnInfo for one generic arrow/function-expression
|
|
6113
6908
|
* binding initializer, with the supportability fences applied ONCE per
|
|
6114
6909
|
* declaration: the binding must sit at module scope (the compiled
|
|
@@ -6150,11 +6945,29 @@ export function bindingGenericFnInfoOf(L, decl, fnNode) {
|
|
|
6150
6945
|
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
6946
|
}
|
|
6152
6947
|
const typeParams = [];
|
|
6153
|
-
|
|
6154
|
-
const
|
|
6155
|
-
|
|
6948
|
+
if (fnNode.typeParameters !== undefined) {
|
|
6949
|
+
for (const tp of fnNode.typeParameters) {
|
|
6950
|
+
const tpSym = L.checker.getSymbolAtLocation(tp.name);
|
|
6951
|
+
if (!tpSym)
|
|
6952
|
+
L.unsupported("SC1090", fnNode, "this function form");
|
|
6953
|
+
typeParams.push(tpSym);
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
else {
|
|
6957
|
+
// The CONTEXTUAL shape (bindingContextualGenericFnNodeOf): the type
|
|
6958
|
+
// parameters live on the annotation's one call signature, and the
|
|
6959
|
+
// checker types the initializer's parameters by them — the same
|
|
6960
|
+
// symbols the instance bodies resolve through.
|
|
6961
|
+
const sigs = L.checker.getCallSignatures(L.typeOf(decl.name));
|
|
6962
|
+
const tps = sigs.length === 1 ? sigs[0].getTypeParameters() : [];
|
|
6963
|
+
if (tps.length === 0)
|
|
6156
6964
|
L.unsupported("SC1090", fnNode, "this function form");
|
|
6157
|
-
|
|
6965
|
+
for (const tp of tps) {
|
|
6966
|
+
const tpSym = tp.getSymbol();
|
|
6967
|
+
if (!tpSym)
|
|
6968
|
+
L.unsupported("SC1090", fnNode, "this function form");
|
|
6969
|
+
typeParams.push(tpSym);
|
|
6970
|
+
}
|
|
6158
6971
|
}
|
|
6159
6972
|
// Only NAME syntax is checkable here; optional/default/rest shapes are
|
|
6160
6973
|
// computed per instantiation from the resolved signature — exactly
|
|
@@ -6181,6 +6994,53 @@ export function bindingGenericFnInfoOf(L, decl, fnNode) {
|
|
|
6181
6994
|
}
|
|
6182
6995
|
return info;
|
|
6183
6996
|
}
|
|
6997
|
+
/** `const h = id` — a binding ALIASING a generic function (a top-level
|
|
6998
|
+
* declaration, a registered generic binding, or another alias — resolved
|
|
6999
|
+
* left to right in declaration order). The alias registers the SAME info
|
|
7000
|
+
* under its own symbol, so calls (`h(3)`) and pinned values (`take(h)`)
|
|
7001
|
+
* resolve exactly like the target's own name, and the binding itself has
|
|
7002
|
+
* no runtime value (a generic function value cannot materialize). Claims
|
|
7003
|
+
* only bindings whose OWN type still keeps type parameters — a
|
|
7004
|
+
* concrete-annotated alias (`const h: (x: number) => number = id`) is a
|
|
7005
|
+
* pinned VALUE, the existing lowerGenericFnValue story. Null when the
|
|
7006
|
+
* shape doesn't match or the target isn't a registered generic; fences
|
|
7007
|
+
* (reassignment, var redeclaration) report by name inside. */
|
|
7008
|
+
export function bindingGenericFnAliasInfoOf(L, decl) {
|
|
7009
|
+
if (!ts.isIdentifier(decl.name) || decl.initializer === undefined)
|
|
7010
|
+
return null;
|
|
7011
|
+
let init = decl.initializer;
|
|
7012
|
+
while (ts.isParenthesizedExpression(init))
|
|
7013
|
+
init = init.expression;
|
|
7014
|
+
if (!ts.isIdentifier(init))
|
|
7015
|
+
return null;
|
|
7016
|
+
const target = genericFnOf(L, init);
|
|
7017
|
+
if (!target)
|
|
7018
|
+
return null;
|
|
7019
|
+
// A concrete annotation pins one signature — that value story
|
|
7020
|
+
// (lowerGenericFnValue at the reference) stays untouched.
|
|
7021
|
+
const ownSigs = L.checker.getCallSignatures(L.typeOf(decl.name));
|
|
7022
|
+
if (ownSigs.length === 0 || !ownSigs.every((s) => s.getTypeParameters().length > 0))
|
|
7023
|
+
return null;
|
|
7024
|
+
const sym = L.checker.getSymbolAtLocation(decl.name);
|
|
7025
|
+
if (!sym)
|
|
7026
|
+
return null;
|
|
7027
|
+
const existing = L.genericFnsBySymbol.get(sym);
|
|
7028
|
+
if (existing)
|
|
7029
|
+
return existing;
|
|
7030
|
+
const name = decl.name.text;
|
|
7031
|
+
const isConst = (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0;
|
|
7032
|
+
// The same holds-it-forever discipline as generic arrow bindings:
|
|
7033
|
+
// calls through the alias resolve statically against the target, so
|
|
7034
|
+
// nothing may ever rebind it (merged `var` redeclarations included).
|
|
7035
|
+
const redeclared = L.checker
|
|
7036
|
+
.declarationsOf(sym)
|
|
7037
|
+
.some((d) => d !== decl && ts.isVariableDeclaration(d) && d.initializer !== undefined);
|
|
7038
|
+
if (!isConst && (redeclared || !bindingNeverReassigned(L, sym, decl))) {
|
|
7039
|
+
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)`);
|
|
7040
|
+
}
|
|
7041
|
+
L.genericFnsBySymbol.set(sym, target);
|
|
7042
|
+
return target;
|
|
7043
|
+
}
|
|
6184
7044
|
/** Static resolution stands in for the receiver's runtime value, so an
|
|
6185
7045
|
* object-literal generic-method receiver must provably HOLD the defining
|
|
6186
7046
|
* literal: a direct read of a binding whose initializer IS that literal
|
|
@@ -6225,10 +7085,56 @@ export function requireObjLitGenericReceiver(L, blame, recvExpr, literal, name)
|
|
|
6225
7085
|
* direct `call` of the instance with the receiver unevaluated. Claims
|
|
6226
7086
|
* every call whose member is generic-callable — lowering it or fencing
|
|
6227
7087
|
* with a named message. */
|
|
7088
|
+
/** URL.revokeObjectURL() with NO argument: Node's ERR_MISSING_ARGS
|
|
7089
|
+
* throws before the registry lookup, so the zero-argument contract is
|
|
7090
|
+
* exact without any blob machinery. The one-argument form (Node's
|
|
7091
|
+
* silent no-op for unregistered ids) and createObjectURL keep their
|
|
7092
|
+
* fences — a compiled program has no blob registry to consult. */
|
|
7093
|
+
function lowerUrlStaticCall(L, call, callee) {
|
|
7094
|
+
if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken !== undefined)
|
|
7095
|
+
return null;
|
|
7096
|
+
if (!ts.isIdentifier(callee.expression) || callee.expression.text !== "URL")
|
|
7097
|
+
return null;
|
|
7098
|
+
if (callee.name.text !== "revokeObjectURL" || call.arguments.length !== 0)
|
|
7099
|
+
return null;
|
|
7100
|
+
const sym = L.resolveValueSymbol(callee.expression);
|
|
7101
|
+
if (!sym || !L.isStdlibSymbol(sym))
|
|
7102
|
+
return null;
|
|
7103
|
+
return nodeThrowExpr(1, "ERR_MISSING_ARGS", 'The "url" argument must be specified', VOID, locOf(call));
|
|
7104
|
+
}
|
|
7105
|
+
const SP_BRAND_METHODS = new Set([
|
|
7106
|
+
"append", "delete", "get", "getAll", "has", "set", "sort",
|
|
7107
|
+
"forEach", "keys", "values", "entries", "toString",
|
|
7108
|
+
]);
|
|
6228
7109
|
export function lowerObjLitGenericMethodCall(L, call, access) {
|
|
6229
7110
|
if (L.chainBlocked(access, call))
|
|
6230
7111
|
return null;
|
|
6231
7112
|
const name = access.name.text;
|
|
7113
|
+
// URLSearchParams method values through Function.prototype.call/apply
|
|
7114
|
+
// with a receiver that is provably NOT a URLSearchParams (the suite's
|
|
7115
|
+
// `params.append.call(undefined)` probes): the WHATWG brand check
|
|
7116
|
+
// throws ERR_INVALID_THIS before any argument conversion — the whole
|
|
7117
|
+
// call IS that throw. A receiver that IS searchParams-typed, or one
|
|
7118
|
+
// whose runtime kind is unknowable (dyn/'any'), keeps the fence.
|
|
7119
|
+
if ((name === "call" || name === "apply") &&
|
|
7120
|
+
ts.isPropertyAccessExpression(access.expression) &&
|
|
7121
|
+
L.mapTypeOf(L.typeOf(access.expression.expression))?.kind === "searchParams" &&
|
|
7122
|
+
SP_BRAND_METHODS.has(access.expression.name.text) &&
|
|
7123
|
+
L.isStdlibMember(access.expression)) {
|
|
7124
|
+
const thisArg = call.arguments[0];
|
|
7125
|
+
const thisT = thisArg ? L.mapTypeOf(L.typeOf(thisArg)) : { kind: "undefinedT" };
|
|
7126
|
+
const provablyNot = thisT !== null &&
|
|
7127
|
+
thisT.kind !== "searchParams" &&
|
|
7128
|
+
thisT.kind !== "dyn" &&
|
|
7129
|
+
thisT.kind !== "jsval" &&
|
|
7130
|
+
(!thisArg || ts.isIdentifier(thisArg) || ts.isLiteralExpression(thisArg) ||
|
|
7131
|
+
thisArg.kind === ts.SyntaxKind.UndefinedKeyword ||
|
|
7132
|
+
thisArg.kind === ts.SyntaxKind.NullKeyword ||
|
|
7133
|
+
isUnitType(thisT));
|
|
7134
|
+
if (provablyNot) {
|
|
7135
|
+
return nodeThrowExpr(1, "ERR_INVALID_THIS", 'Value of "this" must be of type URLSearchParams', L.mapTypeOf(L.typeOf(call)) ?? VOID, locOf(call));
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
6232
7138
|
const recvT = L.typeOf(access.expression);
|
|
6233
7139
|
const propSym = L.checker.getPropertyOfType(recvT, name);
|
|
6234
7140
|
if (!propSym)
|
|
@@ -6241,8 +7147,36 @@ export function lowerObjLitGenericMethodCall(L, call, access) {
|
|
|
6241
7147
|
if (L.checker.declarationsOf(propSym).some((d) => d.parent !== undefined && (ts.isClassDeclaration(d.parent) || ts.isClassExpression(d.parent)))) {
|
|
6242
7148
|
return null;
|
|
6243
7149
|
}
|
|
7150
|
+
// An INTERFACE-typed receiver over a class instance (`const r: Repo =
|
|
7151
|
+
// new MemRepo(); r.get(...)` — the declaration is signature-only, but
|
|
7152
|
+
// the receiver's exact class is statically proven and the binding
|
|
7153
|
+
// kept the class representation, genericIfaceBindingKeepsClass): the
|
|
7154
|
+
// call is a class generic-method call on that exact class. The
|
|
7155
|
+
// receiver must LOWER as the class — a record-held value (a `let`, a
|
|
7156
|
+
// produced value, a parameter) has already dropped it, and keeps the
|
|
7157
|
+
// named fence below.
|
|
7158
|
+
{
|
|
7159
|
+
const exact = exactInstanceClassOf(L, access.expression);
|
|
7160
|
+
const gfound = exact ? findGenericMethodOn(L, exact, name) : null;
|
|
7161
|
+
if (gfound && ts.isIdentifier(access.expression)) {
|
|
7162
|
+
const recv = L.lowerExpr(access.expression); // identifier reads are pure — no double evaluation
|
|
7163
|
+
if (recv.type.kind === "object") {
|
|
7164
|
+
return lowerClassGenericMethodCall(L, call, access, exact, gfound, recv);
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
6244
7168
|
const found = objLitGenericFnNodeOf(L, propSym);
|
|
6245
7169
|
if (!found) {
|
|
7170
|
+
// Interface-declared generic methods dispatch statically, so the
|
|
7171
|
+
// receiver's runtime class must be provable — name that discipline
|
|
7172
|
+
// instead of the object-literal wording when the method lives on an
|
|
7173
|
+
// interface.
|
|
7174
|
+
const onInterface = L.checker
|
|
7175
|
+
.declarationsOf(propSym)
|
|
7176
|
+
.some((d) => d.parent !== undefined && ts.isInterfaceDeclaration(d.parent));
|
|
7177
|
+
if (onInterface) {
|
|
7178
|
+
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(...)')`);
|
|
7179
|
+
}
|
|
6246
7180
|
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
7181
|
}
|
|
6248
7182
|
requireObjLitGenericReceiver(L, call, access.expression, found.literal, name);
|