graphddb 0.7.9 → 0.8.0
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/README.md +6 -6
- package/dist/cdc/index.d.ts +389 -4
- package/dist/cdc/index.js +4 -3
- package/dist/{chunk-ZPNRLOKA.js → chunk-GS4C5VGO.js} +4 -6
- package/dist/chunk-HNY2EJPV.js +1184 -0
- package/dist/{chunk-NYM7K2ST.js → chunk-I4LEJ4TF.js} +3812 -6724
- package/dist/{chunk-PFFPLD4B.js → chunk-L2NEDS7U.js} +725 -2112
- package/dist/chunk-L4QRCHRQ.js +278 -0
- package/dist/chunk-LGHSZIEE.js +187 -0
- package/dist/chunk-N4NWYNGZ.js +1987 -0
- package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
- package/dist/cli.js +63 -252
- package/dist/index.d.ts +23 -1548
- package/dist/index.js +100 -1778
- package/dist/internal/index.d.ts +84 -0
- package/dist/internal/index.js +701 -0
- package/dist/{maintenance-view-adapter-BATUh_I8.d.ts → key-DR7_lpyk.d.ts} +538 -2975
- package/dist/linter/index.d.ts +39 -6
- package/dist/linter/index.js +22 -4
- package/dist/{registry-CXhP4TaE.d.ts → linter-C-vypgut.d.ts} +22 -22
- package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
- package/dist/spec/index.d.ts +506 -4
- package/dist/spec/index.js +36 -17
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +4 -3
- package/dist/transform/index.d.ts +460 -1
- package/dist/transform/index.js +2085 -2
- package/dist/types-2PMXEn5x.d.ts +1205 -0
- package/dist/types-BXLzIcQD.d.ts +450 -0
- package/docs/cdc-projection.md +5 -5
- package/docs/class-hydration.md +1 -1
- package/docs/cqrs-contract.md +28 -20
- package/docs/design-patterns.md +5 -5
- package/docs/docs-generation.md +6 -6
- package/docs/middleware.md +15 -15
- package/docs/mutation-command-derivation.md +52 -42
- package/docs/prepared-statements.md +14 -14
- package/docs/python-bridge.md +113 -66
- package/docs/spec.md +153 -124
- package/docs/testing.md +9 -8
- package/package.json +20 -5
- package/dist/chunk-MMVHOUM4.js +0 -24
- package/dist/from-change-DanwjE5b.d.ts +0 -327
- package/dist/index-CtPJSMrc.d.ts +0 -934
- package/dist/relation-depth-Dg3yhl7S.d.ts +0 -36
package/dist/transform/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXPR_VERSION,
|
|
3
|
+
SCP_LOWERED_MARKER,
|
|
4
|
+
canonicalizeExpressionSpec
|
|
5
|
+
} from "../chunk-L4QRCHRQ.js";
|
|
6
|
+
import "../chunk-L2NEDS7U.js";
|
|
7
|
+
import "../chunk-XTWXMOHD.js";
|
|
8
|
+
|
|
1
9
|
// src/transform/prepared-transform.ts
|
|
2
10
|
import ts from "typescript";
|
|
3
11
|
var HOIST_SLOT_PREFIX = "__gddbPrepared";
|
|
@@ -923,7 +931,7 @@ function transformPreparedSourceAot(fileName, sourceText, options) {
|
|
|
923
931
|
let header = "";
|
|
924
932
|
const nl = sourceText.includes("\r\n") ? "\r\n" : "\n";
|
|
925
933
|
if (needsHeaderImports && !bindings.has(AOT_LOADER_LOCAL)) {
|
|
926
|
-
header += `${nl}${nl}// graphddb transform prepared --aot (#208): static prepared plans \u2014 each${nl}// call site loads its build-time-compiled plan; the body stays in source as${nl}// the plan's compile source (drift regeneration) and typed fallback.${nl}import { loadPreparedPlan as ${AOT_LOADER_LOCAL} } from 'graphddb';${nl}import ${AOT_PLANS_LOCAL} from '${options.artifactImport}';`;
|
|
934
|
+
header += `${nl}${nl}// graphddb transform prepared --aot (#208): static prepared plans \u2014 each${nl}// call site loads its build-time-compiled plan; the body stays in source as${nl}// the plan's compile source (drift regeneration) and typed fallback.${nl}import { loadPreparedPlan as ${AOT_LOADER_LOCAL} } from 'graphddb/internal';${nl}import ${AOT_PLANS_LOCAL} from '${options.artifactImport}';`;
|
|
927
935
|
}
|
|
928
936
|
if (slotNames.length > 0) {
|
|
929
937
|
header += `${nl}${nl}// graphddb transform prepared (#206): module-scope lazy slots.` + slotNames.map((s) => `${nl}let ${s};`).join("");
|
|
@@ -971,12 +979,2087 @@ function resolveBodyFn(arg, bindings) {
|
|
|
971
979
|
}
|
|
972
980
|
return void 0;
|
|
973
981
|
}
|
|
982
|
+
|
|
983
|
+
// src/transform/expression-lower.ts
|
|
984
|
+
import ts2 from "typescript";
|
|
985
|
+
function formatExpressionLowerDiagnostic(d) {
|
|
986
|
+
return `${d.file}:${d.line}:${d.column} \u2014 SCP lowering error [${d.construct}]: ${d.message}`;
|
|
987
|
+
}
|
|
988
|
+
var ExpressionLowerError = class extends Error {
|
|
989
|
+
diagnostics;
|
|
990
|
+
constructor(diagnostics) {
|
|
991
|
+
super(diagnostics.map(formatExpressionLowerDiagnostic).join("\n"));
|
|
992
|
+
this.name = "ExpressionLowerError";
|
|
993
|
+
this.diagnostics = diagnostics;
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
var NUMERIC_KINDS = /* @__PURE__ */ new Set(["int", "float", "num"]);
|
|
997
|
+
function scopeOfRoot(ctx, id) {
|
|
998
|
+
const symbol = ctx.checker.getSymbolAtLocation(id);
|
|
999
|
+
if (symbol === void 0) return void 0;
|
|
1000
|
+
if (ctx.inputRootSymbols.has(symbol)) return "input";
|
|
1001
|
+
if (ctx.itemRootSymbols.has(symbol)) return "item";
|
|
1002
|
+
return void 0;
|
|
1003
|
+
}
|
|
1004
|
+
function shadowsRootName(ctx, name) {
|
|
1005
|
+
for (const s of ctx.inputRootSymbols) if (s.getName() === name) return true;
|
|
1006
|
+
for (const s of ctx.itemRootSymbols) if (s.getName() === name) return true;
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
function report2(ctx, node, code, construct, message) {
|
|
1010
|
+
const { line, character } = ctx.sf.getLineAndCharacterOfPosition(
|
|
1011
|
+
node.getStart(ctx.sf)
|
|
1012
|
+
);
|
|
1013
|
+
ctx.diagnostics.push({
|
|
1014
|
+
code,
|
|
1015
|
+
category: "error",
|
|
1016
|
+
message,
|
|
1017
|
+
construct,
|
|
1018
|
+
file: ctx.fileName,
|
|
1019
|
+
line: line + 1,
|
|
1020
|
+
column: character + 1
|
|
1021
|
+
});
|
|
1022
|
+
return void 0;
|
|
1023
|
+
}
|
|
1024
|
+
function unwrapExpr2(e) {
|
|
1025
|
+
let cur = e;
|
|
1026
|
+
for (; ; ) {
|
|
1027
|
+
if (ts2.isParenthesizedExpression(cur)) cur = cur.expression;
|
|
1028
|
+
else if (ts2.isAsExpression(cur)) cur = cur.expression;
|
|
1029
|
+
else if (ts2.isSatisfiesExpression(cur)) cur = cur.expression;
|
|
1030
|
+
else if (ts2.isNonNullExpression(cur)) cur = cur.expression;
|
|
1031
|
+
else if (ts2.isTypeAssertionExpression(cur)) cur = cur.expression;
|
|
1032
|
+
else return cur;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function classifyType(type, checker) {
|
|
1036
|
+
const NULLISH = ts2.TypeFlags.Null | ts2.TypeFlags.Undefined | ts2.TypeFlags.VoidLike;
|
|
1037
|
+
const parts = type.isUnion() ? type.types : [type];
|
|
1038
|
+
let nullable = false;
|
|
1039
|
+
let kind;
|
|
1040
|
+
let seen = false;
|
|
1041
|
+
for (const part of parts) {
|
|
1042
|
+
if (part.flags & NULLISH) {
|
|
1043
|
+
nullable = true;
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
const k = classifySingle(part, checker);
|
|
1047
|
+
if (k === void 0) return { kind: void 0, nullable };
|
|
1048
|
+
if (!seen) {
|
|
1049
|
+
kind = k;
|
|
1050
|
+
seen = true;
|
|
1051
|
+
} else if (kind !== k) {
|
|
1052
|
+
if (kind !== void 0 && NUMERIC_KINDS.has(kind) && NUMERIC_KINDS.has(k)) {
|
|
1053
|
+
kind = "num";
|
|
1054
|
+
} else {
|
|
1055
|
+
return { kind: void 0, nullable };
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (!seen) return { kind: "null", nullable: true };
|
|
1060
|
+
return { kind, nullable };
|
|
1061
|
+
}
|
|
1062
|
+
function classifySingle(type, checker) {
|
|
1063
|
+
const f = type.flags;
|
|
1064
|
+
if (f & (ts2.TypeFlags.Any | ts2.TypeFlags.Unknown | ts2.TypeFlags.Never)) {
|
|
1065
|
+
return void 0;
|
|
1066
|
+
}
|
|
1067
|
+
if (f & ts2.TypeFlags.StringLike) return "string";
|
|
1068
|
+
if (f & ts2.TypeFlags.BooleanLike) return "bool";
|
|
1069
|
+
if (f & ts2.TypeFlags.NumberLike) {
|
|
1070
|
+
if (type.isNumberLiteral()) {
|
|
1071
|
+
return Number.isInteger(type.value) ? "int" : "float";
|
|
1072
|
+
}
|
|
1073
|
+
return "num";
|
|
1074
|
+
}
|
|
1075
|
+
if (f & ts2.TypeFlags.Null) return "null";
|
|
1076
|
+
if (f & ts2.TypeFlags.Object) {
|
|
1077
|
+
return checker.isArrayLikeType(type) ? "arr" : "obj";
|
|
1078
|
+
}
|
|
1079
|
+
return void 0;
|
|
1080
|
+
}
|
|
1081
|
+
function classifyAt(ctx, expr) {
|
|
1082
|
+
return classifyType(ctx.checker.getTypeAtLocation(expr), ctx.checker);
|
|
1083
|
+
}
|
|
1084
|
+
function unifyNumeric(a, b) {
|
|
1085
|
+
if (!NUMERIC_KINDS.has(a) || !NUMERIC_KINDS.has(b)) return void 0;
|
|
1086
|
+
if (a === b) return a;
|
|
1087
|
+
if (a === "num" || b === "num") return "num";
|
|
1088
|
+
return void 0;
|
|
1089
|
+
}
|
|
1090
|
+
function unifyBranch(a, b) {
|
|
1091
|
+
if (a === "null") return b;
|
|
1092
|
+
if (b === "null") return a;
|
|
1093
|
+
if (a === b) return a;
|
|
1094
|
+
return unifyNumeric(a, b);
|
|
1095
|
+
}
|
|
1096
|
+
var KNOWN_HOST_GLOBALS = /* @__PURE__ */ new Set([
|
|
1097
|
+
"Date",
|
|
1098
|
+
"Math",
|
|
1099
|
+
"JSON",
|
|
1100
|
+
"RegExp",
|
|
1101
|
+
"Symbol",
|
|
1102
|
+
"Number",
|
|
1103
|
+
"String",
|
|
1104
|
+
"Boolean",
|
|
1105
|
+
"Object",
|
|
1106
|
+
"Array",
|
|
1107
|
+
"Promise",
|
|
1108
|
+
"Reflect",
|
|
1109
|
+
"BigInt",
|
|
1110
|
+
"globalThis",
|
|
1111
|
+
"process",
|
|
1112
|
+
"window",
|
|
1113
|
+
"console"
|
|
1114
|
+
]);
|
|
1115
|
+
function lower(ctx, expr) {
|
|
1116
|
+
const e = unwrapExpr2(expr);
|
|
1117
|
+
if (ts2.isStringLiteral(e) || ts2.isNoSubstitutionTemplateLiteral(e)) {
|
|
1118
|
+
return { node: e.text, kind: "string", nullable: false };
|
|
1119
|
+
}
|
|
1120
|
+
if (ts2.isNumericLiteral(e)) return lowerNumericLiteral(ctx, e, false);
|
|
1121
|
+
if (e.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
1122
|
+
return { node: true, kind: "bool", nullable: false };
|
|
1123
|
+
}
|
|
1124
|
+
if (e.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
1125
|
+
return { node: false, kind: "bool", nullable: false };
|
|
1126
|
+
}
|
|
1127
|
+
if (e.kind === ts2.SyntaxKind.NullKeyword) {
|
|
1128
|
+
return { node: null, kind: "null", nullable: true };
|
|
1129
|
+
}
|
|
1130
|
+
if (ts2.isBigIntLiteral(e)) {
|
|
1131
|
+
return report2(
|
|
1132
|
+
ctx,
|
|
1133
|
+
e,
|
|
1134
|
+
"scp-closed-set",
|
|
1135
|
+
"bigint literal",
|
|
1136
|
+
'A bigint literal (`\u2026n`) is outside the Expression IR closed set \u2014 write a plain integer literal instead (the lowerer reads the token text, so values beyond \xB12^53\u22121 are carried exactly as `{int:"\u2026"}`).'
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
if (ts2.isPrefixUnaryExpression(e)) return lowerPrefixUnary(ctx, e);
|
|
1140
|
+
if (ts2.isPostfixUnaryExpression(e)) {
|
|
1141
|
+
const op = e.operator === ts2.SyntaxKind.PlusPlusToken ? "++" : "--";
|
|
1142
|
+
return report2(
|
|
1143
|
+
ctx,
|
|
1144
|
+
e,
|
|
1145
|
+
"scp-closed-set",
|
|
1146
|
+
op,
|
|
1147
|
+
`The mutation operator \`${op}\` is outside the Expression IR closed set (expressions are pure \u2014 no assignment, no mutation).`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
if (ts2.isBinaryExpression(e)) return lowerBinary(ctx, e);
|
|
1151
|
+
if (ts2.isConditionalExpression(e)) return lowerConditional(ctx, e);
|
|
1152
|
+
if (ts2.isObjectLiteralExpression(e)) return lowerObjectLiteral(ctx, e);
|
|
1153
|
+
if (ts2.isArrayLiteralExpression(e)) return lowerArrayLiteral(ctx, e);
|
|
1154
|
+
if (ts2.isTemplateExpression(e)) return lowerTemplate(ctx, e);
|
|
1155
|
+
if (ts2.isPropertyAccessExpression(e)) return lowerPropertyAccess(ctx, e);
|
|
1156
|
+
if (ts2.isIdentifier(e)) return lowerBareIdentifier(ctx, e);
|
|
1157
|
+
return lowerReject(ctx, e);
|
|
1158
|
+
}
|
|
1159
|
+
function lowerNumericLiteral(ctx, lit, negate) {
|
|
1160
|
+
const raw = lit.getText(ctx.sf).replace(/_/g, "");
|
|
1161
|
+
const isRadixInt = /^0[xob]/i.test(raw);
|
|
1162
|
+
const isFloat = !isRadixInt && /[.eE]/.test(raw);
|
|
1163
|
+
if (isFloat) {
|
|
1164
|
+
const value = Number(raw) * (negate ? -1 : 1);
|
|
1165
|
+
if (!Number.isFinite(value)) {
|
|
1166
|
+
return report2(
|
|
1167
|
+
ctx,
|
|
1168
|
+
lit,
|
|
1169
|
+
"scp-type",
|
|
1170
|
+
"non-finite literal",
|
|
1171
|
+
`The float literal ${raw} evaluates to a non-finite value \u2014 NaN / \xB1Infinity cannot exist in the Expression IR (expression-ir.md \xA72.1).`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
return { node: { float: value }, kind: "float", nullable: false };
|
|
1175
|
+
}
|
|
1176
|
+
const v = BigInt(raw) * (negate ? -1n : 1n);
|
|
1177
|
+
if (v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
1178
|
+
return { node: Number(v), kind: "int", nullable: false };
|
|
1179
|
+
}
|
|
1180
|
+
return { node: { int: v.toString() }, kind: "int", nullable: false };
|
|
1181
|
+
}
|
|
1182
|
+
function lowerPrefixUnary(ctx, e) {
|
|
1183
|
+
const operand = unwrapExpr2(e.operand);
|
|
1184
|
+
switch (e.operator) {
|
|
1185
|
+
case ts2.SyntaxKind.MinusToken: {
|
|
1186
|
+
if (ts2.isNumericLiteral(operand)) return lowerNumericLiteral(ctx, operand, true);
|
|
1187
|
+
const a = lower(ctx, operand);
|
|
1188
|
+
if (a === void 0) return void 0;
|
|
1189
|
+
if (!NUMERIC_KINDS.has(a.kind) || a.nullable) {
|
|
1190
|
+
return report2(
|
|
1191
|
+
ctx,
|
|
1192
|
+
e,
|
|
1193
|
+
"scp-type",
|
|
1194
|
+
"unary -",
|
|
1195
|
+
`The operand of unary \`-\` must be a non-null number, got ${describeKind(a)} \u2014 the IR \`neg\` operator is numeric-only.`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
return { node: { neg: [a.node] }, kind: a.kind, nullable: false };
|
|
1199
|
+
}
|
|
1200
|
+
case ts2.SyntaxKind.PlusToken:
|
|
1201
|
+
if (ts2.isNumericLiteral(operand)) return lowerNumericLiteral(ctx, operand, false);
|
|
1202
|
+
return report2(
|
|
1203
|
+
ctx,
|
|
1204
|
+
e,
|
|
1205
|
+
"scp-closed-set",
|
|
1206
|
+
"unary +",
|
|
1207
|
+
"Unary `+` (numeric coercion) is outside the Expression IR closed set \u2014 implicit type conversion does not exist (expression-ir.md \xA75)."
|
|
1208
|
+
);
|
|
1209
|
+
case ts2.SyntaxKind.ExclamationToken: {
|
|
1210
|
+
const a = lower(ctx, operand);
|
|
1211
|
+
if (a === void 0) return void 0;
|
|
1212
|
+
if (a.kind !== "bool" || a.nullable) {
|
|
1213
|
+
return report2(
|
|
1214
|
+
ctx,
|
|
1215
|
+
e,
|
|
1216
|
+
"scp-type",
|
|
1217
|
+
"truthiness",
|
|
1218
|
+
`The operand of \`!\` must be a non-null boolean, got ${describeKind(a)} \u2014 truthiness coercion is outside the closed set (compare explicitly instead).`
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
return { node: { not: [a.node] }, kind: "bool", nullable: false };
|
|
1222
|
+
}
|
|
1223
|
+
case ts2.SyntaxKind.TildeToken:
|
|
1224
|
+
return report2(
|
|
1225
|
+
ctx,
|
|
1226
|
+
e,
|
|
1227
|
+
"scp-closed-set",
|
|
1228
|
+
"bitwise ~",
|
|
1229
|
+
"The bitwise operator `~` is outside the Expression IR closed set (bitwise semantics are undefined in v1 \u2014 expression-ir.md \xA73)."
|
|
1230
|
+
);
|
|
1231
|
+
default: {
|
|
1232
|
+
const op = e.operator === ts2.SyntaxKind.PlusPlusToken ? "++" : "--";
|
|
1233
|
+
return report2(
|
|
1234
|
+
ctx,
|
|
1235
|
+
e,
|
|
1236
|
+
"scp-closed-set",
|
|
1237
|
+
op,
|
|
1238
|
+
`The mutation operator \`${op}\` is outside the Expression IR closed set (expressions are pure \u2014 no assignment, no mutation).`
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
var BITWISE_TOKENS = /* @__PURE__ */ new Map([
|
|
1244
|
+
[ts2.SyntaxKind.AmpersandToken, "&"],
|
|
1245
|
+
[ts2.SyntaxKind.BarToken, "|"],
|
|
1246
|
+
[ts2.SyntaxKind.CaretToken, "^"],
|
|
1247
|
+
[ts2.SyntaxKind.LessThanLessThanToken, "<<"],
|
|
1248
|
+
[ts2.SyntaxKind.GreaterThanGreaterThanToken, ">>"],
|
|
1249
|
+
[ts2.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, ">>>"]
|
|
1250
|
+
]);
|
|
1251
|
+
var COMPARISON_TOKENS = /* @__PURE__ */ new Map([
|
|
1252
|
+
[ts2.SyntaxKind.LessThanToken, "lt"],
|
|
1253
|
+
[ts2.SyntaxKind.LessThanEqualsToken, "le"],
|
|
1254
|
+
[ts2.SyntaxKind.GreaterThanToken, "gt"],
|
|
1255
|
+
[ts2.SyntaxKind.GreaterThanEqualsToken, "ge"]
|
|
1256
|
+
]);
|
|
1257
|
+
var ARITH_TOKENS = /* @__PURE__ */ new Map([
|
|
1258
|
+
[ts2.SyntaxKind.MinusToken, "sub"],
|
|
1259
|
+
[ts2.SyntaxKind.AsteriskToken, "mul"],
|
|
1260
|
+
[ts2.SyntaxKind.SlashToken, "div"],
|
|
1261
|
+
[ts2.SyntaxKind.PercentToken, "mod"]
|
|
1262
|
+
]);
|
|
1263
|
+
function lowerBinary(ctx, e) {
|
|
1264
|
+
const opKind = e.operatorToken.kind;
|
|
1265
|
+
const opText = e.operatorToken.getText(ctx.sf);
|
|
1266
|
+
if (opKind === ts2.SyntaxKind.EqualsEqualsToken || opKind === ts2.SyntaxKind.ExclamationEqualsToken) {
|
|
1267
|
+
return report2(
|
|
1268
|
+
ctx,
|
|
1269
|
+
e,
|
|
1270
|
+
"scp-closed-set",
|
|
1271
|
+
opText,
|
|
1272
|
+
`Loose equality \`${opText}\` is outside the Expression IR closed set \u2014 use \`${opText === "==" ? "===" : "!=="}\` (strict, monomorphic comparison; expression-ir.md \xA73).`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
{
|
|
1276
|
+
const bit = BITWISE_TOKENS.get(opKind);
|
|
1277
|
+
if (bit !== void 0) {
|
|
1278
|
+
return report2(
|
|
1279
|
+
ctx,
|
|
1280
|
+
e,
|
|
1281
|
+
"scp-closed-set",
|
|
1282
|
+
`bitwise ${bit}`,
|
|
1283
|
+
`The bitwise operator \`${bit}\` is outside the Expression IR closed set (bitwise semantics are undefined in v1 \u2014 expression-ir.md \xA73).`
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (opKind === ts2.SyntaxKind.InstanceOfKeyword) {
|
|
1288
|
+
return report2(
|
|
1289
|
+
ctx,
|
|
1290
|
+
e,
|
|
1291
|
+
"scp-closed-set",
|
|
1292
|
+
"instanceof",
|
|
1293
|
+
"`instanceof` is outside the Expression IR closed set (no runtime type reflection \u2014 expression-ir.md \xA73)."
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
if (opKind === ts2.SyntaxKind.InKeyword) {
|
|
1297
|
+
return report2(
|
|
1298
|
+
ctx,
|
|
1299
|
+
e,
|
|
1300
|
+
"scp-closed-set",
|
|
1301
|
+
"in",
|
|
1302
|
+
"The `in` operator is outside the Expression IR closed set."
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
if (opKind === ts2.SyntaxKind.CommaToken) {
|
|
1306
|
+
return report2(
|
|
1307
|
+
ctx,
|
|
1308
|
+
e,
|
|
1309
|
+
"scp-closed-set",
|
|
1310
|
+
"comma operator",
|
|
1311
|
+
"The comma operator is outside the Expression IR closed set (expressions are single pure values)."
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
if (opKind === ts2.SyntaxKind.AsteriskAsteriskToken) {
|
|
1315
|
+
return report2(
|
|
1316
|
+
ctx,
|
|
1317
|
+
e,
|
|
1318
|
+
"scp-closed-set",
|
|
1319
|
+
"**",
|
|
1320
|
+
"Exponentiation `**` is outside the Expression IR closed set."
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
if (opKind >= ts2.SyntaxKind.FirstAssignment && opKind <= ts2.SyntaxKind.LastAssignment) {
|
|
1324
|
+
return report2(
|
|
1325
|
+
ctx,
|
|
1326
|
+
e,
|
|
1327
|
+
"scp-closed-set",
|
|
1328
|
+
"assignment",
|
|
1329
|
+
`Assignment (\`${opText}\`) is outside the Expression IR closed set (expressions are pure \u2014 no assignment, no mutation).`
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
if (opKind === ts2.SyntaxKind.QuestionQuestionToken) {
|
|
1333
|
+
const a = lower(ctx, e.left);
|
|
1334
|
+
const b = lower(ctx, e.right);
|
|
1335
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
1336
|
+
const kind = unifyBranch(a.kind, b.kind);
|
|
1337
|
+
if (kind === void 0) {
|
|
1338
|
+
return report2(
|
|
1339
|
+
ctx,
|
|
1340
|
+
e,
|
|
1341
|
+
"scp-type",
|
|
1342
|
+
"??",
|
|
1343
|
+
`The sides of \`??\` must have the same type, got ${describeKind(a)} and ${describeKind(b)} (expression-ir.md \xA73: monomorphic branches).`
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
return { node: { coalesce: [a.node, b.node] }, kind, nullable: b.nullable };
|
|
1347
|
+
}
|
|
1348
|
+
if (opKind === ts2.SyntaxKind.AmpersandAmpersandToken || opKind === ts2.SyntaxKind.BarBarToken) {
|
|
1349
|
+
const op = opKind === ts2.SyntaxKind.AmpersandAmpersandToken ? "and" : "or";
|
|
1350
|
+
const a = lower(ctx, e.left);
|
|
1351
|
+
const b = lower(ctx, e.right);
|
|
1352
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
1353
|
+
for (const [side, v] of [
|
|
1354
|
+
["left", a],
|
|
1355
|
+
["right", b]
|
|
1356
|
+
]) {
|
|
1357
|
+
if (v.kind !== "bool" || v.nullable) {
|
|
1358
|
+
return report2(
|
|
1359
|
+
ctx,
|
|
1360
|
+
e,
|
|
1361
|
+
"scp-type",
|
|
1362
|
+
"truthiness",
|
|
1363
|
+
`The ${side} operand of \`${opText}\` must be a non-null boolean, got ${describeKind(v)} \u2014 truthiness coercion is outside the closed set (expression-ir.md \xA73: bool\xD7bool only).`
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return { node: { [op]: [a.node, b.node] }, kind: "bool", nullable: false };
|
|
1368
|
+
}
|
|
1369
|
+
if (opKind === ts2.SyntaxKind.EqualsEqualsEqualsToken || opKind === ts2.SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
1370
|
+
const op = opKind === ts2.SyntaxKind.EqualsEqualsEqualsToken ? "eq" : "ne";
|
|
1371
|
+
const a = lower(ctx, e.left);
|
|
1372
|
+
const b = lower(ctx, e.right);
|
|
1373
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
1374
|
+
const isNullCheck = a.kind === "null" || b.kind === "null";
|
|
1375
|
+
if (!isNullCheck) {
|
|
1376
|
+
const kind = unifyNumeric(a.kind, b.kind) ?? (a.kind === b.kind ? a.kind : void 0);
|
|
1377
|
+
if (kind === void 0 || kind === "obj" || kind === "arr") {
|
|
1378
|
+
return report2(
|
|
1379
|
+
ctx,
|
|
1380
|
+
e,
|
|
1381
|
+
"scp-type",
|
|
1382
|
+
opText,
|
|
1383
|
+
`\`${opText}\` compares same-type scalars only (int / float / string / bool \u2014 or \`x === null\`), got ${describeKind(a)} and ${describeKind(b)} (expression-ir.md \xA73).`
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
if (a.nullable || b.nullable) {
|
|
1387
|
+
return report2(
|
|
1388
|
+
ctx,
|
|
1389
|
+
e,
|
|
1390
|
+
"scp-type",
|
|
1391
|
+
opText,
|
|
1392
|
+
`An operand of \`${opText}\` is possibly null \u2014 compare against \`null\` explicitly or coalesce (\`??\`) first.`
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return { node: { [op]: [a.node, b.node] }, kind: "bool", nullable: false };
|
|
1397
|
+
}
|
|
1398
|
+
{
|
|
1399
|
+
const cmp = COMPARISON_TOKENS.get(opKind);
|
|
1400
|
+
if (cmp !== void 0) {
|
|
1401
|
+
const a = lower(ctx, e.left);
|
|
1402
|
+
const b = lower(ctx, e.right);
|
|
1403
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
1404
|
+
const numeric = unifyNumeric(a.kind, b.kind);
|
|
1405
|
+
const stringPair = a.kind === "string" && b.kind === "string";
|
|
1406
|
+
if (numeric === void 0 && !stringPair || a.nullable || b.nullable) {
|
|
1407
|
+
return report2(
|
|
1408
|
+
ctx,
|
|
1409
|
+
e,
|
|
1410
|
+
"scp-type",
|
|
1411
|
+
opText,
|
|
1412
|
+
`\`${opText}\` compares two non-null ints, floats, or strings, got ${describeKind(a)} and ${describeKind(b)} (expression-ir.md \xA73; int\xD7float mixing needs an explicit float literal \u2014 \xA75).`
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
return { node: { [cmp]: [a.node, b.node] }, kind: "bool", nullable: false };
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
if (opKind === ts2.SyntaxKind.PlusToken) {
|
|
1419
|
+
const a = lower(ctx, e.left);
|
|
1420
|
+
const b = lower(ctx, e.right);
|
|
1421
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
1422
|
+
if (a.nullable || b.nullable) {
|
|
1423
|
+
return report2(
|
|
1424
|
+
ctx,
|
|
1425
|
+
e,
|
|
1426
|
+
"scp-type",
|
|
1427
|
+
"+",
|
|
1428
|
+
"An operand of `+` is possibly null \u2014 coalesce (`??`) it first (the IR has no null-tolerant arithmetic)."
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
if (a.kind === "string" && b.kind === "string") {
|
|
1432
|
+
return { node: { concat: [a.node, b.node] }, kind: "string", nullable: false };
|
|
1433
|
+
}
|
|
1434
|
+
const numeric = unifyNumeric(a.kind, b.kind);
|
|
1435
|
+
if (numeric !== void 0) {
|
|
1436
|
+
return { node: { add: [a.node, b.node] }, kind: numeric, nullable: false };
|
|
1437
|
+
}
|
|
1438
|
+
if (a.kind === "string" && NUMERIC_KINDS.has(b.kind) || NUMERIC_KINDS.has(a.kind) && b.kind === "string") {
|
|
1439
|
+
return report2(
|
|
1440
|
+
ctx,
|
|
1441
|
+
e,
|
|
1442
|
+
"scp-type",
|
|
1443
|
+
"+",
|
|
1444
|
+
`\`+\` on ${describeKind(a)} and ${describeKind(b)}: implicit number\u2192string conversion is outside the closed set (expression-ir.md \xA73 \u2014 \`concat\` is string\xD7string only; no implicit toString).`
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
return report2(
|
|
1448
|
+
ctx,
|
|
1449
|
+
e,
|
|
1450
|
+
"scp-type",
|
|
1451
|
+
"+",
|
|
1452
|
+
`\`+\` needs two numbers (\u2192 \`add\`) or two strings (\u2192 \`concat\`), got ${describeKind(a)} and ${describeKind(b)} \u2014 the lowerer resolves \`+\` statically and never guesses (expression-ir.md \xA75; int\xD7float mixing needs an explicit float literal).`
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
{
|
|
1456
|
+
const arith = ARITH_TOKENS.get(opKind);
|
|
1457
|
+
if (arith !== void 0) {
|
|
1458
|
+
const a = lower(ctx, e.left);
|
|
1459
|
+
const b = lower(ctx, e.right);
|
|
1460
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
1461
|
+
const numeric = unifyNumeric(a.kind, b.kind);
|
|
1462
|
+
if (numeric === void 0 || a.nullable || b.nullable) {
|
|
1463
|
+
return report2(
|
|
1464
|
+
ctx,
|
|
1465
|
+
e,
|
|
1466
|
+
"scp-type",
|
|
1467
|
+
opText,
|
|
1468
|
+
`\`${opText}\` needs two non-null numbers of the same kind, got ${describeKind(a)} and ${describeKind(b)} (expression-ir.md \xA75: int\xD7float mixing needs an explicit float literal).`
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
const kind = arith === "div" ? "float" : numeric;
|
|
1472
|
+
return { node: { [arith]: [a.node, b.node] }, kind, nullable: false };
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return report2(
|
|
1476
|
+
ctx,
|
|
1477
|
+
e,
|
|
1478
|
+
"scp-closed-set",
|
|
1479
|
+
opText,
|
|
1480
|
+
`The operator \`${opText}\` is outside the Expression IR closed set (expression-ir.md \xA73).`
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
function lowerConditional(ctx, e) {
|
|
1484
|
+
const c = lower(ctx, e.condition);
|
|
1485
|
+
const t = lower(ctx, e.whenTrue);
|
|
1486
|
+
const f = lower(ctx, e.whenFalse);
|
|
1487
|
+
if (c === void 0 || t === void 0 || f === void 0) return void 0;
|
|
1488
|
+
if (c.kind !== "bool" || c.nullable) {
|
|
1489
|
+
return report2(
|
|
1490
|
+
ctx,
|
|
1491
|
+
e.condition,
|
|
1492
|
+
"scp-type",
|
|
1493
|
+
"truthiness",
|
|
1494
|
+
`The condition of \`?:\` must be a non-null boolean, got ${describeKind(c)} \u2014 truthiness coercion is outside the closed set (compare explicitly instead).`
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
const kind = unifyBranch(t.kind, f.kind);
|
|
1498
|
+
if (kind === void 0) {
|
|
1499
|
+
return report2(
|
|
1500
|
+
ctx,
|
|
1501
|
+
e,
|
|
1502
|
+
"scp-type",
|
|
1503
|
+
"?:",
|
|
1504
|
+
`The branches of \`?:\` must have the same type, got ${describeKind(t)} and ${describeKind(f)} (expression-ir.md \xA73: t/e \u306F\u540C\u4E00\u578B).`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
return {
|
|
1508
|
+
node: { cond: [c.node, t.node, f.node] },
|
|
1509
|
+
kind,
|
|
1510
|
+
nullable: t.nullable || f.nullable || t.kind === "null" || f.kind === "null"
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
function lowerObjectLiteral(ctx, e) {
|
|
1514
|
+
const out = {};
|
|
1515
|
+
let failed = false;
|
|
1516
|
+
for (const prop of e.properties) {
|
|
1517
|
+
if (ts2.isPropertyAssignment(prop)) {
|
|
1518
|
+
if (ts2.isComputedPropertyName(prop.name)) {
|
|
1519
|
+
report2(
|
|
1520
|
+
ctx,
|
|
1521
|
+
prop.name,
|
|
1522
|
+
"scp-closed-set",
|
|
1523
|
+
"computed property name",
|
|
1524
|
+
"A computed property name is outside the Expression IR closed set \u2014 `obj` construction needs statically known keys."
|
|
1525
|
+
);
|
|
1526
|
+
failed = true;
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
if (!ts2.isIdentifier(prop.name) && !ts2.isStringLiteralLike(prop.name)) {
|
|
1530
|
+
report2(
|
|
1531
|
+
ctx,
|
|
1532
|
+
prop.name,
|
|
1533
|
+
"scp-closed-set",
|
|
1534
|
+
"property name",
|
|
1535
|
+
"Only identifier / string-literal property names are supported in `obj` construction."
|
|
1536
|
+
);
|
|
1537
|
+
failed = true;
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (prop.name.text === "__proto__") {
|
|
1541
|
+
reportProtoKey(ctx, prop.name);
|
|
1542
|
+
failed = true;
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
const v = lower(ctx, prop.initializer);
|
|
1546
|
+
if (v === void 0) {
|
|
1547
|
+
failed = true;
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
out[prop.name.text] = v.node;
|
|
1551
|
+
} else if (ts2.isShorthandPropertyAssignment(prop)) {
|
|
1552
|
+
if (prop.name.text === "__proto__") {
|
|
1553
|
+
reportProtoKey(ctx, prop.name);
|
|
1554
|
+
failed = true;
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
const v = lower(ctx, prop.name);
|
|
1558
|
+
if (v === void 0) {
|
|
1559
|
+
failed = true;
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
out[prop.name.text] = v.node;
|
|
1563
|
+
} else if (ts2.isSpreadAssignment(prop)) {
|
|
1564
|
+
report2(
|
|
1565
|
+
ctx,
|
|
1566
|
+
prop,
|
|
1567
|
+
"scp-closed-set",
|
|
1568
|
+
"spread",
|
|
1569
|
+
"Spread is outside the Expression IR closed set (expression-ir.md \xA73)."
|
|
1570
|
+
);
|
|
1571
|
+
failed = true;
|
|
1572
|
+
} else {
|
|
1573
|
+
report2(
|
|
1574
|
+
ctx,
|
|
1575
|
+
prop,
|
|
1576
|
+
"scp-closed-set",
|
|
1577
|
+
"object member",
|
|
1578
|
+
"Only plain property assignments are supported in `obj` construction (no methods / accessors)."
|
|
1579
|
+
);
|
|
1580
|
+
failed = true;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
if (failed) return void 0;
|
|
1584
|
+
return { node: { obj: out }, kind: "obj", nullable: false };
|
|
1585
|
+
}
|
|
1586
|
+
function reportProtoKey(ctx, name) {
|
|
1587
|
+
return report2(
|
|
1588
|
+
ctx,
|
|
1589
|
+
name,
|
|
1590
|
+
"scp-closed-set",
|
|
1591
|
+
"__proto__",
|
|
1592
|
+
"The property name `__proto__` cannot be an `obj` construction key \u2014 in a JS object literal it sets the prototype rather than carrying data, and Expression IR obj keys are plain data. Choose a different key."
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
function lowerArrayLiteral(ctx, e) {
|
|
1596
|
+
const items = [];
|
|
1597
|
+
let failed = false;
|
|
1598
|
+
for (const el of e.elements) {
|
|
1599
|
+
if (ts2.isSpreadElement(el)) {
|
|
1600
|
+
report2(
|
|
1601
|
+
ctx,
|
|
1602
|
+
el,
|
|
1603
|
+
"scp-closed-set",
|
|
1604
|
+
"spread",
|
|
1605
|
+
"Spread is outside the Expression IR closed set (expression-ir.md \xA73)."
|
|
1606
|
+
);
|
|
1607
|
+
failed = true;
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (ts2.isOmittedExpression(el)) {
|
|
1611
|
+
report2(
|
|
1612
|
+
ctx,
|
|
1613
|
+
el,
|
|
1614
|
+
"scp-closed-set",
|
|
1615
|
+
"array hole",
|
|
1616
|
+
"An array hole (elision) is outside the Expression IR closed set."
|
|
1617
|
+
);
|
|
1618
|
+
failed = true;
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
const v = lower(ctx, el);
|
|
1622
|
+
if (v === void 0) {
|
|
1623
|
+
failed = true;
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
items.push(v.node);
|
|
1627
|
+
}
|
|
1628
|
+
if (failed) return void 0;
|
|
1629
|
+
return { node: { arr: items }, kind: "arr", nullable: false };
|
|
1630
|
+
}
|
|
1631
|
+
function lowerTemplate(ctx, e) {
|
|
1632
|
+
const parts = [];
|
|
1633
|
+
let failed = false;
|
|
1634
|
+
if (e.head.text !== "") parts.push(e.head.text);
|
|
1635
|
+
for (const span of e.templateSpans) {
|
|
1636
|
+
const v = lower(ctx, span.expression);
|
|
1637
|
+
if (v === void 0) {
|
|
1638
|
+
failed = true;
|
|
1639
|
+
} else if (v.kind !== "string" || v.nullable) {
|
|
1640
|
+
report2(
|
|
1641
|
+
ctx,
|
|
1642
|
+
span.expression,
|
|
1643
|
+
"scp-type",
|
|
1644
|
+
"template literal",
|
|
1645
|
+
`A template substitution must be a non-null string, got ${describeKind(v)} \u2014 implicit toString is outside the closed set (expression-ir.md \xA73: \u5168\u30D1\u30FC\u30C8\u304C string \u578B\u3067\u3042\u308B\u3053\u3068).`
|
|
1646
|
+
);
|
|
1647
|
+
failed = true;
|
|
1648
|
+
} else {
|
|
1649
|
+
parts.push(v.node);
|
|
1650
|
+
}
|
|
1651
|
+
if (span.literal.text !== "") parts.push(span.literal.text);
|
|
1652
|
+
}
|
|
1653
|
+
if (failed) return void 0;
|
|
1654
|
+
if (parts.length === 0) return { node: "", kind: "string", nullable: false };
|
|
1655
|
+
let node = parts[0];
|
|
1656
|
+
for (let i = 1; i < parts.length; i += 1) {
|
|
1657
|
+
node = { concat: [node, parts[i]] };
|
|
1658
|
+
}
|
|
1659
|
+
return { node, kind: "string", nullable: false };
|
|
1660
|
+
}
|
|
1661
|
+
function lowerPropertyAccess(ctx, e) {
|
|
1662
|
+
if (e.name.text === "length") {
|
|
1663
|
+
const receiver = e.expression;
|
|
1664
|
+
const recType = classifyAt(ctx, receiver);
|
|
1665
|
+
if (recType.kind === void 0) {
|
|
1666
|
+
return report2(
|
|
1667
|
+
ctx,
|
|
1668
|
+
e,
|
|
1669
|
+
"scp-type",
|
|
1670
|
+
".length",
|
|
1671
|
+
"The static type of the `.length` receiver is unresolvable (any / unknown) \u2014 the lowerer cannot disambiguate array `len` and never guesses."
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
if (recType.kind === "string") {
|
|
1675
|
+
return report2(
|
|
1676
|
+
ctx,
|
|
1677
|
+
e,
|
|
1678
|
+
"scp-closed-set",
|
|
1679
|
+
"string .length",
|
|
1680
|
+
"`.length` on a string is outside the Expression IR v1 closed set (expression-ir.md \xA78: the string-length unit differs across runtimes \u2014 arrays only)."
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
if (recType.kind === "arr") {
|
|
1684
|
+
if (recType.nullable || e.questionDotToken !== void 0) {
|
|
1685
|
+
return report2(
|
|
1686
|
+
ctx,
|
|
1687
|
+
e,
|
|
1688
|
+
"scp-type",
|
|
1689
|
+
".length",
|
|
1690
|
+
"The receiver of `.length` is possibly null \u2014 `len` needs a non-null array (coalesce or null-check first)."
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
const xs = lower(ctx, receiver);
|
|
1694
|
+
if (xs === void 0) return void 0;
|
|
1695
|
+
return { node: { len: [xs.node] }, kind: "int", nullable: false };
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
const segments = [];
|
|
1699
|
+
const links = [];
|
|
1700
|
+
let optional = false;
|
|
1701
|
+
let cur = e;
|
|
1702
|
+
for (; ; ) {
|
|
1703
|
+
const u = unwrapExpr2(cur);
|
|
1704
|
+
if (ts2.isPropertyAccessExpression(u)) {
|
|
1705
|
+
if (u.questionDotToken !== void 0) optional = true;
|
|
1706
|
+
segments.unshift(u.name.text);
|
|
1707
|
+
links.unshift(u);
|
|
1708
|
+
cur = u.expression;
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
cur = u;
|
|
1712
|
+
break;
|
|
1713
|
+
}
|
|
1714
|
+
if (!ts2.isIdentifier(cur)) {
|
|
1715
|
+
if (ts2.isElementAccessExpression(cur)) return lowerReject(ctx, cur);
|
|
1716
|
+
if (ts2.isCallExpression(cur) || ts2.isNewExpression(cur)) {
|
|
1717
|
+
return lowerReject(ctx, cur);
|
|
1718
|
+
}
|
|
1719
|
+
return report2(
|
|
1720
|
+
ctx,
|
|
1721
|
+
e,
|
|
1722
|
+
"scp-ref",
|
|
1723
|
+
"computed reference root",
|
|
1724
|
+
"A reference must be a plain property chain rooted at an input binding (`$.<field>\u2026`) \u2014 property access on a computed value is outside the closed set."
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
const rootScope = scopeOfRoot(ctx, cur);
|
|
1728
|
+
if (rootScope === void 0) {
|
|
1729
|
+
if (shadowsRootName(ctx, cur.text)) {
|
|
1730
|
+
return report2(
|
|
1731
|
+
ctx,
|
|
1732
|
+
cur,
|
|
1733
|
+
"scp-ref",
|
|
1734
|
+
cur.text,
|
|
1735
|
+
`'${cur.text}' does not resolve to the Input Port binding here \u2014 it is shadowed by a local declaration (a nested parameter, a block-scoped binding, or an import of the same name). Roots are matched by symbol identity, never by name (issue #264); rename the shadowing binding.`
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
if (KNOWN_HOST_GLOBALS.has(cur.text)) {
|
|
1739
|
+
return report2(
|
|
1740
|
+
ctx,
|
|
1741
|
+
cur,
|
|
1742
|
+
"scp-closed-set",
|
|
1743
|
+
cur.text,
|
|
1744
|
+
`The host global \`${cur.text}\` is outside the Expression IR closed set (no host objects, no runtime environment access \u2014 expression-ir.md \xA73).`
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
return report2(
|
|
1748
|
+
ctx,
|
|
1749
|
+
cur,
|
|
1750
|
+
"scp-ref",
|
|
1751
|
+
cur.text,
|
|
1752
|
+
`'${cur.text}' does not resolve to a recognized input binding \u2014 an expression may reference the Input Port (\`$.<field>\`, scope \`input\`) or a fan-out element (scope \`item\`) only (issue #264 unified reference model).`
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
const declared = rootScope === "input" ? ctx.inputFields : ctx.itemFields;
|
|
1756
|
+
if (declared !== void 0 && segments.length > 0 && !declared.has(segments[0])) {
|
|
1757
|
+
const surface = rootScope === "input" ? "input" : "fan-out element";
|
|
1758
|
+
return report2(
|
|
1759
|
+
ctx,
|
|
1760
|
+
e,
|
|
1761
|
+
"scp-ref",
|
|
1762
|
+
`${cur.text}.${segments[0]}`,
|
|
1763
|
+
`'${segments[0]}' is not a declared ${surface} field \u2014 declared: ${[...declared].sort().map((f) => `'${f}'`).join(", ") || "(none)"}. An expression reference must name a declared \`param.*\` input (issue #264: expression refs and ParamSpec names share one namespace; #243: unbound references are compile errors, never runtime leaks).`
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
for (const link of links) {
|
|
1767
|
+
if (link.questionDotToken !== void 0) continue;
|
|
1768
|
+
const recv = classifyAt(ctx, link.expression);
|
|
1769
|
+
if (recv.kind !== void 0 && recv.nullable) {
|
|
1770
|
+
return report2(
|
|
1771
|
+
ctx,
|
|
1772
|
+
link,
|
|
1773
|
+
"scp-type",
|
|
1774
|
+
"reference",
|
|
1775
|
+
`The intermediate value \`${link.expression.getText(ctx.sf)}\` of this reference is possibly null \u2014 use \`?.\` (refOpt) or coalesce (\`??\`) it first (expression-ir.md \xA73: \`a.b.c\` requires type-level non-null intermediates).`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
const cls = classifyAt(ctx, e);
|
|
1780
|
+
if (cls.kind === void 0) {
|
|
1781
|
+
return report2(
|
|
1782
|
+
ctx,
|
|
1783
|
+
e,
|
|
1784
|
+
"scp-type",
|
|
1785
|
+
"reference",
|
|
1786
|
+
`The static type of \`${e.getText(ctx.sf)}\` is unresolvable (any / unknown / unsupported) \u2014 every reference must carry a resolvable scalar / obj / arr type (the lowerer never guesses).`
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
const path = [rootScope, ...segments];
|
|
1790
|
+
return optional ? { node: { refOpt: path }, kind: cls.kind, nullable: true } : { node: { ref: path }, kind: cls.kind, nullable: cls.nullable };
|
|
1791
|
+
}
|
|
1792
|
+
function lowerBareIdentifier(ctx, e) {
|
|
1793
|
+
const name = e.text;
|
|
1794
|
+
if (name === "undefined") {
|
|
1795
|
+
return report2(
|
|
1796
|
+
ctx,
|
|
1797
|
+
e,
|
|
1798
|
+
"scp-closed-set",
|
|
1799
|
+
"undefined",
|
|
1800
|
+
"`undefined` does not exist in the Expression IR (\xA72.1) \u2014 use `null`."
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
if (name === "NaN" || name === "Infinity") {
|
|
1804
|
+
return report2(
|
|
1805
|
+
ctx,
|
|
1806
|
+
e,
|
|
1807
|
+
"scp-closed-set",
|
|
1808
|
+
name,
|
|
1809
|
+
`\`${name}\` cannot exist in the Expression IR (\xA72.1: non-finite values are Failures, never values).`
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
const scope = scopeOfRoot(ctx, e);
|
|
1813
|
+
if (scope !== void 0) {
|
|
1814
|
+
return report2(
|
|
1815
|
+
ctx,
|
|
1816
|
+
e,
|
|
1817
|
+
"scp-ref",
|
|
1818
|
+
name,
|
|
1819
|
+
`The ${scope === "item" ? "element" : "input"} placeholder \`${name}\` itself is not a value \u2014 reference a specific field (\`${name}.<field>\`).`
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
if (shadowsRootName(ctx, name)) {
|
|
1823
|
+
return report2(
|
|
1824
|
+
ctx,
|
|
1825
|
+
e,
|
|
1826
|
+
"scp-ref",
|
|
1827
|
+
name,
|
|
1828
|
+
`'${name}' does not resolve to the Input Port binding here \u2014 it is shadowed by a local declaration (a nested parameter, a block-scoped binding, or an import of the same name). Roots are matched by symbol identity, never by name (issue #264); rename the shadowing binding.`
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
if (KNOWN_HOST_GLOBALS.has(name)) {
|
|
1832
|
+
return report2(
|
|
1833
|
+
ctx,
|
|
1834
|
+
e,
|
|
1835
|
+
"scp-closed-set",
|
|
1836
|
+
name,
|
|
1837
|
+
`The host global \`${name}\` is outside the Expression IR closed set (no host objects \u2014 expression-ir.md \xA73).`
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
return report2(
|
|
1841
|
+
ctx,
|
|
1842
|
+
e,
|
|
1843
|
+
"scp-ref",
|
|
1844
|
+
name,
|
|
1845
|
+
`'${name}' does not resolve to a recognized input binding \u2014 an expression may reference the Input Port (\`$.<field>\`, scope \`input\`) or a fan-out element (scope \`item\`) only (issue #264 unified reference model).`
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
function lowerReject(ctx, e) {
|
|
1849
|
+
if (ts2.isCallExpression(e)) {
|
|
1850
|
+
return report2(
|
|
1851
|
+
ctx,
|
|
1852
|
+
e,
|
|
1853
|
+
"scp-closed-set",
|
|
1854
|
+
"function call",
|
|
1855
|
+
"A function call is outside the Expression IR closed set \u2014 `Math.*`, methods, and user functions cannot be lowered (expression-ir.md \xA73; the sole planned exception, structural `.map` lowering, is Phase 3 S3 \u2014 issue #263)."
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
if (ts2.isNewExpression(e)) {
|
|
1859
|
+
return report2(
|
|
1860
|
+
ctx,
|
|
1861
|
+
e,
|
|
1862
|
+
"scp-closed-set",
|
|
1863
|
+
"new",
|
|
1864
|
+
"`new` is outside the Expression IR closed set \u2014 no object construction beyond `obj` / `arr` literals (and `Date` etc. are host objects; expression-ir.md \xA73)."
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
if (ts2.isElementAccessExpression(e)) {
|
|
1868
|
+
return report2(
|
|
1869
|
+
ctx,
|
|
1870
|
+
e,
|
|
1871
|
+
"scp-closed-set",
|
|
1872
|
+
"element access",
|
|
1873
|
+
"Element access (`a[i]` / `a?.[i]`) is outside the Expression IR closed set \u2014 references are plain property chains (`ref` / `refOpt`) only."
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
if (ts2.isTypeOfExpression(e)) {
|
|
1877
|
+
return report2(
|
|
1878
|
+
ctx,
|
|
1879
|
+
e,
|
|
1880
|
+
"scp-closed-set",
|
|
1881
|
+
"typeof",
|
|
1882
|
+
"`typeof` is outside the Expression IR closed set (no runtime type reflection \u2014 expression-ir.md \xA73)."
|
|
1883
|
+
);
|
|
1884
|
+
}
|
|
1885
|
+
if (ts2.isRegularExpressionLiteral(e)) {
|
|
1886
|
+
return report2(
|
|
1887
|
+
ctx,
|
|
1888
|
+
e,
|
|
1889
|
+
"scp-closed-set",
|
|
1890
|
+
"regex literal",
|
|
1891
|
+
"A regex literal is outside the Expression IR closed set (`RegExp` is a host object \u2014 expression-ir.md \xA73)."
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
if (ts2.isTaggedTemplateExpression(e)) {
|
|
1895
|
+
return report2(
|
|
1896
|
+
ctx,
|
|
1897
|
+
e,
|
|
1898
|
+
"scp-closed-set",
|
|
1899
|
+
"tagged template",
|
|
1900
|
+
"A tagged template is a function call \u2014 outside the Expression IR closed set."
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
if (ts2.isArrowFunction(e) || ts2.isFunctionExpression(e)) {
|
|
1904
|
+
return report2(
|
|
1905
|
+
ctx,
|
|
1906
|
+
e,
|
|
1907
|
+
"scp-closed-set",
|
|
1908
|
+
"function expression",
|
|
1909
|
+
"A closure is outside the Expression IR closed set (expression-ir.md \xA73; inline arrows are only meaningful under the S3 structural `.map` lowering \u2014 issue #263)."
|
|
1910
|
+
);
|
|
1911
|
+
}
|
|
1912
|
+
if (e.kind === ts2.SyntaxKind.ThisKeyword) {
|
|
1913
|
+
return report2(
|
|
1914
|
+
ctx,
|
|
1915
|
+
e,
|
|
1916
|
+
"scp-closed-set",
|
|
1917
|
+
"this",
|
|
1918
|
+
"`this` is outside the Expression IR closed set (expressions are pure over their declared inputs)."
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
if (ts2.isVoidExpression(e)) {
|
|
1922
|
+
return report2(
|
|
1923
|
+
ctx,
|
|
1924
|
+
e,
|
|
1925
|
+
"scp-closed-set",
|
|
1926
|
+
"void",
|
|
1927
|
+
"`void` is outside the Expression IR closed set (`undefined` does not exist in the IR)."
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
if (ts2.isDeleteExpression(e)) {
|
|
1931
|
+
return report2(
|
|
1932
|
+
ctx,
|
|
1933
|
+
e,
|
|
1934
|
+
"scp-closed-set",
|
|
1935
|
+
"delete",
|
|
1936
|
+
"`delete` is outside the Expression IR closed set (expressions are pure)."
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
if (ts2.isAwaitExpression(e)) {
|
|
1940
|
+
return report2(
|
|
1941
|
+
ctx,
|
|
1942
|
+
e,
|
|
1943
|
+
"scp-closed-set",
|
|
1944
|
+
"await",
|
|
1945
|
+
"`await` is outside the Expression IR closed set (expressions are synchronous and pure)."
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
return report2(
|
|
1949
|
+
ctx,
|
|
1950
|
+
e,
|
|
1951
|
+
"scp-closed-set",
|
|
1952
|
+
ts2.SyntaxKind[e.kind],
|
|
1953
|
+
`Unsupported expression construct (${ts2.SyntaxKind[e.kind]}) \u2014 outside the Expression IR closed set (expression-ir.md \xA73: \u3053\u306E\u8868\u306B\u306A\u3044\u3082\u306E\u306F\u3059\u3079\u3066\u30B3\u30F3\u30D1\u30A4\u30EB\u30A8\u30E9\u30FC).`
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
function describeKind(v) {
|
|
1957
|
+
const base = v.kind === "num" ? "number (int-or-float)" : v.kind === "bool" ? "boolean" : v.kind;
|
|
1958
|
+
return v.nullable && v.kind !== "null" ? `${base} | null` : base;
|
|
1959
|
+
}
|
|
1960
|
+
var EMPTY_SYMBOLS = /* @__PURE__ */ new Set();
|
|
1961
|
+
function lowerTsExpression(expr, options) {
|
|
1962
|
+
return lowerTsExpressionWithKind(expr, options).result;
|
|
1963
|
+
}
|
|
1964
|
+
function lowerTsExpressionWithKind(expr, options) {
|
|
1965
|
+
const ctx = {
|
|
1966
|
+
checker: options.checker,
|
|
1967
|
+
sf: options.sourceFile,
|
|
1968
|
+
fileName: options.fileName ?? options.sourceFile.fileName,
|
|
1969
|
+
inputRootSymbols: options.inputRootSymbols,
|
|
1970
|
+
itemRootSymbols: options.itemRootSymbols ?? EMPTY_SYMBOLS,
|
|
1971
|
+
inputFields: options.inputFields,
|
|
1972
|
+
itemFields: options.itemFields,
|
|
1973
|
+
diagnostics: []
|
|
1974
|
+
};
|
|
1975
|
+
const lowered = lower(ctx, expr);
|
|
1976
|
+
if (ctx.diagnostics.length > 0 || lowered === void 0) {
|
|
1977
|
+
if (ctx.diagnostics.length === 0) {
|
|
1978
|
+
report2(
|
|
1979
|
+
ctx,
|
|
1980
|
+
expr,
|
|
1981
|
+
"scp-closed-set",
|
|
1982
|
+
"expression",
|
|
1983
|
+
"The expression could not be lowered to the Expression IR closed set."
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
return {
|
|
1987
|
+
result: { diagnostics: ctx.diagnostics, errorCount: ctx.diagnostics.length }
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
try {
|
|
1991
|
+
const expression = canonicalizeExpressionSpec(
|
|
1992
|
+
{ exprVersion: EXPR_VERSION, expr: lowered.node },
|
|
1993
|
+
"expression lowering"
|
|
1994
|
+
);
|
|
1995
|
+
return {
|
|
1996
|
+
result: { expression, diagnostics: [], errorCount: 0 },
|
|
1997
|
+
kind: lowered.kind,
|
|
1998
|
+
nullable: lowered.nullable
|
|
1999
|
+
};
|
|
2000
|
+
} catch (err) {
|
|
2001
|
+
report2(
|
|
2002
|
+
ctx,
|
|
2003
|
+
expr,
|
|
2004
|
+
"scp-closed-set",
|
|
2005
|
+
"expression",
|
|
2006
|
+
`Lowered node failed spec validation: ${err instanceof Error ? err.message : String(err)}`
|
|
2007
|
+
);
|
|
2008
|
+
return {
|
|
2009
|
+
result: { diagnostics: ctx.diagnostics, errorCount: ctx.diagnostics.length }
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
function inputParamTypeText(d) {
|
|
2014
|
+
switch (d.kind) {
|
|
2015
|
+
case "string":
|
|
2016
|
+
return "string";
|
|
2017
|
+
case "number":
|
|
2018
|
+
return "number";
|
|
2019
|
+
case "literal": {
|
|
2020
|
+
const parts = (d.literals ?? []).map(
|
|
2021
|
+
(v) => typeof v === "string" ? JSON.stringify(v) : String(v)
|
|
2022
|
+
);
|
|
2023
|
+
if (parts.length === 0) {
|
|
2024
|
+
throw new Error(
|
|
2025
|
+
"expression lowering: a `literal` input param descriptor must carry its allowed values (fail-closed \u2014 an unconstrained literal cannot be typed)."
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
return parts.join(" | ");
|
|
2029
|
+
}
|
|
2030
|
+
case "array": {
|
|
2031
|
+
const fields = Object.entries(d.element ?? {}).map(
|
|
2032
|
+
([f, fd]) => `readonly ${JSON.stringify(f)}: ${inputParamTypeText(fd)};`
|
|
2033
|
+
);
|
|
2034
|
+
return `ReadonlyArray<{ ${fields.join(" ")} }>`;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
var LIB_CACHE = /* @__PURE__ */ new Map();
|
|
2039
|
+
var TEXT_ENTRY_BINDING = "__scpLowerTarget";
|
|
2040
|
+
function lowerExpressionText(exprText, options = {}) {
|
|
2041
|
+
return lowerExpressionTextWithLayout(exprText, options).result;
|
|
2042
|
+
}
|
|
2043
|
+
function lowerExpressionTextWithLayout(exprText, options = {}) {
|
|
2044
|
+
const inputName = options.inputName ?? "$";
|
|
2045
|
+
const fileName = options.fileName ?? "scp-expression.ts";
|
|
2046
|
+
const inputs = {};
|
|
2047
|
+
for (const [name, d] of Object.entries(options.params ?? {})) {
|
|
2048
|
+
inputs[name] = inputParamTypeText(d);
|
|
2049
|
+
}
|
|
2050
|
+
Object.assign(inputs, options.inputs ?? {});
|
|
2051
|
+
const declareOf = (name, record) => {
|
|
2052
|
+
const fields = Object.entries(record).map(([k, t]) => ` readonly ${JSON.stringify(k)}: ${t};`).join("\n");
|
|
2053
|
+
return `declare const ${name}: {
|
|
2054
|
+
${fields}
|
|
2055
|
+
};
|
|
2056
|
+
`;
|
|
2057
|
+
};
|
|
2058
|
+
let declares = declareOf(inputName, inputs);
|
|
2059
|
+
const itemName = options.itemName ?? "$el";
|
|
2060
|
+
if (options.itemInputs !== void 0 && itemName === inputName) {
|
|
2061
|
+
throw new Error(
|
|
2062
|
+
`expression lowering: the element binding '${itemName}' collides with the input binding \u2014 the input/item scopes are one namespace of distinct bindings (fail-closed; rename one).`
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
if (options.itemInputs !== void 0) {
|
|
2066
|
+
declares += declareOf(itemName, options.itemInputs);
|
|
2067
|
+
}
|
|
2068
|
+
const prefix = declares + `const ${TEXT_ENTRY_BINDING} = (
|
|
2069
|
+
`;
|
|
2070
|
+
const exprLine = prefix.split("\n").length;
|
|
2071
|
+
const source = prefix + `${exprText}
|
|
2072
|
+
);
|
|
2073
|
+
export default ${TEXT_ENTRY_BINDING};
|
|
2074
|
+
`;
|
|
2075
|
+
const compilerOptions = {
|
|
2076
|
+
strict: true,
|
|
2077
|
+
target: ts2.ScriptTarget.ES2022,
|
|
2078
|
+
module: ts2.ModuleKind.ESNext,
|
|
2079
|
+
lib: ["lib.es2022.d.ts"],
|
|
2080
|
+
types: [],
|
|
2081
|
+
skipLibCheck: true,
|
|
2082
|
+
noEmit: true,
|
|
2083
|
+
noResolve: true
|
|
2084
|
+
};
|
|
2085
|
+
const base = ts2.createCompilerHost(compilerOptions, true);
|
|
2086
|
+
const host = {
|
|
2087
|
+
...base,
|
|
2088
|
+
getSourceFile: (name, languageVersionOrOptions) => {
|
|
2089
|
+
if (name === fileName) {
|
|
2090
|
+
return ts2.createSourceFile(
|
|
2091
|
+
fileName,
|
|
2092
|
+
source,
|
|
2093
|
+
ts2.ScriptTarget.ES2022,
|
|
2094
|
+
/* setParentNodes */
|
|
2095
|
+
true
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
const cached = LIB_CACHE.get(name);
|
|
2099
|
+
if (cached !== void 0) return cached;
|
|
2100
|
+
const sf2 = base.getSourceFile(name, languageVersionOrOptions);
|
|
2101
|
+
if (sf2 !== void 0) LIB_CACHE.set(name, sf2);
|
|
2102
|
+
return sf2;
|
|
2103
|
+
},
|
|
2104
|
+
fileExists: (name) => name === fileName || base.fileExists(name),
|
|
2105
|
+
readFile: (name) => name === fileName ? source : base.readFile(name),
|
|
2106
|
+
writeFile: () => {
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
const program = ts2.createProgram([fileName], compilerOptions, host);
|
|
2110
|
+
const sf = program.getSourceFile(fileName);
|
|
2111
|
+
if (sf === void 0) {
|
|
2112
|
+
throw new Error(`expression lowering: virtual source file '${fileName}' missing.`);
|
|
2113
|
+
}
|
|
2114
|
+
const syntactic = program.getSyntacticDiagnostics(sf);
|
|
2115
|
+
if (syntactic.length > 0) {
|
|
2116
|
+
const diagnostics = syntactic.map((d) => {
|
|
2117
|
+
const pos = d.start ?? 0;
|
|
2118
|
+
const { line, character } = sf.getLineAndCharacterOfPosition(pos);
|
|
2119
|
+
return {
|
|
2120
|
+
code: "scp-syntax",
|
|
2121
|
+
category: "error",
|
|
2122
|
+
message: `Expression does not parse: ${ts2.flattenDiagnosticMessageText(d.messageText, " ")}`,
|
|
2123
|
+
construct: "syntax error",
|
|
2124
|
+
file: fileName,
|
|
2125
|
+
line: line + 1,
|
|
2126
|
+
column: character + 1
|
|
2127
|
+
};
|
|
2128
|
+
});
|
|
2129
|
+
return { result: { diagnostics, errorCount: diagnostics.length }, exprLine };
|
|
2130
|
+
}
|
|
2131
|
+
let target;
|
|
2132
|
+
let inputDecl;
|
|
2133
|
+
let itemDecl;
|
|
2134
|
+
for (const stmt of sf.statements) {
|
|
2135
|
+
if (!ts2.isVariableStatement(stmt)) continue;
|
|
2136
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
2137
|
+
if (!ts2.isIdentifier(decl.name)) continue;
|
|
2138
|
+
if (decl.name.text === TEXT_ENTRY_BINDING && decl.initializer !== void 0) {
|
|
2139
|
+
target = decl.initializer;
|
|
2140
|
+
} else if (decl.name.text === inputName && inputDecl === void 0) {
|
|
2141
|
+
inputDecl = decl.name;
|
|
2142
|
+
} else if (decl.name.text === itemName && itemDecl === void 0) {
|
|
2143
|
+
itemDecl = decl.name;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
if (target === void 0) {
|
|
2148
|
+
throw new Error("expression lowering: synthesized target binding missing.");
|
|
2149
|
+
}
|
|
2150
|
+
const checker = program.getTypeChecker();
|
|
2151
|
+
const inputSymbol = inputDecl !== void 0 ? checker.getSymbolAtLocation(inputDecl) : void 0;
|
|
2152
|
+
if (inputSymbol === void 0) {
|
|
2153
|
+
throw new Error(
|
|
2154
|
+
`expression lowering: the synthesized input binding '${inputName}' did not resolve to a symbol (internal error \u2014 fail-closed).`
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2157
|
+
const itemSymbol = options.itemInputs !== void 0 && itemDecl !== void 0 ? checker.getSymbolAtLocation(itemDecl) : void 0;
|
|
2158
|
+
if (options.itemInputs !== void 0 && itemSymbol === void 0) {
|
|
2159
|
+
throw new Error(
|
|
2160
|
+
`expression lowering: the synthesized element binding '${itemName}' did not resolve to a symbol (internal error \u2014 fail-closed).`
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
const { result, kind, nullable } = lowerTsExpressionWithKind(target, {
|
|
2164
|
+
checker,
|
|
2165
|
+
sourceFile: sf,
|
|
2166
|
+
fileName,
|
|
2167
|
+
inputRootSymbols: /* @__PURE__ */ new Set([inputSymbol]),
|
|
2168
|
+
inputFields: new Set(Object.keys(inputs)),
|
|
2169
|
+
...itemSymbol !== void 0 ? {
|
|
2170
|
+
itemRootSymbols: /* @__PURE__ */ new Set([itemSymbol]),
|
|
2171
|
+
itemFields: new Set(Object.keys(options.itemInputs ?? {}))
|
|
2172
|
+
} : {}
|
|
2173
|
+
});
|
|
2174
|
+
return {
|
|
2175
|
+
result,
|
|
2176
|
+
exprLine,
|
|
2177
|
+
...kind !== void 0 ? { kind } : {},
|
|
2178
|
+
...nullable !== void 0 ? { nullable } : {}
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
function lowerExpressionTextOrThrow(exprText, options = {}) {
|
|
2182
|
+
const result = lowerExpressionText(exprText, options);
|
|
2183
|
+
if (result.expression === void 0) {
|
|
2184
|
+
throw new ExpressionLowerError(result.diagnostics);
|
|
2185
|
+
}
|
|
2186
|
+
return result.expression;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
// src/transform/structural-lower.ts
|
|
2190
|
+
import ts3 from "typescript";
|
|
2191
|
+
var WRITE_OPS = /* @__PURE__ */ new Set([
|
|
2192
|
+
"put",
|
|
2193
|
+
"update",
|
|
2194
|
+
"delete",
|
|
2195
|
+
"conditionCheck"
|
|
2196
|
+
]);
|
|
2197
|
+
var OPTIONS_ARG_INDEX = {
|
|
2198
|
+
put: 2,
|
|
2199
|
+
update: 3,
|
|
2200
|
+
delete: 2,
|
|
2201
|
+
conditionCheck: 2
|
|
2202
|
+
};
|
|
2203
|
+
function unwrapExpr3(e) {
|
|
2204
|
+
let cur = e;
|
|
2205
|
+
for (; ; ) {
|
|
2206
|
+
if (ts3.isParenthesizedExpression(cur)) cur = cur.expression;
|
|
2207
|
+
else if (ts3.isAsExpression(cur)) cur = cur.expression;
|
|
2208
|
+
else if (ts3.isSatisfiesExpression(cur)) cur = cur.expression;
|
|
2209
|
+
else if (ts3.isNonNullExpression(cur)) cur = cur.expression;
|
|
2210
|
+
else if (ts3.isTypeAssertionExpression(cur)) cur = cur.expression;
|
|
2211
|
+
else return cur;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
function referencesIdentifier(node, name) {
|
|
2215
|
+
let found = false;
|
|
2216
|
+
const visit = (n) => {
|
|
2217
|
+
if (found) return;
|
|
2218
|
+
if (ts3.isIdentifier(n) && n.text === name) {
|
|
2219
|
+
const parent = n.parent;
|
|
2220
|
+
if (parent !== void 0) {
|
|
2221
|
+
if (ts3.isPropertyAccessExpression(parent) && parent.name === n) return;
|
|
2222
|
+
if (ts3.isPropertyAssignment(parent) && parent.name === n) return;
|
|
2223
|
+
if (ts3.isQualifiedName(parent) && parent.right === n) return;
|
|
2224
|
+
}
|
|
2225
|
+
found = true;
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
ts3.forEachChild(n, visit);
|
|
2229
|
+
};
|
|
2230
|
+
visit(node);
|
|
2231
|
+
return found;
|
|
2232
|
+
}
|
|
2233
|
+
function scalarParamTypeText(expr) {
|
|
2234
|
+
const e = unwrapExpr3(expr);
|
|
2235
|
+
if (!ts3.isCallExpression(e) || !ts3.isPropertyAccessExpression(e.expression)) {
|
|
2236
|
+
return void 0;
|
|
2237
|
+
}
|
|
2238
|
+
const kind = e.expression.name.text;
|
|
2239
|
+
if (kind === "string") return "string";
|
|
2240
|
+
if (kind === "number") return "number";
|
|
2241
|
+
if (kind !== "literal") return void 0;
|
|
2242
|
+
const parts = [];
|
|
2243
|
+
for (let i = 0; i < e.arguments.length; i += 1) {
|
|
2244
|
+
const arg = unwrapExpr3(e.arguments[i]);
|
|
2245
|
+
if (i === e.arguments.length - 1 && ts3.isObjectLiteralExpression(arg)) break;
|
|
2246
|
+
if (ts3.isStringLiteralLike(arg)) {
|
|
2247
|
+
parts.push(JSON.stringify(arg.text));
|
|
2248
|
+
} else if (ts3.isNumericLiteral(arg)) {
|
|
2249
|
+
parts.push(arg.text);
|
|
2250
|
+
} else if (ts3.isPrefixUnaryExpression(arg) && arg.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(arg.operand)) {
|
|
2251
|
+
parts.push(`-${arg.operand.text}`);
|
|
2252
|
+
} else {
|
|
2253
|
+
return void 0;
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
return parts.length > 0 ? parts.join(" | ") : void 0;
|
|
2257
|
+
}
|
|
2258
|
+
function readParamsModel(expr) {
|
|
2259
|
+
const obj = unwrapExpr3(expr);
|
|
2260
|
+
if (!ts3.isObjectLiteralExpression(obj)) return void 0;
|
|
2261
|
+
const scalars = /* @__PURE__ */ new Map();
|
|
2262
|
+
const arrays = /* @__PURE__ */ new Map();
|
|
2263
|
+
for (const prop of obj.properties) {
|
|
2264
|
+
if (!ts3.isPropertyAssignment(prop) || !ts3.isIdentifier(prop.name) && !ts3.isStringLiteralLike(prop.name)) {
|
|
2265
|
+
return void 0;
|
|
2266
|
+
}
|
|
2267
|
+
const name = prop.name.text;
|
|
2268
|
+
const init = unwrapExpr3(prop.initializer);
|
|
2269
|
+
if (ts3.isCallExpression(init) && ts3.isPropertyAccessExpression(init.expression) && init.expression.name.text === "array") {
|
|
2270
|
+
const shape = init.arguments.length >= 1 ? unwrapExpr3(init.arguments[0]) : void 0;
|
|
2271
|
+
if (shape === void 0 || !ts3.isObjectLiteralExpression(shape)) return void 0;
|
|
2272
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2273
|
+
for (const f of shape.properties) {
|
|
2274
|
+
if (!ts3.isPropertyAssignment(f) || !ts3.isIdentifier(f.name) && !ts3.isStringLiteralLike(f.name)) {
|
|
2275
|
+
return void 0;
|
|
2276
|
+
}
|
|
2277
|
+
const t2 = scalarParamTypeText(f.initializer);
|
|
2278
|
+
if (t2 === void 0) return void 0;
|
|
2279
|
+
fields.set(f.name.text, t2);
|
|
2280
|
+
}
|
|
2281
|
+
arrays.set(name, fields);
|
|
2282
|
+
continue;
|
|
2283
|
+
}
|
|
2284
|
+
const t = scalarParamTypeText(prop.initializer);
|
|
2285
|
+
if (t === void 0) return void 0;
|
|
2286
|
+
scalars.set(name, t);
|
|
2287
|
+
}
|
|
2288
|
+
return { scalars, arrays };
|
|
2289
|
+
}
|
|
2290
|
+
function conditionInputs(params) {
|
|
2291
|
+
const out = {};
|
|
2292
|
+
for (const [name, t] of params.scalars) out[name] = t;
|
|
2293
|
+
for (const [name, fields] of params.arrays) {
|
|
2294
|
+
const rec = [...fields].map(([f, t]) => `readonly ${JSON.stringify(f)}: ${t};`);
|
|
2295
|
+
out[name] = `ReadonlyArray<{ ${rec.join(" ")} }>`;
|
|
2296
|
+
}
|
|
2297
|
+
return out;
|
|
2298
|
+
}
|
|
2299
|
+
function report3(ctx, node, code, construct, message) {
|
|
2300
|
+
const { line, character } = ctx.sf.getLineAndCharacterOfPosition(node.getStart(ctx.sf));
|
|
2301
|
+
ctx.diagnostics.push({
|
|
2302
|
+
code,
|
|
2303
|
+
category: "error",
|
|
2304
|
+
message,
|
|
2305
|
+
construct,
|
|
2306
|
+
file: ctx.fileName,
|
|
2307
|
+
line: line + 1,
|
|
2308
|
+
column: character + 1
|
|
2309
|
+
});
|
|
2310
|
+
return void 0;
|
|
2311
|
+
}
|
|
2312
|
+
function formatStructuralLowerDiagnostic(d) {
|
|
2313
|
+
return `${d.file}:${d.line}:${d.column} \u2014 SCP structural lowering error [${d.construct}]: ${d.message}`;
|
|
2314
|
+
}
|
|
2315
|
+
function createSingleFileChecker(sf, fileName, sourceText) {
|
|
2316
|
+
const compilerOptions = {
|
|
2317
|
+
noResolve: true,
|
|
2318
|
+
noLib: true,
|
|
2319
|
+
allowJs: true,
|
|
2320
|
+
noEmit: true,
|
|
2321
|
+
target: ts3.ScriptTarget.Latest,
|
|
2322
|
+
module: ts3.ModuleKind.ESNext
|
|
2323
|
+
};
|
|
2324
|
+
const host = {
|
|
2325
|
+
getSourceFile: (name) => name === fileName ? sf : void 0,
|
|
2326
|
+
getDefaultLibFileName: () => "lib.d.ts",
|
|
2327
|
+
writeFile: () => {
|
|
2328
|
+
},
|
|
2329
|
+
getCurrentDirectory: () => "",
|
|
2330
|
+
getCanonicalFileName: (f) => f,
|
|
2331
|
+
useCaseSensitiveFileNames: () => true,
|
|
2332
|
+
getNewLine: () => "\n",
|
|
2333
|
+
fileExists: (name) => name === fileName,
|
|
2334
|
+
readFile: (name) => name === fileName ? sourceText : void 0
|
|
2335
|
+
};
|
|
2336
|
+
return ts3.createProgram([fileName], compilerOptions, host).getTypeChecker();
|
|
2337
|
+
}
|
|
2338
|
+
function verifyConditionRootBindings(ctx, scope, cond) {
|
|
2339
|
+
const expected = /* @__PURE__ */ new Map();
|
|
2340
|
+
if (scope.element !== void 0) {
|
|
2341
|
+
expected.set(scope.element.name, {
|
|
2342
|
+
symbol: scope.element.symbol,
|
|
2343
|
+
origin: "fan-out element parameter"
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
if (scope.pName !== void 0 && !expected.has(scope.pName)) {
|
|
2347
|
+
expected.set(scope.pName, {
|
|
2348
|
+
symbol: scope.pSymbol,
|
|
2349
|
+
origin: "defineScpTransaction params parameter"
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
if (expected.size === 0) return true;
|
|
2353
|
+
let ok = true;
|
|
2354
|
+
const visit = (n) => {
|
|
2355
|
+
if (ts3.isIdentifier(n)) {
|
|
2356
|
+
const exp = expected.get(n.text);
|
|
2357
|
+
if (exp !== void 0) {
|
|
2358
|
+
const parent = n.parent;
|
|
2359
|
+
const isPropertyPosition = parent !== void 0 && (ts3.isPropertyAccessExpression(parent) && parent.name === n || ts3.isPropertyAssignment(parent) && parent.name === n || ts3.isQualifiedName(parent) && parent.right === n);
|
|
2360
|
+
if (!isPropertyPosition) {
|
|
2361
|
+
const symbol = ctx.checker.getSymbolAtLocation(n);
|
|
2362
|
+
if (exp.symbol === void 0 || symbol === void 0 || symbol !== exp.symbol) {
|
|
2363
|
+
report3(
|
|
2364
|
+
ctx,
|
|
2365
|
+
n,
|
|
2366
|
+
"scp-ref",
|
|
2367
|
+
n.text,
|
|
2368
|
+
`'${n.text}' here does not resolve to the ${exp.origin} \u2014 it is shadowed by another declaration (a nested parameter, a block-scoped binding, or an import of the same name). Input Port references are matched by symbol identity, never by name (issue #264); rename the shadowing binding.`
|
|
2369
|
+
);
|
|
2370
|
+
ok = false;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
ts3.forEachChild(n, visit);
|
|
2376
|
+
};
|
|
2377
|
+
visit(cond);
|
|
2378
|
+
return ok;
|
|
2379
|
+
}
|
|
2380
|
+
function lowerCondition(ctx, scope, cond) {
|
|
2381
|
+
if (scope.params === void 0) {
|
|
2382
|
+
return report3(
|
|
2383
|
+
ctx,
|
|
2384
|
+
scope.paramsNode,
|
|
2385
|
+
"scp-params",
|
|
2386
|
+
"params",
|
|
2387
|
+
"The defineScpTransaction params map must be an inline object literal of `param.*` factories \u2014 the guard condition types are derived from it for the monomorphic Expression IR lowering (fail-closed)."
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
if (!verifyConditionRootBindings(ctx, scope, cond)) return void 0;
|
|
2391
|
+
const exprText = ctx.sourceText.slice(cond.getStart(ctx.sf), cond.getEnd());
|
|
2392
|
+
const { result, exprLine, kind, nullable } = lowerExpressionTextWithLayout(exprText, {
|
|
2393
|
+
inputs: conditionInputs(scope.params),
|
|
2394
|
+
inputName: scope.pName ?? "$",
|
|
2395
|
+
...scope.element !== void 0 ? {
|
|
2396
|
+
itemInputs: Object.fromEntries(scope.element.fields),
|
|
2397
|
+
itemName: scope.element.name
|
|
2398
|
+
} : {},
|
|
2399
|
+
fileName: ctx.fileName
|
|
2400
|
+
});
|
|
2401
|
+
if (result.expression !== void 0) {
|
|
2402
|
+
if (kind !== "bool" || nullable === true) {
|
|
2403
|
+
return report3(
|
|
2404
|
+
ctx,
|
|
2405
|
+
cond,
|
|
2406
|
+
"scp-type",
|
|
2407
|
+
"truthiness",
|
|
2408
|
+
`A structural guard condition must be a non-null boolean expression, got ${kind === void 0 ? "an unresolvable type" : `${kind}${nullable === true ? " | null" : ""}`} \u2014 truthiness coercion is outside the Expression IR closed set (compare explicitly instead, e.g. \`!== null\` / \`=== value\`).`
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
return result.expression;
|
|
2412
|
+
}
|
|
2413
|
+
const { line: condLine0, character: condCol0 } = ctx.sf.getLineAndCharacterOfPosition(
|
|
2414
|
+
cond.getStart(ctx.sf)
|
|
2415
|
+
);
|
|
2416
|
+
for (const d of result.diagnostics) {
|
|
2417
|
+
ctx.diagnostics.push(remapDiagnostic(d, exprLine, condLine0 + 1, condCol0 + 1));
|
|
2418
|
+
}
|
|
2419
|
+
return void 0;
|
|
2420
|
+
}
|
|
2421
|
+
function remapDiagnostic(d, exprLine, condLine, condCol) {
|
|
2422
|
+
const inExpr = d.line >= exprLine;
|
|
2423
|
+
return {
|
|
2424
|
+
code: d.code,
|
|
2425
|
+
category: "error",
|
|
2426
|
+
message: d.message,
|
|
2427
|
+
construct: d.construct,
|
|
2428
|
+
file: d.file,
|
|
2429
|
+
line: inExpr ? condLine + (d.line - exprLine) : condLine,
|
|
2430
|
+
column: inExpr ? d.line === exprLine ? condCol + d.column - 1 : d.column : condCol
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
function notOf(expression) {
|
|
2434
|
+
return canonicalizeExpressionSpec(
|
|
2435
|
+
{ exprVersion: EXPR_VERSION, expr: { not: [expression.expr] } },
|
|
2436
|
+
"structural lowering (\u03A6 else-guard)"
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
function writeCallOp(e, txName) {
|
|
2440
|
+
if (!ts3.isCallExpression(e)) return void 0;
|
|
2441
|
+
const callee = e.expression;
|
|
2442
|
+
if (!ts3.isPropertyAccessExpression(callee)) return void 0;
|
|
2443
|
+
const recv = unwrapExpr3(callee.expression);
|
|
2444
|
+
if (!ts3.isIdentifier(recv) || recv.text !== txName) return void 0;
|
|
2445
|
+
return WRITE_OPS.has(callee.name.text) ? callee.name.text : void 0;
|
|
2446
|
+
}
|
|
2447
|
+
function validateWriteTarget(ctx, call, op, txName, attachingGuard) {
|
|
2448
|
+
for (const arg of call.arguments) {
|
|
2449
|
+
if (referencesIdentifier(arg, txName)) {
|
|
2450
|
+
report3(
|
|
2451
|
+
ctx,
|
|
2452
|
+
arg,
|
|
2453
|
+
"scp-structural",
|
|
2454
|
+
op,
|
|
2455
|
+
`A recorder reference ('${txName}') inside a write's arguments is not a lowerable structural shape \u2014 writes do not nest (semantic-contract-programming.md: Component \u5BA3\u8A00\u306F\u69CB\u9020\u5316\u69CB\u6587\u306E\u672C\u4F53\u306B\u306E\u307F\u73FE\u308C\u308B).`
|
|
2456
|
+
);
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
if (attachingGuard) {
|
|
2461
|
+
const options = optionsArgument(call, op);
|
|
2462
|
+
if (options !== void 0 && ts3.isObjectLiteralExpression(options)) {
|
|
2463
|
+
for (const prop of options.properties) {
|
|
2464
|
+
if (ts3.isSpreadAssignment(prop)) {
|
|
2465
|
+
report3(
|
|
2466
|
+
ctx,
|
|
2467
|
+
prop,
|
|
2468
|
+
"scp-structural",
|
|
2469
|
+
op,
|
|
2470
|
+
"A spread inside the options of a guarded write is not lowerable \u2014 it could carry a `guard` member that silently overrides the lowered condition at runtime. Spell the options out as plain properties (fail-closed, issue #263)."
|
|
2471
|
+
);
|
|
2472
|
+
return false;
|
|
2473
|
+
}
|
|
2474
|
+
const name = prop.name;
|
|
2475
|
+
if (name !== void 0 && ts3.isComputedPropertyName(name)) {
|
|
2476
|
+
report3(
|
|
2477
|
+
ctx,
|
|
2478
|
+
name,
|
|
2479
|
+
"scp-structural",
|
|
2480
|
+
op,
|
|
2481
|
+
"A computed property name inside the options of a guarded write is not lowerable \u2014 it could evaluate to `guard` and silently override the lowered condition at runtime. Use a statically named property (fail-closed, issue #263)."
|
|
2482
|
+
);
|
|
2483
|
+
return false;
|
|
2484
|
+
}
|
|
2485
|
+
if (name !== void 0 && (ts3.isIdentifier(name) || ts3.isStringLiteralLike(name)) && name.text === "guard") {
|
|
2486
|
+
report3(
|
|
2487
|
+
ctx,
|
|
2488
|
+
prop,
|
|
2489
|
+
"scp-nesting",
|
|
2490
|
+
"guard",
|
|
2491
|
+
"guard-in-guard is a loud reject in v1 (issue #263): this write already carries a `guard` option. Compose the conditions into one guard instead \u2014 `(a && b) && tx.put(\u2026)`."
|
|
2492
|
+
);
|
|
2493
|
+
return false;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
} else if (options !== void 0) {
|
|
2497
|
+
report3(
|
|
2498
|
+
ctx,
|
|
2499
|
+
options,
|
|
2500
|
+
"scp-structural",
|
|
2501
|
+
op,
|
|
2502
|
+
"A structural guard can only be merged into an inline options object literal \u2014 this write passes a non-literal options argument."
|
|
2503
|
+
);
|
|
2504
|
+
return false;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
return true;
|
|
2508
|
+
}
|
|
2509
|
+
function optionsArgument(call, op) {
|
|
2510
|
+
const idx = OPTIONS_ARG_INDEX[op];
|
|
2511
|
+
return call.arguments.length > idx ? call.arguments[idx] : void 0;
|
|
2512
|
+
}
|
|
2513
|
+
function guardOptionEdit(ctx, call, op, guardJson) {
|
|
2514
|
+
const options = optionsArgument(call, op);
|
|
2515
|
+
if (options !== void 0 && ts3.isObjectLiteralExpression(options)) {
|
|
2516
|
+
if (options.properties.length > 0) {
|
|
2517
|
+
const at2 = options.properties[0].getStart(ctx.sf);
|
|
2518
|
+
return { pos: at2, end: at2, insert: `guard: ${guardJson}, ` };
|
|
2519
|
+
}
|
|
2520
|
+
return {
|
|
2521
|
+
pos: options.getStart(ctx.sf),
|
|
2522
|
+
end: options.getEnd(),
|
|
2523
|
+
insert: `{ guard: ${guardJson} }`
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
const lastArg = call.arguments[call.arguments.length - 1];
|
|
2527
|
+
const at = lastArg.getEnd();
|
|
2528
|
+
return { pos: at, end: at, insert: `, { guard: ${guardJson} }` };
|
|
2529
|
+
}
|
|
2530
|
+
function writeCallTextWithGuard(ctx, call, op, guardJson) {
|
|
2531
|
+
const start = call.getStart(ctx.sf);
|
|
2532
|
+
const edit = guardOptionEdit(ctx, call, op, guardJson);
|
|
2533
|
+
return ctx.sourceText.slice(start, edit.pos) + edit.insert + ctx.sourceText.slice(edit.end, call.getEnd());
|
|
2534
|
+
}
|
|
2535
|
+
function processStatementExpression(ctx, scope, expr, container, exprBody) {
|
|
2536
|
+
if (!referencesIdentifier(expr, scope.txName)) return;
|
|
2537
|
+
const e = unwrapExpr3(expr);
|
|
2538
|
+
{
|
|
2539
|
+
const op = writeCallOp(e, scope.txName);
|
|
2540
|
+
if (op !== void 0) {
|
|
2541
|
+
validateWriteTarget(ctx, e, op, scope.txName, false);
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
if (isForEachCall(e, scope.txName)) {
|
|
2546
|
+
processForEachCall(ctx, scope, e);
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
if (ts3.isBinaryExpression(e) && e.operatorToken.kind === ts3.SyntaxKind.AmpersandAmpersandToken) {
|
|
2550
|
+
processGuard(ctx, scope, e);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (ts3.isConditionalExpression(e)) {
|
|
2554
|
+
processConditional(ctx, scope, e, container, exprBody);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression) && e.expression.name.text === "map") {
|
|
2558
|
+
processMap(ctx, scope, e);
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
report3(
|
|
2562
|
+
ctx,
|
|
2563
|
+
expr,
|
|
2564
|
+
"scp-structural",
|
|
2565
|
+
ts3.SyntaxKind[e.kind],
|
|
2566
|
+
`This statement contains a write declaration ('${scope.txName}.*') in an undecidable structural shape. The lowerable forms are: a plain write, \`cond && tx.<write>(\u2026)\` (Guard), \`cond ? tx.<write>(\u2026) : tx.<write>(\u2026)\` (Conditional), and \`p.<arrayParam>.map(($el) => \u2026)\` (Map) \u2014 anything else is a loud reject, never a silent path (issue #263).`
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
function isForEachCall(e, txName) {
|
|
2570
|
+
return ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression) && e.expression.name.text === "forEach" && ts3.isIdentifier(unwrapExpr3(e.expression.expression)) && unwrapExpr3(e.expression.expression).text === txName;
|
|
2571
|
+
}
|
|
2572
|
+
function assertPureCondition(ctx, scope, cond, construct) {
|
|
2573
|
+
if (referencesIdentifier(cond, scope.txName)) {
|
|
2574
|
+
report3(
|
|
2575
|
+
ctx,
|
|
2576
|
+
cond,
|
|
2577
|
+
"scp-structural",
|
|
2578
|
+
construct,
|
|
2579
|
+
`The condition of a structural \`${construct}\` must be a pure expression \u2014 it contains a write declaration ('${scope.txName}.*'). A Component-bearing branch is structure, never Expression IR (semantic-contract-programming.md: \u69CB\u9020\u5316 / Expression \u306E dispatch).`
|
|
2580
|
+
);
|
|
2581
|
+
return false;
|
|
2582
|
+
}
|
|
2583
|
+
return true;
|
|
2584
|
+
}
|
|
2585
|
+
function processGuard(ctx, scope, e) {
|
|
2586
|
+
const right = unwrapExpr3(e.right);
|
|
2587
|
+
const op = writeCallOp(right, scope.txName);
|
|
2588
|
+
if (op === void 0) {
|
|
2589
|
+
if (ts3.isBinaryExpression(right) && right.operatorToken.kind === ts3.SyntaxKind.AmpersandAmpersandToken && referencesIdentifier(right, scope.txName)) {
|
|
2590
|
+
report3(
|
|
2591
|
+
ctx,
|
|
2592
|
+
right,
|
|
2593
|
+
"scp-nesting",
|
|
2594
|
+
"&&",
|
|
2595
|
+
"guard-in-guard is a loud reject in v1 (decided in issue #263): nest no guard inside a guard body \u2014 compose the conditions instead: `(a && b) && tx.put(\u2026)` (note `a && b && tx.put(\u2026)` already parses that way)."
|
|
2596
|
+
);
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
if (ts3.isConditionalExpression(right) && referencesIdentifier(right, scope.txName)) {
|
|
2600
|
+
report3(
|
|
2601
|
+
ctx,
|
|
2602
|
+
right,
|
|
2603
|
+
"scp-nesting",
|
|
2604
|
+
"&&",
|
|
2605
|
+
"A guarded conditional (`a && (b ? A(\u2026) : B(\u2026))`) is a loud reject in v1 (issue #263) \u2014 write two guarded items or compose each branch condition with `&&`."
|
|
2606
|
+
);
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
report3(
|
|
2610
|
+
ctx,
|
|
2611
|
+
e,
|
|
2612
|
+
"scp-structural",
|
|
2613
|
+
"&&",
|
|
2614
|
+
"The right side of a structural guard must be a single write declaration (`cond && tx.put(\u2026)`); this shape is undecidable and loud-rejected (issue #263)."
|
|
2615
|
+
);
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
if (!assertPureCondition(ctx, scope, e.left, "&&")) return;
|
|
2619
|
+
if (!validateWriteTarget(ctx, right, op, scope.txName, true)) {
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
const guard = lowerCondition(ctx, scope, e.left);
|
|
2623
|
+
if (guard === void 0) return;
|
|
2624
|
+
ctx.edits.push({
|
|
2625
|
+
pos: e.getStart(ctx.sf),
|
|
2626
|
+
end: e.right.getStart(ctx.sf),
|
|
2627
|
+
insert: ""
|
|
2628
|
+
});
|
|
2629
|
+
ctx.edits.push(
|
|
2630
|
+
guardOptionEdit(ctx, right, op, JSON.stringify(guard))
|
|
2631
|
+
);
|
|
2632
|
+
ctx.guardCount += 1;
|
|
2633
|
+
}
|
|
2634
|
+
function processConditional(ctx, scope, e, container, exprBody) {
|
|
2635
|
+
const whenTrue = unwrapExpr3(e.whenTrue);
|
|
2636
|
+
const whenFalse = unwrapExpr3(e.whenFalse);
|
|
2637
|
+
const opTrue = writeCallOp(whenTrue, scope.txName);
|
|
2638
|
+
const opFalse = writeCallOp(whenFalse, scope.txName);
|
|
2639
|
+
if (opTrue === void 0 || opFalse === void 0) {
|
|
2640
|
+
const offending = opTrue === void 0 ? whenTrue : whenFalse;
|
|
2641
|
+
if (referencesIdentifier(offending, scope.txName)) {
|
|
2642
|
+
report3(
|
|
2643
|
+
ctx,
|
|
2644
|
+
offending,
|
|
2645
|
+
"scp-nesting",
|
|
2646
|
+
"?:",
|
|
2647
|
+
"A conditional branch may only be a single write declaration in v1 (issue #263) \u2014 nested guards / ternary chains are loud rejects. Write explicit guarded items (`cond && tx.put(\u2026)`) with composed conditions instead."
|
|
2648
|
+
);
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
report3(
|
|
2652
|
+
ctx,
|
|
2653
|
+
offending,
|
|
2654
|
+
"scp-structural",
|
|
2655
|
+
"?:",
|
|
2656
|
+
"Both branches of a structural conditional must be write declarations (`cond ? tx.put(A, \u2026) : tx.put(B, \u2026)`). For a one-sided write use the guard form `cond && tx.put(\u2026)` (an unguarded else does not exist at the transaction seam \u2014 semantic-contract-programming.md \u03A6)."
|
|
2657
|
+
);
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
if (!assertPureCondition(ctx, scope, e.condition, "?:")) return;
|
|
2661
|
+
const okTrue = validateWriteTarget(
|
|
2662
|
+
ctx,
|
|
2663
|
+
whenTrue,
|
|
2664
|
+
opTrue,
|
|
2665
|
+
scope.txName,
|
|
2666
|
+
true
|
|
2667
|
+
);
|
|
2668
|
+
const okFalse = validateWriteTarget(
|
|
2669
|
+
ctx,
|
|
2670
|
+
whenFalse,
|
|
2671
|
+
opFalse,
|
|
2672
|
+
scope.txName,
|
|
2673
|
+
true
|
|
2674
|
+
);
|
|
2675
|
+
if (!okTrue || !okFalse) return;
|
|
2676
|
+
const guard = lowerCondition(ctx, scope, e.condition);
|
|
2677
|
+
if (guard === void 0) return;
|
|
2678
|
+
const thenText = writeCallTextWithGuard(
|
|
2679
|
+
ctx,
|
|
2680
|
+
whenTrue,
|
|
2681
|
+
opTrue,
|
|
2682
|
+
JSON.stringify(guard)
|
|
2683
|
+
);
|
|
2684
|
+
const elseText = writeCallTextWithGuard(
|
|
2685
|
+
ctx,
|
|
2686
|
+
whenFalse,
|
|
2687
|
+
opFalse,
|
|
2688
|
+
JSON.stringify(notOf(guard))
|
|
2689
|
+
);
|
|
2690
|
+
if (exprBody) {
|
|
2691
|
+
ctx.edits.push({
|
|
2692
|
+
pos: e.getStart(ctx.sf),
|
|
2693
|
+
end: e.getEnd(),
|
|
2694
|
+
insert: `{ ${thenText}; ${elseText}; }`
|
|
2695
|
+
});
|
|
2696
|
+
} else {
|
|
2697
|
+
const stmtStart = container.getStart(ctx.sf);
|
|
2698
|
+
const lineStart = ctx.sourceText.lastIndexOf("\n", stmtStart - 1) + 1;
|
|
2699
|
+
const lead = ctx.sourceText.slice(lineStart, stmtStart);
|
|
2700
|
+
const indent = /^[ \t]*$/.test(lead) ? lead : "";
|
|
2701
|
+
ctx.edits.push({
|
|
2702
|
+
pos: container.getStart(ctx.sf),
|
|
2703
|
+
end: container.getEnd(),
|
|
2704
|
+
insert: `${thenText};
|
|
2705
|
+
${indent}${elseText};`
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
ctx.conditionalCount += 1;
|
|
2709
|
+
}
|
|
2710
|
+
function processMap(ctx, scope, e) {
|
|
2711
|
+
const callee = e.expression;
|
|
2712
|
+
const recv = unwrapExpr3(callee.expression);
|
|
2713
|
+
if (scope.element !== void 0) {
|
|
2714
|
+
report3(
|
|
2715
|
+
ctx,
|
|
2716
|
+
e,
|
|
2717
|
+
"scp-map",
|
|
2718
|
+
".map",
|
|
2719
|
+
"Nested `.map` fan-out is not supported \u2014 the forEach seam is single-level (the recorder rejects nested forEach identically); flatten the iteration."
|
|
2720
|
+
);
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
const recvRoot = ts3.isPropertyAccessExpression(recv) ? unwrapExpr3(recv.expression) : void 0;
|
|
2724
|
+
const recvOk = ts3.isPropertyAccessExpression(recv) && recvRoot !== void 0 && ts3.isIdentifier(recvRoot) && scope.pName !== void 0 && recvRoot.text === scope.pName && scope.pSymbol !== void 0 && ctx.checker.getSymbolAtLocation(recvRoot) === scope.pSymbol;
|
|
2725
|
+
const arrayName = recvOk ? recv.name.text : void 0;
|
|
2726
|
+
if (scope.params === void 0) {
|
|
2727
|
+
report3(
|
|
2728
|
+
ctx,
|
|
2729
|
+
scope.paramsNode,
|
|
2730
|
+
"scp-params",
|
|
2731
|
+
".map",
|
|
2732
|
+
"The defineScpTransaction params map must be an inline object literal of `param.*` factories \u2014 the `.map` fan-out binds to a declared array param (fail-closed)."
|
|
2733
|
+
);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
const fields = arrayName !== void 0 ? scope.params.arrays.get(arrayName) : void 0;
|
|
2737
|
+
if (!recvOk || arrayName === void 0 || fields === void 0) {
|
|
2738
|
+
report3(
|
|
2739
|
+
ctx,
|
|
2740
|
+
recv,
|
|
2741
|
+
"scp-map",
|
|
2742
|
+
".map",
|
|
2743
|
+
`A \`.map\` fan-out is only lowerable over a declared array param (\`${scope.pName ?? "p"}.<name>\` where the params map declares \`param.array({\u2026})\`) \u2014 the array-Port restriction that guarantees termination (semantic-contract-programming.md Map).`
|
|
2744
|
+
);
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
if (e.arguments.length !== 1) {
|
|
2748
|
+
report3(
|
|
2749
|
+
ctx,
|
|
2750
|
+
e,
|
|
2751
|
+
"scp-map",
|
|
2752
|
+
".map",
|
|
2753
|
+
"A `.map` fan-out takes exactly the inline callback (no thisArg)."
|
|
2754
|
+
);
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
const cb = unwrapExpr3(e.arguments[0]);
|
|
2758
|
+
if (!ts3.isArrowFunction(cb)) {
|
|
2759
|
+
report3(
|
|
2760
|
+
ctx,
|
|
2761
|
+
e.arguments[0],
|
|
2762
|
+
"scp-map",
|
|
2763
|
+
".map",
|
|
2764
|
+
"The `.map` callback must be an inline arrow function (the AST must be lowerable \u2014 semantic-contract-programming.md Map)."
|
|
2765
|
+
);
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
if (cb.parameters.length !== 1 || !ts3.isIdentifier(cb.parameters[0].name)) {
|
|
2769
|
+
report3(
|
|
2770
|
+
ctx,
|
|
2771
|
+
cb,
|
|
2772
|
+
"scp-map",
|
|
2773
|
+
".map",
|
|
2774
|
+
"The `.map` callback takes exactly one plain identifier parameter (the element, v1) \u2014 no destructuring, no index parameter."
|
|
2775
|
+
);
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
const elName = cb.parameters[0].name.text;
|
|
2779
|
+
if (elName === scope.txName || scope.pName !== void 0 && elName === scope.pName) {
|
|
2780
|
+
report3(
|
|
2781
|
+
ctx,
|
|
2782
|
+
cb.parameters[0],
|
|
2783
|
+
"scp-map",
|
|
2784
|
+
".map",
|
|
2785
|
+
`The element parameter '${elName}' shadows the ${elName === scope.txName ? "recorder" : "params"} parameter \u2014 rename it.`
|
|
2786
|
+
);
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
const before = ctx.diagnostics.length;
|
|
2790
|
+
const elementScope = {
|
|
2791
|
+
...scope,
|
|
2792
|
+
element: {
|
|
2793
|
+
name: elName,
|
|
2794
|
+
symbol: ctx.checker.getSymbolAtLocation(cb.parameters[0].name),
|
|
2795
|
+
fields
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
if (ts3.isBlock(cb.body)) {
|
|
2799
|
+
for (const stmt of cb.body.statements) {
|
|
2800
|
+
processBodyStatement(ctx, elementScope, stmt);
|
|
2801
|
+
}
|
|
2802
|
+
} else {
|
|
2803
|
+
processStatementExpression(ctx, elementScope, cb.body, cb.body, true);
|
|
2804
|
+
}
|
|
2805
|
+
if (ctx.diagnostics.length > before) return;
|
|
2806
|
+
ctx.edits.push({
|
|
2807
|
+
pos: recv.getStart(ctx.sf),
|
|
2808
|
+
end: recv.getStart(ctx.sf),
|
|
2809
|
+
insert: `${scope.txName}.forEach(`
|
|
2810
|
+
});
|
|
2811
|
+
ctx.edits.push({
|
|
2812
|
+
pos: recv.getEnd(),
|
|
2813
|
+
end: cb.getStart(ctx.sf),
|
|
2814
|
+
insert: ", "
|
|
2815
|
+
});
|
|
2816
|
+
ctx.mapCount += 1;
|
|
2817
|
+
}
|
|
2818
|
+
function processForEachCall(ctx, scope, e) {
|
|
2819
|
+
if (scope.element !== void 0) {
|
|
2820
|
+
report3(
|
|
2821
|
+
ctx,
|
|
2822
|
+
e,
|
|
2823
|
+
"scp-map",
|
|
2824
|
+
"forEach",
|
|
2825
|
+
"Nested fan-out (`tx.forEach` inside a `.map` body) is not supported \u2014 the forEach seam is single-level; flatten the iteration."
|
|
2826
|
+
);
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
if (e.arguments.length < 2) return;
|
|
2830
|
+
const cb = unwrapExpr3(e.arguments[1]);
|
|
2831
|
+
if (!ts3.isArrowFunction(cb) && !ts3.isFunctionExpression(cb)) {
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
const src = unwrapExpr3(e.arguments[0]);
|
|
2835
|
+
let fields;
|
|
2836
|
+
if (ts3.isPropertyAccessExpression(src) && scope.pName !== void 0) {
|
|
2837
|
+
const srcRoot = unwrapExpr3(src.expression);
|
|
2838
|
+
if (ts3.isIdentifier(srcRoot) && srcRoot.text === scope.pName && scope.pSymbol !== void 0 && ctx.checker.getSymbolAtLocation(srcRoot) === scope.pSymbol) {
|
|
2839
|
+
fields = scope.params?.arrays.get(src.name.text);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
const elParam = cb.parameters[0];
|
|
2843
|
+
const elName = elParam !== void 0 && ts3.isIdentifier(elParam.name) ? elParam.name.text : void 0;
|
|
2844
|
+
if (elName !== void 0 && (elName === scope.txName || scope.pName !== void 0 && elName === scope.pName)) {
|
|
2845
|
+
report3(
|
|
2846
|
+
ctx,
|
|
2847
|
+
elParam,
|
|
2848
|
+
"scp-map",
|
|
2849
|
+
"forEach",
|
|
2850
|
+
`The forEach element parameter '${elName}' shadows the ${elName === scope.txName ? "recorder" : "params"} parameter \u2014 rename it.`
|
|
2851
|
+
);
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
const elementScope = elName !== void 0 ? {
|
|
2855
|
+
...scope,
|
|
2856
|
+
element: {
|
|
2857
|
+
name: elName,
|
|
2858
|
+
symbol: ctx.checker.getSymbolAtLocation(elParam.name),
|
|
2859
|
+
fields: fields ?? /* @__PURE__ */ new Map()
|
|
2860
|
+
}
|
|
2861
|
+
} : scope;
|
|
2862
|
+
if (ts3.isBlock(cb.body)) {
|
|
2863
|
+
for (const stmt of cb.body.statements) {
|
|
2864
|
+
processBodyStatement(ctx, elementScope, stmt);
|
|
2865
|
+
}
|
|
2866
|
+
} else if (ts3.isArrowFunction(cb)) {
|
|
2867
|
+
processStatementExpression(ctx, elementScope, cb.body, cb.body, true);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
function processBodyStatement(ctx, scope, stmt) {
|
|
2871
|
+
if (ts3.isExpressionStatement(stmt)) {
|
|
2872
|
+
processStatementExpression(ctx, scope, stmt.expression, stmt, false);
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
if (referencesIdentifier(stmt, scope.txName)) {
|
|
2876
|
+
report3(
|
|
2877
|
+
ctx,
|
|
2878
|
+
stmt,
|
|
2879
|
+
"scp-structural",
|
|
2880
|
+
ts3.SyntaxKind[stmt.kind],
|
|
2881
|
+
`A write declaration ('${scope.txName}.*') inside a ${ts3.SyntaxKind[stmt.kind]} is not a lowerable structural shape \u2014 only expression statements lower (issue #263; loud, never silent).`
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
var SCP_DEFINE_NAME = "defineScpTransaction";
|
|
2886
|
+
function collectScpImportNames(sf) {
|
|
2887
|
+
const locals = /* @__PURE__ */ new Set();
|
|
2888
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
2889
|
+
for (const stmt of sf.statements) {
|
|
2890
|
+
if (!ts3.isImportDeclaration(stmt) || stmt.importClause === void 0) continue;
|
|
2891
|
+
const bindings = stmt.importClause.namedBindings;
|
|
2892
|
+
if (bindings === void 0) continue;
|
|
2893
|
+
if (ts3.isNamespaceImport(bindings)) {
|
|
2894
|
+
namespaces.add(bindings.name.text);
|
|
2895
|
+
} else {
|
|
2896
|
+
for (const spec of bindings.elements) {
|
|
2897
|
+
if ((spec.propertyName ?? spec.name).text === SCP_DEFINE_NAME) {
|
|
2898
|
+
locals.add(spec.name.text);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
return { locals, namespaces };
|
|
2904
|
+
}
|
|
2905
|
+
function isScpDefineCall(node, names) {
|
|
2906
|
+
const callee = unwrapExpr3(node.expression);
|
|
2907
|
+
if (ts3.isIdentifier(callee)) return names.locals.has(callee.text);
|
|
2908
|
+
if (ts3.isPropertyAccessExpression(callee) && callee.name.text === SCP_DEFINE_NAME) {
|
|
2909
|
+
const root = unwrapExpr3(callee.expression);
|
|
2910
|
+
return ts3.isIdentifier(root) && names.namespaces.has(root.text);
|
|
2911
|
+
}
|
|
2912
|
+
return false;
|
|
2913
|
+
}
|
|
2914
|
+
function transformScpTransactionSource(fileName, sourceText, options = {}) {
|
|
2915
|
+
const mode = options.mode ?? "transform";
|
|
2916
|
+
const scriptKind = fileName.endsWith(".tsx") ? ts3.ScriptKind.TSX : fileName.endsWith(".jsx") ? ts3.ScriptKind.JSX : ts3.ScriptKind.TS;
|
|
2917
|
+
const sf = ts3.createSourceFile(
|
|
2918
|
+
fileName,
|
|
2919
|
+
sourceText,
|
|
2920
|
+
ts3.ScriptTarget.Latest,
|
|
2921
|
+
/* setParentNodes */
|
|
2922
|
+
true,
|
|
2923
|
+
scriptKind
|
|
2924
|
+
);
|
|
2925
|
+
const ctx = {
|
|
2926
|
+
sf,
|
|
2927
|
+
fileName,
|
|
2928
|
+
sourceText,
|
|
2929
|
+
checker: createSingleFileChecker(sf, fileName, sourceText),
|
|
2930
|
+
diagnostics: [],
|
|
2931
|
+
edits: [],
|
|
2932
|
+
guardCount: 0,
|
|
2933
|
+
conditionalCount: 0,
|
|
2934
|
+
mapCount: 0
|
|
2935
|
+
};
|
|
2936
|
+
const names = collectScpImportNames(sf);
|
|
2937
|
+
const visit = (node) => {
|
|
2938
|
+
if (ts3.isCallExpression(node) && isScpDefineCall(node, names)) {
|
|
2939
|
+
processDefineCall(ctx, node);
|
|
2940
|
+
}
|
|
2941
|
+
ts3.forEachChild(node, visit);
|
|
2942
|
+
};
|
|
2943
|
+
visit(sf);
|
|
2944
|
+
const errorCount = ctx.diagnostics.length;
|
|
2945
|
+
if (errorCount > 0 || mode === "check" || ctx.edits.length === 0) {
|
|
2946
|
+
return {
|
|
2947
|
+
fileName,
|
|
2948
|
+
text: sourceText,
|
|
2949
|
+
changed: false,
|
|
2950
|
+
guardCount: errorCount > 0 ? 0 : ctx.guardCount,
|
|
2951
|
+
conditionalCount: errorCount > 0 ? 0 : ctx.conditionalCount,
|
|
2952
|
+
mapCount: errorCount > 0 ? 0 : ctx.mapCount,
|
|
2953
|
+
diagnostics: ctx.diagnostics,
|
|
2954
|
+
errorCount
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
const edits = [...ctx.edits].sort((a, b) => b.pos - a.pos || b.end - a.end);
|
|
2958
|
+
let text = sourceText;
|
|
2959
|
+
for (const e of edits) {
|
|
2960
|
+
text = text.slice(0, e.pos) + e.insert + text.slice(e.end);
|
|
2961
|
+
}
|
|
2962
|
+
return {
|
|
2963
|
+
fileName,
|
|
2964
|
+
text,
|
|
2965
|
+
changed: text !== sourceText,
|
|
2966
|
+
guardCount: ctx.guardCount,
|
|
2967
|
+
conditionalCount: ctx.conditionalCount,
|
|
2968
|
+
mapCount: ctx.mapCount,
|
|
2969
|
+
diagnostics: ctx.diagnostics,
|
|
2970
|
+
errorCount: 0
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
function processDefineCall(ctx, call) {
|
|
2974
|
+
if (call.arguments.length < 2) {
|
|
2975
|
+
report3(
|
|
2976
|
+
ctx,
|
|
2977
|
+
call,
|
|
2978
|
+
"scp-params",
|
|
2979
|
+
SCP_DEFINE_NAME,
|
|
2980
|
+
"defineScpTransaction takes (params, (tx, p) => { \u2026 })."
|
|
2981
|
+
);
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
const cb = unwrapExpr3(call.arguments[1]);
|
|
2985
|
+
if (!ts3.isArrowFunction(cb) && !ts3.isFunctionExpression(cb)) {
|
|
2986
|
+
report3(
|
|
2987
|
+
ctx,
|
|
2988
|
+
call.arguments[1],
|
|
2989
|
+
"scp-params",
|
|
2990
|
+
SCP_DEFINE_NAME,
|
|
2991
|
+
"The defineScpTransaction body must be an inline arrow / function expression \u2014 the native-syntax form is lowered from the source AST and cannot be verified through an indirect reference (fail-closed)."
|
|
2992
|
+
);
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
const txParam = cb.parameters[0];
|
|
2996
|
+
if (txParam === void 0 || !ts3.isIdentifier(txParam.name)) {
|
|
2997
|
+
report3(
|
|
2998
|
+
ctx,
|
|
2999
|
+
cb,
|
|
3000
|
+
"scp-params",
|
|
3001
|
+
SCP_DEFINE_NAME,
|
|
3002
|
+
"The defineScpTransaction body must name its recorder parameter as a plain identifier (`(tx, p) => \u2026`)."
|
|
3003
|
+
);
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
const pParam = cb.parameters[1];
|
|
3007
|
+
if (pParam !== void 0 && !ts3.isIdentifier(pParam.name)) {
|
|
3008
|
+
report3(
|
|
3009
|
+
ctx,
|
|
3010
|
+
pParam,
|
|
3011
|
+
"scp-params",
|
|
3012
|
+
SCP_DEFINE_NAME,
|
|
3013
|
+
"The defineScpTransaction params parameter must be a plain identifier (no destructuring) \u2014 conditions are typed from the params map."
|
|
3014
|
+
);
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
if (call.arguments.length >= 3) {
|
|
3018
|
+
const third = call.arguments[2];
|
|
3019
|
+
if (!ts3.isStringLiteral(third) || third.text !== SCP_LOWERED_MARKER) {
|
|
3020
|
+
report3(
|
|
3021
|
+
ctx,
|
|
3022
|
+
third,
|
|
3023
|
+
"scp-params",
|
|
3024
|
+
SCP_DEFINE_NAME,
|
|
3025
|
+
`Unexpected third argument to defineScpTransaction \u2014 the lowered-body marker ('${SCP_LOWERED_MARKER}') is stamped by the transform itself; remove the argument and re-run the transform.`
|
|
3026
|
+
);
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
} else {
|
|
3030
|
+
const at = call.arguments[1].getEnd();
|
|
3031
|
+
ctx.edits.push({ pos: at, end: at, insert: `, '${SCP_LOWERED_MARKER}'` });
|
|
3032
|
+
}
|
|
3033
|
+
const pSymbol = pParam !== void 0 ? ctx.checker.getSymbolAtLocation(pParam.name) : void 0;
|
|
3034
|
+
const scope = {
|
|
3035
|
+
txName: txParam.name.text,
|
|
3036
|
+
pName: pParam !== void 0 ? pParam.name.text : void 0,
|
|
3037
|
+
pSymbol,
|
|
3038
|
+
params: readParamsModel(call.arguments[0]),
|
|
3039
|
+
paramsNode: call.arguments[0]
|
|
3040
|
+
};
|
|
3041
|
+
if (ts3.isBlock(cb.body)) {
|
|
3042
|
+
for (const stmt of cb.body.statements) {
|
|
3043
|
+
processBodyStatement(ctx, scope, stmt);
|
|
3044
|
+
}
|
|
3045
|
+
} else {
|
|
3046
|
+
processStatementExpression(ctx, scope, cb.body, cb.body, true);
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
974
3049
|
export {
|
|
975
3050
|
AOT_LOADER_LOCAL,
|
|
976
3051
|
AOT_PLANS_LOCAL,
|
|
977
3052
|
AOT_SHIM_EXPORT,
|
|
3053
|
+
ExpressionLowerError,
|
|
978
3054
|
HOIST_SLOT_PREFIX,
|
|
3055
|
+
formatExpressionLowerDiagnostic,
|
|
3056
|
+
formatStructuralLowerDiagnostic,
|
|
3057
|
+
inputParamTypeText,
|
|
979
3058
|
lintPreparedSource,
|
|
3059
|
+
lowerExpressionText,
|
|
3060
|
+
lowerExpressionTextOrThrow,
|
|
3061
|
+
lowerTsExpression,
|
|
980
3062
|
transformPreparedSource,
|
|
981
|
-
transformPreparedSourceAot
|
|
3063
|
+
transformPreparedSourceAot,
|
|
3064
|
+
transformScpTransactionSource
|
|
982
3065
|
};
|