@scriptc/compiler 0.0.8 → 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 +108 -5
- 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 +23 -10
- 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/emitter.js +206 -11
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +55 -10
- package/dist/diagnostics/diagnostic.js +82 -20
- package/dist/diagnostics/diagnostic.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 +111 -3
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +30 -11
- package/dist/frontend/lowering/lower-calls.js +418 -8
- 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 +4 -2
- package/dist/frontend/lowering/lower-containers.js +193 -43
- package/dist/frontend/lowering/lower-containers.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 +52 -7
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +445 -13
- 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 +66 -14
- package/dist/frontend/lowering/lowerer.js +273 -28
- 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.js +4 -2
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.js +150 -25
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +131 -7
- package/dist/ir/nodes.js +41 -3
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +58 -8
- package/dist/ir/validate.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +10 -3
|
@@ -373,6 +373,36 @@ export declare const TIMER_MODULE_MEMBERS: ReadonlySet<string>;
|
|
|
373
373
|
* timer function (the caller's fence machinery takes over). */
|
|
374
374
|
export declare function lowerTimersMemberCall(L: Lowerer, expr: ts.CallExpression, member: string, loc: SrcLoc): IrExpr | null;
|
|
375
375
|
export declare function lowerCall(L: Lowerer, expr: ts.CallExpression): IrExpr;
|
|
376
|
+
/** The RUNTIME-ARITY spread call — `f(...args)`, the rest-forwarding idiom
|
|
377
|
+
* (`const f = (...args) => from(...args)`): a spread whose length is a
|
|
378
|
+
* runtime fact has no home in the compile-time completion, so the call
|
|
379
|
+
* rides a dynamic boundary instead. Two lanes, picked by the spread
|
|
380
|
+
* source's tier:
|
|
381
|
+
*
|
|
382
|
+
* - CHECKED-DYNAMIC (dyn spread sources — a JS rest binding, a dyn
|
|
383
|
+
* value): box the callee (dynFrom; a dyn callee is already boxed),
|
|
384
|
+
* convert every argument into dyn, and emit the spread-marked dynCall
|
|
385
|
+
* — the emitters build one fresh DOM argument array (spreads flatten
|
|
386
|
+
* left-to-right, non-iterables throw V8's TypeError) and apply through
|
|
387
|
+
* it; the boxed thunk delivers JS arity exactly. Result dyn, checked
|
|
388
|
+
* per use like every any-origin value.
|
|
389
|
+
* - ISLAND (a jsval spread source — the --dynamic rest binding is the
|
|
390
|
+
* engine's own arguments array): marshal the callee in and emit jsOp
|
|
391
|
+
* callSpread — the prelude helper's REAL `f(...pre, ...s)`, so
|
|
392
|
+
* iterator protocols and the not-iterable TypeError are the engine's
|
|
393
|
+
* own. One trailing spread after the fixed arguments is the modeled
|
|
394
|
+
* shape (exactly the forwarding idiom).
|
|
395
|
+
*
|
|
396
|
+
* Answers null when neither lane fits (typed .ts spreads keep
|
|
397
|
+
* completeArgs' rest packing and its fences). JS sources only — the
|
|
398
|
+
* same guard as the over-arity dynCall precedent. */
|
|
399
|
+
export declare function lowerSpreadArgsCall(L: Lowerer, expr: ts.CallExpression, callee: IrExpr, loc: SrcLoc): IrExpr | null;
|
|
400
|
+
/** True when a spread argument lands where the compile-time completion
|
|
401
|
+
* cannot take it — a FIXED parameter position, or a dynamic rest slot
|
|
402
|
+
* (dynRest/islandRest, whose packs are built per-argument): the shapes
|
|
403
|
+
* the runtime-arity lane (lowerSpreadArgsCall) serves. Typed `rest`
|
|
404
|
+
* slots keep completeArgs' same-element spread packing. */
|
|
405
|
+
export declare function spreadNeedsRuntimeArity(shapes: readonly ParamShape[], argNodes: readonly ts.Expression[]): boolean;
|
|
376
406
|
/** The SHARED prototype names scr_dyn_invoke dispatches at runtime (the
|
|
377
407
|
* subset of DOM_PROTO_METHOD_NAMES with a receiver-kind dispatch): the
|
|
378
408
|
* runtime runs the real method for the receiver's kind, throws Node's
|
|
@@ -600,17 +630,6 @@ export declare function bindingGenericFnAliasInfoOf(L: Lowerer, decl: ts.Variabl
|
|
|
600
630
|
* structurally identical literal with a DIFFERENT body, which static
|
|
601
631
|
* resolution would silently miss. */
|
|
602
632
|
export declare function requireObjLitGenericReceiver(L: Lowerer, blame: ts.Node, recvExpr: ts.Expression, literal: ts.ObjectLiteralExpression, name: string): void;
|
|
603
|
-
/** `o.m(args)` where `m` is an object-literal GENERIC method (own type
|
|
604
|
-
* parameters — the member is excluded from the record shape, see
|
|
605
|
-
* isGenericCallableMemberType): monomorphized per call site against the
|
|
606
|
-
* DEFINING literal's declaration, exactly like top-level generic
|
|
607
|
-
* functions. Resolution is static, so the receiver must provably BE the
|
|
608
|
-
* defining literal: a const binding whose initializer is that literal,
|
|
609
|
-
* read directly. The receiver read is pure and the compiled instance is
|
|
610
|
-
* a plain module function (no `this`, fenced), so the call lowers to a
|
|
611
|
-
* direct `call` of the instance with the receiver unevaluated. Claims
|
|
612
|
-
* every call whose member is generic-callable — lowering it or fencing
|
|
613
|
-
* with a named message. */
|
|
614
633
|
export declare function lowerObjLitGenericMethodCall(L: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression): IrExpr | null;
|
|
615
634
|
/** `obj.method(args)` — whole-program devirtualization decides the form:
|
|
616
635
|
* a method some strict subclass of the receiver's STATIC class overrides
|
|
@@ -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";
|
|
@@ -2804,6 +2804,9 @@ export function lowerCall(L, expr) {
|
|
|
2804
2804
|
const streamServed = lowerStreamModuleCall(L, expr, bi, loc);
|
|
2805
2805
|
if (streamServed)
|
|
2806
2806
|
return streamServed;
|
|
2807
|
+
const fsTs = L.lowerFsToUnixTimestampCall(expr, bi, loc);
|
|
2808
|
+
if (fsTs)
|
|
2809
|
+
return fsTs;
|
|
2807
2810
|
const builtinFn = builtinModuleFnOf(L, bi.module, bi.member);
|
|
2808
2811
|
if (!builtinFn) {
|
|
2809
2812
|
// Typed by @types/node (the fallback declarations only declare
|
|
@@ -3033,15 +3036,24 @@ export function lowerCall(L, expr) {
|
|
|
3033
3036
|
// closure object, plain C call). Generic functions route through
|
|
3034
3037
|
// monomorphization (the call targets a per-instantiation instance).
|
|
3035
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());
|
|
3036
3045
|
if (L.isTopLevelFnSymbol(expr.expression) && !L.peekLocal(expr.expression)) {
|
|
3037
3046
|
// `import g = N.f; g()` — the alias's own source-order guards
|
|
3038
3047
|
// (a no-op for every non-import= binding).
|
|
3039
3048
|
fenceEarlyAliasUse(L, expr.expression, expr);
|
|
3040
3049
|
const generic = L.genericFnOf(expr.expression);
|
|
3041
|
-
|
|
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))
|
|
3042
3054
|
return L.lowerGenericCall(expr, generic);
|
|
3043
|
-
const sig = L.fnSigOf(expr.expression);
|
|
3044
|
-
if (sig) {
|
|
3055
|
+
const sig = generic ? null : L.fnSigOf(expr.expression);
|
|
3056
|
+
if (sig && !(jsSpreadArgs && spreadNeedsRuntimeArity(sig.params, expr.arguments))) {
|
|
3045
3057
|
L.noteEdge(sig.name);
|
|
3046
3058
|
const args = L.completeArgs(expr.arguments, sig.params, loc, expr);
|
|
3047
3059
|
return reconcileOverloadReturn(L, expr, { kind: "call", callee: sig.name, args, type: sig.returnType, loc });
|
|
@@ -3065,10 +3077,12 @@ export function lowerCall(L, expr) {
|
|
|
3065
3077
|
// the call monomorphizes against it exactly like a generic function
|
|
3066
3078
|
// declaration and the binding is never read. Symbol identity does
|
|
3067
3079
|
// the discrimination — a shadowing local has its own symbol, and
|
|
3068
|
-
// 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.
|
|
3069
3083
|
{
|
|
3070
3084
|
const generic = L.genericFnOf(expr.expression);
|
|
3071
|
-
if (generic)
|
|
3085
|
+
if (generic && !(jsSpreadArgs && generic.implicitParams))
|
|
3072
3086
|
return L.lowerGenericCall(expr, generic);
|
|
3073
3087
|
}
|
|
3074
3088
|
}
|
|
@@ -3277,6 +3291,9 @@ export function lowerCall(L, expr) {
|
|
|
3277
3291
|
// island path (bytes never cross the boundary).
|
|
3278
3292
|
L.lowerBytesMethodCall(expr, expr.expression) ??
|
|
3279
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) ??
|
|
3280
3297
|
// Readable.from — the stream classes' one static (before the
|
|
3281
3298
|
// stdlib chokepoint claims the member).
|
|
3282
3299
|
lowerStreamStaticCall(L, expr, expr.expression) ??
|
|
@@ -3312,6 +3329,9 @@ export function lowerCall(L, expr) {
|
|
|
3312
3329
|
lowerSymbolStaticCall(L, expr, expr.expression) ??
|
|
3313
3330
|
lowerSymbolMethodCall(L, expr, expr.expression) ??
|
|
3314
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) ??
|
|
3315
3335
|
lowerGroupByStaticCall(L, expr, expr.expression) ??
|
|
3316
3336
|
// Iterator-helper chains rooted at arr.values() — before the
|
|
3317
3337
|
// array method paths (the terminal names collide with array
|
|
@@ -3420,6 +3440,12 @@ export function lowerCall(L, expr) {
|
|
|
3420
3440
|
// any-origin value). Spread arguments keep their fence.
|
|
3421
3441
|
if (callee.type.kind === "dyn") {
|
|
3422
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;
|
|
3423
3449
|
L.unsupported("SC1090", expr, "spread arguments in calls through 'unknown' values");
|
|
3424
3450
|
}
|
|
3425
3451
|
const args = expr.arguments.map((a) => L.lowerExprExpecting(a, DYN));
|
|
@@ -3433,6 +3459,42 @@ export function lowerCall(L, expr) {
|
|
|
3433
3459
|
if (callee.type.kind !== "func") {
|
|
3434
3460
|
L.badType(expr.expression, L.typeOf(expr.expression));
|
|
3435
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
|
+
}
|
|
3436
3498
|
// A JS call with MORE arguments than the callee's lowered signature
|
|
3437
3499
|
// (`cb(1, 'x')` where the mustCall wrapper's inferred type declared
|
|
3438
3500
|
// fewer params — tsc's JS world doesn't police arity): ride the
|
|
@@ -3470,6 +3532,101 @@ export function lowerCall(L, expr) {
|
|
|
3470
3532
|
}
|
|
3471
3533
|
return { kind: "callValue", callee, args, type: callee.type.ret, loc };
|
|
3472
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
|
+
}
|
|
3473
3630
|
/** METHOD calls on dyn receivers (`pkg.name.replace(...)`, `rawName.split`,
|
|
3474
3631
|
* `ws.packages.filter(...)` — JSON.parse-derived values): validate the
|
|
3475
3632
|
* receiver's DOM kind, extract, and ride the STATIC method machinery — the
|
|
@@ -4008,6 +4165,32 @@ export function lowerPrimitiveProtoCall(L, call, recv, name, memberSym) {
|
|
|
4008
4165
|
const fn = name === "toExponential" ? "num.toExponential" : "num.toFixed0";
|
|
4009
4166
|
return { kind: "libCall", fn, args: [operand], type: STRING, loc };
|
|
4010
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
|
+
}
|
|
4011
4194
|
if (name === "charAt" && recvKind === "string" && call.arguments.length === 1 &&
|
|
4012
4195
|
!ts.isSpreadElement(call.arguments[0])) {
|
|
4013
4196
|
const receiver = L.lowerExpr(recv);
|
|
@@ -4602,6 +4785,15 @@ export function lowerLambda(L, node) {
|
|
|
4602
4785
|
const first = captured[0];
|
|
4603
4786
|
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? node.getSourceFile(), first.loc.start);
|
|
4604
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
|
+
}
|
|
4605
4797
|
const lifted = {
|
|
4606
4798
|
name: fnName,
|
|
4607
4799
|
params,
|
|
@@ -5782,12 +5974,184 @@ function lowerRegExpStaticCall(L, call, access) {
|
|
|
5782
5974
|
L.badType(call.arguments[0], L.typeOf(call.arguments[0]));
|
|
5783
5975
|
return { kind: "libCall", fn: "regexp.escape", args: [arg], type: STRING, loc: locOf(call) };
|
|
5784
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
|
+
}
|
|
5785
6051
|
function lowerObjectStaticCall(L, call, access) {
|
|
5786
6052
|
if (call.questionDotToken || access.questionDotToken)
|
|
5787
6053
|
return null;
|
|
5788
6054
|
if (!L.isStdlibGlobal(access.expression, "Object"))
|
|
5789
6055
|
return null;
|
|
5790
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
|
+
}
|
|
5791
6155
|
// `Object.assign(fn, { props })` whose RESULT type maps to the hybrid
|
|
5792
6156
|
// (function-with-properties) record: the chalk-shape CONSTRUCTOR.
|
|
5793
6157
|
if (member === "assign") {
|
|
@@ -6721,10 +7085,56 @@ export function requireObjLitGenericReceiver(L, blame, recvExpr, literal, name)
|
|
|
6721
7085
|
* direct `call` of the instance with the receiver unevaluated. Claims
|
|
6722
7086
|
* every call whose member is generic-callable — lowering it or fencing
|
|
6723
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
|
+
]);
|
|
6724
7109
|
export function lowerObjLitGenericMethodCall(L, call, access) {
|
|
6725
7110
|
if (L.chainBlocked(access, call))
|
|
6726
7111
|
return null;
|
|
6727
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
|
+
}
|
|
6728
7138
|
const recvT = L.typeOf(access.expression);
|
|
6729
7139
|
const propSym = L.checker.getPropertyOfType(recvT, name);
|
|
6730
7140
|
if (!propSym)
|