@scriptc/compiler 0.0.9 → 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.
Files changed (62) hide show
  1. package/dist/backend/emission/emit-exprs.js +136 -12
  2. package/dist/backend/emission/emit-exprs.js.map +1 -1
  3. package/dist/backend/emission/emit-walkers.js +29 -2
  4. package/dist/backend/emission/emit-walkers.js.map +1 -1
  5. package/dist/backend/llvm/dyn.d.ts +1 -0
  6. package/dist/backend/llvm/dyn.js +83 -1
  7. package/dist/backend/llvm/dyn.js.map +1 -1
  8. package/dist/backend/llvm/emitter.js +151 -9
  9. package/dist/backend/llvm/emitter.js.map +1 -1
  10. package/dist/diagnostics/diagnostic.d.ts +21 -7
  11. package/dist/diagnostics/diagnostic.js +35 -16
  12. package/dist/diagnostics/diagnostic.js.map +1 -1
  13. package/dist/diagnostics/render.js +8 -0
  14. package/dist/diagnostics/render.js.map +1 -1
  15. package/dist/frontend/lowering/lib-boundary.js +11 -1
  16. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  17. package/dist/frontend/lowering/lower-builtins.d.ts +13 -0
  18. package/dist/frontend/lowering/lower-builtins.js +146 -1
  19. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  20. package/dist/frontend/lowering/lower-calls.d.ts +41 -0
  21. package/dist/frontend/lowering/lower-calls.js +524 -10
  22. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  23. package/dist/frontend/lowering/lower-containers.d.ts +17 -0
  24. package/dist/frontend/lowering/lower-containers.js +193 -10
  25. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  26. package/dist/frontend/lowering/lower-dgram.js +92 -17
  27. package/dist/frontend/lowering/lower-dgram.js.map +1 -1
  28. package/dist/frontend/lowering/lower-exprs.js +179 -10
  29. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  30. package/dist/frontend/lowering/lower-modules.js +33 -2
  31. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  32. package/dist/frontend/lowering/lower-namespaces.d.ts +25 -11
  33. package/dist/frontend/lowering/lower-namespaces.js +50 -15
  34. package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
  35. package/dist/frontend/lowering/lower-server.js +211 -4
  36. package/dist/frontend/lowering/lower-server.js.map +1 -1
  37. package/dist/frontend/lowering/lower-stmts.d.ts +1 -5
  38. package/dist/frontend/lowering/lower-stmts.js +216 -7
  39. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  40. package/dist/frontend/lowering/lower-stream.js +65 -4
  41. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  42. package/dist/frontend/lowering/lowerer.d.ts +36 -1
  43. package/dist/frontend/lowering/lowerer.js +106 -3
  44. package/dist/frontend/lowering/lowerer.js.map +1 -1
  45. package/dist/frontend/types.d.ts +13 -0
  46. package/dist/frontend/types.js +84 -7
  47. package/dist/frontend/types.js.map +1 -1
  48. package/dist/index.js +20 -8
  49. package/dist/index.js.map +1 -1
  50. package/dist/ir/nodes.d.ts +99 -5
  51. package/dist/ir/nodes.js +44 -3
  52. package/dist/ir/nodes.js.map +1 -1
  53. package/dist/ir/validate.js +53 -3
  54. package/dist/ir/validate.js.map +1 -1
  55. package/dist/library/fence-eval.d.ts +77 -0
  56. package/dist/library/fence-eval.js +385 -0
  57. package/dist/library/fence-eval.js.map +1 -0
  58. package/dist/library/profile.d.ts +11 -1
  59. package/dist/library/profile.js +102 -18
  60. package/dist/library/profile.js.map +1 -1
  61. package/package.json +2 -2
  62. package/surface-manifest.json +1 -1
@@ -577,6 +577,47 @@ export declare function objLitGenericFnInfoOf(L: Lowerer, blame: ts.Node, name:
577
577
  * bindings are read-only — no other file can write one. Cached per
578
578
  * symbol (the scan walks the whole file once). */
579
579
  export declare function bindingNeverReassigned(L: Lowerer, sym: ts.Symbol, decl: ts.Node): boolean;
580
+ /** Strips the value-preserving wrappers off an expression: parens,
581
+ * non-null assertions, `as`/`satisfies`/angle-bracket casts. What
582
+ * remains is the expression that actually evaluates. */
583
+ export declare function stripValueWrappers(e: ts.Expression): ts.Expression;
584
+ /** The nullish unit `e` provably evaluates to: a bare null/undefined
585
+ * literal (assertion-wrapped — `null as any`, `null!`), or a read of a
586
+ * registered NULLISH binding (nullishGenericBindingUnitOf). Null when
587
+ * the value could be anything else. */
588
+ export declare function nullishExprUnitOf(L: Lowerer, e: ts.Expression): "null" | "undefined" | null;
589
+ /** The nullish unit a binding provably holds FOREVER, by VALUE alone: its
590
+ * initializer is nullish (`const i: I<A & B> = null as any`) and every
591
+ * write in its declaring file is nullish too (`a = b` where b is
592
+ * another nullish binding). No type condition — callers add their own
593
+ * (nullishGenericBindingUnitOf gates the no-storage family on
594
+ * unmappable types; the generic-method call path rescues its fence with
595
+ * the value fact alone). Cached per symbol; the pre-seeded null entry
596
+ * guards probe cycles (mutually-assigned bindings resolve link by link,
597
+ * declaration order). */
598
+ export declare function nullishValueUnitOf(L: Lowerer, sym: ts.Symbol | null): "null" | "undefined" | null;
599
+ /** nullishValueUnitOf gated on a declared type that CANNOT hold the
600
+ * value — the NO-STORAGE family: an unmappable type has no other story,
601
+ * and a RECORD-mapped one (`const i: I<A & B> = null as any` — an
602
+ * interface whose members are all generic signatures interns an empty
603
+ * shape) has a slot null can never inhabit, so storing would throw the
604
+ * representation error where Node stores null silently. Either way the
605
+ * declaration emits nothing and reads know the value. Null-tolerant
606
+ * mappings (unions with a null/undefined arm, dyn) keep their real
607
+ * storage and every ordinary lowering. */
608
+ export declare function nullishGenericBindingUnitOf(L: Lowerer, sym: ts.Symbol | null): "null" | "undefined" | null;
609
+ /** True when `sym` — a binding whose type has NO static mapping — is DEAD:
610
+ * never read anywhere in the program, not exported through a specifier,
611
+ * declared with no initializer or a side-effect-free one, and written
612
+ * (if at all) only by plain assignments of side-effect-free values. Node
613
+ * materializes those values and drops them — zero observable effect —
614
+ * so the declaration and its writes lower to NOTHING instead of fencing
615
+ * on a type the program never consumes (`var xs2: typeof Array;`, the
616
+ * write-only `var f2: { <T, U>(x: T, y: U): T }`). TS program files
617
+ * only: JS bindings keep their checked-dynamic fallbacks. Positive
618
+ * answers register in L.deadBindings (the assignment lowering skips
619
+ * writes by the same set). */
620
+ export declare function deadUnmappableBinding(L: Lowerer, sym: ts.Symbol | null, decl: ts.VariableDeclaration): boolean;
580
621
  /** The generic function-like INITIALIZER behind a binding declaration —
581
622
  * `const f = <T>(x: T) => x` or `const f = function g<T>(x: T) {...}`
582
623
  * (parens stripped). Null when the declaration isn't that shape; the
@@ -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. Island-typed arguments meet the boundary pass's
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)
@@ -2807,6 +2875,12 @@ export function lowerCall(L, expr) {
2807
2875
  const fsTs = L.lowerFsToUnixTimestampCall(expr, bi, loc);
2808
2876
  if (fsTs)
2809
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;
2810
2884
  const builtinFn = builtinModuleFnOf(L, bi.module, bi.member);
2811
2885
  if (!builtinFn) {
2812
2886
  // Typed by @types/node (the fallback declarations only declare
@@ -3704,10 +3778,17 @@ function lowerDynReceiverMethodCall(L, call, access) {
3704
3778
  const enc = call.arguments[0]
3705
3779
  ? bufEncoding(L, "toString", call.arguments[0])
3706
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.
3707
3784
  return {
3708
3785
  kind: "libCall",
3709
3786
  fn: "dyn.toString",
3710
- args: [recv, { kind: "strLit", value: enc, type: STRING, loc: locOf(call) }],
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
+ ],
3711
3792
  type: STRING,
3712
3793
  loc: locOf(call),
3713
3794
  };
@@ -6152,6 +6233,57 @@ function lowerObjectStaticCall(L, call, access) {
6152
6233
  }
6153
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)");
6154
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
+ }
6155
6287
  // `Object.assign(fn, { props })` whose RESULT type maps to the hybrid
6156
6288
  // (function-with-properties) record: the chalk-shape CONSTRUCTOR.
6157
6289
  if (member === "assign") {
@@ -6228,6 +6360,91 @@ function lowerObjectStaticCall(L, call, access) {
6228
6360
  }
6229
6361
  }
6230
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
+ }
6231
6448
  return null;
6232
6449
  }
6233
6450
  // Object.defineProperties over a CHECKED-DYNAMIC target (test/common's
@@ -6864,6 +7081,280 @@ export function bindingNeverReassigned(L, sym, decl) {
6864
7081
  L.neverReassignedCache.set(sym, !written);
6865
7082
  return !written;
6866
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
+ }
6867
7358
  /** The generic function-like INITIALIZER behind a binding declaration —
6868
7359
  * `const f = <T>(x: T) => x` or `const f = function g<T>(x: T) {...}`
6869
7360
  * (parens stripped). Null when the declaration isn't that shape; the
@@ -6872,9 +7363,9 @@ export function bindingNeverReassigned(L, sym, decl) {
6872
7363
  export function bindingGenericFnNodeOf(decl) {
6873
7364
  if (!ts.isIdentifier(decl.name) || decl.initializer === undefined)
6874
7365
  return null;
6875
- let init = decl.initializer;
6876
- while (ts.isParenthesizedExpression(init))
6877
- init = init.expression;
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);
6878
7369
  if ((ts.isArrowFunction(init) || ts.isFunctionExpression(init)) &&
6879
7370
  init.typeParameters !== undefined && init.body !== undefined) {
6880
7371
  return init;
@@ -6890,11 +7381,16 @@ export function bindingGenericFnNodeOf(decl) {
6890
7381
  * signature. Null when the shape doesn't match (a concrete annotation, a
6891
7382
  * generic arrow — the syntactic probe's case, an overloaded alias). */
6892
7383
  export function bindingContextualGenericFnNodeOf(L, decl) {
6893
- if (!ts.isIdentifier(decl.name) || decl.initializer === undefined || decl.type === undefined)
7384
+ if (!ts.isIdentifier(decl.name) || decl.initializer === undefined)
6894
7385
  return null;
6895
- let init = decl.initializer;
6896
- while (ts.isParenthesizedExpression(init))
6897
- init = init.expression;
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);
6898
7394
  if (!(ts.isArrowFunction(init) || ts.isFunctionExpression(init)) ||
6899
7395
  init.typeParameters !== undefined || init.body === undefined) {
6900
7396
  return null;
@@ -7167,6 +7663,24 @@ export function lowerObjLitGenericMethodCall(L, call, access) {
7167
7663
  }
7168
7664
  const found = objLitGenericFnNodeOf(L, propSym);
7169
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;
7170
7684
  // Interface-declared generic methods dispatch statically, so the
7171
7685
  // receiver's runtime class must be provable — name that discipline
7172
7686
  // instead of the object-literal wording when the method lives on an