@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.
- package/ambient/scriptc-node-fallback.d.ts +7 -5
- package/ambient/scriptc-overrides.d.ts +0 -7
- package/ambient/scriptc.d.ts +1 -1
- package/dist/backend/c/exprs.js +69 -45
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/may-throw.js +3 -0
- package/dist/backend/c/may-throw.js.map +1 -1
- package/dist/backend/c/stmts.js +18 -0
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/walkers.js +5 -7
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/external-c.d.ts +33 -0
- package/dist/backend/external-c.js +51 -0
- package/dist/backend/external-c.js.map +1 -0
- package/dist/backend/link-plan.d.ts +37 -0
- package/dist/backend/link-plan.js +26 -0
- package/dist/backend/link-plan.js.map +1 -0
- package/dist/backend/linker.d.ts +22 -0
- package/dist/backend/linker.js +236 -0
- package/dist/backend/linker.js.map +1 -0
- package/dist/backend/llvm/dyn.js +7 -0
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +34 -7
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +68 -54
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +1 -1
- package/dist/backend/llvm/expr-dispatch.js +2 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +20 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +25 -1
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/walkers.js +31 -4
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +8 -1
- package/dist/backend/native-codegen.js +39 -25
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +42 -4
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info.d.ts +1 -1
- package/dist/backend/native-link-info.js +42 -18
- package/dist/backend/native-link-info.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +5 -1
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
- package/dist/backend/runtime-pack-targets.test.js +54 -0
- package/dist/backend/runtime-pack-targets.test.js.map +1 -0
- package/dist/backend/runtime-pack.d.ts +5 -30
- package/dist/backend/runtime-pack.js +46 -141
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +49 -14
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/targets.d.ts +63 -17
- package/dist/backend/targets.js +251 -35
- package/dist/backend/targets.js.map +1 -1
- package/dist/backend/targets.test.js +27 -5
- package/dist/backend/targets.test.js.map +1 -1
- package/dist/coverage/surface-manifest.js +10 -1
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +8 -0
- package/dist/frontend/lowering/array-values.js +60 -0
- package/dist/frontend/lowering/array-values.js.map +1 -0
- package/dist/frontend/lowering/lower-array-sort.d.ts +22 -0
- package/dist/frontend/lowering/lower-array-sort.js +619 -0
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +164 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +3 -4
- package/dist/frontend/lowering/lower-calls.js +260 -27
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +728 -655
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
- package/dist/frontend/lowering/lower-exprs.js +769 -101
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +47 -33
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +5 -4
- package/dist/frontend/lowering/lower-island.js +10 -5
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +10 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +302 -71
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +74 -2
- package/dist/frontend/lowering/lowerer.js +1087 -10
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +10 -2
- package/dist/frontend/lowering/surfaces.js +15 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +45 -23
- package/dist/index.js.map +1 -1
- package/dist/ir/ir.d.ts +65 -11
- package/dist/ir/ir.js +25 -25
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +84 -37
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.js +3 -1
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/int-infer.js +12 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/library-cache.test.js +5 -5
- package/dist/startup-cache.d.ts +9 -1
- package/dist/startup-cache.js +16 -1
- package/dist/startup-cache.js.map +1 -1
- package/package.json +17 -4
- 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:
|
|
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
|
-
/**
|
|
3198
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|