gofront 1.8.0 → 1.9.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.
Files changed (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -2,6 +2,7 @@
2
2
  // Installed as a mixin on TypeChecker.prototype.
3
3
 
4
4
  import { constIntValue } from "./const-fold.js";
5
+ import { exprText, operandKind } from "./operand-text.js";
5
6
  import { sharedInfo } from "./stdlib/shared.js";
6
7
  import {
7
8
  ANY,
@@ -9,6 +10,7 @@ import {
9
10
  CMP_OPS,
10
11
  COMPLEX128,
11
12
  FLOAT64,
13
+ is64BitInt,
12
14
  isAny,
13
15
  isComplex,
14
16
  isComplex64,
@@ -133,6 +135,7 @@ export const assignabilityMethods = {
133
135
 
134
136
  _binaryResultTypeCmp(op, lt, rt, node) {
135
137
  if (this._checkComplexCmpOp(op, lt, rt, node)) return ANY;
138
+ this._checkIntegerCmpKinds(op, lt, rt, node);
136
139
  this._checkEqualityComparable(op, lt, rt, node);
137
140
  return BOOL;
138
141
  },
@@ -173,6 +176,17 @@ export const assignabilityMethods = {
173
176
  return isFloat ? FLOAT64 : lt;
174
177
  },
175
178
 
179
+ // Comparing two typed integers of different types (`byte != int`) is a
180
+ // mismatch in Go; untyped constants adapt to either side.
181
+ _checkIntegerCmpKinds(op, lt, rt, node) {
182
+ if (!node || lt.kind === "untyped" || rt.kind === "untyped") return;
183
+ if (!isInteger(lt) || !isInteger(rt) || typeStr(lt) === typeStr(rt)) return;
184
+ this.err(
185
+ `invalid operation: ${exprText(node)} (mismatched types ${typeStr(lt)} and ${typeStr(rt)})`,
186
+ node,
187
+ );
188
+ },
189
+
176
190
  _checkComplexCmpOp(op, lt, rt, node) {
177
191
  if (isComplex(lt) || isComplex(rt)) {
178
192
  if (op !== "==" && op !== "!=") {
@@ -204,13 +218,68 @@ export const assignabilityMethods = {
204
218
 
205
219
  // ── Assignability ─────────────────────────────────────────────────
206
220
 
207
- assertAssignable(target, source, node, ctx = "assignment") {
221
+ _tag64BitNode(target, node) {
222
+ if (node && typeof node === "object" && is64BitInt(target)) {
223
+ node._is64Bit = true;
224
+ if (node.kind === "KeyValueExpr" && node.value)
225
+ node.value._is64Bit = true;
226
+ if (node.kind === "UnaryExpr" && node.operand)
227
+ node.operand._is64Bit = true;
228
+ if (node.kind === "ParenExpr" && node.expr) node.expr._is64Bit = true;
229
+ }
230
+ },
231
+
232
+ // `var m uint64 = (1 << k) - 1`: a non-constant shift whose left operand is
233
+ // an untyped constant takes the type its context gives it, so codegen
234
+ // computes it in that type (BigInt for 64-bit integers).
235
+ _retypeUntypedShift(target, source, valueNode) {
236
+ if (!isUntyped(source) || !isInteger(target)) return;
237
+ this._retypeShiftTree(valueNode, target);
238
+ },
239
+
240
+ _retypeShiftTree(e, target) {
241
+ if (!e || typeof e !== "object" || e._type?.kind !== "untyped") return;
242
+ if (e.kind === "ParenExpr") this._retypeShiftTree(e.expr, target);
243
+ else if (e.kind === "UnaryExpr") this._retypeShiftTree(e.operand, target);
244
+ else if (e.kind === "BinaryExpr" && !this._constValue(e)) {
245
+ const isShift = e.op === "<<" || e.op === ">>";
246
+ this._retypeShiftTree(e.left, target);
247
+ if (isShift && e.left._type?.kind === "untyped") {
248
+ e.left._type = target;
249
+ if (is64BitInt(target)) e.left._is64Bit = true;
250
+ } else if (!isShift) this._retypeShiftTree(e.right, target);
251
+ } else return;
252
+ if (this._constValue(e)) return;
253
+ e._type = target;
254
+ if (is64BitInt(target)) e._is64Bit = true;
255
+ },
256
+
257
+ // An untyped real constant flowing into a complex type becomes a complex
258
+ // value in codegen.
259
+ _tagComplexConst(target, source, valueNode) {
260
+ if (
261
+ valueNode &&
262
+ typeof valueNode === "object" &&
263
+ isComplex(target) &&
264
+ isUntyped(source) &&
265
+ !isComplex(source)
266
+ )
267
+ valueNode._asComplex = true;
268
+ },
269
+
270
+ // `operand` is the expression whose value flows in, when `node` (the
271
+ // position and codegen tag) is not that expression.
272
+ assertAssignable(target, source, node, ctx = "assignment", operand = node) {
208
273
  if (!target || !source) return;
209
274
  if (this._checkConstOverflow(target, source, node, ctx)) return;
210
275
  target = this.resolveType(target);
211
276
  source = this.resolveType(source);
277
+ this._tag64BitNode(target, node);
278
+ this._retypeUntypedShift(target, source, operand ?? node);
279
+ this._tagComplexConst(target, source, operand ?? node);
212
280
  this._markIfaceBox(target, source, node);
213
281
  this._checkWasmIfaceProvider(target, source, node);
282
+ if (this._checkIfaceToConcrete(target, source, node, ctx, operand)) return;
214
283
  if (this._assertAssignableEarlyReturn(target, source)) return;
215
284
  if (isPointer(target) && isPointer(source)) {
216
285
  this.assertAssignable(
@@ -221,14 +290,7 @@ export const assignabilityMethods = {
221
290
  return;
222
291
  }
223
292
  if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
224
- if (this._isNumericCoercible(target, source)) {
225
- if (!this._isGenericCallResult(node)) return;
226
- this.err(
227
- `mismatched types ${typeStr(source)} and ${typeStr(target)} in ${ctx}`,
228
- node,
229
- );
230
- return;
231
- }
293
+ if (this._checkNumericCoercion(target, source, node, ctx, operand)) return;
232
294
  if (this._checkArrayAssignable(target, source, node)) return;
233
295
  if (this._checkTypedArrayAssignable(target, source, node)) return;
234
296
  if (this._checkFuncAssignable(target, source, node)) return;
@@ -300,7 +362,7 @@ export const assignabilityMethods = {
300
362
  return { type: t, ptr: isPtr };
301
363
  if (!methods || methods.size === 0) return null;
302
364
  // Only user-declared types (methods carry _ptrRecv); stdlib named types
303
- // such as time.Duration have no box class.
365
+ // such as strings.Builder have no box class.
304
366
  if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
305
367
  return null;
306
368
  return { type: t, ptr: isPtr };
@@ -379,6 +441,28 @@ export const assignabilityMethods = {
379
441
  );
380
442
  },
381
443
 
444
+ // An interface-typed operand needs an explicit type assertion before it can
445
+ // flow into a concrete (non-interface) type.
446
+ _checkIfaceToConcrete(target, source, node, ctx, operand) {
447
+ if (!source?._declared || !operand || typeof operand !== "object")
448
+ return false;
449
+ if (!this._isConcreteTarget(target)) return false;
450
+ this.err(
451
+ `cannot use ${exprText(operand)} (${operandKind(operand, typeStr(source))}) as ${typeStr(target)} value in ${ctx}: need type assertion`,
452
+ node,
453
+ );
454
+ return true;
455
+ },
456
+
457
+ _isConcreteTarget(target) {
458
+ if (!target || target.kind === "typeParam" || isAny(target)) return false;
459
+ if (isNil(target) || isUntyped(target)) return false;
460
+ const base = this.resolveType(
461
+ target.kind === "named" ? target.underlying : target,
462
+ );
463
+ return !!base && base.kind !== "interface" && base.kind !== "typeParam";
464
+ },
465
+
382
466
  _assertAssignableEarlyReturn(target, source) {
383
467
  return (
384
468
  target?.kind === "typeParam" ||
@@ -502,13 +586,42 @@ export const assignabilityMethods = {
502
586
  const pair = `${target.name}:${source.name}`;
503
587
  return (
504
588
  pair === "float64:int" ||
505
- pair === "int:float64" ||
506
589
  // rune was historically int in GoFront; keep rune/int interchangeable.
507
590
  pair === "int:int32" ||
508
591
  pair === "int32:int"
509
592
  );
510
593
  },
511
594
 
595
+ // The lenient numeric pairs: accepted, except that a generic call result
596
+ // is a mismatch and a float64 never reaches an int implicitly.
597
+ // Returns true when the pair was handled.
598
+ _checkNumericCoercion(target, source, node, ctx, operand) {
599
+ const floatToInt = this._isFloatToInt(target, source);
600
+ if (!floatToInt && !this._isNumericCoercible(target, source)) return false;
601
+ if (this._isGenericCallResult(node))
602
+ this.err(
603
+ `mismatched types ${typeStr(source)} and ${typeStr(target)} in ${ctx}`,
604
+ node,
605
+ );
606
+ else if (floatToInt)
607
+ this.err(
608
+ `cannot use ${exprText(operand)} (${operandKind(operand, typeStr(source))}) as ${typeStr(target)} value in ${ctx}`,
609
+ node,
610
+ );
611
+ return true;
612
+ },
613
+
614
+ // Go has no implicit float64 -> int conversion; only an untyped constant
615
+ // (handled earlier) or an explicit `int(x)` reaches an int.
616
+ _isFloatToInt(target, source) {
617
+ return (
618
+ target.kind === "basic" &&
619
+ source.kind === "basic" &&
620
+ target.name === "int" &&
621
+ source.name === "float64"
622
+ );
623
+ },
624
+
512
625
  _checkArrayAssignable(target, source, node) {
513
626
  if (target.kind === "array" && source.kind === "array") {
514
627
  if (
@@ -8,6 +8,8 @@
8
8
  // Go type until they are converted or assigned
9
9
  // i — for an untyped integral FLOAT, its exact BigInt value, so that
10
10
  // `1e18 + 1` stays exact when it later becomes an integer
11
+ // r — for an untyped decimal FLOAT, its exact value `{ n, e }` = n * 10^-e
12
+ // (BigInt n), so `0.2 + 0.1` is exactly 0.3 before it rounds to float64
11
13
  //
12
14
  // The checker evaluates constant expressions with these helpers and attaches
13
15
  // the result to the AST node (`_const`); both backends emit that value.
@@ -22,6 +24,7 @@ const BOOL = "BOOL";
22
24
  // Shift counts beyond this are left to the runtime (Go allows larger shifts
23
25
  // of untyped constants, but no program needs a 4096-bit intermediate).
24
26
  const MAX_SHIFT = 4096n;
27
+ const MAX_DECIMAL_EXP = 400;
25
28
 
26
29
  // Value ranges of the integer types.
27
30
  export const INT_RANGES = {
@@ -42,24 +45,6 @@ export const INT_RANGES = {
42
45
 
43
46
  const FLOAT_TYPES = new Set(["float32", "float64"]);
44
47
 
45
- // Untyped integer constants of package `math` (Go declares them untyped).
46
- // `MaxInt`, `MinInt` and `MaxUint` are left out: the JS backend maps them to
47
- // the safe-integer range on purpose.
48
- export const MATH_INT_CONSTS = {
49
- MaxInt8: 2n ** 7n - 1n,
50
- MinInt8: -(2n ** 7n),
51
- MaxInt16: 2n ** 15n - 1n,
52
- MinInt16: -(2n ** 15n),
53
- MaxInt32: 2n ** 31n - 1n,
54
- MinInt32: -(2n ** 31n),
55
- MaxInt64: 2n ** 63n - 1n,
56
- MinInt64: -(2n ** 63n),
57
- MaxUint8: 2n ** 8n - 1n,
58
- MaxUint16: 2n ** 16n - 1n,
59
- MaxUint32: 2n ** 32n - 1n,
60
- MaxUint64: 2n ** 64n - 1n,
61
- };
62
-
63
48
  export const intConst = (v) => ({ k: INT, v });
64
49
 
65
50
  // Exact integer value of a decimal float literal (`1e18`, `2.50e1`), or null.
@@ -76,6 +61,40 @@ function exactIntOfFloatText(text) {
76
61
  return cut > 0 ? BigInt(digits.slice(0, cut)) : 0n;
77
62
  }
78
63
 
64
+ // Exact decimal value `{ n, e }` (n * 10^-e) of a decimal float literal, or null.
65
+ function exactDecimalOfFloatText(text) {
66
+ const m = /^(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/.exec(text);
67
+ if (!m) return null;
68
+ const frac = m[2] ?? "";
69
+ const digits = `${m[1]}${frac}` || "0";
70
+ const e = frac.length - Number(m[3] ?? 0);
71
+ if (Math.abs(e) > MAX_DECIMAL_EXP) return null;
72
+ return e >= 0
73
+ ? { n: BigInt(digits), e }
74
+ : { n: BigInt(digits) * 10n ** BigInt(-e), e: 0 };
75
+ }
76
+
77
+ // Exact decimal value of an untyped INT or FLOAT constant, or null.
78
+ function exactDecimal(val) {
79
+ if (val.t) return null;
80
+ if (val.r) return val.r;
81
+ const i = constIntValue(val);
82
+ return i === null ? null : { n: i, e: 0 };
83
+ }
84
+
85
+ function decimalToFloat({ n, e }) {
86
+ return Number(`${n}e-${e}`);
87
+ }
88
+
89
+ function floatConstFromText(s) {
90
+ const n = Number(s);
91
+ if (!Number.isFinite(n)) return null;
92
+ const i = exactIntOfFloatText(s);
93
+ const r = exactDecimalOfFloatText(s);
94
+ const out = i === null ? { k: FLOAT, v: n } : { k: FLOAT, v: n, i };
95
+ return r === null ? out : { ...out, r };
96
+ }
97
+
79
98
  export function constFromLit(lit) {
80
99
  switch (lit.litKind) {
81
100
  case INT: {
@@ -83,13 +102,8 @@ export function constFromLit(lit) {
83
102
  const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
84
103
  return { k: INT, v: BigInt(clean) };
85
104
  }
86
- case FLOAT: {
87
- const s = String(lit.value).replaceAll("_", "");
88
- const n = Number(s);
89
- if (!Number.isFinite(n)) return null;
90
- const i = exactIntOfFloatText(s);
91
- return i === null ? { k: FLOAT, v: n } : { k: FLOAT, v: n, i };
92
- }
105
+ case FLOAT:
106
+ return floatConstFromText(String(lit.value).replaceAll("_", ""));
93
107
  case STRING:
94
108
  return { k: STRING, v: String(lit.value ?? "") };
95
109
  case BOOL:
@@ -146,13 +160,19 @@ function typedFloat(v, t) {
146
160
  return t ? { k: FLOAT, v: r, t } : { k: FLOAT, v: r };
147
161
  }
148
162
 
163
+ function negateFloat(x) {
164
+ const r = typedFloat(-x.v, x.t);
165
+ if (r && x.i !== undefined) r.i = -x.i;
166
+ if (r && x.r && !x.t) r.r = { n: -x.r.n, e: x.r.e };
167
+ return r;
168
+ }
169
+
149
170
  export function foldUnary(op, x) {
150
171
  if (op === "+" && (x.k === INT || x.k === FLOAT)) return x;
151
172
  if (op === "!" && x.k === BOOL) return { ...x, v: !x.v };
152
173
  if (op === "-" && x.k === INT) return typedInt(-x.v, x.t);
153
174
  if (op === "-" && x.k === FLOAT) {
154
- const r = typedFloat(-x.v, x.t);
155
- return r && x.i !== undefined ? { ...r, i: -x.i } : r;
175
+ return negateFloat(x);
156
176
  }
157
177
  if (op === "^" && x.k === INT) {
158
178
  // Unsigned: flip the bits of the type's width; otherwise -x-1.
@@ -211,6 +231,27 @@ function foldInts(op, a, b, t) {
211
231
  }
212
232
  }
213
233
 
234
+ // `x op y` on exact decimals for + - *.
235
+ function combineDecimals(op, x, y) {
236
+ if (op === "*") return { n: x.n * y.n, e: x.e + y.e };
237
+ const e = Math.max(x.e, y.e);
238
+ const xn = x.n * 10n ** BigInt(e - x.e);
239
+ const yn = y.n * 10n ** BigInt(e - y.e);
240
+ return { n: op === "+" ? xn + yn : xn - yn, e };
241
+ }
242
+
243
+ // Untyped decimal + - * in exact arithmetic, rounded to float64 once at the end.
244
+ function foldExactDecimals(op, a, b) {
245
+ if (op !== "+" && op !== "-" && op !== "*") return null;
246
+ const x = exactDecimal(a);
247
+ const y = exactDecimal(b);
248
+ if (!x || !y) return null;
249
+ const r = combineDecimals(op, x, y);
250
+ if (r.e > MAX_DECIMAL_EXP) return null;
251
+ const v = decimalToFloat(r);
252
+ return Number.isFinite(v) ? { k: FLOAT, v, r } : null;
253
+ }
254
+
214
255
  function foldFloats(op, a, b, t) {
215
256
  // Untyped integral operands stay exact for + - * (`1e18 + 1`).
216
257
  const ai = t ? null : constIntValue(a);
@@ -219,6 +260,8 @@ function foldFloats(op, a, b, t) {
219
260
  const i = op === "+" ? ai + bi : op === "-" ? ai - bi : ai * bi;
220
261
  return { k: FLOAT, v: Number(i), i };
221
262
  }
263
+ const exact = t ? null : foldExactDecimals(op, a, b);
264
+ if (exact) return exact;
222
265
  const x = asFloat(a);
223
266
  const y = asFloat(b);
224
267
  switch (op) {
@@ -14,7 +14,6 @@ import {
14
14
  foldUnary,
15
15
  INT_RANGES,
16
16
  intConst,
17
- MATH_INT_CONSTS,
18
17
  } from "./const-fold.js";
19
18
  import { defaultType, isAny, typeStr, VOID } from "./types.js";
20
19
 
@@ -142,14 +141,12 @@ export const constantCheckMethods = {
142
141
  return typed?.overflow ? null : typed;
143
142
  },
144
143
 
145
- // `pkg.K` (an imported GoFront package) and the integer `math` constants.
144
+ // `pkg.K` of an imported GoFront package.
146
145
  _evalConstSelector(expr, scope) {
147
146
  if (expr.expr?.kind !== "Ident") return null;
148
147
  const ns = scope.lookup(expr.expr.name);
149
148
  if (ns?.kind !== "namespace") return null;
150
- if (ns._gofront) return ns._consts?.get(expr.field) ?? null;
151
- const m = ns.name === "math" ? MATH_INT_CONSTS[expr.field] : undefined;
152
- return m === undefined ? null : intConst(m);
149
+ return ns._gofront ? (ns._consts?.get(expr.field) ?? null) : null;
153
150
  },
154
151
 
155
152
  // Conversions, `len` of a constant string, and `min`/`max` of constants.
@@ -201,7 +198,18 @@ export const constantCheckMethods = {
201
198
  );
202
199
  if (base?.kind === "interface" || isAny(base)) target = defaultType(source);
203
200
  const i = constIntValue(node._const);
204
- const range = i === null ? null : this._intRange(target);
201
+ const range = this._intRange(target);
202
+ if (i === null && range)
203
+ return this._reportTruncatedConst(target, node, ctx);
204
+ // A float or complex target holds the constant as a Number: Go rounds
205
+ // the same way, so the JS backend has nothing to warn about.
206
+ if (!range && node.kind) {
207
+ const u = this.resolveType(
208
+ target?.kind === "named" ? target.underlying : target,
209
+ );
210
+ if (u?.kind === "basic" && /^(float|complex)/.test(u.name))
211
+ node._floatConst = true;
212
+ }
205
213
  if (!range) return false;
206
214
  if (i >= range[0] && i <= range[1]) {
207
215
  node._intConst = true; // the JS backend's precision warning reads this
@@ -223,6 +231,24 @@ export const constantCheckMethods = {
223
231
  return true;
224
232
  },
225
233
 
234
+ // `var i int = 1.5`: an untyped float constant with a fractional part
235
+ // cannot become an integer.
236
+ _reportTruncatedConst(target, node, ctx) {
237
+ const c = node._const;
238
+ if (c.k !== "FLOAT" || c.t) return false;
239
+ const shown =
240
+ node.kind === "BasicLit"
241
+ ? `${node.value} (untyped float constant)`
242
+ : `${c.v} (untyped float constant ${c.v})`;
243
+ this.err(
244
+ ctx
245
+ ? `cannot use ${shown} as ${typeStr(target)} value in ${ctx} (truncated)`
246
+ : `${shown} truncated to ${typeStr(target)}`,
247
+ node,
248
+ );
249
+ return true;
250
+ },
251
+
226
252
  // `x := 1 << 63`, `println(1 << 64)`: the constant takes its default type.
227
253
  _checkConstDefault(source, node, ctx) {
228
254
  return this._checkConstOverflow(defaultType(source), source, node, ctx);