@scriptc/compiler 0.0.28 → 0.0.30

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 (55) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +5 -13
  2. package/dist/backend/cc.d.ts +23 -2
  3. package/dist/backend/cc.js +250 -9
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-async.d.ts +1 -0
  6. package/dist/backend/emission/emit-async.js +27 -1
  7. package/dist/backend/emission/emit-async.js.map +1 -1
  8. package/dist/backend/emission/emit-exprs.js +109 -18
  9. package/dist/backend/emission/emit-exprs.js.map +1 -1
  10. package/dist/backend/emission/emitter.d.ts +1 -0
  11. package/dist/backend/emission/emitter.js +25 -3
  12. package/dist/backend/emission/emitter.js.map +1 -1
  13. package/dist/backend/llvm/emitter.js +152 -0
  14. package/dist/backend/llvm/emitter.js.map +1 -1
  15. package/dist/backend/mangle.d.ts +1 -0
  16. package/dist/backend/mangle.js +3 -0
  17. package/dist/backend/mangle.js.map +1 -1
  18. package/dist/coverage/surface-manifest.js +1 -0
  19. package/dist/coverage/surface-manifest.js.map +1 -1
  20. package/dist/diagnostics/diagnostic.d.ts +23 -1
  21. package/dist/diagnostics/diagnostic.js +34 -0
  22. package/dist/diagnostics/diagnostic.js.map +1 -1
  23. package/dist/frontend/lowering/lower-calls.d.ts +3 -1
  24. package/dist/frontend/lowering/lower-calls.js +196 -33
  25. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  26. package/dist/frontend/lowering/lower-containers.d.ts +6 -5
  27. package/dist/frontend/lowering/lower-containers.js +15 -11
  28. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  29. package/dist/frontend/lowering/lower-exprs.d.ts +11 -1
  30. package/dist/frontend/lowering/lower-exprs.js +366 -25
  31. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  32. package/dist/frontend/lowering/lower-server.d.ts +5 -0
  33. package/dist/frontend/lowering/lower-server.js +105 -55
  34. package/dist/frontend/lowering/lower-server.js.map +1 -1
  35. package/dist/frontend/lowering/lower-stmts.js +71 -9
  36. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  37. package/dist/frontend/lowering/lowerer.d.ts +24 -5
  38. package/dist/frontend/lowering/lowerer.js +28 -4
  39. package/dist/frontend/lowering/lowerer.js.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +84 -5
  42. package/dist/index.js.map +1 -1
  43. package/dist/ir/nodes.d.ts +52 -19
  44. package/dist/ir/nodes.js +2 -2
  45. package/dist/ir/nodes.js.map +1 -1
  46. package/dist/ir/validate.js +67 -9
  47. package/dist/ir/validate.js.map +1 -1
  48. package/dist/library/profile.d.ts +33 -0
  49. package/dist/library/profile.js +178 -4
  50. package/dist/library/profile.js.map +1 -1
  51. package/dist/library/sidecar.d.ts +3 -2
  52. package/dist/library/sidecar.js +7 -3
  53. package/dist/library/sidecar.js.map +1 -1
  54. package/package.json +2 -2
  55. package/surface-manifest.json +2 -1
@@ -186,6 +186,8 @@ export declare function finishOptionalChain(L: Lowerer, expr: ts.Expression, id:
186
186
  * short-circuiting, and mixed operand kinds that have no VALUE
187
187
  * representation (`u && flag` — a union and a bool) test fine here. */
188
188
  export declare function lowerCondition(L: Lowerer, expr: ts.Expression): IrExpr;
189
+ export declare function runtimeOptionalTrueIds(L: Lowerer, node: ts.Expression): IrLocal[];
190
+ export declare function withRuntimeOptionalNarrowed<T>(L: Lowerer, locals: readonly IrLocal[], fn: () => T): T;
189
191
  /** JS ToBoolean: bool passes through; f64/string get a `toBool` wrapper
190
192
  * (falsy: 0, -0, NaN, ""); unions get the same wrapper answered by a
191
193
  * per-union interned helper (unit arms falsy; scalar/string arms by
@@ -288,7 +290,15 @@ export declare function lowerElementAccess(L: Lowerer, expr: ts.ElementAccessExp
288
290
  * fields outside that stay fenced. Declared-only shapes support reads
289
291
  * whose key type proves membership (tsc's keyof check): all fields must
290
292
  * share the result type, and a smuggled miss traps. */
291
- export declare function lowerRecordKeyRead(L: Lowerer, expr: ts.ElementAccessExpression, shapeId: string, shape: IrRecordShape): IrExpr;
293
+ export declare function lowerRecordKeyRead(L: Lowerer, expr: ts.ElementAccessExpression, shapeId: string, shape: IrRecordShape, includeUndefined?: boolean): IrExpr;
294
+ /** A keyed read used only to observe absence (`if (map[k])`, or the left
295
+ * side of `||`/`??`). JavaScript answers undefined for missing record keys
296
+ * and array indices even when noUncheckedIndexedAccess is disabled. Keep
297
+ * that value in an explicit union until the surrounding guard/default has
298
+ * consumed it; ordinary unchecked reads retain the typed trap. Assertions
299
+ * around the read are erased here because this context handles the missing
300
+ * arm instead of consuming it as the asserted type. */
301
+ export declare function lowerAbsenceProbe(L: Lowerer, node: ts.Expression): IrExpr | null;
292
302
  /** The compile-time string spelling of a record key literal: a string
293
303
  * literal's text, or a NON-NEGATIVE numeric literal's canonical JS
294
304
  * spelling (`1` → "1", `1e21` → "1e+21" — String(Number(text)), which is
@@ -12,7 +12,7 @@ import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReaso
12
12
  import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
13
13
  import { blockedBindingUseDiag, recordShapeMismatchDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
14
14
  import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
15
- import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, strCharsCall } from "./lower-containers.js";
15
+ import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall } from "./lower-containers.js";
16
16
  import { npmStaticPackageOfPath } from "../npm-static.js";
17
17
  import { unsupportedModuleFeatureOf } from "../shared.js";
18
18
  import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
@@ -204,6 +204,9 @@ function lowerExprInner(L, expr) {
204
204
  if (ts.isNonNullExpression(expr)) {
205
205
  const inner = L.lowerExpr(expr.expression);
206
206
  if (inner.type.kind === "union") {
207
+ const use = runtimeOptionalUseOf(expr);
208
+ if (runtimeOptionalAssertionErases(L, expr, inner, use))
209
+ return inner;
207
210
  const target = L.mapTypeOf(L.typeOf(expr));
208
211
  if (target && target.kind !== "union" && !typeEquals(target, inner.type)) {
209
212
  const helper = L.narrowedArmHelper(inner.type.unionId, target, loc);
@@ -650,6 +653,37 @@ function lowerExprInner(L, expr) {
650
653
  if (local) {
651
654
  if (local.type.kind === "caught")
652
655
  return L.caughtRead(expr, local, loc);
656
+ const symbol = L.resolveValueSymbol(expr);
657
+ const runtimeOptionalRoot = L.runtimeOptionalRootOf(local);
658
+ const active = L.runtimeOptionalLocals.has(runtimeOptionalRoot);
659
+ const captured = !!symbol &&
660
+ L.runtimeOptionalStorageLocals.has(runtimeOptionalRoot) &&
661
+ L.ctx.captureBySymbol.get(symbol) === local;
662
+ if (active || captured) {
663
+ const use = runtimeOptionalUseOf(expr);
664
+ if (use?.complex) {
665
+ L.unsupported("SC1090", expr, "a logical or conditional receiver containing a runtime-optional capture");
666
+ }
667
+ if (use?.optional)
668
+ return { kind: "varRef", localId: local.id, type: local.type, loc };
669
+ if (use?.kind === "property")
670
+ return runtimeOptionalReceiverRead(L, expr, local, use.access.name.text, loc);
671
+ if (use?.kind === "element") {
672
+ const key = runtimeOptionalElementKey(use.access.argumentExpression);
673
+ if (key === null) {
674
+ L.unsupported("SC1090", use.access, "a computed read from a runtime-optional capture (use a literal key)");
675
+ }
676
+ return runtimeOptionalReceiverRead(L, expr, local, key, loc);
677
+ }
678
+ if (use?.kind === "call") {
679
+ if (use.comma && !use.optional) {
680
+ L.unsupported("SC1090", use.access, "a direct call through a comma-wrapped runtime-optional capture");
681
+ }
682
+ return runtimeOptionalReceiverRead(L, expr, local, null, loc);
683
+ }
684
+ if (active || captured)
685
+ return { kind: "varRef", localId: local.id, type: local.type, loc };
686
+ }
653
687
  return L.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, expr);
654
688
  }
655
689
  // `import x = N.y` aliases resolve transparently through globalOf/
@@ -877,11 +911,8 @@ function lowerExprInner(L, expr) {
877
911
  L.noLowering(expr.text, expr, globalHints[expr.text], sym ?? undefined);
878
912
  }
879
913
  }
880
- // `declare const __VERSION__: string` — an ambient global NOTHING
881
- // defines (a bundler define in the real pipeline; scriptc has no
882
- // define mechanism, and neither does Node running the source —
883
- // the oracle). The read compiles to exactly what Node does at the
884
- // access: the catchable ReferenceError "<name> is not defined".
914
+ // An ambient global nothing defines compiles to exactly what Node does
915
+ // at the access: the catchable ReferenceError "<name> is not defined".
885
916
  // Stdlib/@types/node ambients never reach here (their chokepoint
886
917
  // is above); only user-file declares do.
887
918
  {
@@ -1913,12 +1944,14 @@ export function lowerTernary(L, expr, expected) {
1913
1944
  })();
1914
1945
  let thenRaw;
1915
1946
  let elseRaw;
1947
+ const runtimeTrueIds = runtimeOptionalTrueIds(L, expr.condition);
1948
+ const lowerTrueArm = () => withRuntimeOptionalNarrowed(L, runtimeTrueIds, () => lowerArm(expr.whenTrue, ownArrayJoin ?? undefined));
1916
1949
  if (!ctxArray && emptyUntypedArrayArm(expr.whenTrue) && !emptyUntypedArrayArm(expr.whenFalse)) {
1917
1950
  elseRaw = L.lowerExpr(expr.whenFalse);
1918
- thenRaw = lowerArm(expr.whenTrue, elseRaw.type);
1951
+ thenRaw = withRuntimeOptionalNarrowed(L, runtimeTrueIds, () => lowerArm(expr.whenTrue, elseRaw.type));
1919
1952
  }
1920
1953
  else {
1921
- thenRaw = lowerArm(expr.whenTrue, ownArrayJoin ?? undefined);
1954
+ thenRaw = lowerTrueArm();
1922
1955
  elseRaw = lowerArm(expr.whenFalse, thenRaw.type);
1923
1956
  }
1924
1957
  // The ternary's IR type is normally the checker's own: it collapses
@@ -2110,6 +2143,109 @@ export function maybeNarrow(L, expr, node) {
2110
2143
  loc: expr.loc,
2111
2144
  };
2112
2145
  }
2146
+ /** A runtime-optional slot can be assigned while a truthy guard is in
2147
+ * force. TypeScript then types a direct member receiver as the plain arm,
2148
+ * even though an OOB-safe assignment may have restored undefined. Check
2149
+ * that hidden arm and throw the member-read TypeError Node would produce. */
2150
+ function runtimeOptionalReceiverRead(L, expr, local, property, loc) {
2151
+ if (local.type.kind !== "union")
2152
+ throw new Error("runtime-optional local is not union-typed");
2153
+ const narrowed = L.mapTypeOf(L.typeOf(expr));
2154
+ if (!narrowed || narrowed.kind === "union" || isUnitType(narrowed)) {
2155
+ L.unsupported("SC1090", expr, "a runtime-optional capture whose narrowed value type is not representable");
2156
+ }
2157
+ const valueTag = L.armTag(local.type.unionId, narrowed);
2158
+ const undefTag = L.armTag(local.type.unionId, UNDEFINED_T);
2159
+ if (valueTag < 0 || undefTag < 0)
2160
+ throw new Error("runtime-optional local is missing its value or undefined arm");
2161
+ const def = L.unions.get(local.type.unionId);
2162
+ if (!def || def.arms.length !== 2) {
2163
+ L.unsupported("SC1090", expr, "a direct read from a runtime-optional capture with multiple value arms");
2164
+ }
2165
+ const ref = () => ({ kind: "varRef", localId: local.id, type: local.type, loc });
2166
+ const message = property === null
2167
+ ? `${expr.text} is not a function`
2168
+ : `Cannot read properties of undefined (reading '${property}')`;
2169
+ return {
2170
+ kind: "ternary",
2171
+ cond: {
2172
+ kind: "unionIsTag",
2173
+ unionId: local.type.unionId,
2174
+ tag: undefTag,
2175
+ negated: false,
2176
+ value: ref(),
2177
+ type: BOOL,
2178
+ loc,
2179
+ },
2180
+ then: nodeThrowExpr(1, "", message, narrowed, loc),
2181
+ else_: {
2182
+ kind: "unionNarrow",
2183
+ unionId: local.type.unionId,
2184
+ tag: valueTag,
2185
+ value: ref(),
2186
+ type: narrowed,
2187
+ loc,
2188
+ },
2189
+ type: narrowed,
2190
+ loc,
2191
+ };
2192
+ }
2193
+ function runtimeOptionalUseOf(expr) {
2194
+ let receiver = expr;
2195
+ let complex = false;
2196
+ let comma = false;
2197
+ for (;;) {
2198
+ const parent = receiver.parent;
2199
+ if ((ts.isParenthesizedExpression(parent) || ts.isAssertionExpression(parent) ||
2200
+ ts.isSatisfiesExpression(parent) ||
2201
+ ts.isNonNullExpression(parent)) &&
2202
+ parent.expression === receiver) {
2203
+ receiver = parent;
2204
+ continue;
2205
+ }
2206
+ if (ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.CommaToken && parent.right === receiver) {
2207
+ comma = true;
2208
+ receiver = parent;
2209
+ continue;
2210
+ }
2211
+ if ((ts.isBinaryExpression(parent) &&
2212
+ (parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
2213
+ parent.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
2214
+ parent.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) &&
2215
+ (parent.left === receiver || parent.right === receiver)) ||
2216
+ (ts.isConditionalExpression(parent) &&
2217
+ (parent.condition === receiver || parent.whenTrue === receiver || parent.whenFalse === receiver))) {
2218
+ complex = true;
2219
+ receiver = parent;
2220
+ continue;
2221
+ }
2222
+ break;
2223
+ }
2224
+ const access = receiver.parent;
2225
+ if (ts.isPropertyAccessExpression(access) && access.expression === receiver) {
2226
+ return { kind: "property", access, optional: access.questionDotToken !== undefined, complex, comma };
2227
+ }
2228
+ if (ts.isElementAccessExpression(access) && access.expression === receiver) {
2229
+ return { kind: "element", access, optional: access.questionDotToken !== undefined, complex, comma };
2230
+ }
2231
+ if (ts.isCallExpression(access) && access.expression === receiver) {
2232
+ return { kind: "call", access, optional: access.questionDotToken !== undefined, complex, comma };
2233
+ }
2234
+ return null;
2235
+ }
2236
+ function runtimeOptionalElementKey(expr) {
2237
+ if (ts.isStringLiteral(expr) || ts.isNumericLiteral(expr))
2238
+ return expr.text;
2239
+ if (ts.isNoSubstitutionTemplateLiteral(expr))
2240
+ return expr.text;
2241
+ return null;
2242
+ }
2243
+ function runtimeOptionalAssertionErases(L, expr, inner, use) {
2244
+ if (inner.type.kind !== "union" || !use?.optional || use.complex || L.armTag(inner.type.unionId, UNDEFINED_T) < 0)
2245
+ return false;
2246
+ const target = L.mapTypeOf(L.typeOf(expr));
2247
+ return !!target && target.kind !== "union" && typeEquals(L.stripUndefinedArm(inner.type), target);
2248
+ }
2113
2249
  /** `v === undefined` / `v !== null` — the narrowing tests for unit-armed
2114
2250
  * unions. A union operand compared with a unit literal lowers to a
2115
2251
  * runtime TAG test (unionIsTag); afterwards tsc's control-flow narrowing
@@ -2374,7 +2510,7 @@ function pureCondExpr(e) {
2374
2510
  * results (several non-unit arms) and defaults that change the result
2375
2511
  * type are fenced with narrow-first hints. */
2376
2512
  export function lowerNullishCoalesce(L, expr, loc) {
2377
- const left = L.lowerExpr(expr.left);
2513
+ const left = lowerAbsenceProbe(L, expr.left) ?? L.lowerExpr(expr.left);
2378
2514
  if (left.type.kind === "dyn") {
2379
2515
  // `a ?? b` on a CHECKED-DYNAMIC left: the deciding test is the
2380
2516
  // runtime kind (scr_dyn_is_nullish — UNDEF/NULL take the default;
@@ -2850,7 +2986,14 @@ export function finishOptionalChain(L, expr, id, receiver, body, loc) {
2850
2986
  if (body.type.kind === "dyn") {
2851
2987
  return { kind: "optChain", id, receiver, body, type: DYN, loc };
2852
2988
  }
2853
- const type = L.irTypeOf(expr);
2989
+ let type = L.irTypeOf(expr);
2990
+ const hiddenOptionalResult = (type.kind !== "union" || L.armTag(type.unionId, UNDEFINED_T) < 0) &&
2991
+ receiver.type.kind === "union" && L.armTag(receiver.type.unionId, UNDEFINED_T) >= 0 &&
2992
+ body.type.kind !== "generator" && body.type.kind !== "jsval"
2993
+ ? L.withUndefinedArmOf(body.type)
2994
+ : null;
2995
+ if (hiddenOptionalResult)
2996
+ type = hiddenOptionalResult;
2854
2997
  if (type.kind !== "union" || L.armTag(type.unionId, UNDEFINED_T) < 0) {
2855
2998
  L.unsupported("SC1090", expr, `'?.' where the result '${L.fmt(type)}' has no undefined arm (narrow the receiver with 'if (x !== undefined)' instead)`);
2856
2999
  }
@@ -2877,7 +3020,18 @@ export function lowerCondition(L, expr) {
2877
3020
  // (true for &&, false for ||) — aliased-typeof narrows the left
2878
3021
  // PROVES under that polarity hold while it lowers (`type ===
2879
3022
  // 'string' && val.length > 0`, the ms entry shape).
2880
- const right = L.narrowingAliases(aliasTypeofNarrows(L, e.left, isAnd), () => L.lowerCondition(e.right));
3023
+ const runtimeOptionalLocal = isAnd ? runtimeOptionalLocalOf(L, e.left) : null;
3024
+ const right = L.narrowingAliases(aliasTypeofNarrows(L, e.left, isAnd), () => {
3025
+ if (runtimeOptionalLocal === null)
3026
+ return L.lowerCondition(e.right);
3027
+ L.runtimeOptionalLocals.delete(L.runtimeOptionalRootOf(runtimeOptionalLocal));
3028
+ try {
3029
+ return L.lowerCondition(e.right);
3030
+ }
3031
+ finally {
3032
+ L.runtimeOptionalLocals.add(L.runtimeOptionalRootOf(runtimeOptionalLocal));
3033
+ }
3034
+ });
2881
3035
  return {
2882
3036
  kind: "logical",
2883
3037
  op: isAnd ? "&&" : "||",
@@ -2888,7 +3042,40 @@ export function lowerCondition(L, expr) {
2888
3042
  };
2889
3043
  }
2890
3044
  }
2891
- return L.ensureBool(L.lowerExpr(expr), expr);
3045
+ return L.ensureBool(lowerAbsenceProbe(L, expr) ?? L.lowerExpr(expr), expr);
3046
+ }
3047
+ function runtimeOptionalLocalOf(L, node) {
3048
+ let expr = node;
3049
+ while (ts.isParenthesizedExpression(expr))
3050
+ expr = expr.expression;
3051
+ if (!ts.isIdentifier(expr))
3052
+ return null;
3053
+ const local = L.resolveLocal(expr);
3054
+ return local && L.runtimeOptionalLocals.has(L.runtimeOptionalRootOf(local)) ? local : null;
3055
+ }
3056
+ export function runtimeOptionalTrueIds(L, node) {
3057
+ let expr = node;
3058
+ while (ts.isParenthesizedExpression(expr))
3059
+ expr = expr.expression;
3060
+ if (ts.isBinaryExpression(expr) &&
3061
+ expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
3062
+ return [...runtimeOptionalTrueIds(L, expr.left), ...runtimeOptionalTrueIds(L, expr.right)];
3063
+ }
3064
+ const local = runtimeOptionalLocalOf(L, expr);
3065
+ return local === null ? [] : [local];
3066
+ }
3067
+ export function withRuntimeOptionalNarrowed(L, locals, fn) {
3068
+ if (locals.length === 0)
3069
+ return fn();
3070
+ for (const local of locals)
3071
+ L.runtimeOptionalLocals.delete(L.runtimeOptionalRootOf(local));
3072
+ try {
3073
+ return fn();
3074
+ }
3075
+ finally {
3076
+ for (const local of locals)
3077
+ L.runtimeOptionalLocals.add(L.runtimeOptionalRootOf(local));
3078
+ }
2892
3079
  }
2893
3080
  /** JS ToBoolean: bool passes through; f64/string get a `toBool` wrapper
2894
3081
  * (falsy: 0, -0, NaN, ""); unions get the same wrapper answered by a
@@ -5595,13 +5782,18 @@ export function lowerElementAccess(L, expr) {
5595
5782
  // signature where the .d.ts says Function[], which would flatten the
5596
5783
  // element to a zero-parameter closure).
5597
5784
  const elemT = arr.type.kind === "array" ? arr.type.elem : receiverIr.elem;
5598
- // --npm-static package files: the OOB-SAFE read (elem | undefined)
5599
- // for the LAST-ELEMENT idiom — indexing a fresh slice() result
5600
- // (commander's `registeredArguments.slice(-1)[0]` probe), where the
5601
- // trap divergence would fire on working Node code and the fresh
5602
- // array's read can only be a probe. General indexed reads keep the
5603
- // documented trap (their results feed writes and member reads whose
5604
- // lowerings need the plain element).
5785
+ // OOB-SAFE reads over fresh/probe array results. These expressions are
5786
+ // commonly used to ask for a first/last match (`filter(...).sort(...)[0]`,
5787
+ // `slice(-1)[0]`); an empty result is ordinary undefined, not a bounds
5788
+ // failure. Downstream lowering keeps the explicit optional union, so an
5789
+ // unchecked consumer still cannot place it into a plain element slot.
5790
+ if (arr.type.kind === "array" && isFreshArrayProbe(expr.expression)) {
5791
+ const safe = lowerSafeIndexRead(L, arr, index, locOf(expr));
5792
+ if (safe)
5793
+ return safe;
5794
+ }
5795
+ // --npm-static package files retain the original slice-specific route
5796
+ // for compatibility with package-source inference.
5605
5797
  if (arr.type.kind === "array" &&
5606
5798
  arr.kind === "arrIntrinsic" &&
5607
5799
  arr.method === "slice" &&
@@ -5615,6 +5807,16 @@ export function lowerElementAccess(L, expr) {
5615
5807
  // narrows literal-index element accesses).
5616
5808
  return L.maybeNarrow({ kind: "arrayGet", arr, index, type: elemT, loc: locOf(expr) }, expr);
5617
5809
  }
5810
+ function isFreshArrayProbe(node) {
5811
+ let current = node;
5812
+ while (ts.isParenthesizedExpression(current))
5813
+ current = current.expression;
5814
+ return (ts.isCallExpression(current) &&
5815
+ ts.isPropertyAccessExpression(current.expression) &&
5816
+ (current.expression.name.text === "filter" ||
5817
+ current.expression.name.text === "slice" ||
5818
+ current.expression.name.text === "sort"));
5819
+ }
5618
5820
  /** `o[k]` where the RECEIVER is an island value — a jsval-mapped checker
5619
5821
  * type, or an array/tuple-typed .d.ts surface whose lowered value is a
5620
5822
  * handle. The key marshals in (any JS key kind — the engine does its
@@ -5654,7 +5856,7 @@ function islandElementRead(L, expr, obj) {
5654
5856
  * fields outside that stay fenced. Declared-only shapes support reads
5655
5857
  * whose key type proves membership (tsc's keyof check): all fields must
5656
5858
  * share the result type, and a smuggled miss traps. */
5657
- export function lowerRecordKeyRead(L, expr, shapeId, shape) {
5859
+ export function lowerRecordKeyRead(L, expr, shapeId, shape, includeUndefined = false) {
5658
5860
  const loc = locOf(expr);
5659
5861
  const keyNode = expr.argumentExpression;
5660
5862
  // Literal declared keys are plain field reads (narrowing included).
@@ -5721,7 +5923,7 @@ export function lowerRecordKeyRead(L, expr, shapeId, shape) {
5721
5923
  let declared = null;
5722
5924
  if (shape.indexValue) {
5723
5925
  declared = shape.indexValue;
5724
- if (L.program.getCompilerOptions().noUncheckedIndexedAccess) {
5926
+ if (includeUndefined || L.program.getCompilerOptions().noUncheckedIndexedAccess) {
5725
5927
  declared = L.withUndefinedArmOf(declared);
5726
5928
  if (!declared)
5727
5929
  L.badType(expr, L.typeOf(expr));
@@ -5740,7 +5942,72 @@ export function lowerRecordKeyRead(L, expr, shapeId, shape) {
5740
5942
  if (!recordKeyResultOk(L, overflowOnly ? { ...shape, fields: [] } : shape, declared)) {
5741
5943
  L.unsupported("SC1090", expr, `dynamic keyed reads of '${L.fmt({ kind: "record", shapeId })}' as '${L.fmt(declared)}' (every declared field must be readable at that type)`);
5742
5944
  }
5743
- return L.maybeNarrow({ kind: "recordKeyGet", obj, shapeId, key, ...(overflowOnly ? { overflowOnly: true } : {}), type: declared, loc }, expr);
5945
+ const read = {
5946
+ kind: "recordKeyGet",
5947
+ obj,
5948
+ shapeId,
5949
+ key,
5950
+ ...(overflowOnly ? { overflowOnly: true } : {}),
5951
+ type: declared,
5952
+ loc,
5953
+ };
5954
+ return includeUndefined ? read : L.maybeNarrow(read, expr);
5955
+ }
5956
+ /** A keyed read used only to observe absence (`if (map[k])`, or the left
5957
+ * side of `||`/`??`). JavaScript answers undefined for missing record keys
5958
+ * and array indices even when noUncheckedIndexedAccess is disabled. Keep
5959
+ * that value in an explicit union until the surrounding guard/default has
5960
+ * consumed it; ordinary unchecked reads retain the typed trap. Assertions
5961
+ * around the read are erased here because this context handles the missing
5962
+ * arm instead of consuming it as the asserted type. */
5963
+ export function lowerAbsenceProbe(L, node) {
5964
+ let expr = node;
5965
+ while (ts.isParenthesizedExpression(expr) || ts.isAsExpression(expr) || ts.isTypeAssertion(expr)) {
5966
+ expr = expr.expression;
5967
+ }
5968
+ if (ts.isPropertyAccessExpression(expr)) {
5969
+ const target = L.fieldTarget(expr);
5970
+ if (target?.container !== "recordOvf")
5971
+ return null;
5972
+ if (target.fieldType.kind === "union" &&
5973
+ L.armTag(target.fieldType.unionId, UNDEFINED_T) >= 0) {
5974
+ return null;
5975
+ }
5976
+ const type = L.withUndefinedArmOf(target.fieldType);
5977
+ if (!type)
5978
+ return null;
5979
+ return {
5980
+ kind: "recordKeyGet",
5981
+ obj: target.obj,
5982
+ shapeId: target.shapeId,
5983
+ key: { kind: "strLit", value: target.field, type: STRING, loc: locOf(expr) },
5984
+ overflowOnly: true,
5985
+ type,
5986
+ loc: locOf(expr),
5987
+ };
5988
+ }
5989
+ if (!ts.isElementAccessExpression(expr))
5990
+ return null;
5991
+ const header = L.lowerHttpHeadersElement(expr);
5992
+ if (header)
5993
+ return header;
5994
+ const receiverT = L.mapTypeOf(L.typeOf(expr.expression));
5995
+ if (receiverT?.kind === "record") {
5996
+ const shape = L.shapes.get(receiverT.shapeId);
5997
+ if (shape && !shape.tuple && shape.indexValue && shape.indexValue.kind !== "dyn") {
5998
+ return L.lowerRecordKeyRead(expr, receiverT.shapeId, shape, true);
5999
+ }
6000
+ return null;
6001
+ }
6002
+ if (receiverT?.kind !== "array")
6003
+ return null;
6004
+ const arr = L.lowerExpr(expr.expression);
6005
+ if (arr.type.kind !== "array")
6006
+ return null;
6007
+ const index = L.lowerExpr(expr.argumentExpression);
6008
+ if (index.type.kind !== "f64")
6009
+ return null;
6010
+ return lowerSafeIndexRead(L, arr, index, locOf(expr));
5744
6011
  }
5745
6012
  /** The compile-time string spelling of a record key literal: a string
5746
6013
  * literal's text, or a NON-NEGATIVE numeric literal's canonical JS
@@ -6320,6 +6587,9 @@ export function lowerAsExpression(L, expr) {
6320
6587
  L.unsupported("SC1063", expr);
6321
6588
  }
6322
6589
  const inner = L.lowerExpr(expr.expression);
6590
+ const use = runtimeOptionalUseOf(expr);
6591
+ if (inner.type.kind === "union" && runtimeOptionalAssertionErases(L, expr, inner, use))
6592
+ return inner;
6323
6593
  if (inner.type.kind !== "dyn" && inner.type.kind !== "jsval") {
6324
6594
  // A STATIC value cast `as any` is the explicit island entrance.
6325
6595
  const targetTs0 = L.checker.getTypeFromTypeNode(expr.type);
@@ -6560,6 +6830,9 @@ export function lowerBinary(L, expr) {
6560
6830
  L.rejectUnresolved(expr.left, `assignment to '${expr.left.text}' (not a writable local or module global)`);
6561
6831
  }
6562
6832
  const value = L.lowerExprExpecting(expr.right, target.type);
6833
+ const runtimeOptionalRoot = L.runtimeOptionalRootOf(target);
6834
+ if (L.runtimeOptionalStorageLocals.has(runtimeOptionalRoot))
6835
+ L.runtimeOptionalLocals.add(runtimeOptionalRoot);
6563
6836
  return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
6564
6837
  }
6565
6838
  // `events.defaultMaxListeners = v` — the module-property write
@@ -6719,11 +6992,16 @@ export function lowerBinary(L, expr) {
6719
6992
  L.unsupported("SC1040", expr);
6720
6993
  }
6721
6994
  if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
6995
+ if (op === ts.SyntaxKind.BarBarToken) {
6996
+ const fallback = lowerAbsenceAwareOrChain(L, expr);
6997
+ if (fallback)
6998
+ return fallback;
6999
+ }
6722
7000
  // JS value semantics: `a && b` is `toBool(a) ? b : a` — the result is
6723
7001
  // an operand value, so both operands must share one IR kind (which is
6724
7002
  // also what tsc's type says, modulo literal-union collapsing that
6725
7003
  // mapType already handles). Mixed kinds (`n && s`) stay rejected.
6726
- const left = L.lowerExpr(expr.left);
7004
+ const left = lowerAbsenceProbe(L, expr.left) ?? L.lowerExpr(expr.left);
6727
7005
  const right = L.lowerExpr(expr.right);
6728
7006
  // A LITERAL-unit left operand (a compile-time undefined/null — the
6729
7007
  // capability-probe members: `process.features.inspector ||
@@ -6894,8 +7172,10 @@ export function lowerBinary(L, expr) {
6894
7172
  if (test)
6895
7173
  return test;
6896
7174
  }
6897
- const left = L.lowerExpr(expr.left);
6898
- const right = L.lowerExpr(expr.right);
7175
+ const strictEquality = op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
7176
+ op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
7177
+ const left = strictEquality ? lowerAbsenceProbe(L, expr.left) ?? L.lowerExpr(expr.left) : L.lowerExpr(expr.left);
7178
+ const right = strictEquality ? lowerAbsenceProbe(L, expr.right) ?? L.lowerExpr(expr.right) : L.lowerExpr(expr.right);
6899
7179
  if (left.type.kind === "dyn" || right.type.kind === "dyn") {
6900
7180
  // The narrowing unit comparisons ARE answerable on unknown: `v ===
6901
7181
  // undefined` / `v !== null` test the dyn node's kind directly, and
@@ -7106,6 +7386,29 @@ export function lowerBinary(L, expr) {
7106
7386
  const ut = left.type.kind === "union" ? left.type : right.type;
7107
7387
  const bothUnion = left.type.kind === "union" && right.type.kind === "union";
7108
7388
  const sameUnion = bothUnion && typeEquals(left.type, right.type);
7389
+ const plainFunc = left.type.kind === "func" ? left : right.type.kind === "func" ? right : null;
7390
+ const unionArms = L.unions.get(ut.unionId)?.arms ?? [];
7391
+ const funcArm = unionArms.find((arm) => arm.kind === "func");
7392
+ // `listeners()` reports original prefix callbacks, but the
7393
+ // checker views an indexed result as function | undefined. For
7394
+ // identity comparison, retain the original closure pointer rather
7395
+ // than adapting it to the tuple signature.
7396
+ if (!bothUnion &&
7397
+ plainFunc !== null &&
7398
+ funcArm?.kind === "func" &&
7399
+ unionArms.filter((arm) => arm.kind === "func").length === 1 &&
7400
+ unionArms.some(isUnitType)) {
7401
+ return {
7402
+ kind: "unionFuncEq",
7403
+ unionId: ut.unionId,
7404
+ tag: L.armTag(ut.unionId, funcArm),
7405
+ union: left.type.kind === "union" ? left : right,
7406
+ func: plainFunc,
7407
+ negated,
7408
+ type: BOOL,
7409
+ loc,
7410
+ };
7411
+ }
7109
7412
  if ((sameUnion || !bothUnion) && L.eqComparableUnion(ut.unionId)) {
7110
7413
  return {
7111
7414
  kind: "unionEq",
@@ -7193,6 +7496,44 @@ export function lowerBinary(L, expr) {
7193
7496
  }
7194
7497
  L.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}'`);
7195
7498
  }
7499
+ function lowerAbsenceAwareOrChain(L, expr) {
7500
+ const nodes = [];
7501
+ const collect = (node) => {
7502
+ if (ts.isBinaryExpression(node) &&
7503
+ node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
7504
+ collect(node.left);
7505
+ collect(node.right);
7506
+ }
7507
+ else {
7508
+ nodes.push(node);
7509
+ }
7510
+ };
7511
+ collect(expr);
7512
+ if (nodes.length < 3)
7513
+ return null;
7514
+ const lowered = nodes.map((node) => lowerAbsenceProbe(L, node) ?? L.lowerExpr(node));
7515
+ if (!lowered.some((value) => value.type.kind === "union" && L.armTag(value.type.unionId, UNDEFINED_T) >= 0)) {
7516
+ return null;
7517
+ }
7518
+ let result = lowered[lowered.length - 1];
7519
+ for (let i = lowered.length - 2; i >= 0; i--) {
7520
+ const left = lowered[i];
7521
+ const loc = locOf(nodes[i]);
7522
+ if (left.type.kind === "union") {
7523
+ const def = L.unions.get(left.type.unionId);
7524
+ const nonUnit = def?.arms.filter((arm) => !isUnitType(arm)) ?? [];
7525
+ if (nonUnit.length !== 1 || !typeEquals(nonUnit[0], result.type))
7526
+ return null;
7527
+ L.requireTruthyUnion(left.type.unionId, expr);
7528
+ result = { kind: "orDefault", left, right: result, type: result.type, loc };
7529
+ continue;
7530
+ }
7531
+ if (!typeEquals(left.type, result.type))
7532
+ return null;
7533
+ result = { kind: "logical", op: "||", left, right: result, type: result.type, loc };
7534
+ }
7535
+ return result;
7536
+ }
7196
7537
  /** `typeof e === "lit"` / `typeof e !== "lit"` where e is a catch
7197
7538
  * binding: the runtime kind test over the snapshot's tag. Only the
7198
7539
  * primitive literals narrow ("string"/"number"/"boolean" — exactly what