@aster-cloud/aster-lang-ts 1.0.27 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/scripts/generate_error_codes.js +49 -6
  2. package/dist/scripts/generate_error_codes.js.map +1 -1
  3. package/dist/src/ai/providers/openai.d.ts +10 -0
  4. package/dist/src/ai/providers/openai.d.ts.map +1 -1
  5. package/dist/src/ai/providers/openai.js +4 -0
  6. package/dist/src/ai/providers/openai.js.map +1 -1
  7. package/dist/src/config/lexicons/regex-guard.d.ts.map +1 -1
  8. package/dist/src/config/lexicons/regex-guard.js +128 -0
  9. package/dist/src/config/lexicons/regex-guard.js.map +1 -1
  10. package/dist/src/core/interpreter.d.ts.map +1 -1
  11. package/dist/src/core/interpreter.js +221 -27
  12. package/dist/src/core/interpreter.js.map +1 -1
  13. package/dist/src/cst/cst_builder.js +0 -14
  14. package/dist/src/cst/cst_builder.js.map +1 -1
  15. package/dist/src/cst/cst_printer.d.ts.map +1 -1
  16. package/dist/src/cst/cst_printer.js +20 -3
  17. package/dist/src/cst/cst_printer.js.map +1 -1
  18. package/dist/src/diagnostics/error_codes.d.ts +3 -3
  19. package/dist/src/diagnostics/error_codes.d.ts.map +1 -1
  20. package/dist/src/diagnostics/error_codes.js +509 -78
  21. package/dist/src/diagnostics/error_codes.js.map +1 -1
  22. package/dist/src/formatter.d.ts +7 -0
  23. package/dist/src/formatter.d.ts.map +1 -1
  24. package/dist/src/formatter.js +175 -5
  25. package/dist/src/formatter.js.map +1 -1
  26. package/dist/src/frontend/canonicalizer.d.ts.map +1 -1
  27. package/dist/src/frontend/canonicalizer.js +47 -6
  28. package/dist/src/frontend/canonicalizer.js.map +1 -1
  29. package/dist/src/frontend/lexer.d.ts +2 -1
  30. package/dist/src/frontend/lexer.d.ts.map +1 -1
  31. package/dist/src/frontend/lexer.js +10 -3
  32. package/dist/src/frontend/lexer.js.map +1 -1
  33. package/dist/src/frontend/transformers.d.ts.map +1 -1
  34. package/dist/src/frontend/transformers.js +50 -3
  35. package/dist/src/frontend/transformers.js.map +1 -1
  36. package/dist/src/lower_to_core.js +101 -4
  37. package/dist/src/lower_to_core.js.map +1 -1
  38. package/dist/src/lsp/canonicalize-options.d.ts +26 -0
  39. package/dist/src/lsp/canonicalize-options.d.ts.map +1 -0
  40. package/dist/src/lsp/canonicalize-options.js +14 -0
  41. package/dist/src/lsp/canonicalize-options.js.map +1 -0
  42. package/dist/src/lsp/completion.d.ts.map +1 -1
  43. package/dist/src/lsp/completion.js +22 -4
  44. package/dist/src/lsp/completion.js.map +1 -1
  45. package/dist/src/lsp/server.js +29 -1
  46. package/dist/src/lsp/server.js.map +1 -1
  47. package/dist/src/lsp/tenant-init.d.ts +55 -0
  48. package/dist/src/lsp/tenant-init.d.ts.map +1 -0
  49. package/dist/src/lsp/tenant-init.js +88 -0
  50. package/dist/src/lsp/tenant-init.js.map +1 -0
  51. package/dist/src/mapping/candidate-generator.d.ts +84 -0
  52. package/dist/src/mapping/candidate-generator.d.ts.map +1 -0
  53. package/dist/src/mapping/candidate-generator.js +114 -0
  54. package/dist/src/mapping/candidate-generator.js.map +1 -0
  55. package/dist/src/mapping/entity-proposer.d.ts +85 -0
  56. package/dist/src/mapping/entity-proposer.d.ts.map +1 -0
  57. package/dist/src/mapping/entity-proposer.js +162 -0
  58. package/dist/src/mapping/entity-proposer.js.map +1 -0
  59. package/dist/src/mapping/mapping-ir.d.ts +85 -0
  60. package/dist/src/mapping/mapping-ir.d.ts.map +1 -0
  61. package/dist/src/mapping/mapping-ir.js +195 -0
  62. package/dist/src/mapping/mapping-ir.js.map +1 -0
  63. package/dist/src/mapping/proof-ir.d.ts +125 -0
  64. package/dist/src/mapping/proof-ir.d.ts.map +1 -0
  65. package/dist/src/mapping/proof-ir.js +48 -0
  66. package/dist/src/mapping/proof-ir.js.map +1 -0
  67. package/dist/src/mapping/quantity-ir.d.ts +93 -0
  68. package/dist/src/mapping/quantity-ir.d.ts.map +1 -0
  69. package/dist/src/mapping/quantity-ir.js +151 -0
  70. package/dist/src/mapping/quantity-ir.js.map +1 -0
  71. package/dist/src/mapping/source-ir.d.ts +82 -0
  72. package/dist/src/mapping/source-ir.d.ts.map +1 -0
  73. package/dist/src/mapping/source-ir.js +231 -0
  74. package/dist/src/mapping/source-ir.js.map +1 -0
  75. package/dist/src/nodeid/change-impact.d.ts +77 -0
  76. package/dist/src/nodeid/change-impact.d.ts.map +1 -0
  77. package/dist/src/nodeid/change-impact.js +132 -0
  78. package/dist/src/nodeid/change-impact.js.map +1 -0
  79. package/dist/src/nodeid/node-id-map.d.ts +69 -0
  80. package/dist/src/nodeid/node-id-map.d.ts.map +1 -0
  81. package/dist/src/nodeid/node-id-map.js +193 -0
  82. package/dist/src/nodeid/node-id-map.js.map +1 -0
  83. package/dist/src/parser/decl-parser.d.ts.map +1 -1
  84. package/dist/src/parser/decl-parser.js +10 -4
  85. package/dist/src/parser/decl-parser.js.map +1 -1
  86. package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
  87. package/dist/src/parser/expr-stmt-parser.js +36 -24
  88. package/dist/src/parser/expr-stmt-parser.js.map +1 -1
  89. package/dist/src/parser/span-utils.d.ts +25 -0
  90. package/dist/src/parser/span-utils.d.ts.map +1 -1
  91. package/dist/src/parser/span-utils.js +43 -0
  92. package/dist/src/parser/span-utils.js.map +1 -1
  93. package/dist/src/parser/type-parser.d.ts.map +1 -1
  94. package/dist/src/parser/type-parser.js +21 -0
  95. package/dist/src/parser/type-parser.js.map +1 -1
  96. package/dist/src/typecheck/expression.d.ts.map +1 -1
  97. package/dist/src/typecheck/expression.js +26 -0
  98. package/dist/src/typecheck/expression.js.map +1 -1
  99. package/dist/test/fuzz/fuzz.test.js +62 -2
  100. package/dist/test/fuzz/fuzz.test.js.map +1 -1
  101. package/dist/test/unit/call-arity.test.d.ts +2 -0
  102. package/dist/test/unit/call-arity.test.d.ts.map +1 -0
  103. package/dist/test/unit/call-arity.test.js +71 -0
  104. package/dist/test/unit/call-arity.test.js.map +1 -0
  105. package/dist/test/unit/canonicalizer/canonicalizer.test.js +42 -4
  106. package/dist/test/unit/canonicalizer/canonicalizer.test.js.map +1 -1
  107. package/dist/test/unit/canonicalizer/line-preservation.test.d.ts +2 -0
  108. package/dist/test/unit/canonicalizer/line-preservation.test.d.ts.map +1 -0
  109. package/dist/test/unit/canonicalizer/line-preservation.test.js +71 -0
  110. package/dist/test/unit/canonicalizer/line-preservation.test.js.map +1 -0
  111. package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts +2 -0
  112. package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts.map +1 -0
  113. package/dist/test/unit/canonicalizer/marker-restore-linear.test.js +98 -0
  114. package/dist/test/unit/canonicalizer/marker-restore-linear.test.js.map +1 -0
  115. package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts +2 -0
  116. package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts.map +1 -0
  117. package/dist/test/unit/canonicalizer/possessive-anchor.test.js +101 -0
  118. package/dist/test/unit/canonicalizer/possessive-anchor.test.js.map +1 -0
  119. package/dist/test/unit/formatter-redos.test.d.ts +2 -0
  120. package/dist/test/unit/formatter-redos.test.d.ts.map +1 -0
  121. package/dist/test/unit/formatter-redos.test.js +196 -0
  122. package/dist/test/unit/formatter-redos.test.js.map +1 -0
  123. package/dist/test/unit/interpreter/interpreter.test.js +7 -2
  124. package/dist/test/unit/interpreter/interpreter.test.js.map +1 -1
  125. package/dist/test/unit/lexer/bom.test.d.ts +2 -0
  126. package/dist/test/unit/lexer/bom.test.d.ts.map +1 -0
  127. package/dist/test/unit/lexer/bom.test.js +49 -0
  128. package/dist/test/unit/lexer/bom.test.js.map +1 -0
  129. package/dist/test/unit/list-get-bounds.test.d.ts +2 -0
  130. package/dist/test/unit/list-get-bounds.test.d.ts.map +1 -0
  131. package/dist/test/unit/list-get-bounds.test.js +66 -0
  132. package/dist/test/unit/list-get-bounds.test.js.map +1 -0
  133. package/dist/test/unit/long-bigint-exact.test.d.ts +2 -0
  134. package/dist/test/unit/long-bigint-exact.test.d.ts.map +1 -0
  135. package/dist/test/unit/long-bigint-exact.test.js +79 -0
  136. package/dist/test/unit/long-bigint-exact.test.js.map +1 -0
  137. package/dist/test/unit/lsp/completion-lexicon.test.d.ts +2 -0
  138. package/dist/test/unit/lsp/completion-lexicon.test.d.ts.map +1 -0
  139. package/dist/test/unit/lsp/completion-lexicon.test.js +83 -0
  140. package/dist/test/unit/lsp/completion-lexicon.test.js.map +1 -0
  141. package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts +2 -0
  142. package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts.map +1 -0
  143. package/dist/test/unit/lsp/tenant-vocabulary.test.js +187 -0
  144. package/dist/test/unit/lsp/tenant-vocabulary.test.js.map +1 -0
  145. package/dist/test/unit/map-builtins.test.js +163 -3
  146. package/dist/test/unit/map-builtins.test.js.map +1 -1
  147. package/dist/test/unit/map-structural-equality.test.d.ts +2 -0
  148. package/dist/test/unit/map-structural-equality.test.d.ts.map +1 -0
  149. package/dist/test/unit/map-structural-equality.test.js +64 -0
  150. package/dist/test/unit/map-structural-equality.test.js.map +1 -0
  151. package/dist/test/unit/map-type-guard.test.js +8 -5
  152. package/dist/test/unit/map-type-guard.test.js.map +1 -1
  153. package/dist/test/unit/mapping/candidate-generator.test.d.ts +2 -0
  154. package/dist/test/unit/mapping/candidate-generator.test.d.ts.map +1 -0
  155. package/dist/test/unit/mapping/candidate-generator.test.js +177 -0
  156. package/dist/test/unit/mapping/candidate-generator.test.js.map +1 -0
  157. package/dist/test/unit/mapping/entity-proposer.test.d.ts +2 -0
  158. package/dist/test/unit/mapping/entity-proposer.test.d.ts.map +1 -0
  159. package/dist/test/unit/mapping/entity-proposer.test.js +168 -0
  160. package/dist/test/unit/mapping/entity-proposer.test.js.map +1 -0
  161. package/dist/test/unit/mapping/mapping-ir.test.d.ts +2 -0
  162. package/dist/test/unit/mapping/mapping-ir.test.d.ts.map +1 -0
  163. package/dist/test/unit/mapping/mapping-ir.test.js +144 -0
  164. package/dist/test/unit/mapping/mapping-ir.test.js.map +1 -0
  165. package/dist/test/unit/mapping/proof-ir.test.d.ts +2 -0
  166. package/dist/test/unit/mapping/proof-ir.test.d.ts.map +1 -0
  167. package/dist/test/unit/mapping/proof-ir.test.js +119 -0
  168. package/dist/test/unit/mapping/proof-ir.test.js.map +1 -0
  169. package/dist/test/unit/mapping/quantity-ir.test.d.ts +2 -0
  170. package/dist/test/unit/mapping/quantity-ir.test.d.ts.map +1 -0
  171. package/dist/test/unit/mapping/quantity-ir.test.js +144 -0
  172. package/dist/test/unit/mapping/quantity-ir.test.js.map +1 -0
  173. package/dist/test/unit/mapping/quantity-redos-property.test.d.ts +2 -0
  174. package/dist/test/unit/mapping/quantity-redos-property.test.d.ts.map +1 -0
  175. package/dist/test/unit/mapping/quantity-redos-property.test.js +182 -0
  176. package/dist/test/unit/mapping/quantity-redos-property.test.js.map +1 -0
  177. package/dist/test/unit/mapping/redos-budget.test.d.ts +2 -0
  178. package/dist/test/unit/mapping/redos-budget.test.d.ts.map +1 -0
  179. package/dist/test/unit/mapping/redos-budget.test.js +168 -0
  180. package/dist/test/unit/mapping/redos-budget.test.js.map +1 -0
  181. package/dist/test/unit/mapping/source-ir.test.d.ts +2 -0
  182. package/dist/test/unit/mapping/source-ir.test.d.ts.map +1 -0
  183. package/dist/test/unit/mapping/source-ir.test.js +113 -0
  184. package/dist/test/unit/mapping/source-ir.test.js.map +1 -0
  185. package/dist/test/unit/nodeid/change-impact.test.d.ts +2 -0
  186. package/dist/test/unit/nodeid/change-impact.test.d.ts.map +1 -0
  187. package/dist/test/unit/nodeid/change-impact.test.js +152 -0
  188. package/dist/test/unit/nodeid/change-impact.test.js.map +1 -0
  189. package/dist/test/unit/nodeid/rename-declaration.test.d.ts +2 -0
  190. package/dist/test/unit/nodeid/rename-declaration.test.d.ts.map +1 -0
  191. package/dist/test/unit/nodeid/rename-declaration.test.js +75 -0
  192. package/dist/test/unit/nodeid/rename-declaration.test.js.map +1 -0
  193. package/dist/test/unit/parse-type-recursion.test.d.ts +2 -0
  194. package/dist/test/unit/parse-type-recursion.test.d.ts.map +1 -0
  195. package/dist/test/unit/parse-type-recursion.test.js +58 -0
  196. package/dist/test/unit/parse-type-recursion.test.js.map +1 -0
  197. package/dist/test/unit/regex-guard.test.js +64 -0
  198. package/dist/test/unit/regex-guard.test.js.map +1 -1
  199. package/dist/test/unit/strict-int-coercion.test.d.ts +2 -0
  200. package/dist/test/unit/strict-int-coercion.test.d.ts.map +1 -0
  201. package/dist/test/unit/strict-int-coercion.test.js +96 -0
  202. package/dist/test/unit/strict-int-coercion.test.js.map +1 -0
  203. package/dist/test/unit/ufcs-receiver.test.d.ts +2 -0
  204. package/dist/test/unit/ufcs-receiver.test.d.ts.map +1 -0
  205. package/dist/test/unit/ufcs-receiver.test.js +75 -0
  206. package/dist/test/unit/ufcs-receiver.test.js.map +1 -0
  207. package/package.json +1 -1
@@ -69,6 +69,56 @@ function decimalScale(scale) {
69
69
  }
70
70
  return n;
71
71
  }
72
+ /**
73
+ * 严格整型换算——与 truffle `Builtins.toInt` 等价(issue #144)。
74
+ *
75
+ * ★为什么不能用裸 `Number()`:`Number("x")` 得 `NaN` 后**所有防线同时失效**——
76
+ * `NaN < MIN || NaN > MAX` 两侧都是 false 于是放行;`size > MAX_RANGE_SIZE` 同理,
77
+ * 然后循环零次返回 `[]`。非法输入不报错,反而产出「看起来正常」的决策值。
78
+ *
79
+ * 实测(修复前):
80
+ *
81
+ * Date.daysBetween("x","b") → success:true, value:NaN
82
+ * Date.addDays("x",5) greater than 0 → success:true, value:false
83
+ * List.length(List.range("a",5)) → success:true, value:0
84
+ *
85
+ * 而 truffle 同输入抛 `NumberFormatException`——**同一条规则两引擎一个静默出错答案、
86
+ * 一个响亮失败**。对合规引擎,静默的那个远更危险。
87
+ *
88
+ * 语义逐条对齐 truffle `toInt`:
89
+ * - number → 截断取整(对齐 Java `Number.intValue()`),但拒绝 NaN/Infinity;
90
+ * - bigint → 精确转换,超出安全整数范围拒绝(Long 已是 BigInt 表示,见 issue #142);
91
+ * - string → 严格整数解析(对齐 `Integer.parseInt`:不接受小数、十六进制、空串);
92
+ * - 其他 → 抛错。
93
+ */
94
+ function toInt(v, ctx) {
95
+ const bad = () => {
96
+ throw new InterpreterError(`${ctx}: expected Int, got ${JSON.stringify(v) ?? String(v)}`);
97
+ };
98
+ if (typeof v === 'number') {
99
+ if (!Number.isFinite(v))
100
+ return bad();
101
+ return Math.trunc(v);
102
+ }
103
+ if (typeof v === 'bigint') {
104
+ if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER))
105
+ return bad();
106
+ return Number(v);
107
+ }
108
+ if (typeof v === 'string') {
109
+ // ★只接受**整数**字面量,与 Integer.parseInt 一致:
110
+ // "5" 收;"5.0"/"0x10"/""/" "/"5x" 拒。前后空白由 trim 容忍(parseInt 亦然)。
111
+ // 正则无歧义分支,线性匹配(避免 decimalScale 注释里记录过的 ReDoS)。
112
+ const t = v.trim();
113
+ if (!/^[+-]?\d+$/.test(t))
114
+ return bad();
115
+ const n = Number(t);
116
+ if (!Number.isSafeInteger(n))
117
+ return bad();
118
+ return n;
119
+ }
120
+ return bad();
121
+ }
72
122
  /** 把任意操作数转成 Decimal(Int/Long 精确提升;Double 禁;已是 Decimal 原样)。 */
73
123
  function toDecimalArg(v, ctx) {
74
124
  if (v instanceof Decimal)
@@ -201,6 +251,12 @@ export function serializeGuestValue(v, depth = 0) {
201
251
  if (depth > SERIALIZE_MAX_DEPTH) {
202
252
  throw new InterpreterError('serializeGuestValue: value nesting exceeds max depth (cycle?)');
203
253
  }
254
+ // ★BigInt(Long 的运行时表示)必须在委托 JSON.stringify 之前拦下——
255
+ // JSON.stringify(1n) 直接抛错。输出为无引号十进制字面量,与 JVM 侧
256
+ // Jackson 序列化 `long` 的形态一致(9223372036854775807,非 "…" 字符串)。
257
+ if (typeof v === 'bigint') {
258
+ return v.toString();
259
+ }
204
260
  if (v instanceof GuestMap) {
205
261
  const parts = [];
206
262
  for (const [k, val] of v) {
@@ -305,8 +361,42 @@ function mapKey(k) {
305
361
  throw new InterpreterError('Map key must not be null: null and the string "null" would collapse to the same key, '
306
362
  + 'silently overwriting each other. Use a non-null key, or Maybe/Option to express absence.');
307
363
  }
364
+ // ★变体(Some/None/Ok/Err)作键时必须按**内容**归一,不能用 `String(obj)`
365
+ // ——JS 里它恒为 `"[object Object]"`,于是 `Some("null")` 与 `None`
366
+ // 塌陷到同一个槽位(`List.groupBy` 上实测两桶并成一桶)。这正是本函数
367
+ // 开头的 null 守卫要消灭的那类**静默丢数据**,只是发生在变体上。
368
+ //
369
+ // truffle 侧 `String.valueOf(map)` 天然渲染内容(`{__type=None}` /
370
+ // `{__type=Some, value=null}`),两键本就不同——故此前 TS 得 1 桶、
371
+ // Java 得 2 桶,是双引擎分叉(aster-lang-ts#137 统一 None 表示后暴露)。
372
+ if (isVariantValue(k)) {
373
+ const v = k;
374
+ return 'value' in v ? `{__type=${v.__type}, value=${mapKeyPart(v.value)}}` : `{__type=${v.__type}}`;
375
+ }
308
376
  return String(k);
309
377
  }
378
+ /**
379
+ * 变体载荷的字符串化:载荷本身可能又是变体,递归走同一条归一。
380
+ *
381
+ * ★已知缺口(两引擎**一致地**存在,非本次引入,故不在此修):
382
+ * 载荷为真 null 时渲染成 `null`,与载荷为字符串 `"null"` 撞键——
383
+ * `Map.put(m, Some(null), 1)` 与 `Map.put(m, Some("null"), 2)` 塌陷成同一槽位。
384
+ * 实测两引擎同为 1 桶(truffle 走 `String.valueOf`,同样得 `value=null`),
385
+ * 故不是分叉,而是 mapKey 开头那条 null 守卫**没有下沉到载荷层**。
386
+ * 修它需先与 truffle 商定载荷 null 的规范渲染并同步改两侧,属独立议题。
387
+ */
388
+ function mapKeyPart(v) {
389
+ if (v === null || v === undefined)
390
+ return 'null';
391
+ return isVariantValue(v) ? mapKey(v) : String(v);
392
+ }
393
+ /** 是否为 Maybe/Result 变体(带 `__type` 标签且标签值属四者之一)。 */
394
+ function isVariantValue(v) {
395
+ if (v === null || typeof v !== 'object')
396
+ return false;
397
+ const t = v.__type;
398
+ return t === 'Some' || t === 'None' || t === 'Ok' || t === 'Err';
399
+ }
310
400
  /**
311
401
  * 值相等(value equality),与 JVM `equals` 契约对齐——**不是** JS 引用相等。
312
402
  * - Decimal↔Decimal 用 `.equals`(`1.5m === 1.5m` 引用不等但值相等)。
@@ -338,9 +428,35 @@ function valueEquals(x, y, depth = 0) {
338
428
  return false;
339
429
  return true;
340
430
  }
431
+ // ★Map 结构相等(issue #143)。此前这里把 Map **排除**在结构比较之外
432
+ // (下面的条件带 `&& !(x instanceof Map)`),GuestMap 落回引用相等:
433
+ // Map.put(Map.empty(),"a",1) == Map.put(Map.empty(),"a",1) → false
434
+ // 而同样内容的列表 [1,2] == [1,2] → true——**TS 自身就不自洽**;
435
+ // truffle 侧 valueEquals 对映射逐键递归 → 内容相同的 map JVM 判 true,
436
+ // 同一规则跨引擎决策翻转,且不报错。
437
+ //
438
+ // 镜像 truffle Builtins.valueEquals 的语义:先比 size,再逐键递归;
439
+ // 一侧是 Map 另一侧不是 → false(载体不同即不等)。
440
+ // GuestMap extends Map,故 `instanceof Map` 同时覆盖两者。
441
+ const xm = x instanceof Map;
442
+ const ym = y instanceof Map;
443
+ if (xm && ym) {
444
+ const mx = x;
445
+ const my = y;
446
+ if (mx.size !== my.size)
447
+ return false;
448
+ for (const [k, v] of mx) {
449
+ if (!my.has(k))
450
+ return false;
451
+ if (!valueEquals(v, my.get(k), depth + 1))
452
+ return false;
453
+ }
454
+ return true;
455
+ }
456
+ if (xm || ym)
457
+ return false;
341
458
  if (x && y && typeof x === 'object' && typeof y === 'object'
342
- && !Array.isArray(x) && !Array.isArray(y)
343
- && !(x instanceof Map) && !(y instanceof Map)) {
459
+ && !Array.isArray(x) && !Array.isArray(y)) {
344
460
  const kx = Object.keys(x);
345
461
  const ky = Object.keys(y);
346
462
  if (kx.length !== ky.length)
@@ -742,12 +858,25 @@ class Interpreter {
742
858
  case 'String':
743
859
  return expr.value;
744
860
  case 'Long':
745
- // Long 值存储为 string,尝试转为 number
746
- return Number(expr.value);
861
+ // ★Long 运行时表示 = BigInt(issue #142)。此前是 `Number(expr.value)`,
862
+ // 把词法层专门用 BigInt 保住的精度又丢了回去:
863
+ // 9223372036854775807L → 9223372036854776000(差 193,静默错答案)
864
+ // 而 JVM 侧 CoreModel.LongE 是精确 `long` → 同一源码两引擎结果不同。
865
+ // 改用 BigInt 后与 JVM 逐位一致。
866
+ //
867
+ // ★BigInt 的三个坑(已实测,见 evalBinaryOp / serializeGuestValue):
868
+ // · JSON.stringify(1n) 抛 "Do not know how to serialize a BigInt"
869
+ // · 1n + 1 抛 "Cannot mix BigInt and other types"
870
+ // · 1n === 1 为 false(== 才为 true)
871
+ return BigInt(expr.value);
747
872
  case 'Null':
748
873
  return null;
874
+ // ★None 的运行期表示是 `{__type:'None'}`,与 truffle 的 `maybeNone()` 对齐
875
+ // (aster-lang-ts#137)。此前返回裸 `null`:同一份 IR 两引擎产出不同 JSON,
876
+ // 宿主一边拿到 `null`、一边拿到 `{"__type":"None"}`,等价性按值比较判为不等;
877
+ // 且 `null` 无法与「真的没有值」区分——None 是**有值的变体**,只是不含载荷。
749
878
  case 'None':
750
- return null;
879
+ return { __type: 'None' };
751
880
  case 'Name':
752
881
  return this.resolveName(expr.name, env);
753
882
  case 'Call':
@@ -834,7 +963,8 @@ class Interpreter {
834
963
  return { __type: ctor, value: this.evalExpr(call.args[0], env) };
835
964
  }
836
965
  if (ctor === 'None' && call.args.length === 0) {
837
- return null;
966
+ // 与 evalExpr 的 case 'None' 同一表示(aster-lang-ts#137)。
967
+ return { __type: 'None' };
838
968
  }
839
969
  }
840
970
  // stdlib namespaced builtin(Text.* 等),与 Java Builtins 对齐。
@@ -1006,11 +1136,11 @@ class Interpreter {
1006
1136
  case 'Text.substring': {
1007
1137
  const args = a();
1008
1138
  const s = text(args[0]);
1009
- const start = Number(args[1]);
1139
+ const start = toInt(args[1], 'Text.substring');
1010
1140
  if (start < 0)
1011
1141
  throw new InterpreterError(`Text.substring: index out of range: ${start}`);
1012
1142
  if (args.length >= 3) {
1013
- const end = Number(args[2]);
1143
+ const end = toInt(args[2], 'Text.substring');
1014
1144
  if (end < 0)
1015
1145
  throw new InterpreterError(`Text.substring: index out of range: ${end}`);
1016
1146
  // Java String.substring(start,end) 对 start>end 或 end>length 抛异常。
@@ -1033,8 +1163,8 @@ class Interpreter {
1033
1163
  case 'List.slice': {
1034
1164
  const args = a();
1035
1165
  const l = reqList('List.slice', args[0]);
1036
- const start = Number(args[1]);
1037
- const end = args.length >= 3 ? Number(args[2]) : l.length;
1166
+ const start = toInt(args[1], 'List.slice');
1167
+ const end = args.length >= 3 ? toInt(args[2], 'List.slice') : l.length;
1038
1168
  // 镜像 Java List.subList 的越界语义。
1039
1169
  if (start < 0 || end > l.length || start > end) {
1040
1170
  throw new InterpreterError(`List.slice: index out of range: begin ${start}, end ${end}, size ${l.length}`);
@@ -1061,8 +1191,19 @@ class Interpreter {
1061
1191
  return reqList('List.isEmpty', l).length === 0;
1062
1192
  }
1063
1193
  case 'List.get': {
1194
+ // ★越界必须抛错,不得静默返回 undefined(issue aster-dev#32)。
1195
+ // JS 的 `arr[99]` 是 undefined,而 truffle Builtins.List.get 有显式
1196
+ // 边界检查并抛 BuiltinException——同一段源码两引擎一个静默产
1197
+ // undefined、一个响亮失败,且文档声称「越界报错且双引擎一致」。
1198
+ // undefined 随后会一路流进裁决结果,是最危险的一类静默错答案。
1199
+ // 消息对齐 truffle ErrorMessages.collectionIndexOutOfBounds 的英文形式。
1064
1200
  const [l, i] = a();
1065
- return reqList('List.get', l)[Number(i)];
1201
+ const list = reqList('List.get', l);
1202
+ const idx = toInt(i, 'List.get');
1203
+ if (idx < 0 || idx >= list.length) {
1204
+ throw new InterpreterError(`List.get: index out of bounds: ${idx} (size=${list.length})`);
1205
+ }
1206
+ return list[idx];
1066
1207
  }
1067
1208
  case 'List.contains': {
1068
1209
  const [l, x] = a();
@@ -1084,16 +1225,16 @@ class Interpreter {
1084
1225
  // 此前返回裸值/null,于是 `Map.get(m,k) plus 1` 能**编译通过、运行才炸**。
1085
1226
  // ★这是对 Stable 集的语义变更,明知破坏 spec-1.0-freeze 承诺,由用户拍板接受。
1086
1227
  //
1087
- // ★TS 侧 None 的运行期表示**就是 null**(见 evalExpr 的 case 'None'),
1088
- // 故缺键分支保持返回 null 即等价于 None——Maybe.isNone/withDefault 都已认它。
1089
- // 真正变的是**命中**分支:现在包一层 Some,与 Java 的 {_type:"Some"} 对齐。
1228
+ // ★None 的运行期表示是 `{__type:'None'}`(见 evalExpr 的 case 'None',
1229
+ // aster-lang-ts#137 起与 truffle 的 maybeNone() 对齐);缺键分支返回它。
1230
+ // 命中分支包一层 Some,与 Java 的 {__type:"Some"} 对齐。
1090
1231
  // 命中值本身为 null 时仍返回 Some(null):「键存在但值为 null」与「键不存在」
1091
1232
  // 是两件事,不能塌陷。
1092
1233
  case 'Map.get': {
1093
1234
  const [m, k] = a();
1094
1235
  const g = asGuestMap('Map.get', m);
1095
1236
  const key = mapKey(k);
1096
- return g.has(key) ? { __type: 'Some', value: g.get(key) } : null;
1237
+ return g.has(key) ? { __type: 'Some', value: g.get(key) } : { __type: 'None' };
1097
1238
  }
1098
1239
  case 'Map.contains': {
1099
1240
  const [m, k] = a();
@@ -1122,11 +1263,11 @@ class Interpreter {
1122
1263
  case 'Date.fromISO': return dateFromISO(a()[0]);
1123
1264
  case 'Date.daysBetween': {
1124
1265
  const [d1, d2] = a();
1125
- return Number(d2) - Number(d1);
1266
+ return toInt(d2, 'Date.daysBetween') - toInt(d1, 'Date.daysBetween');
1126
1267
  }
1127
1268
  case 'Date.addDays': {
1128
1269
  const [d, n] = a();
1129
- const r = Number(d) + Number(n);
1270
+ const r = toInt(d, 'Date.addDays') + toInt(n, 'Date.addDays');
1130
1271
  if (r < DATE_MIN_EPOCH || r > DATE_MAX_EPOCH)
1131
1272
  throw new InterpreterError(`Date.OutOfRange: epoch-day ${r}`);
1132
1273
  return r;
@@ -1179,13 +1320,13 @@ class Interpreter {
1179
1320
  // Ok/Err/Int/Text 四格分叉(共 8 格),isSome/isOk/isErr 全对齐。
1180
1321
  // JVM 那侧是对的——isNone 不该对 `Ok(1)`、`42` 返回 true。
1181
1322
  //
1182
- // ★两种 None 表示都要认:`None` 字面量求值为裸 `null`(evalExpr 的 case 'None'),
1183
- // 而 Maybe.map 等返回 `{__type:'None'}`。只认后者会让 `Maybe.isNone(None)` 变 false。
1323
+ // ★None 只有一种运行期表示 `{__type:'None'}`(aster-lang-ts#137 起统一)。
1324
+ // 此前 `None` 字面量求值为裸 `null`,故这里额外把 `null`/`undefined` 判为 None;
1325
+ // 现已不需要——truffle 侧 `"None".equals(m.get("__type"))` 对 null 返回 false,
1326
+ // 继续放行会让 `Maybe.isNone(null)` 两引擎分叉(TS true / JVM false)。
1184
1327
  case 'Maybe.isNone':
1185
1328
  case 'Option.isNone': {
1186
1329
  const [x] = a();
1187
- if (x === null || x === undefined)
1188
- return true;
1189
1330
  return x?.__type === 'None';
1190
1331
  }
1191
1332
  case 'Result.isOk': {
@@ -1296,9 +1437,11 @@ class Interpreter {
1296
1437
  }
1297
1438
  case 'List.range': {
1298
1439
  const [s, e] = a();
1299
- const start = Number(s);
1300
- const end = Number(e);
1440
+ const start = toInt(s, 'List.range');
1441
+ const end = toInt(e, 'List.range');
1301
1442
  // 红队 P0-B:先按长度判上限,超限即抛(不先生成,防 DoS)。
1443
+ // ★start/end 走 toInt 后不可能是 NaN——此前裸 Number() 下 NaN 会让
1444
+ // `size > MAX_RANGE_SIZE` 为 false 直接放行,循环零次静默返回 []。
1302
1445
  const size = end - start;
1303
1446
  if (size > MAX_RANGE_SIZE) {
1304
1447
  throw new InterpreterError(`List.range: 长度过大(${size} > ${MAX_RANGE_SIZE}),拒绝以防内存耗尽 DoS`);
@@ -1426,9 +1569,10 @@ class Interpreter {
1426
1569
  // `operationExpectedType("Maybe.map", "Maybe (Some or None)", …)`)。
1427
1570
  // 既是双引擎分叉,也是静默错答案,与 #123 的 arity 缺口同一模式。
1428
1571
  //
1429
- // 注意 TS 侧 None 有**两种**运行期表示,都要认:`None` 字面量求值为 `null`
1430
- // (evalExpr 的 case 'None'),而 Maybe.map 自身返回 `{__type:'None'}`。
1431
- // 只认后者会误伤 `Maybe.map(None, f)`。
1572
+ // ★None 只有一种运行期表示 `{__type:'None'}`(aster-lang-ts#137 起统一)。
1573
+ // 此前 `None` 字面量求值为裸 `null`,故这里额外放行 `null`;现已不需要——
1574
+ // 且 truffle 的 `Maybe.map` 要求带标签的 map、对裸 null 直接报错,
1575
+ // 继续放行 `null` 本身就是新的分叉。
1432
1576
  //
1433
1577
  // ★不在放行之列(有意为之):`undefined`、以及 JVM 形的 `{_type:'None'}`
1434
1578
  // (单下划线——truffle 用 `_type`,TS 全仓用 `__type`)。后者若从跨引擎
@@ -1442,7 +1586,7 @@ class Interpreter {
1442
1586
  const fn = callableArg(1);
1443
1587
  return { __type: 'Some', value: this.applyCallable(fn, [o.value]) };
1444
1588
  }
1445
- if (o !== null && o?.__type !== 'None') {
1589
+ if (o?.__type !== 'None') {
1446
1590
  // 消息**主体**与 truffle 的 operationExpectedType 对齐
1447
1591
  // (`Maybe.map: expected Maybe (Some or None), got X`),但**类型名词汇表
1448
1592
  // 两侧不同**,不止大小写:本引擎用 JS `typeof`(7 值域),truffle 用
@@ -1559,6 +1703,56 @@ class Interpreter {
1559
1703
  throw new InterpreterError(`Decimal: unsupported operator '${op}'`);
1560
1704
  }
1561
1705
  }
1706
+ // ★BigInt(Long)分派——必须在下面的原生算术之前。
1707
+ // JS 不允许 BigInt 与 number 混算(`1n + 1` 抛错),故:
1708
+ // · 两侧都是 BigInt → 用 BigInt 运算,精确 64 位,与 JVM long 一致
1709
+ // · 一侧 BigInt 一侧 Int → 把整数值 number 提升为 BigInt(精确,无损)
1710
+ // · 一侧 BigInt 一侧非整数 Double → 响亮失败,不静默降级
1711
+ // 与 Decimal 分派同构(见上),理由相同:静默错答案比响亮失败危险得多。
1712
+ if (typeof left === 'bigint' || typeof right === 'bigint') {
1713
+ const toBig = (v, side) => {
1714
+ if (typeof v === 'bigint')
1715
+ return v;
1716
+ if (typeof v === 'number') {
1717
+ if (!Number.isInteger(v)) {
1718
+ throw new InterpreterError(`Cannot combine Long and non-integer Double (${side}). Long arithmetic is exact 64-bit.`);
1719
+ }
1720
+ if (!Number.isSafeInteger(v)) {
1721
+ // >2^53 的 number 本身已不精确,提升过去只会把错误带进精确域
1722
+ throw new InterpreterError(`Cannot combine Long and unsafe integer ${v} (${side}); value already lost precision as a JS number.`);
1723
+ }
1724
+ return BigInt(v);
1725
+ }
1726
+ throw new InterpreterError(`Long ${op}: expected Long/Int operand, got ${typeof v}`);
1727
+ };
1728
+ const lb = toBig(left, 'left'), rb = toBig(right, 'right');
1729
+ switch (op) {
1730
+ case '+': return lb + rb;
1731
+ case '-': return lb - rb;
1732
+ case '*': return lb * rb;
1733
+ case '/':
1734
+ if (rb === 0n)
1735
+ throw new InterpreterError('Division by zero');
1736
+ // Long 除法与 JVM 一致:向零截断的整数除法
1737
+ return lb / rb;
1738
+ case '//':
1739
+ if (rb === 0n)
1740
+ throw new InterpreterError('Division by zero');
1741
+ return lb / rb;
1742
+ case '%':
1743
+ if (rb === 0n)
1744
+ throw new InterpreterError('Division by zero');
1745
+ return lb % rb;
1746
+ case '==': return lb === rb;
1747
+ case '!=': return lb !== rb;
1748
+ case '<': return lb < rb;
1749
+ case '<=': return lb <= rb;
1750
+ case '>': return lb > rb;
1751
+ case '>=': return lb >= rb;
1752
+ default:
1753
+ throw new InterpreterError(`Long: unsupported operator '${op}'`);
1754
+ }
1755
+ }
1562
1756
  // 算术运算
1563
1757
  switch (op) {
1564
1758
  case '+': {