@scriptc/compiler 0.0.36 → 0.1.1

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 (113) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +7 -5
  2. package/ambient/scriptc-overrides.d.ts +0 -7
  3. package/ambient/scriptc.d.ts +1 -1
  4. package/dist/backend/c/exprs.js +69 -45
  5. package/dist/backend/c/exprs.js.map +1 -1
  6. package/dist/backend/c/may-throw.js +3 -0
  7. package/dist/backend/c/may-throw.js.map +1 -1
  8. package/dist/backend/c/stmts.js +18 -0
  9. package/dist/backend/c/stmts.js.map +1 -1
  10. package/dist/backend/c/walkers.js +5 -7
  11. package/dist/backend/c/walkers.js.map +1 -1
  12. package/dist/backend/external-c.d.ts +33 -0
  13. package/dist/backend/external-c.js +51 -0
  14. package/dist/backend/external-c.js.map +1 -0
  15. package/dist/backend/link-plan.d.ts +37 -0
  16. package/dist/backend/link-plan.js +26 -0
  17. package/dist/backend/link-plan.js.map +1 -0
  18. package/dist/backend/linker.d.ts +22 -0
  19. package/dist/backend/linker.js +236 -0
  20. package/dist/backend/linker.js.map +1 -0
  21. package/dist/backend/llvm/dyn.js +7 -0
  22. package/dist/backend/llvm/dyn.js.map +1 -1
  23. package/dist/backend/llvm/emitter.js +34 -7
  24. package/dist/backend/llvm/emitter.js.map +1 -1
  25. package/dist/backend/llvm/expr-containers.js +68 -54
  26. package/dist/backend/llvm/expr-containers.js.map +1 -1
  27. package/dist/backend/llvm/expr-context.d.ts +1 -1
  28. package/dist/backend/llvm/expr-dispatch.js +2 -0
  29. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  30. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  31. package/dist/backend/llvm/expr-primitives.js +20 -0
  32. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  33. package/dist/backend/llvm/expr-stream-bridges.js +25 -1
  34. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  35. package/dist/backend/llvm/walkers.js +31 -4
  36. package/dist/backend/llvm/walkers.js.map +1 -1
  37. package/dist/backend/native-codegen.d.ts +8 -1
  38. package/dist/backend/native-codegen.js +39 -25
  39. package/dist/backend/native-codegen.js.map +1 -1
  40. package/dist/backend/native-codegen.test.js +42 -4
  41. package/dist/backend/native-codegen.test.js.map +1 -1
  42. package/dist/backend/native-link-info.d.ts +1 -1
  43. package/dist/backend/native-link-info.js +42 -18
  44. package/dist/backend/native-link-info.js.map +1 -1
  45. package/dist/backend/native-toolchain.test.js +5 -1
  46. package/dist/backend/native-toolchain.test.js.map +1 -1
  47. package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
  48. package/dist/backend/runtime-pack-targets.test.js +54 -0
  49. package/dist/backend/runtime-pack-targets.test.js.map +1 -0
  50. package/dist/backend/runtime-pack.d.ts +5 -30
  51. package/dist/backend/runtime-pack.js +46 -141
  52. package/dist/backend/runtime-pack.js.map +1 -1
  53. package/dist/backend/runtime-pack.test.js +49 -14
  54. package/dist/backend/runtime-pack.test.js.map +1 -1
  55. package/dist/backend/targets.d.ts +63 -17
  56. package/dist/backend/targets.js +251 -35
  57. package/dist/backend/targets.js.map +1 -1
  58. package/dist/backend/targets.test.js +27 -5
  59. package/dist/backend/targets.test.js.map +1 -1
  60. package/dist/coverage/surface-manifest.js +10 -1
  61. package/dist/coverage/surface-manifest.js.map +1 -1
  62. package/dist/frontend/lowering/array-values.d.ts +8 -0
  63. package/dist/frontend/lowering/array-values.js +60 -0
  64. package/dist/frontend/lowering/array-values.js.map +1 -0
  65. package/dist/frontend/lowering/lower-array-sort.d.ts +22 -0
  66. package/dist/frontend/lowering/lower-array-sort.js +619 -0
  67. package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
  68. package/dist/frontend/lowering/lower-builtins.js +164 -4
  69. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  70. package/dist/frontend/lowering/lower-calls.d.ts +3 -4
  71. package/dist/frontend/lowering/lower-calls.js +260 -27
  72. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  73. package/dist/frontend/lowering/lower-containers.js +728 -655
  74. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  75. package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
  76. package/dist/frontend/lowering/lower-exprs.js +769 -101
  77. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  78. package/dist/frontend/lowering/lower-inspect.js +47 -33
  79. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  80. package/dist/frontend/lowering/lower-island.d.ts +5 -4
  81. package/dist/frontend/lowering/lower-island.js +10 -5
  82. package/dist/frontend/lowering/lower-island.js.map +1 -1
  83. package/dist/frontend/lowering/lower-server.js +10 -0
  84. package/dist/frontend/lowering/lower-server.js.map +1 -1
  85. package/dist/frontend/lowering/lower-stmts.js +302 -71
  86. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  87. package/dist/frontend/lowering/lowerer.d.ts +74 -2
  88. package/dist/frontend/lowering/lowerer.js +1087 -10
  89. package/dist/frontend/lowering/lowerer.js.map +1 -1
  90. package/dist/frontend/lowering/surfaces.d.ts +10 -2
  91. package/dist/frontend/lowering/surfaces.js +15 -3
  92. package/dist/frontend/lowering/surfaces.js.map +1 -1
  93. package/dist/index.d.ts +3 -3
  94. package/dist/index.js +45 -23
  95. package/dist/index.js.map +1 -1
  96. package/dist/ir/ir.d.ts +65 -11
  97. package/dist/ir/ir.js +25 -25
  98. package/dist/ir/ir.js.map +1 -1
  99. package/dist/ir/serialize.d.ts +1 -1
  100. package/dist/ir/serialize.js +1 -1
  101. package/dist/ir/validate.js +84 -37
  102. package/dist/ir/validate.js.map +1 -1
  103. package/dist/library/fence-eval.js +3 -1
  104. package/dist/library/fence-eval.js.map +1 -1
  105. package/dist/library/int-infer.js +12 -1
  106. package/dist/library/int-infer.js.map +1 -1
  107. package/dist/library/int-infer.test.js +4 -4
  108. package/dist/library/library-cache.test.js +5 -5
  109. package/dist/startup-cache.d.ts +9 -1
  110. package/dist/startup-cache.js +16 -1
  111. package/dist/startup-cache.js.map +1 -1
  112. package/package.json +17 -4
  113. package/surface-manifest.json +5 -5
@@ -18,7 +18,7 @@ import { tsgoPath } from "../dts-paths.js";
18
18
  import { isRelativeSpecifier } from "../workspace-registry.js";
19
19
  import * as ts from "../ts7/adapter.js";
20
20
  import { anyOpRequiresDynamicDiag, blockedBindingUseDiag, checkerPanicDiag, componentTypeDiag, isCheckerPanic, genericSignatureTypeDiag, indexSignatureTypeDiag, intersectionTypeDiag, noLoweringDiag, overloadedSignatureTypeDiag, recordShapeMismatchDiag, requiresDynamicApiDiag, requiresDynamicPackageDiag, requiresDynamicTypeDiag, unionMismatchDiag, unsupportedDiag, unsupportedTypeDiag, } from "../../diagnostics/diagnostic.js";
21
- import { arrayOf, BOOL, canAdaptDynFuncTo, canConvertToDyn, canCrossIslandBoundary, canExitIslandToType, canMarshalTypedFuncIntoIsland, DYN, F64, isJsonSafeType, isUndefinedArmedUnion, isUnitType, JSVAL, RUNTIME_ERROR_CLASSES, STRING, typeEquals, UNDEFINED_T, VOID } from "../../ir/ir.js";
21
+ import { arrayOf, BOOL, canAdaptDynFuncTo, canConvertToDyn, canCrossIslandBoundary, canExitIslandToType, canMarshalTypedFuncIntoIsland, DYN, F64, isJsonSafeType, isJsonStringifySafeType, isUndefinedArmedUnion, isUnitType, JSVAL, RUNTIME_ERROR_CLASSES, STRING, typeEquals, UNDEFINED_T, VOID } from "../../ir/ir.js";
22
22
  import { provenanceActive } from "../provenance-registry.js";
23
23
  import { ambientDtsPath, canonicalBuiltinModule, cjsExportAssignmentOf, cjsExportDiscardReason, fallbackDtsPath, isCjsExportTableLiteral, isJsSourceFile, isNodeEsmFile, isNodeTypesPath, locOf, orderedImportsOf, overridesDtsPath, npmStaticDepSf7, requireSpecOf, resolveImport, workspacePackageOfPath, } from "../program.js";
24
24
  import { containsRecord, containsUnion, describeComponentBlocker, describeRecordMemberBlocker, formatIrType, ISLAND_AMBIENT_TYPES, isUnitOnlyTsType, mapType, ShapeRegistry, typeKey, UnionRegistry, withUndefinedArm as withUndefinedArmCanonical, } from "../type-mapper.js";
@@ -81,7 +81,7 @@ export function newFnCtx(lifted, selfSymbol, selfType, returnType) {
81
81
  * always visited. */
82
82
  const IR_STMT_KINDS = new Set([
83
83
  "varDecl", "assign", "exprStmt", "if", "while", "doWhile", "switch",
84
- "arraySet", "forOf", "return", "fieldSet", "recordSet", "break",
84
+ "arraySet", "arraySetLength", "arraySetUndefined", "arrayDelete", "forOf", "return", "fieldSet", "recordSet", "break",
85
85
  "continue", "block", "tryCatch", "throw", "rethrow", "runtimeFence",
86
86
  ]);
87
87
  /** True when a lowered statement's OWN expressions contain island
@@ -821,12 +821,233 @@ export class Lowerer {
821
821
  /** All storage slots widened for runtime absence, including slots whose
822
822
  * current control-flow branch has temporarily narrowed the value. */
823
823
  runtimeOptionalStorageLocals = new Set();
824
+ /** Globals whose checker-bare type was widened because an indexed read can
825
+ * carry undefined at runtime. Identifier reads keep the union tag so
826
+ * typeof and later boundaries observe the real value. */
827
+ runtimeOptionalGlobals = new Set();
828
+ /** Bindings whose unchecked string arithmetic can produce either a number
829
+ * (NaN for an absent read) or a string. Preserve that result union at bare
830
+ * reads instead of applying the checker-bare string narrowing adapter. */
831
+ runtimeOptionalArithmeticLocals = new Set();
832
+ runtimeOptionalArithmeticGlobals = new Set();
833
+ /** Binding symbols whose IR storage type was widened by the indexed-read
834
+ * prepass. This includes explicit annotations: unchecked TS annotations do
835
+ * not prove that an array property read produced a value. */
836
+ runtimeOptionalBindingTypes = new Map();
837
+ /** Concise callback/lambda returns promoted by the HOF callback prepass;
838
+ * arrows have no declaration symbol to key in fnSigsBySymbol. */
839
+ runtimeOptionalFunctionReturns = new WeakMap();
840
+ /** Arithmetic over an unchecked string read can answer either NaN or a
841
+ * string. Bindings and returns use this marker to retain that result union
842
+ * through checker-bare string annotations. */
843
+ runtimeOptionalArithmeticTypes = new WeakMap();
844
+ /** Record fields promoted to an undefined-armed union by an indexed-read
845
+ * value. The key is the concrete emitted shape and field name. */
846
+ runtimeOptionalFields = new Set();
847
+ /** Callback pattern parameters carry an optional source without making
848
+ * every name destructured from a present source optional. */
849
+ runtimeOptionalPatternTypes = new WeakMap();
850
+ runtimeOptionalReduceTypes = new WeakMap();
824
851
  /** Capture entries and their origin share one mutable box. Normalize each
825
852
  * entry to the origin so writes and flow proofs stay synchronized. */
826
853
  runtimeOptionalRoots = new Map();
827
854
  runtimeOptionalRootOf(local) {
828
855
  return this.runtimeOptionalRoots.get(local) ?? local;
829
856
  }
857
+ runtimeOptionalBindingType(node, fallback) {
858
+ const patternType = this.runtimeOptionalPatternTypes.get(node);
859
+ if (patternType)
860
+ return patternType;
861
+ const symbol = ts.isIdentifier(node) ? this.resolveValueSymbol(node) : this.checker.getSymbolAtLocation(node);
862
+ if (!symbol)
863
+ return fallback ?? null;
864
+ const known = this.runtimeOptionalBindingTypes.get(symbol);
865
+ if (known)
866
+ return known;
867
+ // A module-scope function alias is collected as a global before the
868
+ // optional-read fixed point settles. Derive its promoted function ABI
869
+ // directly from the aliased declaration as a final read-side hook, so
870
+ // the initializer cannot fall back to a narrowing adapter.
871
+ if (ts.isIdentifier(node) && fallback?.kind === "func") {
872
+ const decl = this.checker.valueDeclarationOf(symbol);
873
+ if (decl && ts.isVariableDeclaration(decl) && decl.initializer) {
874
+ let init = decl.initializer;
875
+ while (ts.isParenthesizedExpression(init))
876
+ init = init.expression;
877
+ if (ts.isIdentifier(init)) {
878
+ const source = this.resolveValueSymbol(init);
879
+ const sig = source ? this.fnSigsBySymbol.get(source) : undefined;
880
+ if (sig) {
881
+ return {
882
+ ...fallback,
883
+ params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
884
+ ret: sig.returnType,
885
+ };
886
+ }
887
+ }
888
+ }
889
+ }
890
+ return fallback ?? null;
891
+ }
892
+ runtimeOptionalIdentifierValue(node) {
893
+ if (!ts.isIdentifier(node) || this.chainRecvByNode.has(node))
894
+ return null;
895
+ const local = this.resolveLocal(node);
896
+ const symbol = this.resolveValueSymbol(node);
897
+ const storage = local ?? (symbol ? this.globalsBySymbol.get(symbol) : undefined);
898
+ if (!storage || storage.type.kind !== "union" || this.armTag(storage.type.unionId, UNDEFINED_T) < 0)
899
+ return null;
900
+ const present = this.stripUndefinedArm(storage.type);
901
+ if (isUnitType(present))
902
+ return null;
903
+ return { value: varRef(storage.id, storage.type, locOf(node)), present, unionId: storage.type.unionId };
904
+ }
905
+ /** Recover the original optional union when a checked single-arm helper
906
+ * was installed before a strict property consumer could name its member.
907
+ * Recovery is limited to array element reads and storage roots marked by
908
+ * the optional-read analysis, so ordinary assertions keep their generic
909
+ * checked-narrow behavior. */
910
+ runtimeOptionalSourceValue(node, value) {
911
+ let origin = node;
912
+ while (ts.isParenthesizedExpression(origin))
913
+ origin = origin.expression;
914
+ let optionalOrigin = ts.isElementAccessExpression(origin);
915
+ if (ts.isIdentifier(origin)) {
916
+ const local = this.resolveLocal(origin);
917
+ const root = local ? this.runtimeOptionalRootOf(local) : null;
918
+ const global = this.globalOf(origin);
919
+ optionalOrigin =
920
+ (!!root && this.runtimeOptionalStorageLocals.has(root)) ||
921
+ (!!global && (this.isRuntimeOptionalGlobal(global) ||
922
+ (global.type.kind === "union" && this.armTag(global.type.unionId, UNDEFINED_T) >= 0)));
923
+ }
924
+ if (!optionalOrigin)
925
+ return null;
926
+ let source = value;
927
+ if (value.kind === "call" &&
928
+ this.checkedNarrowHelpers.has(value.callee) &&
929
+ value.args.length === 1 &&
930
+ value.args[0]?.type.kind === "union") {
931
+ source = value.args[0];
932
+ }
933
+ return source.type.kind === "union" && this.armTag(source.type.unionId, UNDEFINED_T) >= 0
934
+ ? source
935
+ : null;
936
+ }
937
+ /** A strict property or method receiver over an array-derived optional
938
+ * value. Stabilize the receiver once, throw Node's member-read TypeError
939
+ * for a unit arm, and extract the expected present arm otherwise. */
940
+ runtimeOptionalPropertyReceiver(node, value, expected, member) {
941
+ const source = this.runtimeOptionalSourceValue(node, value);
942
+ if (source?.type.kind !== "union")
943
+ return null;
944
+ const def = this.unions.get(source.type.unionId);
945
+ const valueTag = this.armTag(source.type.unionId, expected);
946
+ if (!def ||
947
+ valueTag < 0 ||
948
+ !def.arms.every((arm) => typeEquals(arm, expected) || isUnitType(arm)))
949
+ return null;
950
+ const loc = locOf(node);
951
+ const stable = this.declareHiddenLocal("%propertyRecv", source.type);
952
+ const stableRef = () => varRef(stable.id, source.type, loc);
953
+ let result = {
954
+ kind: "unionNarrow",
955
+ unionId: source.type.unionId,
956
+ tag: valueTag,
957
+ value: stableRef(),
958
+ type: expected,
959
+ loc,
960
+ };
961
+ for (let i = def.arms.length - 1; i >= 0; i--) {
962
+ const arm = def.arms[i];
963
+ if (!arm || !isUnitType(arm))
964
+ continue;
965
+ const unit = arm.kind === "nullT" ? "null" : "undefined";
966
+ result = {
967
+ kind: "ternary",
968
+ cond: {
969
+ kind: "unionIsTag",
970
+ unionId: source.type.unionId,
971
+ tag: i,
972
+ negated: false,
973
+ value: stableRef(),
974
+ type: BOOL,
975
+ loc,
976
+ },
977
+ then: nodeThrowExpr(1, "", `Cannot read properties of ${unit} (reading '${member}')`, expected, loc),
978
+ else_: result,
979
+ type: expected,
980
+ loc,
981
+ };
982
+ }
983
+ return {
984
+ kind: "seqExpr",
985
+ stmts: [{ kind: "varDecl", localId: stable.id, init: source, loc }],
986
+ result,
987
+ type: expected,
988
+ loc,
989
+ };
990
+ }
991
+ runtimeOptionalFieldKey(shapeId, field) {
992
+ return `${shapeId}:${field}`;
993
+ }
994
+ isRuntimeOptionalField(shapeId, field) {
995
+ return this.runtimeOptionalFields.has(this.runtimeOptionalFieldKey(shapeId, field));
996
+ }
997
+ isRuntimeOptionalGlobal(global) {
998
+ return this.runtimeOptionalGlobals.has(global);
999
+ }
1000
+ isRuntimeOptionalArithmeticGlobal(global) {
1001
+ return this.runtimeOptionalArithmeticGlobals.has(global);
1002
+ }
1003
+ /** The union produced when an unchecked array read reaches a typed slot
1004
+ * whose checker type is the corresponding bare arm. */
1005
+ runtimeOptionalWidening(actual, expected) {
1006
+ if (actual.kind !== "union" || this.armTag(actual.unionId, UNDEFINED_T) < 0)
1007
+ return null;
1008
+ return typeEquals(this.stripUndefinedArm(actual), expected) ? actual : null;
1009
+ }
1010
+ runtimeOptionalType(t) {
1011
+ if (t.kind === "union")
1012
+ return this.armTag(t.unionId, UNDEFINED_T) >= 0 ? t : this.withUndefinedArmOf(t) ?? t;
1013
+ if (t.kind === "void" || t.kind === "dyn" || t.kind === "jsval" || t.kind === "generator")
1014
+ return t;
1015
+ return this.withUndefinedArm(t);
1016
+ }
1017
+ promoteRuntimeOptionalParameter(node, type) {
1018
+ const widened = this.runtimeOptionalType(type);
1019
+ const symbol = this.checker.getSymbolAtLocation(node);
1020
+ if (symbol)
1021
+ this.runtimeOptionalBindingTypes.set(symbol, widened);
1022
+ return widened;
1023
+ }
1024
+ promoteRuntimeOptionalFunctionReturn(node, type) {
1025
+ const widened = this.runtimeOptionalType(type);
1026
+ this.runtimeOptionalFunctionReturns.set(node, widened);
1027
+ return widened;
1028
+ }
1029
+ runtimeOptionalFunctionReturnType(node, fallback) {
1030
+ return this.runtimeOptionalFunctionReturns.get(node) ?? fallback;
1031
+ }
1032
+ /** Promote one field of a record storage shape to the value's runtime
1033
+ * type. Shapes are immutable by identity, so this interns a sibling shape
1034
+ * and records the field as runtime-optional for property-read narrowing. */
1035
+ runtimeOptionalRecordField(type, field, fieldType) {
1036
+ if (type.kind !== "record")
1037
+ return type;
1038
+ const shape = this.shapes.get(type.shapeId);
1039
+ if (!shape)
1040
+ return type;
1041
+ const existing = shape.fields.find((f) => f.name === field);
1042
+ if (!existing || typeEquals(existing.type, fieldType)) {
1043
+ if (existing)
1044
+ this.runtimeOptionalFields.add(this.runtimeOptionalFieldKey(type.shapeId, field));
1045
+ return type;
1046
+ }
1047
+ const shapeId = this.shapes.intern(shape.fields.map((f) => ({ name: f.name, type: f.name === field ? fieldType : f.type })), shape.tuple === true, shape.indexValue, shape.declaredOrder);
1048
+ this.runtimeOptionalFields.add(this.runtimeOptionalFieldKey(shapeId, field));
1049
+ return { kind: "record", shapeId };
1050
+ }
830
1051
  /** Runs `fn` with the given aliased-typeof narrows applied (and restored
831
1052
  * after) — the branch-scoping primitive. */
832
1053
  narrowingAliases(narrows, fn) {
@@ -920,6 +1141,10 @@ export class Lowerer {
920
1141
  * declaration: member reads call the getter (lower-exprs). */
921
1142
  cjsAccessorFns = new Map();
922
1143
  narrowHelpers = new Map();
1144
+ /** Exact members of narrowHelpers produced by narrowedArmHelper. This
1145
+ * lets property consumers recognize an earlier checked extraction by
1146
+ * provenance instead of relying on its generated-name prefix. */
1147
+ checkedNarrowHelpers = new Set();
923
1148
  /** Interned `%iter.drain.<n>` helpers (classIteratorDrainCall): one per
924
1149
  * receiver class — the eager drain of a class iterable's protocol into
925
1150
  * a fresh element array, behind array/call spreads. */
@@ -2011,6 +2236,720 @@ export class Lowerer {
2011
2236
  collectJsonImports(parts) {
2012
2237
  return collectJsonImports(this, parts);
2013
2238
  }
2239
+ /** Discover the unchecked-array values that cross static ABI/storage
2240
+ * boundaries before any function body or module initializer is emitted.
2241
+ * TypeScript's default indexed-access type is a useful source annotation,
2242
+ * but it is not a runtime proof; promoting only the affected slots keeps
2243
+ * the rest of the dense ABI unchanged. */
2244
+ analyzeRuntimeOptionalArrayReads(parts) {
2245
+ const optionalSymbols = new Set();
2246
+ const optionalReturns = new Set();
2247
+ const arithmeticReturns = new Map();
2248
+ const optionalParams = new Map();
2249
+ const optionalFields = new Map();
2250
+ const dynamicObjectEntryRows = new Set();
2251
+ const sourceFiles = parts.map((p) => p.sf);
2252
+ const fnDecls = parts.flatMap((p) => p.fnDecls);
2253
+ // This fixed-point scan intentionally sees deferred bodies before
2254
+ // reachability is known. Batch their binding queries through the
2255
+ // facade's panic fence, then keep lookup free of lowering side effects:
2256
+ // resolveValueSymbol also flushes deferred diagnostics and applies
2257
+ // merged-namespace fences, whose authority remains ordinary lowering.
2258
+ this.checker.prefetchSymbolRoots(sourceFiles);
2259
+ const symbolOf = (node) => {
2260
+ const s = this.checker.getSymbolAtLocation(node);
2261
+ if (!s)
2262
+ return null;
2263
+ return s.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(s) : s;
2264
+ };
2265
+ const functionDeclBySymbol = new Map();
2266
+ const signatureBySymbol = new Map();
2267
+ for (const [symbol, sig] of this.fnSigsBySymbol)
2268
+ signatureBySymbol.set(symbol, { params: sig.params, returnType: sig.returnType, top: sig });
2269
+ for (const decl of fnDecls) {
2270
+ const symbol = declSymbolOf(this, decl);
2271
+ if (symbol)
2272
+ functionDeclBySymbol.set(symbol, decl);
2273
+ }
2274
+ for (const sf of sourceFiles) {
2275
+ ts.walkPreorder(sf, (node) => {
2276
+ if ((ts.isMethodDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) &&
2277
+ node.body && node.name && (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name))) {
2278
+ const symbol = symbolOf(node.name);
2279
+ if (symbol)
2280
+ functionDeclBySymbol.set(symbol, node);
2281
+ }
2282
+ });
2283
+ }
2284
+ for (const info of this.classes.values()) {
2285
+ for (const { mName, member } of this.classMethodMembers(info)) {
2286
+ if (!member.name || !(ts.isIdentifier(member.name) || ts.isStringLiteral(member.name)))
2287
+ continue;
2288
+ const symbol = symbolOf(member.name);
2289
+ const sig = info.methods.get(mName);
2290
+ if (symbol && sig)
2291
+ signatureBySymbol.set(symbol, { params: sig.params, returnType: sig.ret, method: sig });
2292
+ }
2293
+ }
2294
+ const peel = (node) => {
2295
+ let e = node;
2296
+ while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isTypeAssertion(e) || ts.isNonNullExpression(e)) {
2297
+ e = e.expression;
2298
+ }
2299
+ return e;
2300
+ };
2301
+ const explicitlyNonNull = (node) => {
2302
+ let e = node;
2303
+ while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isTypeAssertion(e) || ts.isNonNullExpression(e)) {
2304
+ if (ts.isNonNullExpression(e))
2305
+ return true;
2306
+ e = e.expression;
2307
+ }
2308
+ return false;
2309
+ };
2310
+ const isArrayRead = (node) => {
2311
+ const e = peel(node);
2312
+ if (!ts.isElementAccessExpression(e))
2313
+ return false;
2314
+ return this.mapTypeOf(this.typeOf(e.expression))?.kind === "array";
2315
+ };
2316
+ const isDynamicObjectEntryRead = (node) => {
2317
+ const read = peel(node);
2318
+ if (!ts.isElementAccessExpression(read) || !ts.isIdentifier(read.expression))
2319
+ return false;
2320
+ const source = symbolOf(read.expression);
2321
+ const declaration = source ? this.checker.valueDeclarationOf(source) : undefined;
2322
+ if (!declaration || !ts.isVariableDeclaration(declaration) || !declaration.initializer)
2323
+ return false;
2324
+ const init = peel(declaration.initializer);
2325
+ if (!ts.isCallExpression(init) || !ts.isPropertyAccessExpression(init.expression))
2326
+ return false;
2327
+ if (init.expression.name.text !== "entries" || !this.isStdlibGlobal(init.expression.expression, "Object"))
2328
+ return false;
2329
+ const object = init.arguments[0];
2330
+ const objectType = object ? this.mapTypeOf(this.typeOf(object)) : null;
2331
+ return objectType?.kind === "dyn" || objectType?.kind === "jsval";
2332
+ };
2333
+ const addUndefined = (t) => {
2334
+ if (t.kind === "union")
2335
+ return this.armTag(t.unionId, UNDEFINED_T) >= 0 ? t : this.withUndefinedArmOf(t) ?? t;
2336
+ if (t.kind === "void" || t.kind === "dyn" || t.kind === "jsval" || t.kind === "generator")
2337
+ return t;
2338
+ return this.withUndefinedArm(t);
2339
+ };
2340
+ const fieldName = (node) => node.name.text;
2341
+ const mayBeOptional = (node) => {
2342
+ // `xs[i]!` is the explicit proven-present form. Its array read keeps
2343
+ // the established dense bounds trap and must not promote the enclosing
2344
+ // function/global ABI to `T | undefined`.
2345
+ if (explicitlyNonNull(node))
2346
+ return false;
2347
+ const e = peel(node);
2348
+ if (isArrayRead(e))
2349
+ return true;
2350
+ if (ts.isCallExpression(e) && this.runtimeOptionalReduceTypes.has(e))
2351
+ return true;
2352
+ if (ts.isCallExpression(e) &&
2353
+ ts.isPropertyAccessExpression(e.expression) &&
2354
+ (e.expression.name.text === "pop" || e.expression.name.text === "shift") &&
2355
+ this.mapTypeOf(this.typeOf(e))?.kind === "union" &&
2356
+ this.armTag(this.mapTypeOf(this.typeOf(e)).unionId, UNDEFINED_T) >= 0)
2357
+ return true;
2358
+ if (ts.isIdentifier(e))
2359
+ return optionalSymbols.has(symbolOf(e) ?? {});
2360
+ if (ts.isCallExpression(e) && ts.isIdentifier(e.expression)) {
2361
+ return optionalReturns.has(symbolOf(e.expression) ?? {});
2362
+ }
2363
+ if (ts.isCallExpression(e) && ts.isPropertyAccessExpression(e.expression)) {
2364
+ return optionalReturns.has(symbolOf(e.expression.name) ?? {});
2365
+ }
2366
+ if (ts.isConditionalExpression(e)) {
2367
+ return mayBeOptional(e.whenTrue) || mayBeOptional(e.whenFalse);
2368
+ }
2369
+ if (ts.isBinaryExpression(e)) {
2370
+ const op = e.operatorToken.kind;
2371
+ if (op === ts.SyntaxKind.AmpersandAmpersandToken ||
2372
+ op === ts.SyntaxKind.BarBarToken ||
2373
+ op === ts.SyntaxKind.QuestionQuestionToken)
2374
+ return mayBeOptional(e.left) || mayBeOptional(e.right);
2375
+ }
2376
+ if (ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression)) {
2377
+ const fields = optionalFields.get(symbolOf(e.expression) ?? {});
2378
+ return fields?.has(fieldName(e)) ?? false;
2379
+ }
2380
+ return false;
2381
+ };
2382
+ const optionalStringArithmeticType = (node) => {
2383
+ const e = peel(node);
2384
+ if (!ts.isBinaryExpression(e) || e.operatorToken.kind !== ts.SyntaxKind.PlusToken)
2385
+ return null;
2386
+ const stringArrayRead = (part) => {
2387
+ const t = this.mapTypeOf(this.typeOf(part));
2388
+ const p = peel(part);
2389
+ if (t?.kind !== "string" || !ts.isElementAccessExpression(p))
2390
+ return false;
2391
+ const recv = this.mapTypeOf(this.typeOf(p.expression));
2392
+ return recv?.kind === "array" && recv.elem.kind === "string";
2393
+ };
2394
+ const primitive = (part) => {
2395
+ const t = this.mapTypeOf(this.typeOf(part));
2396
+ return t?.kind === "f64" || t?.kind === "string";
2397
+ };
2398
+ if (!stringArrayRead(e.left) && !stringArrayRead(e.right))
2399
+ return null;
2400
+ if (!(primitive(e.left) || stringArrayRead(e.left)) || !(primitive(e.right) || stringArrayRead(e.right)))
2401
+ return null;
2402
+ return { kind: "union", unionId: this.unions.intern([F64, STRING]) };
2403
+ };
2404
+ const noteField = (decl, name) => {
2405
+ if (!ts.isIdentifier(decl.name))
2406
+ return false;
2407
+ const symbol = symbolOf(decl.name);
2408
+ if (!symbol)
2409
+ return false;
2410
+ return noteFieldSymbol(symbol, name);
2411
+ };
2412
+ const noteFieldSymbol = (symbol, name) => {
2413
+ const set = optionalFields.get(symbol) ?? new Set();
2414
+ const before = set.size;
2415
+ set.add(name);
2416
+ optionalFields.set(symbol, set);
2417
+ return set.size !== before;
2418
+ };
2419
+ const scanPattern = (name, init) => {
2420
+ if (!ts.isArrayBindingPattern(name))
2421
+ return false;
2422
+ const sourceType = this.mapTypeOf(this.typeOf(init));
2423
+ if (sourceType?.kind !== "array")
2424
+ return false;
2425
+ let changed = false;
2426
+ name.elements.forEach((el) => {
2427
+ if (ts.isOmittedExpression(el) || el.name === undefined || el.dotDotDotToken)
2428
+ return;
2429
+ if (ts.isIdentifier(el.name)) {
2430
+ const symbol = symbolOf(el.name);
2431
+ if (symbol && !el.initializer && !optionalSymbols.has(symbol)) {
2432
+ optionalSymbols.add(symbol);
2433
+ // Indexed flow facts can survive a mutating method call in the
2434
+ // checker. The array's element ABI describes every value a
2435
+ // position may hold after that call, so bind from that type.
2436
+ const mapped = sourceType.elem;
2437
+ if (mapped && mapped.kind !== "void" && mapped.kind !== "dyn" && mapped.kind !== "jsval") {
2438
+ const widened = mapped.kind === "union" ? this.withUndefinedArmOf(mapped) : this.withUndefinedArm(mapped);
2439
+ if (widened) {
2440
+ this.runtimeOptionalBindingTypes.set(symbol, widened);
2441
+ const global = this.globalsBySymbol.get(symbol);
2442
+ if (global) {
2443
+ global.type = widened;
2444
+ this.runtimeOptionalGlobals.add(global);
2445
+ }
2446
+ }
2447
+ }
2448
+ changed = true;
2449
+ }
2450
+ }
2451
+ });
2452
+ return changed;
2453
+ };
2454
+ const hofCallbackIndices = (name, hasInitialValue) => {
2455
+ if (name === "reduce" || name === "reduceRight")
2456
+ return hasInitialValue ? [1] : [0, 1];
2457
+ if (name === "map" || name === "filter" || name === "forEach" ||
2458
+ name === "find" || name === "findIndex" || name === "findLast" ||
2459
+ name === "findLastIndex" || name === "some" || name === "every" ||
2460
+ name === "flatMap")
2461
+ return [0];
2462
+ return null;
2463
+ };
2464
+ const bodyReturnsOptional = (fn) => {
2465
+ if (!fn.body)
2466
+ return false;
2467
+ if (!ts.isBlock(fn.body))
2468
+ return mayBeOptional(fn.body);
2469
+ let found = false;
2470
+ ts.walkPreorder(fn.body, (node) => {
2471
+ if (node !== fn.body && (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)))
2472
+ return "skip";
2473
+ if (ts.isReturnStatement(node) && node.expression && mayBeOptional(node.expression))
2474
+ found = true;
2475
+ return undefined;
2476
+ });
2477
+ return found;
2478
+ };
2479
+ const promoteHofCallback = (callback, parameterIndices, seen = new Set()) => {
2480
+ let changed = false;
2481
+ const checkerTypeContainsTypeParameter = (type) => {
2482
+ if ((type.flags & ts.TypeFlags.TypeParameter) !== 0)
2483
+ return true;
2484
+ return type.isUnionType() && ts.constituentTypes(type).some(checkerTypeContainsTypeParameter);
2485
+ };
2486
+ const promoteNode = (fn) => {
2487
+ for (const parameterIndex of parameterIndices) {
2488
+ const parameter = fn.parameters[parameterIndex];
2489
+ if (!parameter)
2490
+ continue;
2491
+ const symbol = ts.isIdentifier(parameter.name) ? symbolOf(parameter.name) : null;
2492
+ const checkerType = this.typeOf(parameter.name);
2493
+ const mapped = this.mapTypeOf(checkerType);
2494
+ // Generic callbacks are monomorphized by their call-site binder.
2495
+ // During this prepass the type parameter has no static ABI yet;
2496
+ // asking irTypeOf to invent one poisons otherwise valid generic
2497
+ // value/HOF programs. Leave that parameter at its generic
2498
+ // signature and let the normal instantiation path specialize it.
2499
+ const current = mapped ??
2500
+ (checkerTypeContainsTypeParameter(checkerType) ? null : this.irTypeOf(parameter.name));
2501
+ if (!current)
2502
+ continue;
2503
+ if (!ts.isIdentifier(parameter.name)) {
2504
+ const widened = this.runtimeOptionalType(current);
2505
+ const previous = this.runtimeOptionalPatternTypes.get(parameter.name);
2506
+ this.runtimeOptionalPatternTypes.set(parameter.name, widened);
2507
+ if (!previous || !typeEquals(previous, widened))
2508
+ changed = true;
2509
+ }
2510
+ if (symbol) {
2511
+ const widened = this.runtimeOptionalType(current);
2512
+ const previous = this.runtimeOptionalBindingTypes.get(symbol);
2513
+ this.runtimeOptionalBindingTypes.set(symbol, widened);
2514
+ if (!previous || !typeEquals(previous, widened))
2515
+ changed = true;
2516
+ if (!optionalSymbols.has(symbol)) {
2517
+ optionalSymbols.add(symbol);
2518
+ changed = true;
2519
+ }
2520
+ }
2521
+ }
2522
+ const fnType = this.mapTypeOf(this.typeOf(fn));
2523
+ if (fnType?.kind === "func" && bodyReturnsOptional(fn)) {
2524
+ const widenedReturn = this.runtimeOptionalType(fnType.ret);
2525
+ const previousReturn = this.runtimeOptionalFunctionReturnType(fn, fnType.ret);
2526
+ this.runtimeOptionalFunctionReturns.set(fn, widenedReturn);
2527
+ if (!typeEquals(previousReturn, widenedReturn))
2528
+ changed = true;
2529
+ }
2530
+ };
2531
+ if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback) || ts.isFunctionDeclaration(callback) || ts.isMethodDeclaration(callback)) {
2532
+ promoteNode(callback);
2533
+ return changed;
2534
+ }
2535
+ const callbackName = ts.isIdentifier(callback)
2536
+ ? callback
2537
+ : ts.isPropertyAccessExpression(callback)
2538
+ ? callback.name
2539
+ : null;
2540
+ if (!callbackName)
2541
+ return false;
2542
+ const symbol = symbolOf(callbackName);
2543
+ if (!symbol || seen.has(symbol))
2544
+ return false;
2545
+ // Generic function values are pinned and monomorphized by the normal
2546
+ // contextual-value path when the HOF lowers its callback. Promoting
2547
+ // their type-parameter body here either asks for an impossible
2548
+ // monomorphic ABI or poisons valid generic value programs.
2549
+ if (this.genericFnsBySymbol.has(symbol))
2550
+ return false;
2551
+ seen.add(symbol);
2552
+ const declaration = this.checker.valueDeclarationOf(symbol);
2553
+ if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer) {
2554
+ if (promoteHofCallback(peel(declaration.initializer), parameterIndices, seen))
2555
+ changed = true;
2556
+ }
2557
+ const sig = signatureBySymbol.get(symbol);
2558
+ if (sig) {
2559
+ for (const parameterIndex of parameterIndices) {
2560
+ if (!sig.params[parameterIndex])
2561
+ continue;
2562
+ const before = sig.params[parameterIndex].type;
2563
+ sig.params[parameterIndex].type = this.runtimeOptionalType(before);
2564
+ if (!typeEquals(before, sig.params[parameterIndex].type))
2565
+ changed = true;
2566
+ }
2567
+ }
2568
+ const fn = functionDeclBySymbol.get(symbol);
2569
+ if (fn)
2570
+ promoteNode(fn);
2571
+ const valueType = this.runtimeOptionalBindingTypes.get(symbol) ?? this.mapTypeOf(this.typeOf(callback));
2572
+ if (valueType?.kind === "func") {
2573
+ const params = valueType.params.slice();
2574
+ for (const parameterIndex of parameterIndices) {
2575
+ if (params[parameterIndex])
2576
+ params[parameterIndex] = this.runtimeOptionalType(params[parameterIndex]);
2577
+ }
2578
+ const promoted = { ...valueType, params };
2579
+ const previous = this.runtimeOptionalBindingTypes.get(symbol);
2580
+ this.runtimeOptionalBindingTypes.set(symbol, promoted);
2581
+ if (!previous || !typeEquals(previous, promoted))
2582
+ changed = true;
2583
+ const global = this.globalsBySymbol.get(symbol);
2584
+ if (global)
2585
+ global.type = promoted;
2586
+ // A callback-typed parameter used by a native array HOF changes
2587
+ // the containing function's own call ABI. Record that edge in the
2588
+ // already-collected signature as well as on the body binding, so a
2589
+ // caller can promote the concrete callback value it supplies on a
2590
+ // later fixed-point pass (`wrapper(xs, predicate)` ->
2591
+ // `xs.some(predicate)`).
2592
+ if (declaration && ts.isParameter(declaration)) {
2593
+ const owner = declaration.parent;
2594
+ let ownerSymbol = null;
2595
+ if (ts.isFunctionDeclaration(owner))
2596
+ ownerSymbol = declSymbolOf(this, owner) ?? null;
2597
+ else if ((ts.isMethodDeclaration(owner) || ts.isGetAccessorDeclaration(owner) || ts.isSetAccessorDeclaration(owner)) &&
2598
+ owner.name)
2599
+ ownerSymbol = symbolOf(owner.name);
2600
+ const ownerSig = ownerSymbol ? signatureBySymbol.get(ownerSymbol) : undefined;
2601
+ const ownerIndex = ts.isFunctionLike(owner) ? owner.parameters.indexOf(declaration) : -1;
2602
+ const slot = ownerIndex >= 0 ? ownerSig?.params[ownerIndex] : undefined;
2603
+ if (slot && !typeEquals(slot.type, promoted)) {
2604
+ slot.type = promoted;
2605
+ changed = true;
2606
+ }
2607
+ }
2608
+ }
2609
+ return changed;
2610
+ };
2611
+ const callbackReturnsOptional = (callback, seen = new Set()) => {
2612
+ const node = peel(callback);
2613
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node))
2614
+ return bodyReturnsOptional(node);
2615
+ const symbol = symbolOf(ts.isPropertyAccessExpression(node) ? node.name : node);
2616
+ if (!symbol || seen.has(symbol))
2617
+ return false;
2618
+ seen.add(symbol);
2619
+ const fn = functionDeclBySymbol.get(symbol);
2620
+ if (fn)
2621
+ return bodyReturnsOptional(fn);
2622
+ const decl = this.checker.valueDeclarationOf(symbol);
2623
+ return !!decl && ts.isVariableDeclaration(decl) && !!decl.initializer && callbackReturnsOptional(decl.initializer, seen);
2624
+ };
2625
+ const scanFile = (sf) => {
2626
+ let changed = false;
2627
+ ts.walkPreorder(sf, (node) => {
2628
+ if (ts.isVariableDeclaration(node)) {
2629
+ if (node.initializer) {
2630
+ if (ts.isIdentifier(node.name) && isDynamicObjectEntryRead(node.initializer)) {
2631
+ const symbol = symbolOf(node.name);
2632
+ if (symbol && !dynamicObjectEntryRows.has(symbol)) {
2633
+ dynamicObjectEntryRows.add(symbol);
2634
+ changed = true;
2635
+ }
2636
+ }
2637
+ else if (ts.isIdentifier(node.name) && mayBeOptional(node.initializer)) {
2638
+ const symbol = symbolOf(node.name);
2639
+ if (symbol && !optionalSymbols.has(symbol)) {
2640
+ optionalSymbols.add(symbol);
2641
+ changed = true;
2642
+ }
2643
+ }
2644
+ if (ts.isIdentifier(node.name)) {
2645
+ const valueSymbol = ts.isIdentifier(peel(node.initializer))
2646
+ ? symbolOf(peel(node.initializer))
2647
+ : ts.isPropertyAccessExpression(peel(node.initializer))
2648
+ ? symbolOf(peel(node.initializer).name)
2649
+ : null;
2650
+ const symbol = symbolOf(node.name);
2651
+ const sourceSig = valueSymbol ? signatureBySymbol.get(valueSymbol) : undefined;
2652
+ const sourceOptional = !!sourceSig &&
2653
+ (optionalReturns.has(valueSymbol) ||
2654
+ (sourceSig.returnType.kind === "union" && this.armTag(sourceSig.returnType.unionId, UNDEFINED_T) >= 0) ||
2655
+ sourceSig.params.some((p) => p.type.kind === "union" && this.armTag(p.type.unionId, UNDEFINED_T) >= 0));
2656
+ if (symbol && valueSymbol && sourceOptional) {
2657
+ const aliasNew = !optionalReturns.has(symbol);
2658
+ if (aliasNew)
2659
+ optionalReturns.add(symbol);
2660
+ if (aliasNew)
2661
+ changed = true;
2662
+ const valueType = this.mapTypeOf(this.typeOf(node.name));
2663
+ if (valueType?.kind === "func" && sourceSig) {
2664
+ const promotedFn = {
2665
+ ...valueType,
2666
+ params: sourceSig.params.map((p) => p.type),
2667
+ ret: sourceSig.returnType,
2668
+ };
2669
+ const previous = this.runtimeOptionalBindingTypes.get(symbol);
2670
+ if (!previous || !typeEquals(previous, promotedFn)) {
2671
+ this.runtimeOptionalBindingTypes.set(symbol, promotedFn);
2672
+ const global = this.globalsBySymbol.get(symbol);
2673
+ if (global)
2674
+ global.type = promotedFn;
2675
+ changed = true;
2676
+ }
2677
+ }
2678
+ }
2679
+ }
2680
+ if (scanPattern(node.name, node.initializer))
2681
+ changed = true;
2682
+ if (ts.isIdentifier(node.name) && ts.isObjectLiteralExpression(peel(node.initializer))) {
2683
+ const object = peel(node.initializer);
2684
+ for (const prop of object.properties) {
2685
+ if (!ts.isPropertyAssignment(prop) && !ts.isShorthandPropertyAssignment(prop))
2686
+ continue;
2687
+ const name = prop.name;
2688
+ if (!name || !(ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)))
2689
+ continue;
2690
+ const value = ts.isPropertyAssignment(prop) ? prop.initializer : prop.name;
2691
+ if (mayBeOptional(value) && noteField(node, name.text))
2692
+ changed = true;
2693
+ }
2694
+ }
2695
+ }
2696
+ }
2697
+ if (ts.isBinaryExpression(node) &&
2698
+ (node.operatorToken.kind === ts.SyntaxKind.EqualsToken ||
2699
+ node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken ||
2700
+ node.operatorToken.kind === ts.SyntaxKind.BarBarEqualsToken ||
2701
+ node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken) &&
2702
+ mayBeOptional(node.right)) {
2703
+ if (ts.isIdentifier(node.left)) {
2704
+ const symbol = symbolOf(node.left);
2705
+ if (symbol && !optionalSymbols.has(symbol)) {
2706
+ optionalSymbols.add(symbol);
2707
+ changed = true;
2708
+ }
2709
+ }
2710
+ else if (ts.isPropertyAccessExpression(node.left) && ts.isIdentifier(node.left.expression)) {
2711
+ const symbol = symbolOf(node.left.expression);
2712
+ if (symbol && noteFieldSymbol(symbol, node.left.name.text))
2713
+ changed = true;
2714
+ }
2715
+ }
2716
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
2717
+ const callbackIndices = hofCallbackIndices(node.expression.name.text, node.arguments.length >= 2);
2718
+ const receiverNode = node.expression.expression;
2719
+ const receiver = this.mapTypeOf(this.typeOf(receiverNode));
2720
+ const callback = callbackIndices === null ? undefined : node.arguments[0];
2721
+ const tuple = receiver?.kind === "record" && this.shapes.get(receiver.shapeId)?.tuple === true;
2722
+ // A JS/evolving-any array may have acquired a precise FLOW type
2723
+ // at this call while its actual binding remains a checked-dynamic
2724
+ // array. Its callback consumes DYN values through runtime method
2725
+ // dispatch, so widening that callback to the native array value
2726
+ // ABI makes it impossible to box back into DYN. Judge a simple
2727
+ // binding by its declaration type, the same storage fact local
2728
+ // declaration lowering uses.
2729
+ let nativeArrayReceiver = receiver?.kind === "array" || tuple;
2730
+ if (nativeArrayReceiver && ts.isIdentifier(receiverNode)) {
2731
+ const receiverSymbol = symbolOf(receiverNode);
2732
+ const declaration = receiverSymbol ? this.checker.valueDeclarationOf(receiverSymbol) : undefined;
2733
+ if (declaration && ts.isVariableDeclaration(declaration)) {
2734
+ const declared = this.mapTypeOf(this.typeOf(declaration.name));
2735
+ if (declared?.kind !== "array" && !(declared?.kind === "record" && this.shapes.get(declared.shapeId)?.tuple === true)) {
2736
+ nativeArrayReceiver = false;
2737
+ }
2738
+ }
2739
+ }
2740
+ if (nativeArrayReceiver && callback && !ts.isSpreadElement(callback)) {
2741
+ if (promoteHofCallback(callback, callbackIndices))
2742
+ changed = true;
2743
+ const method = node.expression.name.text;
2744
+ if ((method === "reduce" || method === "reduceRight") &&
2745
+ (node.arguments.length < 2 || callbackReturnsOptional(callback))) {
2746
+ if (promoteHofCallback(callback, [0, 1]))
2747
+ changed = true;
2748
+ const result = this.runtimeOptionalType(this.irTypeOf(node));
2749
+ const previous = this.runtimeOptionalReduceTypes.get(node);
2750
+ if (!previous || !typeEquals(previous, result)) {
2751
+ this.runtimeOptionalReduceTypes.set(node, result);
2752
+ changed = true;
2753
+ }
2754
+ }
2755
+ }
2756
+ }
2757
+ if (ts.isCallExpression(node) &&
2758
+ (ts.isIdentifier(node.expression) || ts.isPropertyAccessExpression(node.expression))) {
2759
+ const calleeNode = ts.isIdentifier(node.expression) ? node.expression : node.expression.name;
2760
+ const symbol = symbolOf(calleeNode);
2761
+ if (!symbol)
2762
+ return;
2763
+ const sig = signatureBySymbol.get(symbol);
2764
+ if (!sig) {
2765
+ const decl = this.checker.valueDeclarationOf(symbol);
2766
+ if (decl && ts.isVariableDeclaration(decl) && decl.initializer && ts.isIdentifier(decl.initializer)) {
2767
+ const source = symbolOf(decl.initializer);
2768
+ const sourceSig = source ? signatureBySymbol.get(source) : undefined;
2769
+ if (sourceSig) {
2770
+ node.arguments.forEach((arg, i) => {
2771
+ if (ts.isSpreadElement(arg) || !mayBeOptional(arg) || !sourceSig.params[i])
2772
+ return;
2773
+ const before = sourceSig.params[i].type;
2774
+ sourceSig.params[i].type = this.runtimeOptionalType(before);
2775
+ if (!typeEquals(before, sourceSig.params[i].type)) {
2776
+ changed = true;
2777
+ const sourceFn = functionDeclBySymbol.get(source);
2778
+ const parameter = sourceFn?.parameters[i];
2779
+ if (parameter) {
2780
+ for (const bound of boundIdentifiersOf(parameter.name)) {
2781
+ const boundSymbol = symbolOf(bound);
2782
+ if (boundSymbol && !optionalSymbols.has(boundSymbol)) {
2783
+ optionalSymbols.add(boundSymbol);
2784
+ changed = true;
2785
+ }
2786
+ }
2787
+ }
2788
+ }
2789
+ });
2790
+ }
2791
+ }
2792
+ return;
2793
+ }
2794
+ node.arguments.forEach((arg, i) => {
2795
+ const callbackSlot = sig.params[i]?.type;
2796
+ if (callbackSlot?.kind === "func" && !ts.isSpreadElement(arg)) {
2797
+ const optionalCallbackParams = callbackSlot.params.flatMap((type, index) => type.kind === "union" && this.armTag(type.unionId, UNDEFINED_T) >= 0 ? [index] : []);
2798
+ if (optionalCallbackParams.length > 0 && promoteHofCallback(arg, optionalCallbackParams)) {
2799
+ changed = true;
2800
+ }
2801
+ }
2802
+ if (ts.isSpreadElement(arg) || !mayBeOptional(arg) || !sig.params[i])
2803
+ return;
2804
+ const set = optionalParams.get(symbol) ?? new Set();
2805
+ const before = set.size;
2806
+ set.add(i);
2807
+ optionalParams.set(symbol, set);
2808
+ if (set.size !== before) {
2809
+ changed = true;
2810
+ const callee = functionDeclBySymbol.get(symbol);
2811
+ const parameter = callee?.parameters[i];
2812
+ if (parameter) {
2813
+ for (const bound of boundIdentifiersOf(parameter.name)) {
2814
+ const boundSymbol = symbolOf(bound);
2815
+ if (boundSymbol && !optionalSymbols.has(boundSymbol)) {
2816
+ optionalSymbols.add(boundSymbol);
2817
+ changed = true;
2818
+ }
2819
+ }
2820
+ }
2821
+ }
2822
+ });
2823
+ }
2824
+ });
2825
+ return changed;
2826
+ };
2827
+ const scanReturns = (symbol, decl) => {
2828
+ if (!decl.body)
2829
+ return false;
2830
+ let changed = false;
2831
+ if (!ts.isBlock(decl.body) && mayBeOptional(decl.body) && !optionalReturns.has(symbol)) {
2832
+ optionalReturns.add(symbol);
2833
+ changed = true;
2834
+ }
2835
+ if (!ts.isBlock(decl.body)) {
2836
+ const arithmetic = optionalStringArithmeticType(decl.body);
2837
+ if (arithmetic && !arithmeticReturns.has(symbol)) {
2838
+ arithmeticReturns.set(symbol, arithmetic);
2839
+ changed = true;
2840
+ }
2841
+ }
2842
+ ts.walkPreorder(decl.body, (node) => {
2843
+ if (node !== decl.body && (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node)))
2844
+ return "skip";
2845
+ if (ts.isReturnStatement(node) && node.expression && mayBeOptional(node.expression) && !optionalReturns.has(symbol)) {
2846
+ optionalReturns.add(symbol);
2847
+ changed = true;
2848
+ }
2849
+ if (ts.isReturnStatement(node) && node.expression) {
2850
+ const arithmetic = optionalStringArithmeticType(node.expression);
2851
+ if (arithmetic && !arithmeticReturns.has(symbol)) {
2852
+ arithmeticReturns.set(symbol, arithmetic);
2853
+ changed = true;
2854
+ }
2855
+ }
2856
+ return undefined;
2857
+ });
2858
+ return changed;
2859
+ };
2860
+ let changed = true;
2861
+ while (changed) {
2862
+ changed = false;
2863
+ for (const sf of sourceFiles)
2864
+ if (scanFile(sf))
2865
+ changed = true;
2866
+ for (const [symbol, decl] of functionDeclBySymbol)
2867
+ if (scanReturns(symbol, decl))
2868
+ changed = true;
2869
+ }
2870
+ for (const [symbol, sig] of signatureBySymbol) {
2871
+ const params = optionalParams.get(symbol);
2872
+ if (params) {
2873
+ for (const i of params) {
2874
+ const shape = sig.params[i];
2875
+ if (shape)
2876
+ shape.type = addUndefined(shape.type);
2877
+ }
2878
+ }
2879
+ if (optionalReturns.has(symbol)) {
2880
+ const next = sig.returnType.kind === "promise"
2881
+ ? { kind: "promise", inner: addUndefined(sig.returnType.inner) }
2882
+ : addUndefined(sig.returnType);
2883
+ sig.returnType = next;
2884
+ if (sig.top)
2885
+ sig.top.returnType = next;
2886
+ if (sig.method)
2887
+ sig.method.ret = next;
2888
+ }
2889
+ const arithmetic = arithmeticReturns.get(symbol);
2890
+ if (arithmetic && sig.returnType.kind !== "promise") {
2891
+ sig.returnType = arithmetic;
2892
+ if (sig.top)
2893
+ sig.top.returnType = arithmetic;
2894
+ if (sig.method)
2895
+ sig.method.ret = arithmetic;
2896
+ const decl = functionDeclBySymbol.get(symbol);
2897
+ if (decl)
2898
+ this.runtimeOptionalFunctionReturns.set(decl, arithmetic);
2899
+ }
2900
+ }
2901
+ for (const sf of sourceFiles) {
2902
+ ts.walkPreorder(sf, (node) => {
2903
+ if (!ts.isVariableDeclaration(node) || !ts.isIdentifier(node.name))
2904
+ return;
2905
+ const symbol = symbolOf(node.name);
2906
+ if (!symbol)
2907
+ return;
2908
+ const fields = optionalFields.get(symbol);
2909
+ if (!dynamicObjectEntryRows.has(symbol) && !optionalSymbols.has(symbol) && !fields)
2910
+ return;
2911
+ const current = this.mapTypeOf(this.typeOf(node.name));
2912
+ if (!current)
2913
+ return;
2914
+ let promoted = dynamicObjectEntryRows.has(symbol) ? DYN : current;
2915
+ if (!dynamicObjectEntryRows.has(symbol) && optionalSymbols.has(symbol))
2916
+ promoted = addUndefined(promoted);
2917
+ if (promoted.kind === "func" && node.initializer && ts.isIdentifier(node.initializer)) {
2918
+ const sourceSig = this.fnSigOf(node.initializer);
2919
+ if (sourceSig) {
2920
+ promoted = {
2921
+ ...promoted,
2922
+ params: sourceSig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
2923
+ ret: sourceSig.returnType,
2924
+ };
2925
+ }
2926
+ }
2927
+ if (fields && promoted.kind === "record") {
2928
+ for (const field of fields) {
2929
+ const source = promoted.kind === "record"
2930
+ ? this.shapes.get(promoted.shapeId)?.fields.find((f) => f.name === field)
2931
+ : undefined;
2932
+ if (!source)
2933
+ continue;
2934
+ promoted = this.runtimeOptionalRecordField(promoted, field, addUndefined(source.type));
2935
+ }
2936
+ }
2937
+ if (!typeEquals(promoted, current)) {
2938
+ this.runtimeOptionalBindingTypes.set(symbol, promoted);
2939
+ const global = ts.isIdentifier(node.name) ? this.globalOf(node.name) : this.globalsBySymbol.get(symbol);
2940
+ if (global) {
2941
+ global.type = promoted;
2942
+ this.runtimeOptionalGlobals.add(global);
2943
+ }
2944
+ }
2945
+ else if (promoted.kind === "union" && this.armTag(promoted.unionId, UNDEFINED_T) >= 0) {
2946
+ const global = ts.isIdentifier(node.name) ? this.globalOf(node.name) : this.globalsBySymbol.get(symbol);
2947
+ if (global)
2948
+ this.runtimeOptionalGlobals.add(global);
2949
+ }
2950
+ });
2951
+ }
2952
+ }
2014
2953
  run() {
2015
2954
  const parts = this.splitFiles();
2016
2955
  // The coverage remainder deliberately visits every body that reachable
@@ -2024,6 +2963,7 @@ export class Lowerer {
2024
2963
  this.checker.prefetchSourceFile(fp.sf);
2025
2964
  }
2026
2965
  this.collectProgram(parts);
2966
+ this.analyzeRuntimeOptionalArrayReads(parts);
2027
2967
  // Decorated classes analyze AFTER the whole collection pass: a
2028
2968
  // decorator's return type may name a subclass declared below the
2029
2969
  // class, and reference lowering needs each class's rebindability
@@ -2252,7 +3192,7 @@ export class Lowerer {
2252
3192
  const module = this.diags.length > 0
2253
3193
  ? null
2254
3194
  : {
2255
- irVersion: 6,
3195
+ irVersion: 8,
2256
3196
  sourceFile: this.entry.fileName,
2257
3197
  functions,
2258
3198
  classes: artifacts.classes,
@@ -2395,6 +3335,7 @@ export class Lowerer {
2395
3335
  // will reuse the same answers later.
2396
3336
  this.prefetchClassCollection(parts.flatMap((fp) => fp.classDecls));
2397
3337
  this.collectProgram(parts);
3338
+ this.analyzeRuntimeOptionalArrayReads(parts);
2398
3339
  // Decorated classes analyze post-collection here too: the %init seeds
2399
3340
  // lower the decoration calls, whose edges (decorator bodies, construct
2400
3341
  // thunks) reachable emit must see.
@@ -3194,11 +4135,14 @@ export class Lowerer {
3194
4135
  const def = this.unions.get(unionId);
3195
4136
  return def ? def.arms.flatMap((arm, tag) => (this.isArrayValueType(arm) ? [tag] : [])) : [];
3196
4137
  }
3197
- /** isJsonSafeType with this Lowerer's registries — the shared fence for
3198
- * what JSON.stringify accepts and what a checked cast can validate. */
4138
+ /** Checked JSON conversion excludes undefined array slots because JSON
4139
+ * text cannot preserve that distinction. */
3199
4140
  jsonSafe(t) {
3200
4141
  return isJsonSafeType(t, (id) => this.shapes.get(id), (id) => this.unions.get(id));
3201
4142
  }
4143
+ jsonStringifySafe(t) {
4144
+ return isJsonStringifySafeType(t, (id) => this.shapes.get(id), (id) => this.unions.get(id));
4145
+ }
3202
4146
  /** True when a type is a BARE undefined-armed union — the one JSON-unsafe
3203
4147
  * shape whose rejections deserve their own wording: Node's stringify of
3204
4148
  * bare undefined is not a string at all and JSON text never matches the
@@ -5599,10 +6543,13 @@ export class Lowerer {
5599
6543
  return null;
5600
6544
  const key = `${fromId}:${tag}`;
5601
6545
  const existing = this.narrowHelpers.get(key);
5602
- if (existing)
6546
+ if (existing) {
6547
+ this.checkedNarrowHelpers.add(existing);
5603
6548
  return existing;
6549
+ }
5604
6550
  const name = `%union.narrow.${this.narrowHelpers.size}`;
5605
6551
  this.narrowHelpers.set(key, name);
6552
+ this.checkedNarrowHelpers.add(name);
5606
6553
  const fromT = { kind: "union", unionId: fromId };
5607
6554
  const u = { kind: "varRef", localId: "u.0", type: fromT, loc };
5608
6555
  const body = [];
@@ -7072,6 +8019,14 @@ export class Lowerer {
7072
8019
  }
7073
8020
  const local = this.declareLocal(decl.name, name, shape.type, true);
7074
8021
  params.push({ localId: local.id, name, type: local.type });
8022
+ if (shape.mode === "required" &&
8023
+ shape.type.kind === "union" &&
8024
+ this.armTag(shape.type.unionId, UNDEFINED_T) >= 0 &&
8025
+ typeEquals(this.stripUndefinedArm(shape.type), this.mapTypeOf(this.typeOf(decl.name)) ?? VOID)) {
8026
+ const root = this.runtimeOptionalRootOf(local);
8027
+ this.runtimeOptionalLocals.add(root);
8028
+ this.runtimeOptionalStorageLocals.add(root);
8029
+ }
7075
8030
  });
7076
8031
  return { params, prologue };
7077
8032
  }
@@ -7540,7 +8495,13 @@ export class Lowerer {
7540
8495
  return lowerExprStatement(this, expr);
7541
8496
  }
7542
8497
  lowerForOf(stmt) {
7543
- return lowerForOf(this, stmt);
8498
+ const optional = this.runtimeOptionalIdentifierValue(stmt.expression);
8499
+ if (optional?.present.kind !== "string")
8500
+ return lowerForOf(this, stmt);
8501
+ const helper = this.narrowedArmHelper(optional.unionId, optional.present, locOf(stmt.expression));
8502
+ if (!helper)
8503
+ return lowerForOf(this, stmt);
8504
+ return this.withExpressionOverride(stmt.expression, { kind: "call", callee: helper, args: [optional.value], type: optional.present, loc: locOf(stmt.expression) }, () => lowerForOf(this, stmt));
7544
8505
  }
7545
8506
  lowerForStatement(stmt) {
7546
8507
  return lowerForStatement(this, stmt);
@@ -7627,7 +8588,38 @@ export class Lowerer {
7627
8588
  return rejectThisInObjectMethod(this, node);
7628
8589
  }
7629
8590
  lowerElementAccess(expr) {
7630
- return lowerElementAccess(this, expr);
8591
+ const receiver = this.runtimeOptionalIdentifierValue(expr.expression);
8592
+ const receiverValue = receiver && (receiver.present.kind === "array" || receiver.present.kind === "record")
8593
+ ? (() => {
8594
+ const helper = this.narrowedArmHelper(receiver.unionId, receiver.present, locOf(expr.expression));
8595
+ return helper ? { kind: "call", callee: helper, args: [receiver.value], type: receiver.present, loc: locOf(expr.expression) } : null;
8596
+ })()
8597
+ : null;
8598
+ if (receiverValue && receiver?.present.kind === "record" && ts.isNumericLiteral(expr.argumentExpression)) {
8599
+ const shape = this.shapes.get(receiver.present.shapeId);
8600
+ const index = expr.argumentExpression.text;
8601
+ const field = shape?.tuple ? shape.fields.find((candidate) => candidate.name === String(Number(index))) : undefined;
8602
+ if (field) {
8603
+ const read = {
8604
+ kind: "recordGet",
8605
+ obj: receiverValue,
8606
+ shapeId: receiver.present.shapeId,
8607
+ field: field.name,
8608
+ type: field.type,
8609
+ loc: locOf(expr),
8610
+ };
8611
+ if (expr.parent && ts.isTypeOfExpression(expr.parent) &&
8612
+ (field.type.kind === "f64" || field.type.kind === "bool" || field.type.kind === "string")) {
8613
+ return { kind: "dynFrom", value: read, type: DYN, loc: locOf(expr) };
8614
+ }
8615
+ return read;
8616
+ }
8617
+ }
8618
+ const key = this.runtimeOptionalIdentifierValue(expr.argumentExpression);
8619
+ const keyValue = key?.present.kind === "string" ? this.ensureString(key.value, expr.argumentExpression) : null;
8620
+ const lower = () => lowerElementAccess(this, expr);
8621
+ const withKey = () => keyValue ? this.withExpressionOverride(expr.argumentExpression, keyValue, lower) : lower();
8622
+ return receiverValue ? this.withExpressionOverride(expr.expression, receiverValue, withKey) : withKey();
7631
8623
  }
7632
8624
  lowerRecordKeyRead(expr, shapeId, shape, includeUndefined = false) {
7633
8625
  return lowerRecordKeyRead(this, expr, shapeId, shape, includeUndefined);
@@ -7694,7 +8686,84 @@ export class Lowerer {
7694
8686
  return caughtToString(this, node);
7695
8687
  }
7696
8688
  lowerInstanceOf(expr, loc) {
7697
- return lowerInstanceOf(this, expr, loc);
8689
+ const lower = () => this.withRuntimeOptionalClassValue(expr.right, () => lowerInstanceOf(this, expr, loc));
8690
+ if (!ts.isIdentifier(expr.right) || this.mapTypeOf(this.typeOf(expr.right))?.kind !== "classval") {
8691
+ return lower();
8692
+ }
8693
+ const optionalLeft = this.runtimeOptionalIdentifierValue(expr.left);
8694
+ const left = optionalLeft?.value ?? (ts.isElementAccessExpression(expr.left) ? this.lowerExpr(expr.left) : null);
8695
+ if (!left)
8696
+ return lower();
8697
+ if (left.type.kind !== "union" || this.armTag(left.type.unionId, UNDEFINED_T) < 0)
8698
+ return lower();
8699
+ const present = this.stripUndefinedArm(left.type);
8700
+ if (present.kind !== "object")
8701
+ return lower();
8702
+ const undefinedTag = this.armTag(left.type.unionId, UNDEFINED_T);
8703
+ const valueTag = this.armTag(left.type.unionId, present);
8704
+ if (undefinedTag < 0 || valueTag < 0)
8705
+ return lower();
8706
+ const stable = this.declareHiddenLocal("%instanceofLeft", left.type);
8707
+ const value = varRef(stable.id, left.type, locOf(expr.left));
8708
+ const optionalRight = this.runtimeOptionalIdentifierValue(expr.right);
8709
+ let right;
8710
+ if (optionalRight?.present.kind === "classval") {
8711
+ const helper = this.narrowedArmHelper(optionalRight.unionId, optionalRight.present, locOf(expr.right));
8712
+ if (!helper)
8713
+ return lower();
8714
+ right = { kind: "call", callee: helper, args: [optionalRight.value], type: optionalRight.present, loc: locOf(expr.right) };
8715
+ }
8716
+ else {
8717
+ right = this.lowerExpr(expr.right);
8718
+ }
8719
+ if (right.type.kind !== "classval")
8720
+ return lower();
8721
+ const stableRight = this.declareHiddenLocal("%instanceofRight", right.type);
8722
+ const rightValue = varRef(stableRight.id, right.type, locOf(expr.right));
8723
+ const body = this.withExpressionOverride(expr.left, {
8724
+ kind: "unionNarrow", unionId: left.type.unionId, tag: valueTag, value, type: present, loc: locOf(expr.left),
8725
+ }, () => this.withExpressionOverride(expr.right, rightValue, () => lowerInstanceOf(this, expr, loc)));
8726
+ return {
8727
+ kind: "seqExpr",
8728
+ stmts: [
8729
+ { kind: "varDecl", localId: stable.id, init: left, loc: locOf(expr.left) },
8730
+ { kind: "varDecl", localId: stableRight.id, init: right, loc: locOf(expr.right) },
8731
+ ],
8732
+ result: {
8733
+ kind: "ternary",
8734
+ cond: { kind: "unionIsTag", unionId: left.type.unionId, tag: undefinedTag, negated: false, value, type: BOOL, loc },
8735
+ then: { kind: "boolLit", value: false, type: BOOL, loc },
8736
+ else_: body,
8737
+ type: BOOL,
8738
+ loc,
8739
+ },
8740
+ type: BOOL,
8741
+ loc,
8742
+ };
8743
+ }
8744
+ withRuntimeOptionalClassValue(node, lower) {
8745
+ if (this.mapTypeOf(this.typeOf(node))?.kind !== "classval")
8746
+ return lower();
8747
+ const optional = this.runtimeOptionalIdentifierValue(node);
8748
+ if (optional?.present.kind !== "classval")
8749
+ return lower();
8750
+ const helper = this.narrowedArmHelper(optional.unionId, optional.present, locOf(node));
8751
+ if (!helper)
8752
+ return lower();
8753
+ return this.withExpressionOverride(node, { kind: "call", callee: helper, args: [optional.value], type: optional.present, loc: locOf(node) }, lower);
8754
+ }
8755
+ withExpressionOverride(node, value, lower) {
8756
+ const previous = this.chainRecvByNode.get(node);
8757
+ this.chainRecvByNode.set(node, value);
8758
+ try {
8759
+ return lower();
8760
+ }
8761
+ finally {
8762
+ if (previous)
8763
+ this.chainRecvByNode.set(node, previous);
8764
+ else
8765
+ this.chainRecvByNode.delete(node);
8766
+ }
7698
8767
  }
7699
8768
  lowerCall(expr) {
7700
8769
  // Ambient fetch (native in static builds, island-backed under
@@ -7834,7 +8903,8 @@ export class Lowerer {
7834
8903
  const arg = this.lowerExpr(expr.arguments[0]);
7835
8904
  return { kind: "jsOp", op: "construct", args: [ctor, arg], type: JSVAL, loc };
7836
8905
  }
7837
- return lowerAbortControllerNew(this, expr) ?? lowerResponseNew(this, expr) ?? lowerStaticReadableStreamNew(this, expr) ?? lowerNew(this, expr);
8906
+ const builtin = lowerAbortControllerNew(this, expr) ?? lowerResponseNew(this, expr) ?? lowerStaticReadableStreamNew(this, expr);
8907
+ return builtin ?? this.withRuntimeOptionalClassValue(expr.expression, () => lowerNew(this, expr));
7838
8908
  }
7839
8909
  lowerFieldRead(expr) {
7840
8910
  // `C.x` static reads first (the receiver is a CLASS, not an instance
@@ -8323,6 +9393,13 @@ export class Lowerer {
8323
9393
  return lowerStringLastIndexOfCall(this, call, access);
8324
9394
  }
8325
9395
  lowerFilterNarrowCall(call, access) {
9396
+ // Native array filters use the state-aware HOF helper: its callback
9397
+ // receives present undefined values and skips holes before a narrowed
9398
+ // survivor is extracted. The older narrowing helper reads the payload
9399
+ // directly and therefore cannot represent that three-state contract.
9400
+ if (this.mapTypeOf(this.typeOf(access.expression))?.kind === "array" && this.isStdlibMember(access)) {
9401
+ return null;
9402
+ }
8326
9403
  return lowerFilterNarrowCall(this, call, access);
8327
9404
  }
8328
9405
  lowerPromiseMethodCall(call, access) {