@scriptc/compiler 0.0.8 → 0.0.10
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 +244 -17
- package/dist/backend/emission/emit-exprs.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 +52 -12
- 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 +30 -9
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +1 -0
- package/dist/backend/llvm/dyn.js +83 -1
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +357 -20
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +73 -14
- package/dist/diagnostics/diagnostic.js +112 -31
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.js +8 -0
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/frontend/lowering/lib-boundary.js +11 -1
- package/dist/frontend/lowering/lib-boundary.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 +24 -0
- package/dist/frontend/lowering/lower-builtins.js +256 -3
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +71 -11
- package/dist/frontend/lowering/lower-calls.js +942 -18
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +27 -1
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +21 -2
- package/dist/frontend/lowering/lower-containers.js +386 -53
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +92 -17
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +54 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +231 -17
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +33 -2
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-namespaces.d.ts +25 -11
- package/dist/frontend/lowering/lower-namespaces.js +50 -15
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +211 -4
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +1 -5
- package/dist/frontend/lowering/lower-stmts.js +660 -19
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +92 -5
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +102 -15
- package/dist/frontend/lowering/lowerer.js +378 -30
- 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 +13 -0
- package/dist/frontend/types.js +88 -9
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.js +169 -32
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +230 -12
- package/dist/ir/nodes.js +85 -6
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +109 -9
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.d.ts +77 -0
- package/dist/library/fence-eval.js +385 -0
- package/dist/library/fence-eval.js.map +1 -0
- package/dist/library/profile.d.ts +11 -1
- package/dist/library/profile.js +102 -18
- package/dist/library/profile.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +10 -3
|
@@ -7,14 +7,14 @@ 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
19
|
import { EMITTER_API_MEMBERS, exactInstanceClassOf, findGenericMethodOn, lowerClassGenericMethodCall, lowerStaticMethodCall } from "./lower-classes.js";
|
|
20
20
|
import { emitterRooted, lowerEmitterMethodCall } from "./lower-emitter.js";
|
|
@@ -2273,6 +2273,49 @@ export function lowerTimersMemberCall(L, expr, member, loc) {
|
|
|
2273
2273
|
}
|
|
2274
2274
|
export function lowerCall(L, expr) {
|
|
2275
2275
|
const loc = locOf(expr);
|
|
2276
|
+
// A call whose chain ROOTS at an ambient-undefined name (`declare
|
|
2277
|
+
// const value: Y | undefined; value?.foo("a")`, `declare function
|
|
2278
|
+
// chain...; chain(o).mapValues(f).value()`, a trap binding's read):
|
|
2279
|
+
// Node evaluates the root FIRST and throws the catchable
|
|
2280
|
+
// ReferenceError before any member, type argument, or argument runs —
|
|
2281
|
+
// the whole call IS that throw, typed by the use site (arguments
|
|
2282
|
+
// never lower; Node never evaluates them). Claimed before every
|
|
2283
|
+
// intrinsic and dispatch path: no lowering can answer differently
|
|
2284
|
+
// when the root read itself is the crash.
|
|
2285
|
+
{
|
|
2286
|
+
const root = ambientUndefVarRootOf(L, expr);
|
|
2287
|
+
if (root !== null) {
|
|
2288
|
+
const mapped = L.mapTypeOf(L.typeOf(expr));
|
|
2289
|
+
const t = mapped && mapped.kind !== "void" && !L.typeNamesUnregisteredClass(mapped)
|
|
2290
|
+
? mapped
|
|
2291
|
+
: (contextualUndefReadType(L, expr) ?? F64);
|
|
2292
|
+
return nsUndefRead(L, root.text, expr, t);
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
// A method call through a NULLISH binding (`const i: I<A & B> = null
|
|
2296
|
+
// as any; i.fn(...)` — the receiver provably holds null/undefined
|
|
2297
|
+
// forever): the member READ throws Node's exact TypeError before any
|
|
2298
|
+
// argument evaluates — the whole call lowers to that throw. Claimed
|
|
2299
|
+
// when the receiver's type has no mapping (no other story exists) or
|
|
2300
|
+
// the member is a generic signature (the alternative is the
|
|
2301
|
+
// interface-dispatch fence — the runtime truth is this throw).
|
|
2302
|
+
if (ts.isPropertyAccessExpression(expr.expression) &&
|
|
2303
|
+
ts.isIdentifier(expr.expression.expression) &&
|
|
2304
|
+
expr.expression.questionDotToken === undefined) {
|
|
2305
|
+
const recvSym = L.resolveValueSymbol(expr.expression.expression);
|
|
2306
|
+
const unit = nullishValueUnitOf(L, recvSym);
|
|
2307
|
+
if (unit !== null) {
|
|
2308
|
+
const recvUnmappable = recvSym !== null && L.mapTypeOf(L.checker.getTypeOfSymbol(recvSym)) === null;
|
|
2309
|
+
const propSym = L.checker.getPropertyOfType(L.typeOf(expr.expression.expression), expr.expression.name.text);
|
|
2310
|
+
const genericMember = propSym !== undefined && propSym !== null &&
|
|
2311
|
+
isGenericCallableMemberType(L.checker.getTypeOfSymbol(propSym), L.checker);
|
|
2312
|
+
if (recvUnmappable || genericMember) {
|
|
2313
|
+
const mapped = L.mapTypeOf(L.typeOf(expr));
|
|
2314
|
+
const t = mapped && mapped.kind !== "void" && !L.typeNamesUnregisteredClass(mapped) ? mapped : F64;
|
|
2315
|
+
return nodeThrowExpr(1, "", `Cannot read properties of ${unit} (reading '${expr.expression.name.text}')`, t, loc);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2276
2319
|
// `process.getuid?.()` — intercepted BEFORE the optional-chain
|
|
2277
2320
|
// machinery (the member always exists on a POSIX target, so the
|
|
2278
2321
|
// optional call IS the call; `process.getuid` itself has no value
|
|
@@ -2685,12 +2728,37 @@ export function lowerCall(L, expr) {
|
|
|
2685
2728
|
return islandPrimitiveExit(L, expr, result);
|
|
2686
2729
|
}
|
|
2687
2730
|
if (L.isIslandExpr(expr.expression)) {
|
|
2731
|
+
// `o.m(...)` where o LOWERS checked-dynamic and the checker types
|
|
2732
|
+
// `o.m` 'any' (a member read behind an 'object'/'unknown'-typed
|
|
2733
|
+
// bag): METHOD-CALL semantics — receiver-kind dispatch (dynInvoke),
|
|
2734
|
+
// so `this` binds and a WRAPPED island receiver runs the ENGINE's
|
|
2735
|
+
// own method (the routed-ops lane). A stored-member dynCall would
|
|
2736
|
+
// call engine prototype methods receiverless — the this-less
|
|
2737
|
+
// `list.slice()` ToObject TypeError. Spread arguments keep the
|
|
2738
|
+
// stored-member path below (the runtime-arity lane owns them).
|
|
2739
|
+
if (ts.isPropertyAccessExpression(expr.expression) &&
|
|
2740
|
+
!expr.expression.questionDotToken &&
|
|
2741
|
+
!expr.questionDotToken &&
|
|
2742
|
+
!expr.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
2743
|
+
const recvProbe = probeLower(L, expr.expression.expression);
|
|
2744
|
+
if (recvProbe?.type.kind === "dyn") {
|
|
2745
|
+
const args = expr.arguments.map((a) => L.lowerExprExpecting(a, DYN));
|
|
2746
|
+
return {
|
|
2747
|
+
kind: "dynInvoke",
|
|
2748
|
+
recv: recvProbe,
|
|
2749
|
+
method: expr.expression.name.text,
|
|
2750
|
+
calleeName: expr.expression.getText(),
|
|
2751
|
+
args,
|
|
2752
|
+
type: DYN,
|
|
2753
|
+
loc,
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2688
2757
|
const callee = L.lowerExpr(expr.expression);
|
|
2689
2758
|
// A checker-`any` callee that LOWERED checked-dynamic (a dyn member
|
|
2690
2759
|
// chain's stored function): the DOM's own call — dynCall reads and
|
|
2691
2760
|
// calls the stored member with Node's is-not-a-function TypeError
|
|
2692
|
-
// on refusal.
|
|
2693
|
-
// dynCall rule (no jsval→DOM bridge exists — the named fence).
|
|
2761
|
+
// on refusal.
|
|
2694
2762
|
if (callee.type.kind === "dyn") {
|
|
2695
2763
|
const args = expr.arguments.map((a) => L.lowerExprExpecting(a, DYN));
|
|
2696
2764
|
const calleeName = ts.isPropertyAccessExpression(expr.expression)
|
|
@@ -2804,6 +2872,15 @@ export function lowerCall(L, expr) {
|
|
|
2804
2872
|
const streamServed = lowerStreamModuleCall(L, expr, bi, loc);
|
|
2805
2873
|
if (streamServed)
|
|
2806
2874
|
return streamServed;
|
|
2875
|
+
const fsTs = L.lowerFsToUnixTimestampCall(expr, bi, loc);
|
|
2876
|
+
if (fsTs)
|
|
2877
|
+
return fsTs;
|
|
2878
|
+
// The fs validation-ladder spoke (checked-dynamic lane): misuse
|
|
2879
|
+
// of implemented-namespace members throws Node's typed errors
|
|
2880
|
+
// instead of meeting the table fence.
|
|
2881
|
+
const fsLadder = L.lowerFsLadderCall(expr, bi, loc);
|
|
2882
|
+
if (fsLadder)
|
|
2883
|
+
return fsLadder;
|
|
2807
2884
|
const builtinFn = builtinModuleFnOf(L, bi.module, bi.member);
|
|
2808
2885
|
if (!builtinFn) {
|
|
2809
2886
|
// Typed by @types/node (the fallback declarations only declare
|
|
@@ -3033,15 +3110,24 @@ export function lowerCall(L, expr) {
|
|
|
3033
3110
|
// closure object, plain C call). Generic functions route through
|
|
3034
3111
|
// monomorphization (the call targets a per-instantiation instance).
|
|
3035
3112
|
if (ts.isIdentifier(expr.expression) && !L.isSelfReference(expr.expression)) {
|
|
3113
|
+
// A JS spread argument the compile-time completion cannot take (a
|
|
3114
|
+
// fixed position, a dynamic rest slot) sends the call down the
|
|
3115
|
+
// VALUE path — the runtime-arity lane (lowerSpreadArgsCall) boxes
|
|
3116
|
+
// the declaration's value and applies through a runtime-built
|
|
3117
|
+
// argument list. Typed .ts spreads keep completeArgs' rest packing.
|
|
3118
|
+
const jsSpreadArgs = expr.arguments.some((a) => ts.isSpreadElement(a)) && isJsSourceFile(expr.getSourceFile());
|
|
3036
3119
|
if (L.isTopLevelFnSymbol(expr.expression) && !L.peekLocal(expr.expression)) {
|
|
3037
3120
|
// `import g = N.f; g()` — the alias's own source-order guards
|
|
3038
3121
|
// (a no-op for every non-import= binding).
|
|
3039
3122
|
fenceEarlyAliasUse(L, expr.expression, expr);
|
|
3040
3123
|
const generic = L.genericFnOf(expr.expression);
|
|
3041
|
-
|
|
3124
|
+
// An implicit-any JS function spread-forwarded into: per-site
|
|
3125
|
+
// monomorphization has no slot for a runtime-length argument
|
|
3126
|
+
// list — the value path's boxed thunk delivers JS arity instead.
|
|
3127
|
+
if (generic && !(jsSpreadArgs && generic.implicitParams))
|
|
3042
3128
|
return L.lowerGenericCall(expr, generic);
|
|
3043
|
-
const sig = L.fnSigOf(expr.expression);
|
|
3044
|
-
if (sig) {
|
|
3129
|
+
const sig = generic ? null : L.fnSigOf(expr.expression);
|
|
3130
|
+
if (sig && !(jsSpreadArgs && spreadNeedsRuntimeArity(sig.params, expr.arguments))) {
|
|
3045
3131
|
L.noteEdge(sig.name);
|
|
3046
3132
|
const args = L.completeArgs(expr.arguments, sig.params, loc, expr);
|
|
3047
3133
|
return reconcileOverloadReturn(L, expr, { kind: "call", callee: sig.name, args, type: sig.returnType, loc });
|
|
@@ -3065,10 +3151,12 @@ export function lowerCall(L, expr) {
|
|
|
3065
3151
|
// the call monomorphizes against it exactly like a generic function
|
|
3066
3152
|
// declaration and the binding is never read. Symbol identity does
|
|
3067
3153
|
// the discrimination — a shadowing local has its own symbol, and
|
|
3068
|
-
// registered bindings never declare locals or globals.
|
|
3154
|
+
// registered bindings never declare locals or globals. Implicit-any
|
|
3155
|
+
// JS bindings spread-forwarded into skip to the value path (the
|
|
3156
|
+
// runtime-arity lane), like the declaration form above.
|
|
3069
3157
|
{
|
|
3070
3158
|
const generic = L.genericFnOf(expr.expression);
|
|
3071
|
-
if (generic)
|
|
3159
|
+
if (generic && !(jsSpreadArgs && generic.implicitParams))
|
|
3072
3160
|
return L.lowerGenericCall(expr, generic);
|
|
3073
3161
|
}
|
|
3074
3162
|
}
|
|
@@ -3277,6 +3365,9 @@ export function lowerCall(L, expr) {
|
|
|
3277
3365
|
// island path (bytes never cross the boundary).
|
|
3278
3366
|
L.lowerBytesMethodCall(expr, expr.expression) ??
|
|
3279
3367
|
L.lowerBufferStaticCall(expr, expr.expression) ??
|
|
3368
|
+
// URL.revokeObjectURL's zero-argument contract (the one-argument
|
|
3369
|
+
// form keeps the fence — createObjectURL does too).
|
|
3370
|
+
lowerUrlStaticCall(L, expr, expr.expression) ??
|
|
3280
3371
|
// Readable.from — the stream classes' one static (before the
|
|
3281
3372
|
// stdlib chokepoint claims the member).
|
|
3282
3373
|
lowerStreamStaticCall(L, expr, expr.expression) ??
|
|
@@ -3312,6 +3403,9 @@ export function lowerCall(L, expr) {
|
|
|
3312
3403
|
lowerSymbolStaticCall(L, expr, expr.expression) ??
|
|
3313
3404
|
lowerSymbolMethodCall(L, expr, expr.expression) ??
|
|
3314
3405
|
lowerRegExpStaticCall(L, expr, expr.expression) ??
|
|
3406
|
+
// The composed en-US Intl.NumberFormat form — before the member
|
|
3407
|
+
// fences (the receiver's Intl.NumberFormat type has no mapping).
|
|
3408
|
+
lowerIntlNumberFormatCall(L, expr, expr.expression) ??
|
|
3315
3409
|
lowerGroupByStaticCall(L, expr, expr.expression) ??
|
|
3316
3410
|
// Iterator-helper chains rooted at arr.values() — before the
|
|
3317
3411
|
// array method paths (the terminal names collide with array
|
|
@@ -3420,6 +3514,12 @@ export function lowerCall(L, expr) {
|
|
|
3420
3514
|
// any-origin value). Spread arguments keep their fence.
|
|
3421
3515
|
if (callee.type.kind === "dyn") {
|
|
3422
3516
|
if (expr.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
3517
|
+
// The runtime-arity lane: a dyn callee is already boxed — the
|
|
3518
|
+
// spread-marked dynCall applies through a fresh DOM argument
|
|
3519
|
+
// array (lowerSpreadArgsCall). Sources outside it keep the fence.
|
|
3520
|
+
const spreadServed = lowerSpreadArgsCall(L, expr, callee, loc);
|
|
3521
|
+
if (spreadServed)
|
|
3522
|
+
return spreadServed;
|
|
3423
3523
|
L.unsupported("SC1090", expr, "spread arguments in calls through 'unknown' values");
|
|
3424
3524
|
}
|
|
3425
3525
|
const args = expr.arguments.map((a) => L.lowerExprExpecting(a, DYN));
|
|
@@ -3433,6 +3533,42 @@ export function lowerCall(L, expr) {
|
|
|
3433
3533
|
if (callee.type.kind !== "func") {
|
|
3434
3534
|
L.badType(expr.expression, L.typeOf(expr.expression));
|
|
3435
3535
|
}
|
|
3536
|
+
// A SPREAD argument on a func-typed callee — the rest-forwarding
|
|
3537
|
+
// idiom (`(...args) => from(...args)`): the runtime-arity lane boxes
|
|
3538
|
+
// or marshals the callee and applies through a runtime-built argument
|
|
3539
|
+
// list (lowerSpreadArgsCall). Shapes outside its lanes fall through
|
|
3540
|
+
// to the historical fences.
|
|
3541
|
+
if (expr.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
3542
|
+
const spreadServed = lowerSpreadArgsCall(L, expr, callee, loc);
|
|
3543
|
+
if (spreadServed)
|
|
3544
|
+
return spreadServed;
|
|
3545
|
+
}
|
|
3546
|
+
// An ISLAND-REST func value called directly (`f(1, 2)` where f is the
|
|
3547
|
+
// --dynamic `(...args) =>` lambda): the type SPELLS its trailing
|
|
3548
|
+
// engine-array param — complete the call exactly like completeArgs'
|
|
3549
|
+
// island pack (fixed slots positionally, missing ones with the
|
|
3550
|
+
// engine's undefined, the surplus marshaled into one fresh engine
|
|
3551
|
+
// array). JS arity, no runtime machinery.
|
|
3552
|
+
if (callee.type.rest === true &&
|
|
3553
|
+
callee.type.restAbi === "jsval" &&
|
|
3554
|
+
callee.type.params.length >= 1 &&
|
|
3555
|
+
callee.type.params[callee.type.params.length - 1].kind === "jsval" &&
|
|
3556
|
+
!expr.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
3557
|
+
const fixed = callee.type.params.slice(0, -1);
|
|
3558
|
+
const args = fixed.map((p, i) => {
|
|
3559
|
+
const a = expr.arguments[i];
|
|
3560
|
+
if (a)
|
|
3561
|
+
return L.lowerExprExpecting(a, p);
|
|
3562
|
+
const absent = omittedArgFor(L, p, loc);
|
|
3563
|
+
if (!absent) {
|
|
3564
|
+
L.unsupported("SC1090", expr, "calls omitting a non-optional parameter of the callee's type");
|
|
3565
|
+
}
|
|
3566
|
+
return absent;
|
|
3567
|
+
});
|
|
3568
|
+
const restArgs = expr.arguments.slice(fixed.length).map((a) => L.lowerExprExpecting(a, JSVAL));
|
|
3569
|
+
args.push({ kind: "jsOp", op: "arrLit", args: restArgs, type: JSVAL, loc });
|
|
3570
|
+
return { kind: "callValue", callee, args, type: callee.type.ret, loc };
|
|
3571
|
+
}
|
|
3436
3572
|
// A JS call with MORE arguments than the callee's lowered signature
|
|
3437
3573
|
// (`cb(1, 'x')` where the mustCall wrapper's inferred type declared
|
|
3438
3574
|
// fewer params — tsc's JS world doesn't police arity): ride the
|
|
@@ -3470,6 +3606,101 @@ export function lowerCall(L, expr) {
|
|
|
3470
3606
|
}
|
|
3471
3607
|
return { kind: "callValue", callee, args, type: callee.type.ret, loc };
|
|
3472
3608
|
}
|
|
3609
|
+
/** The RUNTIME-ARITY spread call — `f(...args)`, the rest-forwarding idiom
|
|
3610
|
+
* (`const f = (...args) => from(...args)`): a spread whose length is a
|
|
3611
|
+
* runtime fact has no home in the compile-time completion, so the call
|
|
3612
|
+
* rides a dynamic boundary instead. Two lanes, picked by the spread
|
|
3613
|
+
* source's tier:
|
|
3614
|
+
*
|
|
3615
|
+
* - CHECKED-DYNAMIC (dyn spread sources — a JS rest binding, a dyn
|
|
3616
|
+
* value): box the callee (dynFrom; a dyn callee is already boxed),
|
|
3617
|
+
* convert every argument into dyn, and emit the spread-marked dynCall
|
|
3618
|
+
* — the emitters build one fresh DOM argument array (spreads flatten
|
|
3619
|
+
* left-to-right, non-iterables throw V8's TypeError) and apply through
|
|
3620
|
+
* it; the boxed thunk delivers JS arity exactly. Result dyn, checked
|
|
3621
|
+
* per use like every any-origin value.
|
|
3622
|
+
* - ISLAND (a jsval spread source — the --dynamic rest binding is the
|
|
3623
|
+
* engine's own arguments array): marshal the callee in and emit jsOp
|
|
3624
|
+
* callSpread — the prelude helper's REAL `f(...pre, ...s)`, so
|
|
3625
|
+
* iterator protocols and the not-iterable TypeError are the engine's
|
|
3626
|
+
* own. One trailing spread after the fixed arguments is the modeled
|
|
3627
|
+
* shape (exactly the forwarding idiom).
|
|
3628
|
+
*
|
|
3629
|
+
* Answers null when neither lane fits (typed .ts spreads keep
|
|
3630
|
+
* completeArgs' rest packing and its fences). JS sources only — the
|
|
3631
|
+
* same guard as the over-arity dynCall precedent. */
|
|
3632
|
+
export function lowerSpreadArgsCall(L, expr, callee, loc) {
|
|
3633
|
+
if (!isJsSourceFile(expr.getSourceFile()))
|
|
3634
|
+
return null;
|
|
3635
|
+
if (!expr.arguments.some((a) => ts.isSpreadElement(a)))
|
|
3636
|
+
return null;
|
|
3637
|
+
if (callee.type.kind !== "dyn" && callee.type.kind !== "func" && callee.type.kind !== "jsval")
|
|
3638
|
+
return null;
|
|
3639
|
+
const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression)
|
|
3640
|
+
? expr.expression.getText()
|
|
3641
|
+
: ts.isIdentifier(expr.expression)
|
|
3642
|
+
? expr.expression.text
|
|
3643
|
+
: "value";
|
|
3644
|
+
// Lower every argument ONCE, in source order (the IR nests them in
|
|
3645
|
+
// exactly this order, so runtime evaluation order is JS's).
|
|
3646
|
+
const parts = expr.arguments.map((a) => ts.isSpreadElement(a)
|
|
3647
|
+
? { spreadOf: a.expression, node: null, v: L.lowerExpr(a.expression) }
|
|
3648
|
+
: { spreadOf: null, node: a, v: L.lowerExpr(a) });
|
|
3649
|
+
const spreadParts = parts.filter((p) => p.spreadOf !== null);
|
|
3650
|
+
const getR = (id) => L.shapes.get(id);
|
|
3651
|
+
const getU = (id) => L.unions.get(id);
|
|
3652
|
+
const anyJsvalSpread = spreadParts.some((p) => p.v.type.kind === "jsval");
|
|
3653
|
+
if (!anyJsvalSpread &&
|
|
3654
|
+
(callee.type.kind === "dyn" || (callee.type.kind === "func" && canBoxFuncIntoDyn(callee.type, getR, getU))) &&
|
|
3655
|
+
spreadParts.every((p) => p.v.type.kind === "dyn" || canConvertToDyn(p.v.type, getR, getU))) {
|
|
3656
|
+
const args = [];
|
|
3657
|
+
const spreads = [];
|
|
3658
|
+
for (const p of parts) {
|
|
3659
|
+
if (p.spreadOf !== null) {
|
|
3660
|
+
// The spelling rides along for V8's nullish spread-call
|
|
3661
|
+
// TypeError ("v is not iterable (cannot read property ...)").
|
|
3662
|
+
spreads.push({ arg: args.length, what: p.spreadOf.getText() });
|
|
3663
|
+
args.push(L.coerceInto(p.spreadOf, p.v, DYN));
|
|
3664
|
+
}
|
|
3665
|
+
else {
|
|
3666
|
+
args.push(L.coerceInto(p.node, p.v, DYN));
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
const boxed = callee.type.kind === "dyn" ? callee : L.coerceInto(expr.expression, callee, DYN);
|
|
3670
|
+
return { kind: "dynCall", callee: boxed, calleeName, args, spreads, type: DYN, loc };
|
|
3671
|
+
}
|
|
3672
|
+
if (spreadParts.length > 0 &&
|
|
3673
|
+
spreadParts.every((p) => p.v.type.kind === "jsval") &&
|
|
3674
|
+
(callee.type.kind === "jsval" || callee.type.kind === "func")) {
|
|
3675
|
+
if (spreadParts.length !== 1 || parts[parts.length - 1].spreadOf === null) {
|
|
3676
|
+
L.unsupported("SC1090", expr, "spread arguments before positional arguments in island calls (one trailing spread after the fixed arguments is the supported form)");
|
|
3677
|
+
}
|
|
3678
|
+
const f = L.coerceInto(expr.expression, callee, JSVAL);
|
|
3679
|
+
const pre = parts
|
|
3680
|
+
.slice(0, -1)
|
|
3681
|
+
.map((p) => L.coerceInto(p.node, p.v, JSVAL));
|
|
3682
|
+
const preArr = { kind: "jsOp", op: "arrLit", args: pre, type: JSVAL, loc };
|
|
3683
|
+
const last = parts[parts.length - 1];
|
|
3684
|
+
// The spelling rides in `name` for V8's nullish spread-call
|
|
3685
|
+
// TypeError ("v is not iterable (cannot read property ...)").
|
|
3686
|
+
return { kind: "jsOp", op: "callSpread", name: last.spreadOf.getText(), args: [f, preArr, last.v], type: JSVAL, loc };
|
|
3687
|
+
}
|
|
3688
|
+
// No lane fits (a spread source outside both tiers, a callee neither
|
|
3689
|
+
// boxable nor marshalable, mixed dyn/jsval spreads): fence HERE — the
|
|
3690
|
+
// arguments are already lowered, and falling back to the historical
|
|
3691
|
+
// per-site fences would lower them a second time (duplicate lambda
|
|
3692
|
+
// lifts, duplicated diagnostics).
|
|
3693
|
+
L.unsupported("SC1090", expr, "spread arguments");
|
|
3694
|
+
}
|
|
3695
|
+
/** True when a spread argument lands where the compile-time completion
|
|
3696
|
+
* cannot take it — a FIXED parameter position, or a dynamic rest slot
|
|
3697
|
+
* (dynRest/islandRest, whose packs are built per-argument): the shapes
|
|
3698
|
+
* the runtime-arity lane (lowerSpreadArgsCall) serves. Typed `rest`
|
|
3699
|
+
* slots keep completeArgs' same-element spread packing. */
|
|
3700
|
+
export function spreadNeedsRuntimeArity(shapes, argNodes) {
|
|
3701
|
+
const restAt = shapes.findIndex((s) => s.mode === "rest" || s.mode === "dynRest" || s.mode === "islandRest");
|
|
3702
|
+
return argNodes.some((a, i) => ts.isSpreadElement(a) && (restAt < 0 || i < restAt || shapes[restAt].mode !== "rest"));
|
|
3703
|
+
}
|
|
3473
3704
|
/** METHOD calls on dyn receivers (`pkg.name.replace(...)`, `rawName.split`,
|
|
3474
3705
|
* `ws.packages.filter(...)` — JSON.parse-derived values): validate the
|
|
3475
3706
|
* receiver's DOM kind, extract, and ride the STATIC method machinery — the
|
|
@@ -3547,10 +3778,17 @@ function lowerDynReceiverMethodCall(L, call, access) {
|
|
|
3547
3778
|
const enc = call.arguments[0]
|
|
3548
3779
|
? bufEncoding(L, "toString", call.arguments[0])
|
|
3549
3780
|
: "utf8";
|
|
3781
|
+
// The source spelling rides along for the ONE receiver whose
|
|
3782
|
+
// prototype lacks toString: a null-prototype dictionary throws
|
|
3783
|
+
// Node's "<spelling> is not a function" at runtime.
|
|
3550
3784
|
return {
|
|
3551
3785
|
kind: "libCall",
|
|
3552
3786
|
fn: "dyn.toString",
|
|
3553
|
-
args: [
|
|
3787
|
+
args: [
|
|
3788
|
+
recv,
|
|
3789
|
+
{ kind: "strLit", value: enc, type: STRING, loc: locOf(call) },
|
|
3790
|
+
{ kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) },
|
|
3791
|
+
],
|
|
3554
3792
|
type: STRING,
|
|
3555
3793
|
loc: locOf(call),
|
|
3556
3794
|
};
|
|
@@ -4008,6 +4246,32 @@ export function lowerPrimitiveProtoCall(L, call, recv, name, memberSym) {
|
|
|
4008
4246
|
const fn = name === "toExponential" ? "num.toExponential" : "num.toFixed0";
|
|
4009
4247
|
return { kind: "libCall", fn, args: [operand], type: STRING, loc };
|
|
4010
4248
|
}
|
|
4249
|
+
// Number.prototype.toLocaleString("en-US") — the spec makes it
|
|
4250
|
+
// NumberFormat(locale).format(this), so the en-US embedded formatter
|
|
4251
|
+
// answers exactly. The unlowered forms fence by NAME: no locale (the
|
|
4252
|
+
// host environment's default, which a compiled binary cannot carry),
|
|
4253
|
+
// other locales (ICU data the binary does not embed), options bags.
|
|
4254
|
+
if (name === "toLocaleString" && recvKind === "f64") {
|
|
4255
|
+
if (call.arguments.length === 0) {
|
|
4256
|
+
L.noLowering("Number.prototype.toLocaleString without a locale", call, "the default locale is the host environment's, which a compiled binary cannot carry — " +
|
|
4257
|
+
'pass it explicitly: x.toLocaleString("en-US")');
|
|
4258
|
+
}
|
|
4259
|
+
if (call.arguments.length > 1) {
|
|
4260
|
+
L.noLowering("Number.prototype.toLocaleString with an options bag", call, "the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction " +
|
|
4261
|
+
'digits, grouping) — x.toLocaleString("en-US")');
|
|
4262
|
+
}
|
|
4263
|
+
const locNode = call.arguments[0];
|
|
4264
|
+
if (ts.isSpreadElement(locNode) || !ts.isStringLiteralLike(locNode) || locNode.text !== "en-US") {
|
|
4265
|
+
L.noLowering(!ts.isSpreadElement(locNode) && ts.isStringLiteralLike(locNode)
|
|
4266
|
+
? `Number.prototype.toLocaleString at locale "${locNode.text}"`
|
|
4267
|
+
: "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 — ' +
|
|
4268
|
+
"everything else is ICU data the binary does not carry");
|
|
4269
|
+
}
|
|
4270
|
+
const operand = L.lowerExpr(recv);
|
|
4271
|
+
if (operand.type.kind !== "f64")
|
|
4272
|
+
return null;
|
|
4273
|
+
return { kind: "libCall", fn: "intl.numFormatEnUs", args: [operand], type: STRING, loc };
|
|
4274
|
+
}
|
|
4011
4275
|
if (name === "charAt" && recvKind === "string" && call.arguments.length === 1 &&
|
|
4012
4276
|
!ts.isSpreadElement(call.arguments[0])) {
|
|
4013
4277
|
const receiver = L.lowerExpr(recv);
|
|
@@ -4602,6 +4866,15 @@ export function lowerLambda(L, node) {
|
|
|
4602
4866
|
const first = captured[0];
|
|
4603
4867
|
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? node.getSourceFile(), first.loc.start);
|
|
4604
4868
|
const params = funcType.params.map((t, i) => ({ localId: `%pf${i}`, name: `%pf${i}`, type: t }));
|
|
4869
|
+
// A REST-MARKED value type hides one synthetic trailing DOM-array
|
|
4870
|
+
// param in the lifted function (the boxed call thunk fills it) —
|
|
4871
|
+
// the fence lambda must spell that slot too or the validator's
|
|
4872
|
+
// closure-signature check trips (SC9001). Island rest types SPELL
|
|
4873
|
+
// their trailing engine-array param, so funcType.params already
|
|
4874
|
+
// covers those.
|
|
4875
|
+
if (funcType.rest === true && funcType.restAbi !== "jsval") {
|
|
4876
|
+
params.push({ localId: "%pfrest", name: "%pfrest", type: DYN });
|
|
4877
|
+
}
|
|
4605
4878
|
const lifted = {
|
|
4606
4879
|
name: fnName,
|
|
4607
4880
|
params,
|
|
@@ -5782,12 +6055,235 @@ function lowerRegExpStaticCall(L, call, access) {
|
|
|
5782
6055
|
L.badType(call.arguments[0], L.typeOf(call.arguments[0]));
|
|
5783
6056
|
return { kind: "libCall", fn: "regexp.escape", args: [arg], type: STRING, loc: locOf(call) };
|
|
5784
6057
|
}
|
|
6058
|
+
/** The composed en-US Intl.NumberFormat form: `new Intl.NumberFormat(
|
|
6059
|
+
* "en-US").format(x)` (and the callable spelling without `new` — the
|
|
6060
|
+
* spec makes them the same formatter). Only the COMPOSED form lowers —
|
|
6061
|
+
* formatter values have no representation — and only the one locale
|
|
6062
|
+
* whose data the runtime embeds, with default options: decimal
|
|
6063
|
+
* notation, 0–3 fraction digits rounded half-up on the shortest
|
|
6064
|
+
* round-tripping decimal (ICU's rounding input — format(1.0005) is
|
|
6065
|
+
* "1.001" though toFixed(3) answers "1.000"), "," grouping. The
|
|
6066
|
+
* unlowered forms fence by NAME (no locale — the host environment's
|
|
6067
|
+
* default, which a compiled binary cannot carry; other locales — ICU
|
|
6068
|
+
* data the binary does not embed; options bags; non-number arguments).
|
|
6069
|
+
* Null when the callee isn't a NumberFormat-construction .format. */
|
|
6070
|
+
function lowerIntlNumberFormatCall(L, call, access) {
|
|
6071
|
+
if (call.questionDotToken || access.questionDotToken)
|
|
6072
|
+
return null;
|
|
6073
|
+
if (access.name.text !== "format")
|
|
6074
|
+
return null;
|
|
6075
|
+
let recv = access.expression;
|
|
6076
|
+
while (ts.isParenthesizedExpression(recv))
|
|
6077
|
+
recv = recv.expression;
|
|
6078
|
+
let ctorArgs;
|
|
6079
|
+
if (ts.isNewExpression(recv) || (ts.isCallExpression(recv) && !recv.questionDotToken)) {
|
|
6080
|
+
const ctor = recv.expression;
|
|
6081
|
+
if (!ts.isPropertyAccessExpression(ctor) || ctor.questionDotToken ||
|
|
6082
|
+
ctor.name.text !== "NumberFormat" || !L.isStdlibGlobal(ctor.expression, "Intl")) {
|
|
6083
|
+
return null;
|
|
6084
|
+
}
|
|
6085
|
+
ctorArgs = recv.arguments ?? [];
|
|
6086
|
+
}
|
|
6087
|
+
else {
|
|
6088
|
+
return null;
|
|
6089
|
+
}
|
|
6090
|
+
const loc = locOf(call);
|
|
6091
|
+
if (ctorArgs.length === 0) {
|
|
6092
|
+
L.noLowering("Intl.NumberFormat without a locale", recv, "the default locale is the host environment's, which a compiled binary cannot carry — " +
|
|
6093
|
+
'pass it explicitly: new Intl.NumberFormat("en-US").format(x)');
|
|
6094
|
+
}
|
|
6095
|
+
if (ctorArgs.length > 1) {
|
|
6096
|
+
L.noLowering("Intl.NumberFormat with an options bag", ctorArgs[1], "the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction digits, " +
|
|
6097
|
+
'grouping) — new Intl.NumberFormat("en-US").format(x)');
|
|
6098
|
+
}
|
|
6099
|
+
const locArg = ctorArgs[0];
|
|
6100
|
+
if (ts.isSpreadElement(locArg) || !ts.isStringLiteralLike(locArg) || locArg.text !== "en-US") {
|
|
6101
|
+
L.noLowering(!ts.isSpreadElement(locArg) && ts.isStringLiteralLike(locArg)
|
|
6102
|
+
? `Intl.NumberFormat at locale "${locArg.text}"`
|
|
6103
|
+
: "Intl.NumberFormat with a non-literal locale", locArg, '"en-US" (Node\'s default-build locale) is the one locale whose data the runtime embeds — ' +
|
|
6104
|
+
"everything else is ICU data the binary does not carry");
|
|
6105
|
+
}
|
|
6106
|
+
if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0])) {
|
|
6107
|
+
L.noLowering(`Intl.NumberFormat("en-US").format with ${call.arguments.length} arguments`, call);
|
|
6108
|
+
}
|
|
6109
|
+
const argNode = call.arguments[0];
|
|
6110
|
+
if (L.mapTypeOf(L.typeOf(argNode))?.kind !== "f64") {
|
|
6111
|
+
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)");
|
|
6112
|
+
}
|
|
6113
|
+
const arg = L.lowerExprExpecting(argNode, F64);
|
|
6114
|
+
if (arg.type.kind !== "f64")
|
|
6115
|
+
L.badType(argNode, L.typeOf(argNode));
|
|
6116
|
+
return { kind: "libCall", fn: "intl.numFormatEnUs", args: [arg], type: STRING, loc };
|
|
6117
|
+
}
|
|
6118
|
+
/** Object.is over statically disjoint kinds: the constant false, with
|
|
6119
|
+
* both operands still evaluated for their effects (droppable statics
|
|
6120
|
+
* fold away — JS evaluates arguments, but nothing observes a pure one). */
|
|
6121
|
+
function objectIsDisjointFalse(left, right, loc) {
|
|
6122
|
+
const stmts = [];
|
|
6123
|
+
for (const e of [left, right]) {
|
|
6124
|
+
if (!droppableStatic(e))
|
|
6125
|
+
stmts.push({ kind: "exprStmt", expr: e, loc });
|
|
6126
|
+
}
|
|
6127
|
+
const answer = { kind: "boolLit", value: false, type: BOOL, loc };
|
|
6128
|
+
if (stmts.length === 0)
|
|
6129
|
+
return answer;
|
|
6130
|
+
return { kind: "seqExpr", stmts, result: answer, type: BOOL, loc };
|
|
6131
|
+
}
|
|
5785
6132
|
function lowerObjectStaticCall(L, call, access) {
|
|
5786
6133
|
if (call.questionDotToken || access.questionDotToken)
|
|
5787
6134
|
return null;
|
|
5788
6135
|
if (!L.isStdlibGlobal(access.expression, "Object"))
|
|
5789
6136
|
return null;
|
|
5790
6137
|
const member = access.name.text;
|
|
6138
|
+
// Object.is — the spec's SameValue over the static kinds. Number
|
|
6139
|
+
// pairs take the runtime SameValue (NaN equals NaN, +0 differs from
|
|
6140
|
+
// -0 — the two divergences from ===); every other supported pair
|
|
6141
|
+
// rides exactly the strict-equality machinery, whose answers
|
|
6142
|
+
// SameValue shares: strings by bytes, bools by value, unit literals
|
|
6143
|
+
// by tag, unions per arm (a number arm's payload compare upgrades to
|
|
6144
|
+
// SameValue via unionEq's flag), and the reference kinds by pointer
|
|
6145
|
+
// identity. Statically DISJOINT kind pairs answer the constant false
|
|
6146
|
+
// with the operands still evaluated (tsc admits any pair — Object.is
|
|
6147
|
+
// is (any, any) — and JS evaluates the arguments either way).
|
|
6148
|
+
// dyn/jsval operands keep strict equality's stance: validate first.
|
|
6149
|
+
if (member === "is") {
|
|
6150
|
+
if (call.arguments.length !== 2 || call.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
6151
|
+
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)");
|
|
6152
|
+
}
|
|
6153
|
+
const loc = locOf(call);
|
|
6154
|
+
const leftNode = call.arguments[0];
|
|
6155
|
+
const rightNode = call.arguments[1];
|
|
6156
|
+
const left = L.lowerExpr(leftNode);
|
|
6157
|
+
const right = L.lowerExpr(rightNode);
|
|
6158
|
+
const lk = left.type.kind;
|
|
6159
|
+
const rk = right.type.kind;
|
|
6160
|
+
if (lk === "f64" && rk === "f64") {
|
|
6161
|
+
return { kind: "libCall", fn: "num.sameValue", args: [left, right], type: BOOL, loc };
|
|
6162
|
+
}
|
|
6163
|
+
if (left.type.kind === "string" && right.type.kind === "string") {
|
|
6164
|
+
return { kind: "strEq", negated: false, left, right, type: BOOL, loc };
|
|
6165
|
+
}
|
|
6166
|
+
if (lk === "bool" && rk === "bool") {
|
|
6167
|
+
return { kind: "bin", op: "===", left, right, type: BOOL, loc };
|
|
6168
|
+
}
|
|
6169
|
+
const unitTest = L.lowerUnitComparison(left, right, false, loc);
|
|
6170
|
+
if (unitTest)
|
|
6171
|
+
return unitTest;
|
|
6172
|
+
if (lk === "dyn" || rk === "dyn" || lk === "jsval" || rk === "jsval") {
|
|
6173
|
+
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)");
|
|
6174
|
+
}
|
|
6175
|
+
if (left.type.kind === "union" || right.type.kind === "union") {
|
|
6176
|
+
const ut = left.type.kind === "union" ? left.type : right.type;
|
|
6177
|
+
const bothUnion = left.type.kind === "union" && right.type.kind === "union";
|
|
6178
|
+
const sameUnion = bothUnion && typeEquals(left.type, right.type);
|
|
6179
|
+
if ((sameUnion || !bothUnion) && L.eqComparableUnion(ut.unionId)) {
|
|
6180
|
+
const plain = left.type.kind === "union" ? right : left;
|
|
6181
|
+
const arms = L.unions.get(ut.unionId)?.arms ?? [];
|
|
6182
|
+
// The plain side wraps into the union exactly like === when the
|
|
6183
|
+
// union holds its type; a plain PRIMITIVE the union has no arm
|
|
6184
|
+
// for is the disjoint constant false (coercing it would strand).
|
|
6185
|
+
if (bothUnion || arms.some((a) => typeEquals(a, plain.type))) {
|
|
6186
|
+
const sameValue = arms.some((a) => a.kind === "f64");
|
|
6187
|
+
return {
|
|
6188
|
+
kind: "unionEq",
|
|
6189
|
+
unionId: ut.unionId,
|
|
6190
|
+
negated: false,
|
|
6191
|
+
sameValue,
|
|
6192
|
+
left: L.coerceInto(leftNode, left, ut),
|
|
6193
|
+
right: L.coerceInto(rightNode, right, ut),
|
|
6194
|
+
type: BOOL,
|
|
6195
|
+
loc,
|
|
6196
|
+
};
|
|
6197
|
+
}
|
|
6198
|
+
if (plain.type.kind === "f64" || plain.type.kind === "string" ||
|
|
6199
|
+
plain.type.kind === "bool" || isUnitType(plain.type)) {
|
|
6200
|
+
return objectIsDisjointFalse(left, right, loc);
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
L.noLowering("Object.is over these union operands", call, `union-typed comparisons need one comparable shape (${NARROW_FIRST})`);
|
|
6204
|
+
}
|
|
6205
|
+
// Reference kinds: pointer identity — exactly strict equality
|
|
6206
|
+
// (hierarchy-related classes widen the derived side first).
|
|
6207
|
+
let idLeft = left;
|
|
6208
|
+
let idRight = right;
|
|
6209
|
+
if (left.type.kind === "object" && right.type.kind === "object") {
|
|
6210
|
+
if (L.isSubclassOf(left.type.className, right.type.className)) {
|
|
6211
|
+
idLeft = L.upcastTo(left, right.type.className);
|
|
6212
|
+
}
|
|
6213
|
+
else if (L.isSubclassOf(right.type.className, left.type.className)) {
|
|
6214
|
+
idRight = L.upcastTo(right, left.type.className);
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
if ((idLeft.type.kind === "func" && idRight.type.kind === "func") ||
|
|
6218
|
+
(idLeft.type.kind === "classval" && idRight.type.kind === "classval")) {
|
|
6219
|
+
return { kind: "bin", op: "===", left: idLeft, right: idRight, type: BOOL, loc };
|
|
6220
|
+
}
|
|
6221
|
+
if ((idLeft.type.kind === "array" || idLeft.type.kind === "map" ||
|
|
6222
|
+
idLeft.type.kind === "set" || idLeft.type.kind === "object" ||
|
|
6223
|
+
idLeft.type.kind === "record" || idLeft.type.kind === "symbol" ||
|
|
6224
|
+
idLeft.type.kind === "bytes" || idLeft.type.kind === "promise") &&
|
|
6225
|
+
typeEquals(idLeft.type, idRight.type)) {
|
|
6226
|
+
return { kind: "bin", op: "===", left: idLeft, right: idRight, type: BOOL, loc };
|
|
6227
|
+
}
|
|
6228
|
+
// Statically disjoint pairs with a primitive/unit side: SameValue
|
|
6229
|
+
// never crosses kinds, so the answer is the constant false.
|
|
6230
|
+
const disjoint = new Set(["f64", "string", "bool", "undefinedT", "nullT"]);
|
|
6231
|
+
if (lk !== rk && (disjoint.has(lk) || disjoint.has(rk))) {
|
|
6232
|
+
return objectIsDisjointFalse(left, right, loc);
|
|
6233
|
+
}
|
|
6234
|
+
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)");
|
|
6235
|
+
}
|
|
6236
|
+
// Object.create — the null-prototype DICTIONARY (`Object.create(null)`
|
|
6237
|
+
// then keyed assignment, the memo-table idiom prettier's index/
|
|
6238
|
+
// group-mode maps spell) and, under --dynamic, the engine's own
|
|
6239
|
+
// Object.create for engine-held prototypes. Everything else is a
|
|
6240
|
+
// NAMED fence: the compiled representations have no prototype chain,
|
|
6241
|
+
// and the own-copy stand-in would answer WRONG observably — Node's
|
|
6242
|
+
// Object.keys/inspect/JSON of the created object list NO own keys,
|
|
6243
|
+
// and mutating the prototype afterwards is visible through the
|
|
6244
|
+
// created object (live delegation), which no copy can honor.
|
|
6245
|
+
if (member === "create") {
|
|
6246
|
+
if (call.arguments.some((a) => ts.isSpreadElement(a))) {
|
|
6247
|
+
L.noLowering("Object.create with spread arguments", call);
|
|
6248
|
+
}
|
|
6249
|
+
if (call.arguments.length >= 2) {
|
|
6250
|
+
L.noLowering("Object.create with a properties-descriptor argument", call, "create first, then assign: const o = Object.create(null); o.k = v");
|
|
6251
|
+
}
|
|
6252
|
+
if (call.arguments.length !== 1) {
|
|
6253
|
+
L.noLowering(`Object.create with ${call.arguments.length} arguments`, call);
|
|
6254
|
+
}
|
|
6255
|
+
const loc = locOf(call);
|
|
6256
|
+
let protoNode = call.arguments[0];
|
|
6257
|
+
while (ts.isParenthesizedExpression(protoNode))
|
|
6258
|
+
protoNode = protoNode.expression;
|
|
6259
|
+
const nullProto = protoNode.kind === ts.SyntaxKind.NullKeyword;
|
|
6260
|
+
if (L.dynamic) {
|
|
6261
|
+
// The checker types the result `any` — an ENGINE value under
|
|
6262
|
+
// --dynamic — and the engine's own Object.create answers with
|
|
6263
|
+
// REAL prototype semantics: reads delegate LIVE, writes shadow,
|
|
6264
|
+
// and inspect renders Node's exact shapes ("[Object: null
|
|
6265
|
+
// prototype]" included). null and engine-held (jsval) prototypes
|
|
6266
|
+
// route; checked-dynamic (DOM) prototypes keep the named fence —
|
|
6267
|
+
// their marshal into the engine is a DEEP COPY, so a later
|
|
6268
|
+
// prototype mutation would be invisible through the created
|
|
6269
|
+
// object where Node delegates live.
|
|
6270
|
+
const objectGlobal = () => ({ kind: "jsOp", op: "globalGet", name: "Object", args: [], type: JSVAL, loc });
|
|
6271
|
+
if (nullProto) {
|
|
6272
|
+
const nullIn = { kind: "jsOp", op: "nullLit", args: [], type: JSVAL, loc };
|
|
6273
|
+
return { kind: "jsOp", op: "callMethod", name: "create", args: [objectGlobal(), nullIn], type: JSVAL, loc };
|
|
6274
|
+
}
|
|
6275
|
+
const proto = L.lowerExpr(protoNode);
|
|
6276
|
+
if (proto.type.kind === "jsval") {
|
|
6277
|
+
return { kind: "jsOp", op: "callMethod", name: "create", args: [objectGlobal(), proto], type: JSVAL, loc };
|
|
6278
|
+
}
|
|
6279
|
+
L.noLowering(`Object.create over '${L.fmt(proto.type)}' prototypes`, call, "prototype reads delegate LIVE in Node (mutating the prototype shows through the created object), which the boundary's deep copy cannot honor — only null and engine-held ('any') prototypes lower");
|
|
6280
|
+
}
|
|
6281
|
+
if (nullProto) {
|
|
6282
|
+
return { kind: "libCall", fn: "dyn.objCreateNullProto", args: [], type: DYN, loc };
|
|
6283
|
+
}
|
|
6284
|
+
const proto = L.lowerExpr(protoNode);
|
|
6285
|
+
L.noLowering(`Object.create over '${L.fmt(proto.type)}' prototypes`, call, "the compiled representations have no prototype chain, and an own-copy would answer wrong observably (Node lists NO own keys on the created object, and prototype mutations show through it live) — only Object.create(null) lowers");
|
|
6286
|
+
}
|
|
5791
6287
|
// `Object.assign(fn, { props })` whose RESULT type maps to the hybrid
|
|
5792
6288
|
// (function-with-properties) record: the chalk-shape CONSTRUCTOR.
|
|
5793
6289
|
if (member === "assign") {
|
|
@@ -5864,6 +6360,91 @@ function lowerObjectStaticCall(L, call, access) {
|
|
|
5864
6360
|
}
|
|
5865
6361
|
}
|
|
5866
6362
|
}
|
|
6363
|
+
// `Object.assign(target, ...sources)` over a CHECKED-DYNAMIC target
|
|
6364
|
+
// — the n-ary/spread form (`Object.assign({}, ...plugins.map(p =>
|
|
6365
|
+
// p.options), coreOptions)`, support.js's option-table merge). The
|
|
6366
|
+
// sources pack into one fresh DOM array FIRST — plain sources
|
|
6367
|
+
// retain in, spread sources flatten through the spread-call walk
|
|
6368
|
+
// (V8's exact TypeError texts, the source spelling carried for the
|
|
6369
|
+
// nullish form) — so every source evaluates and flattens before any
|
|
6370
|
+
// copying (JS's ArgumentListEvaluation: a throwing spread leaves
|
|
6371
|
+
// the target untouched), then one runtime walk copies each source's
|
|
6372
|
+
// own enumerable keys left to right and answers the TARGET
|
|
6373
|
+
// (identity, like JS). Each source must enter the dyn world (dyn
|
|
6374
|
+
// already, or dynFrom's JSON-safe conversion — a STATIC array
|
|
6375
|
+
// spread copies in at the boundary, the documented aliasing
|
|
6376
|
+
// stance); anything else keeps the fence. Targets: dyn values, a
|
|
6377
|
+
// FRESH object-literal target (`Object.assign({}, ...)` — no alias
|
|
6378
|
+
// exists, so building it as a DOM object instead of a record is
|
|
6379
|
+
// unobservable), or a nullish unit (Node's ToObject TypeError
|
|
6380
|
+
// throws at the call, catchably); aliased record targets keep the
|
|
6381
|
+
// fence — their identity could not survive the conversion.
|
|
6382
|
+
if (call.arguments.length >= 1 && !ts.isSpreadElement(call.arguments[0])) {
|
|
6383
|
+
let targetNode = call.arguments[0];
|
|
6384
|
+
while (ts.isParenthesizedExpression(targetNode))
|
|
6385
|
+
targetNode = targetNode.expression;
|
|
6386
|
+
const freshLiteralTarget = ts.isObjectLiteralExpression(targetNode);
|
|
6387
|
+
const tProbe = freshLiteralTarget ? null : probeLower(L, call.arguments[0]);
|
|
6388
|
+
const tKind = tProbe?.type.kind;
|
|
6389
|
+
if (freshLiteralTarget || tKind === "dyn" || tKind === "nullT" || tKind === "undefinedT") {
|
|
6390
|
+
const loc = locOf(call);
|
|
6391
|
+
const target = L.lowerExprExpecting(call.arguments[0], DYN);
|
|
6392
|
+
if (target.type.kind === "dyn") {
|
|
6393
|
+
const t = L.declareHiddenLocal("%oat", DYN);
|
|
6394
|
+
const p = L.declareHiddenLocal("%oap", DYN);
|
|
6395
|
+
const tRef = () => ({ kind: "varRef", localId: t.id, type: DYN, loc });
|
|
6396
|
+
const pRef = () => ({ kind: "varRef", localId: p.id, type: DYN, loc });
|
|
6397
|
+
const stmts = [
|
|
6398
|
+
{ kind: "varDecl", localId: t.id, init: target, loc },
|
|
6399
|
+
{ kind: "varDecl", localId: p.id, init: { kind: "dynArrLit", elems: [], type: DYN, loc }, loc },
|
|
6400
|
+
];
|
|
6401
|
+
// V8 spells the optimized apply-path texts (the expression
|
|
6402
|
+
// named for a nullish source) only when the spread is the
|
|
6403
|
+
// SINGLE LAST argument; every other spread position drives
|
|
6404
|
+
// the real iterator protocol, whose failure describes the
|
|
6405
|
+
// value — the two runtime variants, picked here by position.
|
|
6406
|
+
const sources = call.arguments.slice(1);
|
|
6407
|
+
const spreadCount = sources.filter((a) => ts.isSpreadElement(a)).length;
|
|
6408
|
+
let ok = true;
|
|
6409
|
+
for (let i = 0; i < sources.length; i++) {
|
|
6410
|
+
const argNode = sources[i];
|
|
6411
|
+
const spread = ts.isSpreadElement(argNode);
|
|
6412
|
+
const srcNode = spread ? argNode.expression : argNode;
|
|
6413
|
+
const src = L.coerceToExpected(L.lowerExpr(srcNode), DYN);
|
|
6414
|
+
if (src.type.kind !== "dyn") {
|
|
6415
|
+
ok = false;
|
|
6416
|
+
break;
|
|
6417
|
+
}
|
|
6418
|
+
const argLoc = locOf(argNode);
|
|
6419
|
+
const optimized = spreadCount === 1 && i === sources.length - 1;
|
|
6420
|
+
stmts.push({
|
|
6421
|
+
kind: "exprStmt",
|
|
6422
|
+
expr: spread
|
|
6423
|
+
? optimized
|
|
6424
|
+
? {
|
|
6425
|
+
kind: "libCall",
|
|
6426
|
+
fn: "dyn.packPushSpread",
|
|
6427
|
+
args: [pRef(), src, { kind: "strLit", value: srcNode.getText(), type: STRING, loc: argLoc }],
|
|
6428
|
+
type: VOID,
|
|
6429
|
+
loc: argLoc,
|
|
6430
|
+
}
|
|
6431
|
+
: { kind: "libCall", fn: "dyn.packPushSpreadIter", args: [pRef(), src], type: VOID, loc: argLoc }
|
|
6432
|
+
: { kind: "libCall", fn: "dyn.packPush", args: [pRef(), src], type: VOID, loc: argLoc },
|
|
6433
|
+
loc: argLoc,
|
|
6434
|
+
});
|
|
6435
|
+
}
|
|
6436
|
+
if (ok) {
|
|
6437
|
+
return {
|
|
6438
|
+
kind: "seqExpr",
|
|
6439
|
+
stmts,
|
|
6440
|
+
result: { kind: "libCall", fn: "dyn.assignAll", args: [tRef(), pRef()], type: DYN, loc },
|
|
6441
|
+
type: DYN,
|
|
6442
|
+
loc,
|
|
6443
|
+
};
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
}
|
|
6447
|
+
}
|
|
5867
6448
|
return null;
|
|
5868
6449
|
}
|
|
5869
6450
|
// Object.defineProperties over a CHECKED-DYNAMIC target (test/common's
|
|
@@ -6500,6 +7081,280 @@ export function bindingNeverReassigned(L, sym, decl) {
|
|
|
6500
7081
|
L.neverReassignedCache.set(sym, !written);
|
|
6501
7082
|
return !written;
|
|
6502
7083
|
}
|
|
7084
|
+
/** Strips the value-preserving wrappers off an expression: parens,
|
|
7085
|
+
* non-null assertions, `as`/`satisfies`/angle-bracket casts. What
|
|
7086
|
+
* remains is the expression that actually evaluates. */
|
|
7087
|
+
export function stripValueWrappers(e) {
|
|
7088
|
+
let v = e;
|
|
7089
|
+
for (;;) {
|
|
7090
|
+
if (ts.isParenthesizedExpression(v) || ts.isNonNullExpression(v) ||
|
|
7091
|
+
ts.isAsExpression(v) || ts.isSatisfiesExpression(v) || ts.isTypeAssertion(v)) {
|
|
7092
|
+
v = v.expression;
|
|
7093
|
+
continue;
|
|
7094
|
+
}
|
|
7095
|
+
return v;
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
7098
|
+
/** The nullish unit `e` provably evaluates to: a bare null/undefined
|
|
7099
|
+
* literal (assertion-wrapped — `null as any`, `null!`), or a read of a
|
|
7100
|
+
* registered NULLISH binding (nullishGenericBindingUnitOf). Null when
|
|
7101
|
+
* the value could be anything else. */
|
|
7102
|
+
export function nullishExprUnitOf(L, e) {
|
|
7103
|
+
const v = stripValueWrappers(e);
|
|
7104
|
+
if (v.kind === ts.SyntaxKind.NullKeyword)
|
|
7105
|
+
return "null";
|
|
7106
|
+
if (ts.isIdentifier(v)) {
|
|
7107
|
+
if (v.text === "undefined" && (L.typeOf(v).flags & ts.TypeFlags.Undefined) !== 0) {
|
|
7108
|
+
return "undefined";
|
|
7109
|
+
}
|
|
7110
|
+
return nullishValueUnitOf(L, L.resolveValueSymbol(v));
|
|
7111
|
+
}
|
|
7112
|
+
return null;
|
|
7113
|
+
}
|
|
7114
|
+
/** The nullish unit a binding provably holds FOREVER, by VALUE alone: its
|
|
7115
|
+
* initializer is nullish (`const i: I<A & B> = null as any`) and every
|
|
7116
|
+
* write in its declaring file is nullish too (`a = b` where b is
|
|
7117
|
+
* another nullish binding). No type condition — callers add their own
|
|
7118
|
+
* (nullishGenericBindingUnitOf gates the no-storage family on
|
|
7119
|
+
* unmappable types; the generic-method call path rescues its fence with
|
|
7120
|
+
* the value fact alone). Cached per symbol; the pre-seeded null entry
|
|
7121
|
+
* guards probe cycles (mutually-assigned bindings resolve link by link,
|
|
7122
|
+
* declaration order). */
|
|
7123
|
+
export function nullishValueUnitOf(L, sym) {
|
|
7124
|
+
if (!sym)
|
|
7125
|
+
return null;
|
|
7126
|
+
const cached = L.nullishBindings.get(sym);
|
|
7127
|
+
if (cached !== undefined)
|
|
7128
|
+
return cached;
|
|
7129
|
+
L.nullishBindings.set(sym, null); // cycle guard: self-referential probes answer non-qualifying
|
|
7130
|
+
const decl = L.checker.valueDeclarationOf(sym);
|
|
7131
|
+
if (!decl || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name) ||
|
|
7132
|
+
decl.getSourceFile().isDeclarationFile || decl.initializer === undefined) {
|
|
7133
|
+
return null;
|
|
7134
|
+
}
|
|
7135
|
+
const unit = nullishExprUnitOf(L, decl.initializer);
|
|
7136
|
+
if (unit === null)
|
|
7137
|
+
return null;
|
|
7138
|
+
// Bindings with a CHECKED-DYNAMIC fallback (`const maybe: any =
|
|
7139
|
+
// undefined`, JS inference residue) keep that story: the dyn world
|
|
7140
|
+
// already holds null/undefined correctly and serves every read form
|
|
7141
|
+
// (optional chains included) — this family exists for types with NO
|
|
7142
|
+
// other home.
|
|
7143
|
+
if (dynFallbackType(L, decl.name, L.checker.getTypeOfSymbol(sym)) !== null)
|
|
7144
|
+
return null;
|
|
7145
|
+
if (!allWritesNullish(L, sym, decl))
|
|
7146
|
+
return null;
|
|
7147
|
+
// A use inside a class HERITAGE clause (`class X extends Mixin(...)`)
|
|
7148
|
+
// declines the whole family: heritage resolution is structural (the
|
|
7149
|
+
// mixin machinery can pin the instantiation from the ARGUMENT class
|
|
7150
|
+
// expression without ever reading the callee binding), so a claimed
|
|
7151
|
+
// nullish callee would compile a working class where Node throws
|
|
7152
|
+
// "Mixin is not a function" evaluating the extends expression. The
|
|
7153
|
+
// declaration keeps its type fence instead.
|
|
7154
|
+
if (usedInHeritageClause(L, sym))
|
|
7155
|
+
return null;
|
|
7156
|
+
L.nullishBindings.set(sym, unit);
|
|
7157
|
+
return unit;
|
|
7158
|
+
}
|
|
7159
|
+
/** True when any identifier resolving to `sym` sits inside a class
|
|
7160
|
+
* heritage clause anywhere in the program. */
|
|
7161
|
+
function usedInHeritageClause(L, sym) {
|
|
7162
|
+
const symText = sym.name;
|
|
7163
|
+
let found = false;
|
|
7164
|
+
const visit = (n) => {
|
|
7165
|
+
if (found)
|
|
7166
|
+
return;
|
|
7167
|
+
if (ts.isIdentifier(n) && n.text === symText &&
|
|
7168
|
+
L.resolveValueSymbol(n) === sym) {
|
|
7169
|
+
for (let p = n.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
|
|
7170
|
+
if (ts.isHeritageClause(p)) {
|
|
7171
|
+
found = true;
|
|
7172
|
+
return;
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
return;
|
|
7176
|
+
}
|
|
7177
|
+
n.forEachChild(visit);
|
|
7178
|
+
};
|
|
7179
|
+
for (const file of L.program.getSourceFiles()) {
|
|
7180
|
+
if (found)
|
|
7181
|
+
break;
|
|
7182
|
+
if (file.isDeclarationFile)
|
|
7183
|
+
continue;
|
|
7184
|
+
file.forEachChild(visit);
|
|
7185
|
+
}
|
|
7186
|
+
return found;
|
|
7187
|
+
}
|
|
7188
|
+
/** nullishValueUnitOf gated on a declared type that CANNOT hold the
|
|
7189
|
+
* value — the NO-STORAGE family: an unmappable type has no other story,
|
|
7190
|
+
* and a RECORD-mapped one (`const i: I<A & B> = null as any` — an
|
|
7191
|
+
* interface whose members are all generic signatures interns an empty
|
|
7192
|
+
* shape) has a slot null can never inhabit, so storing would throw the
|
|
7193
|
+
* representation error where Node stores null silently. Either way the
|
|
7194
|
+
* declaration emits nothing and reads know the value. Null-tolerant
|
|
7195
|
+
* mappings (unions with a null/undefined arm, dyn) keep their real
|
|
7196
|
+
* storage and every ordinary lowering. */
|
|
7197
|
+
export function nullishGenericBindingUnitOf(L, sym) {
|
|
7198
|
+
if (!sym)
|
|
7199
|
+
return null;
|
|
7200
|
+
// The VALUE probe first — it is purely syntactic, so no checker type
|
|
7201
|
+
// query runs for the overwhelmingly common non-nullish declarations
|
|
7202
|
+
// (a query can even panic upstream — the 1e999 checker bug).
|
|
7203
|
+
const unit = nullishValueUnitOf(L, sym);
|
|
7204
|
+
if (unit === null)
|
|
7205
|
+
return null;
|
|
7206
|
+
const mapped = L.mapTypeOf(L.checker.getTypeOfSymbol(sym));
|
|
7207
|
+
if (mapped !== null && mapped.kind !== "record")
|
|
7208
|
+
return null;
|
|
7209
|
+
return unit;
|
|
7210
|
+
}
|
|
7211
|
+
/** True when every write of `sym` in its declaring file is a plain `x =
|
|
7212
|
+
* <nullish>` assignment — the discipline that keeps a nullish binding's
|
|
7213
|
+
* value knowable. Compound assignments, ++/--, for-in/of cursors, and
|
|
7214
|
+
* destructuring targets all disqualify. */
|
|
7215
|
+
function allWritesNullish(L, sym, decl) {
|
|
7216
|
+
const symText = sym.name;
|
|
7217
|
+
const namesSym = (e) => ts.isIdentifier(e) && e.text === symText && L.resolveValueSymbol(e) === sym;
|
|
7218
|
+
let ok = true;
|
|
7219
|
+
const visit = (n) => {
|
|
7220
|
+
if (!ok)
|
|
7221
|
+
return;
|
|
7222
|
+
if (ts.isBinaryExpression(n)) {
|
|
7223
|
+
const k = n.operatorToken.kind;
|
|
7224
|
+
if (k >= ts.SyntaxKind.FirstAssignment && k <= ts.SyntaxKind.LastAssignment) {
|
|
7225
|
+
let lhs = n.left;
|
|
7226
|
+
while (ts.isParenthesizedExpression(lhs))
|
|
7227
|
+
lhs = lhs.expression;
|
|
7228
|
+
if (namesSym(lhs)) {
|
|
7229
|
+
if (k !== ts.SyntaxKind.EqualsToken || nullishExprUnitOf(L, n.right) === null)
|
|
7230
|
+
ok = false;
|
|
7231
|
+
}
|
|
7232
|
+
else if (ts.isArrayLiteralExpression(lhs) || ts.isObjectLiteralExpression(lhs)) {
|
|
7233
|
+
const walk = (m) => {
|
|
7234
|
+
if (namesSym(m))
|
|
7235
|
+
ok = false;
|
|
7236
|
+
else
|
|
7237
|
+
m.forEachChild(walk);
|
|
7238
|
+
};
|
|
7239
|
+
walk(lhs);
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
}
|
|
7243
|
+
else if ((ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n)) &&
|
|
7244
|
+
(n.operator === ts.SyntaxKind.PlusPlusToken || n.operator === ts.SyntaxKind.MinusMinusToken)) {
|
|
7245
|
+
let op = n.operand;
|
|
7246
|
+
while (ts.isParenthesizedExpression(op))
|
|
7247
|
+
op = op.expression;
|
|
7248
|
+
if (namesSym(op))
|
|
7249
|
+
ok = false;
|
|
7250
|
+
}
|
|
7251
|
+
else if ((ts.isForOfStatement(n) || ts.isForInStatement(n)) && !ts.isVariableDeclarationList(n.initializer)) {
|
|
7252
|
+
let t = n.initializer;
|
|
7253
|
+
while (ts.isParenthesizedExpression(t))
|
|
7254
|
+
t = t.expression;
|
|
7255
|
+
if (namesSym(t))
|
|
7256
|
+
ok = false;
|
|
7257
|
+
}
|
|
7258
|
+
n.forEachChild(visit);
|
|
7259
|
+
};
|
|
7260
|
+
decl.getSourceFile().forEachChild(visit);
|
|
7261
|
+
return ok;
|
|
7262
|
+
}
|
|
7263
|
+
/** A VALUE-ONLY expression: materializing it has no observable effect
|
|
7264
|
+
* beyond the value itself — function/arrow literals, class-free
|
|
7265
|
+
* literals, nullish units. The dead-binding rule's purity test: Node
|
|
7266
|
+
* builds the value and drops it, so skipping the build entirely is
|
|
7267
|
+
* unobservable. Bare identifier reads stay OUT (a read above a `let`
|
|
7268
|
+
* declaration is a TDZ throw Node WOULD serve). */
|
|
7269
|
+
function sideEffectFreeValueExpr(L, e) {
|
|
7270
|
+
const v = stripValueWrappers(e);
|
|
7271
|
+
if (ts.isArrowFunction(v) || ts.isFunctionExpression(v))
|
|
7272
|
+
return true;
|
|
7273
|
+
if (ts.isLiteralExpression(v) || v.kind === ts.SyntaxKind.NullKeyword ||
|
|
7274
|
+
v.kind === ts.SyntaxKind.TrueKeyword || v.kind === ts.SyntaxKind.FalseKeyword) {
|
|
7275
|
+
return true;
|
|
7276
|
+
}
|
|
7277
|
+
if (ts.isIdentifier(v) && v.text === "undefined" && (L.typeOf(v).flags & ts.TypeFlags.Undefined) !== 0) {
|
|
7278
|
+
return true;
|
|
7279
|
+
}
|
|
7280
|
+
return false;
|
|
7281
|
+
}
|
|
7282
|
+
/** True when `sym` — a binding whose type has NO static mapping — is DEAD:
|
|
7283
|
+
* never read anywhere in the program, not exported through a specifier,
|
|
7284
|
+
* declared with no initializer or a side-effect-free one, and written
|
|
7285
|
+
* (if at all) only by plain assignments of side-effect-free values. Node
|
|
7286
|
+
* materializes those values and drops them — zero observable effect —
|
|
7287
|
+
* so the declaration and its writes lower to NOTHING instead of fencing
|
|
7288
|
+
* on a type the program never consumes (`var xs2: typeof Array;`, the
|
|
7289
|
+
* write-only `var f2: { <T, U>(x: T, y: U): T }`). TS program files
|
|
7290
|
+
* only: JS bindings keep their checked-dynamic fallbacks. Positive
|
|
7291
|
+
* answers register in L.deadBindings (the assignment lowering skips
|
|
7292
|
+
* writes by the same set). */
|
|
7293
|
+
export function deadUnmappableBinding(L, sym, decl) {
|
|
7294
|
+
if (!sym)
|
|
7295
|
+
return false;
|
|
7296
|
+
if (L.deadBindings.has(sym))
|
|
7297
|
+
return true;
|
|
7298
|
+
if (!ts.isIdentifier(decl.name))
|
|
7299
|
+
return false;
|
|
7300
|
+
const sf = decl.getSourceFile();
|
|
7301
|
+
if (sf.isDeclarationFile || isJsSourceFile(sf))
|
|
7302
|
+
return false;
|
|
7303
|
+
if (decl.initializer !== undefined && !sideEffectFreeValueExpr(L, decl.initializer))
|
|
7304
|
+
return false;
|
|
7305
|
+
// Exported bindings stay out: a library build's exports are consumed
|
|
7306
|
+
// from outside the graph, and export specifiers double as reads.
|
|
7307
|
+
if (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Export)
|
|
7308
|
+
return false;
|
|
7309
|
+
// The type gate LAST among the cheap checks: querying the checker for
|
|
7310
|
+
// a type is the expensive step (and can panic upstream — the 1e999
|
|
7311
|
+
// bug), so only survivors of the syntactic filters pay it. Mappable
|
|
7312
|
+
// types keep their real storage.
|
|
7313
|
+
if (L.mapTypeOf(L.checker.getTypeOfSymbol(sym)) !== null)
|
|
7314
|
+
return false;
|
|
7315
|
+
const symText = sym.name;
|
|
7316
|
+
const namesSym = (e) => ts.isIdentifier(e) && e.text === symText && L.resolveValueSymbol(e) === sym;
|
|
7317
|
+
let dead = true;
|
|
7318
|
+
const visit = (n) => {
|
|
7319
|
+
if (!dead)
|
|
7320
|
+
return;
|
|
7321
|
+
if (ts.isIdentifier(n) && n.text === symText) {
|
|
7322
|
+
// Declaration-name occurrences are not reads.
|
|
7323
|
+
if (n.parent !== undefined && ts.isVariableDeclaration(n.parent) && n.parent.name === n) {
|
|
7324
|
+
n.forEachChild(visit);
|
|
7325
|
+
return;
|
|
7326
|
+
}
|
|
7327
|
+
// A plain-assignment LHS is a WRITE — dead only when the RHS
|
|
7328
|
+
// builds no observable effect (the value is dropped with the
|
|
7329
|
+
// binding).
|
|
7330
|
+
const p = n.parent;
|
|
7331
|
+
if (p !== undefined && ts.isBinaryExpression(p) &&
|
|
7332
|
+
p.operatorToken.kind === ts.SyntaxKind.EqualsToken && p.left === n) {
|
|
7333
|
+
if (!namesSym(n))
|
|
7334
|
+
return;
|
|
7335
|
+
if (!sideEffectFreeValueExpr(L, p.right))
|
|
7336
|
+
dead = false;
|
|
7337
|
+
return;
|
|
7338
|
+
}
|
|
7339
|
+
// Import/export specifiers, and every other occurrence, count as
|
|
7340
|
+
// reads.
|
|
7341
|
+
if (namesSym(n))
|
|
7342
|
+
dead = false;
|
|
7343
|
+
return;
|
|
7344
|
+
}
|
|
7345
|
+
n.forEachChild(visit);
|
|
7346
|
+
};
|
|
7347
|
+
for (const file of L.program.getSourceFiles()) {
|
|
7348
|
+
if (!dead)
|
|
7349
|
+
break;
|
|
7350
|
+
if (file.isDeclarationFile)
|
|
7351
|
+
continue;
|
|
7352
|
+
file.forEachChild(visit);
|
|
7353
|
+
}
|
|
7354
|
+
if (dead)
|
|
7355
|
+
L.deadBindings.add(sym);
|
|
7356
|
+
return dead;
|
|
7357
|
+
}
|
|
6503
7358
|
/** The generic function-like INITIALIZER behind a binding declaration —
|
|
6504
7359
|
* `const f = <T>(x: T) => x` or `const f = function g<T>(x: T) {...}`
|
|
6505
7360
|
* (parens stripped). Null when the declaration isn't that shape; the
|
|
@@ -6508,9 +7363,9 @@ export function bindingNeverReassigned(L, sym, decl) {
|
|
|
6508
7363
|
export function bindingGenericFnNodeOf(decl) {
|
|
6509
7364
|
if (!ts.isIdentifier(decl.name) || decl.initializer === undefined)
|
|
6510
7365
|
return null;
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
7366
|
+
// Assertion wrappers strip like parens: `const r = (<T>(x: T) => x) as
|
|
7367
|
+
// Mapper` evaluates the arrow — the cast only renames its type.
|
|
7368
|
+
const init = stripValueWrappers(decl.initializer);
|
|
6514
7369
|
if ((ts.isArrowFunction(init) || ts.isFunctionExpression(init)) &&
|
|
6515
7370
|
init.typeParameters !== undefined && init.body !== undefined) {
|
|
6516
7371
|
return init;
|
|
@@ -6526,11 +7381,16 @@ export function bindingGenericFnNodeOf(decl) {
|
|
|
6526
7381
|
* signature. Null when the shape doesn't match (a concrete annotation, a
|
|
6527
7382
|
* generic arrow — the syntactic probe's case, an overloaded alias). */
|
|
6528
7383
|
export function bindingContextualGenericFnNodeOf(L, decl) {
|
|
6529
|
-
if (!ts.isIdentifier(decl.name) || decl.initializer === undefined
|
|
7384
|
+
if (!ts.isIdentifier(decl.name) || decl.initializer === undefined)
|
|
6530
7385
|
return null;
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
7386
|
+
// The generic signature can arrive as an ANNOTATION or as a type
|
|
7387
|
+
// ASSERTION on the initializer (`var r = < <T>(x: T) => T >((x) => x)`
|
|
7388
|
+
// — the checker contextually types the operand's parameters by the
|
|
7389
|
+
// asserted signature exactly like an annotation would).
|
|
7390
|
+
const asserted = ts.isAsExpression(decl.initializer) || ts.isTypeAssertion(decl.initializer);
|
|
7391
|
+
if (decl.type === undefined && !asserted)
|
|
7392
|
+
return null;
|
|
7393
|
+
const init = stripValueWrappers(decl.initializer);
|
|
6534
7394
|
if (!(ts.isArrowFunction(init) || ts.isFunctionExpression(init)) ||
|
|
6535
7395
|
init.typeParameters !== undefined || init.body === undefined) {
|
|
6536
7396
|
return null;
|
|
@@ -6721,10 +7581,56 @@ export function requireObjLitGenericReceiver(L, blame, recvExpr, literal, name)
|
|
|
6721
7581
|
* direct `call` of the instance with the receiver unevaluated. Claims
|
|
6722
7582
|
* every call whose member is generic-callable — lowering it or fencing
|
|
6723
7583
|
* with a named message. */
|
|
7584
|
+
/** URL.revokeObjectURL() with NO argument: Node's ERR_MISSING_ARGS
|
|
7585
|
+
* throws before the registry lookup, so the zero-argument contract is
|
|
7586
|
+
* exact without any blob machinery. The one-argument form (Node's
|
|
7587
|
+
* silent no-op for unregistered ids) and createObjectURL keep their
|
|
7588
|
+
* fences — a compiled program has no blob registry to consult. */
|
|
7589
|
+
function lowerUrlStaticCall(L, call, callee) {
|
|
7590
|
+
if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken !== undefined)
|
|
7591
|
+
return null;
|
|
7592
|
+
if (!ts.isIdentifier(callee.expression) || callee.expression.text !== "URL")
|
|
7593
|
+
return null;
|
|
7594
|
+
if (callee.name.text !== "revokeObjectURL" || call.arguments.length !== 0)
|
|
7595
|
+
return null;
|
|
7596
|
+
const sym = L.resolveValueSymbol(callee.expression);
|
|
7597
|
+
if (!sym || !L.isStdlibSymbol(sym))
|
|
7598
|
+
return null;
|
|
7599
|
+
return nodeThrowExpr(1, "ERR_MISSING_ARGS", 'The "url" argument must be specified', VOID, locOf(call));
|
|
7600
|
+
}
|
|
7601
|
+
const SP_BRAND_METHODS = new Set([
|
|
7602
|
+
"append", "delete", "get", "getAll", "has", "set", "sort",
|
|
7603
|
+
"forEach", "keys", "values", "entries", "toString",
|
|
7604
|
+
]);
|
|
6724
7605
|
export function lowerObjLitGenericMethodCall(L, call, access) {
|
|
6725
7606
|
if (L.chainBlocked(access, call))
|
|
6726
7607
|
return null;
|
|
6727
7608
|
const name = access.name.text;
|
|
7609
|
+
// URLSearchParams method values through Function.prototype.call/apply
|
|
7610
|
+
// with a receiver that is provably NOT a URLSearchParams (the suite's
|
|
7611
|
+
// `params.append.call(undefined)` probes): the WHATWG brand check
|
|
7612
|
+
// throws ERR_INVALID_THIS before any argument conversion — the whole
|
|
7613
|
+
// call IS that throw. A receiver that IS searchParams-typed, or one
|
|
7614
|
+
// whose runtime kind is unknowable (dyn/'any'), keeps the fence.
|
|
7615
|
+
if ((name === "call" || name === "apply") &&
|
|
7616
|
+
ts.isPropertyAccessExpression(access.expression) &&
|
|
7617
|
+
L.mapTypeOf(L.typeOf(access.expression.expression))?.kind === "searchParams" &&
|
|
7618
|
+
SP_BRAND_METHODS.has(access.expression.name.text) &&
|
|
7619
|
+
L.isStdlibMember(access.expression)) {
|
|
7620
|
+
const thisArg = call.arguments[0];
|
|
7621
|
+
const thisT = thisArg ? L.mapTypeOf(L.typeOf(thisArg)) : { kind: "undefinedT" };
|
|
7622
|
+
const provablyNot = thisT !== null &&
|
|
7623
|
+
thisT.kind !== "searchParams" &&
|
|
7624
|
+
thisT.kind !== "dyn" &&
|
|
7625
|
+
thisT.kind !== "jsval" &&
|
|
7626
|
+
(!thisArg || ts.isIdentifier(thisArg) || ts.isLiteralExpression(thisArg) ||
|
|
7627
|
+
thisArg.kind === ts.SyntaxKind.UndefinedKeyword ||
|
|
7628
|
+
thisArg.kind === ts.SyntaxKind.NullKeyword ||
|
|
7629
|
+
isUnitType(thisT));
|
|
7630
|
+
if (provablyNot) {
|
|
7631
|
+
return nodeThrowExpr(1, "ERR_INVALID_THIS", 'Value of "this" must be of type URLSearchParams', L.mapTypeOf(L.typeOf(call)) ?? VOID, locOf(call));
|
|
7632
|
+
}
|
|
7633
|
+
}
|
|
6728
7634
|
const recvT = L.typeOf(access.expression);
|
|
6729
7635
|
const propSym = L.checker.getPropertyOfType(recvT, name);
|
|
6730
7636
|
if (!propSym)
|
|
@@ -6757,6 +7663,24 @@ export function lowerObjLitGenericMethodCall(L, call, access) {
|
|
|
6757
7663
|
}
|
|
6758
7664
|
const found = objLitGenericFnNodeOf(L, propSym);
|
|
6759
7665
|
if (!found) {
|
|
7666
|
+
// Function.prototype.apply/call/bind spelled through a FUNCTION
|
|
7667
|
+
// receiver: compiled functions are direct calls with no runtime
|
|
7668
|
+
// `this`/arguments object to re-route — name the working spelling
|
|
7669
|
+
// instead of a class-receiver hint (or an SC2020 recitation) no
|
|
7670
|
+
// function value can follow. Before the stdlib decline: these ARE
|
|
7671
|
+
// stdlib members (CallableFunction), but the pointed message is
|
|
7672
|
+
// the honest one.
|
|
7673
|
+
if ((name === "apply" || name === "call" || name === "bind") &&
|
|
7674
|
+
L.checker.getCallSignatures(recvT).length > 0) {
|
|
7675
|
+
L.unsupported("SC1090", call, `Function.prototype.${name} on a compiled function value (compiled calls are direct — no runtime 'this' or arguments object exists to re-route; spell the call directly: '${access.expression.getText()}(...)')`);
|
|
7676
|
+
}
|
|
7677
|
+
// STANDARD-LIBRARY generic members (Promise.then, Object.
|
|
7678
|
+
// defineProperty, Array-augmentation methods) are the lib fence's
|
|
7679
|
+
// story (SC2020, naming the member) — decline so the stdlib
|
|
7680
|
+
// chokepoint downstream reports, instead of an interface-dispatch
|
|
7681
|
+
// recitation about a receiver no user constructed.
|
|
7682
|
+
if (L.isStdlibMember(access))
|
|
7683
|
+
return null;
|
|
6760
7684
|
// Interface-declared generic methods dispatch statically, so the
|
|
6761
7685
|
// receiver's runtime class must be provable — name that discipline
|
|
6762
7686
|
// instead of the object-literal wording when the method lives on an
|