gofront 1.6.1 → 1.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.
Files changed (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  // Assignability, binary result types, and interface satisfaction.
2
2
  // Installed as a mixin on TypeChecker.prototype.
3
3
 
4
+ import { constIntValue } from "./const-fold.js";
4
5
  import { sharedInfo } from "./stdlib/shared.js";
5
6
  import {
6
7
  ANY,
@@ -10,7 +11,9 @@ import {
10
11
  FLOAT64,
11
12
  isAny,
12
13
  isComplex,
14
+ isComplex64,
13
15
  isComplexOrNumeric,
16
+ isInteger,
14
17
  isNil,
15
18
  isNumeric,
16
19
  isPointer,
@@ -27,6 +30,16 @@ import {
27
30
  UNTYPED_INT,
28
31
  } from "./types.js";
29
32
 
33
+ // Unnamed type literal kinds for Go's "identical underlying types" assignability rule.
34
+ const TYPE_LITERAL_KINDS = new Set([
35
+ "slice",
36
+ "map",
37
+ "func",
38
+ "pointer",
39
+ "array",
40
+ "struct",
41
+ ]);
42
+
30
43
  /** @typedef {import('./index.js').TypeChecker} TypeChecker */
31
44
 
32
45
  // Untyped constant assignability: maps source.base → Set of compatible target.name values
@@ -69,6 +82,8 @@ export const assignabilityMethods = {
69
82
  if (isAny(lt) || isAny(rt)) return ANY;
70
83
  if (isComplex(lt) || isComplex(rt))
71
84
  return this._binaryResultTypeComplex(op, lt, rt, node);
85
+ if ((op === "<<" || op === ">>") && isNumeric(lt) && isNumeric(rt))
86
+ return this._binaryResultTypeShift(op, lt, rt, node);
72
87
  if (isNumeric(lt) && isNumeric(rt))
73
88
  return this._binaryResultTypeNumeric(lt, rt);
74
89
  if (isString(lt) && isString(rt) && op === "+")
@@ -78,6 +93,37 @@ export const assignabilityMethods = {
78
93
  return ANY;
79
94
  },
80
95
 
96
+ // A shift has the type of its left operand; the count may be any integer
97
+ // type or an untyped constant representable as uint.
98
+ _binaryResultTypeShift(_op, lt, rt, node) {
99
+ if (node) this.checkShiftCount(rt, node.right);
100
+ if (node && lt.kind !== "untyped" && !isInteger(lt))
101
+ this.err(`shifted operand ${typeStr(lt)} must be integer`, node.left);
102
+ return lt;
103
+ },
104
+
105
+ // Reports a shift count that is not an integer or is a negative constant.
106
+ checkShiftCount(rt, countExpr) {
107
+ const v = this._constValue(countExpr);
108
+ if (v) {
109
+ const n = constIntValue(v);
110
+ if (n === null) {
111
+ this.err(
112
+ `invalid shift count ${typeStr(rt)} constant: not an integer`,
113
+ countExpr,
114
+ );
115
+ } else if (n < 0n) {
116
+ this.err(`invalid shift count: ${n} is negative`, countExpr);
117
+ }
118
+ return;
119
+ }
120
+ if (!isInteger(rt))
121
+ this.err(
122
+ `invalid shift count: ${typeStr(rt)} is not an integer type`,
123
+ countExpr,
124
+ );
125
+ },
126
+
81
127
  _binaryResultTypeString(lt, rt) {
82
128
  if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
83
129
  if (lt.kind === "untyped") return rt;
@@ -101,6 +147,9 @@ export const assignabilityMethods = {
101
147
  return ANY;
102
148
  }
103
149
  if (lt.kind === "untyped" && rt.kind === "untyped") return UNTYPED_COMPLEX;
150
+ if (lt.kind === "untyped") return rt;
151
+ if (rt.kind === "untyped") return lt;
152
+ if (isComplex64(lt) && isComplex64(rt)) return lt;
104
153
  return COMPLEX128;
105
154
  },
106
155
 
@@ -155,8 +204,9 @@ export const assignabilityMethods = {
155
204
 
156
205
  // ── Assignability ─────────────────────────────────────────────────
157
206
 
158
- assertAssignable(target, source, node) {
207
+ assertAssignable(target, source, node, ctx = "assignment") {
159
208
  if (!target || !source) return;
209
+ if (this._checkConstOverflow(target, source, node, ctx)) return;
160
210
  target = this.resolveType(target);
161
211
  source = this.resolveType(source);
162
212
  this._markIfaceBox(target, source, node);
@@ -171,7 +221,14 @@ export const assignabilityMethods = {
171
221
  return;
172
222
  }
173
223
  if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
174
- if (this._isNumericCoercible(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
+ }
175
232
  if (this._checkArrayAssignable(target, source, node)) return;
176
233
  if (this._checkTypedArrayAssignable(target, source, node)) return;
177
234
  if (this._checkFuncAssignable(target, source, node)) return;
@@ -191,17 +248,62 @@ export const assignabilityMethods = {
191
248
  )
192
249
  return;
193
250
  if (target.kind === "typeParam") return;
194
- const tBase = this.resolveType(
195
- target.kind === "named" ? target.underlying : target,
196
- );
251
+ let tBase = this.resolveType(target);
252
+ if (tBase?.kind === "named") tBase = this.resolveType(tBase.underlying);
197
253
  if (!isAny(target) && tBase?.kind !== "interface") return;
254
+ // The static type behind the interface value: codegen derives the
255
+ // fmt metadata (Stringer elements of slices and maps) from it.
256
+ node._ifaceSrc = source;
257
+ const meta = this._ifaceBoxMeta(source);
258
+ if (!meta) return;
259
+ node._ifaceBox = meta.box;
260
+ if (meta.type) node._ifaceNamedType = meta.type;
261
+ if (meta.ptrFields) node._ifacePtrFields = meta.ptrFields;
262
+ },
263
+
264
+ // How a value of static type `source` is boxed on entering an interface:
265
+ // { box, type? (named kinds), ptrFields? }, or null when it stays raw.
266
+ // Shared by _markIfaceBox and the JS backend's multi-value fmt operands.
267
+ _ifaceBoxMeta(source) {
268
+ const named = this._namedMethodBoxSource(source);
269
+ if (named)
270
+ return { box: named.ptr ? "named_ptr" : "named", type: named.type };
198
271
  const sBase = this.resolveType(
199
272
  source.kind === "named" ? source.underlying : source,
200
273
  );
201
274
  const info = this._classifyIfaceBox(sBase);
202
- if (!info) return;
203
- node._ifaceBox = info.box;
204
- if (info.ptrFields) node._ifacePtrFields = info.ptrFields;
275
+ if (!info) return null;
276
+ return { box: info.box, ptrFields: info.ptrFields };
277
+ },
278
+
279
+ // A named non-struct type with methods (or a pointer to one) entering an
280
+ // interface: the JS backend keeps such values raw and boxes them here so
281
+ // dynamic method dispatch works.
282
+ _namedMethodBoxSource(source) {
283
+ const isPtr = source?.kind === "pointer";
284
+ let t = isPtr ? source.base : source;
285
+ if (t?.kind === "TypeName" || t?.kind === "Ident") t = this.resolveType(t);
286
+ if (t?.kind !== "named") return null;
287
+ const u = this.resolveType(t.underlying);
288
+ if (!u || u.kind === "struct" || u.kind === "interface") return null;
289
+ const methods = t.methods ?? t._generic?.methods;
290
+ // A user-declared named basic type without methods is boxed as well, so
291
+ // a type assertion to its underlying type fails like Go.
292
+ if (
293
+ u.kind === "basic" &&
294
+ !t._generic &&
295
+ t.methods instanceof Map &&
296
+ t.methods.size === 0 &&
297
+ t._target !== undefined &&
298
+ t._target !== "wasm"
299
+ )
300
+ return { type: t, ptr: isPtr };
301
+ if (!methods || methods.size === 0) return null;
302
+ // Only user-declared types (methods carry _ptrRecv); stdlib named types
303
+ // such as time.Duration have no box class.
304
+ if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
305
+ return null;
306
+ return { type: t, ptr: isPtr };
205
307
  },
206
308
 
207
309
  _getStructPtrFields(st, prefix = "", seen = new Set()) {
@@ -316,7 +418,56 @@ export const assignabilityMethods = {
316
418
  );
317
419
  return;
318
420
  }
319
- this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
421
+ if (this._identicalUnderlyingLiteral(target, source, tBase, sBase)) return;
422
+ this.err(
423
+ `Cannot assign ${typeStr(source)} to ${typeStr(target)}`,
424
+ node,
425
+ this._seqToSliceHint(source, tBase, node),
426
+ );
427
+ },
428
+
429
+ // maps.Keys/Values (and every iter.Seq) no longer return a slice: point the
430
+ // slice-expecting caller at slices.Collect.
431
+ _seqToSliceHint(source, tBase, node) {
432
+ if (tBase?.kind !== "slice" || !this._isIterSeq(source)) return null;
433
+ return this._seqCollectHint(node);
434
+ },
435
+
436
+ _isIterSeq(t) {
437
+ return t?.kind === "named" && /^iter\.Seq\d*\[/.test(typeStr(t));
438
+ },
439
+
440
+ _seqCollectHint(node) {
441
+ const f = node?.kind === "CallExpr" ? node.func : null;
442
+ if (
443
+ f?.kind === "SelectorExpr" &&
444
+ f.expr?.kind === "Ident" &&
445
+ f.expr.name === "maps" &&
446
+ (f.field === "Keys" || f.field === "Values")
447
+ )
448
+ return `use slices.Collect(maps.${f.field}(m)) for a slice`;
449
+ return "use slices.Collect(seq) for a slice";
450
+ },
451
+
452
+ // len/cap, spread and slice conversion are lenient on func types, and an
453
+ // iter.Seq value is one: reject it there instead of miscompiling.
454
+ _rejectSeqAsSlice(type, node, use) {
455
+ if (!this._isIterSeq(type)) return;
456
+ this.err(
457
+ `cannot use ${typeStr(type)} in ${use}`,
458
+ node,
459
+ this._seqCollectHint(node),
460
+ );
461
+ },
462
+
463
+ // Identical underlying types are assignable when one side is an unnamed
464
+ // type literal (`var n Names = []string{...}`), as in Go.
465
+ _identicalUnderlyingLiteral(target, source, tBase, sBase) {
466
+ if ((target.kind === "named") === (source.kind === "named")) return false;
467
+ const unnamed = target.kind === "named" ? source : target;
468
+ return (
469
+ TYPE_LITERAL_KINDS.has(unnamed.kind) && typeStr(tBase) === typeStr(sBase)
470
+ );
320
471
  },
321
472
 
322
473
  _isUntypedAssignable(target, source) {
@@ -332,6 +483,20 @@ export const assignabilityMethods = {
332
483
  );
333
484
  },
334
485
 
486
+ // A value computed from a generic call (`3 + Sum[float64](1, 2)`) has the
487
+ // exact instantiated type, so the loose int/float64 promotion that plain
488
+ // code enjoys does not apply to it.
489
+ _isGenericCallResult(node) {
490
+ if (!node || typeof node !== "object") return false;
491
+ if (node.kind === "ParenExpr") return this._isGenericCallResult(node.expr);
492
+ if (node.kind === "BinaryExpr")
493
+ return (
494
+ this._isGenericCallResult(node.left) ||
495
+ this._isGenericCallResult(node.right)
496
+ );
497
+ return node.kind === "CallExpr" && Boolean(node.func?._genericFnType);
498
+ },
499
+
335
500
  _isNumericCoercible(target, source) {
336
501
  if (target.kind !== "basic" || source.kind !== "basic") return false;
337
502
  const pair = `${target.name}:${source.name}`;
@@ -420,10 +585,47 @@ export const assignabilityMethods = {
420
585
 
421
586
  // ── Interface satisfaction ────────────────────────────────────────
422
587
 
588
+ // `interface{}` and `any` are the same type, but print differently.
589
+ _sigTypeStr(t) {
590
+ switch (t?.kind) {
591
+ case "interface":
592
+ if (
593
+ !t.name &&
594
+ t.methods.size === 0 &&
595
+ !t.unionConstraint &&
596
+ !t._typeSetEmbeds
597
+ )
598
+ return "any";
599
+ return typeStr(t);
600
+ case "slice":
601
+ return `[]${this._sigTypeStr(t.elem)}`;
602
+ case "array":
603
+ return `[${t.size ?? "..."}]${this._sigTypeStr(t.elem)}`;
604
+ case "pointer":
605
+ return `*${this._sigTypeStr(t.base)}`;
606
+ case "map":
607
+ return `map[${this._sigTypeStr(t.key)}]${this._sigTypeStr(t.value)}`;
608
+ case "tuple":
609
+ return `(${t.types.map((x) => this._sigTypeStr(x)).join(", ")})`;
610
+ case "func": {
611
+ const ret =
612
+ t.returns.length > 1
613
+ ? `(${t.returns.map((x) => this._sigTypeStr(x)).join(", ")})`
614
+ : t.returns[0]
615
+ ? this._sigTypeStr(t.returns[0])
616
+ : "void";
617
+ return `func(${t.params.map((x) => this._sigTypeStr(x)).join(", ")}) ${ret}`;
618
+ }
619
+ default:
620
+ return typeStr(t);
621
+ }
622
+ },
623
+
423
624
  _sigParamsMatch(reqList, actList) {
424
625
  if (reqList.length !== actList.length) return false;
425
626
  for (let i = 0; i < reqList.length; i++) {
426
- if (typeStr(reqList[i]) !== typeStr(actList[i])) return false;
627
+ if (this._sigTypeStr(reqList[i]) !== this._sigTypeStr(actList[i]))
628
+ return false;
427
629
  }
428
630
  return true;
429
631
  },
@@ -466,7 +668,9 @@ export const assignabilityMethods = {
466
668
  }
467
669
  const methodMap =
468
670
  base?.kind === "struct"
469
- ? base.methods || srcType.methods
671
+ ? base.methods?.size
672
+ ? base.methods
673
+ : srcType.methods || base.methods
470
674
  : srcType.kind === "named"
471
675
  ? srcType.methods
472
676
  : null;
@@ -0,0 +1,356 @@
1
+ // Backend-independent evaluation of Go constant expressions.
2
+ //
3
+ // A constant value is `{ k, v, t, i }`:
4
+ // k — "INT" | "FLOAT" | "STRING" | "BOOL"
5
+ // v — BigInt (INT), Number (FLOAT), string or boolean
6
+ // t — basic type name of a typed constant ("int8", "float64", …); absent
7
+ // for untyped constants, which are exact (BigInt) and may exceed every
8
+ // Go type until they are converted or assigned
9
+ // i — for an untyped integral FLOAT, its exact BigInt value, so that
10
+ // `1e18 + 1` stays exact when it later becomes an integer
11
+ //
12
+ // The checker evaluates constant expressions with these helpers and attaches
13
+ // the result to the AST node (`_const`); both backends emit that value.
14
+ // `foldConstExpr` folds an AST directly (the WASM backend uses it for
15
+ // package-level constants).
16
+
17
+ const INT = "INT";
18
+ const FLOAT = "FLOAT";
19
+ const STRING = "STRING";
20
+ const BOOL = "BOOL";
21
+
22
+ // Shift counts beyond this are left to the runtime (Go allows larger shifts
23
+ // of untyped constants, but no program needs a 4096-bit intermediate).
24
+ const MAX_SHIFT = 4096n;
25
+
26
+ // Value ranges of the integer types.
27
+ export const INT_RANGES = {
28
+ int8: [-(2n ** 7n), 2n ** 7n - 1n],
29
+ int16: [-(2n ** 15n), 2n ** 15n - 1n],
30
+ int32: [-(2n ** 31n), 2n ** 31n - 1n],
31
+ rune: [-(2n ** 31n), 2n ** 31n - 1n],
32
+ int64: [-(2n ** 63n), 2n ** 63n - 1n],
33
+ int: [-(2n ** 63n), 2n ** 63n - 1n],
34
+ uint8: [0n, 2n ** 8n - 1n],
35
+ byte: [0n, 2n ** 8n - 1n],
36
+ uint16: [0n, 2n ** 16n - 1n],
37
+ uint32: [0n, 2n ** 32n - 1n],
38
+ uint64: [0n, 2n ** 64n - 1n],
39
+ uint: [0n, 2n ** 64n - 1n],
40
+ uintptr: [0n, 2n ** 64n - 1n],
41
+ };
42
+
43
+ const FLOAT_TYPES = new Set(["float32", "float64"]);
44
+
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
+ export const intConst = (v) => ({ k: INT, v });
64
+
65
+ // Exact integer value of a decimal float literal (`1e18`, `2.50e1`), or null.
66
+ function exactIntOfFloatText(text) {
67
+ const m = /^(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/.exec(text);
68
+ if (!m) return null;
69
+ const frac = m[2] ?? "";
70
+ const digits = `${m[1]}${frac}` || "0";
71
+ const exp = Number(m[3] ?? 0) - frac.length;
72
+ if (exp > 400) return null;
73
+ if (exp >= 0) return BigInt(digits) * 10n ** BigInt(exp);
74
+ const cut = digits.length + exp;
75
+ if (!/^0*$/.test(digits.slice(Math.max(cut, 0)))) return null;
76
+ return cut > 0 ? BigInt(digits.slice(0, cut)) : 0n;
77
+ }
78
+
79
+ export function constFromLit(lit) {
80
+ switch (lit.litKind) {
81
+ case INT: {
82
+ const s = String(lit.value).replaceAll("_", "");
83
+ const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
84
+ return { k: INT, v: BigInt(clean) };
85
+ }
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
+ }
93
+ case STRING:
94
+ return { k: STRING, v: String(lit.value ?? "") };
95
+ case BOOL:
96
+ return { k: BOOL, v: lit.value === "true" || lit.value === true };
97
+ default:
98
+ return null;
99
+ }
100
+ }
101
+
102
+ export function constToLit(val) {
103
+ switch (val.k) {
104
+ case INT:
105
+ return { kind: "BasicLit", litKind: INT, value: val.v.toString() };
106
+ case FLOAT:
107
+ return {
108
+ kind: "BasicLit",
109
+ litKind: FLOAT,
110
+ value: Object.is(val.v, -0) ? "-0" : String(val.v),
111
+ };
112
+ case STRING:
113
+ return { kind: "BasicLit", litKind: STRING, value: val.v };
114
+ default:
115
+ return {
116
+ kind: "BasicLit",
117
+ litKind: BOOL,
118
+ value: val.v ? "true" : "false",
119
+ };
120
+ }
121
+ }
122
+
123
+ // Exact integer value of an INT or integral FLOAT, else null.
124
+ export function constIntValue(val) {
125
+ if (val?.k === INT) return val.v;
126
+ if (val?.k !== FLOAT) return null;
127
+ if (val.i !== undefined) return val.i;
128
+ return Number.isInteger(val.v) ? BigInt(val.v) : null;
129
+ }
130
+
131
+ function asFloat(val) {
132
+ return val.k === INT ? Number(val.v) : val.v;
133
+ }
134
+
135
+ // An integer result typed `t` that does not fit is an overflow.
136
+ function typedInt(v, t) {
137
+ const range = t ? INT_RANGES[t] : null;
138
+ if (range && (v < range[0] || v > range[1]))
139
+ return { k: INT, v, t, overflow: true };
140
+ return t ? { k: INT, v, t } : { k: INT, v };
141
+ }
142
+
143
+ function typedFloat(v, t) {
144
+ if (!Number.isFinite(v)) return null;
145
+ const r = t === "float32" ? Math.fround(v) : v;
146
+ return t ? { k: FLOAT, v: r, t } : { k: FLOAT, v: r };
147
+ }
148
+
149
+ export function foldUnary(op, x) {
150
+ if (op === "+" && (x.k === INT || x.k === FLOAT)) return x;
151
+ if (op === "!" && x.k === BOOL) return { ...x, v: !x.v };
152
+ if (op === "-" && x.k === INT) return typedInt(-x.v, x.t);
153
+ if (op === "-" && x.k === FLOAT) {
154
+ const r = typedFloat(-x.v, x.t);
155
+ return r && x.i !== undefined ? { ...r, i: -x.i } : r;
156
+ }
157
+ if (op === "^" && x.k === INT) {
158
+ // Unsigned: flip the bits of the type's width; otherwise -x-1.
159
+ const range = x.t ? INT_RANGES[x.t] : null;
160
+ if (range && range[0] === 0n) return { k: INT, v: range[1] ^ x.v, t: x.t };
161
+ return typedInt(~x.v, x.t);
162
+ }
163
+ return null;
164
+ }
165
+
166
+ function compare(op, a, b) {
167
+ switch (op) {
168
+ case "==":
169
+ return a === b;
170
+ case "!=":
171
+ return a !== b;
172
+ case "<":
173
+ return a < b;
174
+ case "<=":
175
+ return a <= b;
176
+ case ">":
177
+ return a > b;
178
+ case ">=":
179
+ return a >= b;
180
+ default:
181
+ return null;
182
+ }
183
+ }
184
+
185
+ function foldInts(op, a, b, t) {
186
+ switch (op) {
187
+ case "+":
188
+ return typedInt(a + b, t);
189
+ case "-":
190
+ return typedInt(a - b, t);
191
+ case "*":
192
+ return typedInt(a * b, t);
193
+ case "/":
194
+ if (b === 0n) throw new Error("invalid operation: division by zero");
195
+ return typedInt(a / b, t);
196
+ case "%":
197
+ if (b === 0n) throw new Error("invalid operation: division by zero");
198
+ return typedInt(a % b, t);
199
+ case "&":
200
+ return typedInt(a & b, t);
201
+ case "|":
202
+ return typedInt(a | b, t);
203
+ case "^":
204
+ return typedInt(a ^ b, t);
205
+ case "&^":
206
+ return typedInt(a & ~b, t);
207
+ default: {
208
+ const r = compare(op, a, b);
209
+ return r === null ? null : { k: BOOL, v: r };
210
+ }
211
+ }
212
+ }
213
+
214
+ function foldFloats(op, a, b, t) {
215
+ // Untyped integral operands stay exact for + - * (`1e18 + 1`).
216
+ const ai = t ? null : constIntValue(a);
217
+ const bi = t ? null : constIntValue(b);
218
+ if (ai !== null && bi !== null && (op === "+" || op === "-" || op === "*")) {
219
+ const i = op === "+" ? ai + bi : op === "-" ? ai - bi : ai * bi;
220
+ return { k: FLOAT, v: Number(i), i };
221
+ }
222
+ const x = asFloat(a);
223
+ const y = asFloat(b);
224
+ switch (op) {
225
+ case "+":
226
+ return typedFloat(x + y, t);
227
+ case "-":
228
+ return typedFloat(x - y, t);
229
+ case "*":
230
+ return typedFloat(x * y, t);
231
+ case "/":
232
+ if (y === 0) throw new Error("invalid operation: division by zero");
233
+ return typedFloat(x / y, t);
234
+ default: {
235
+ const r = compare(op, x, y);
236
+ return r === null ? null : { k: BOOL, v: r };
237
+ }
238
+ }
239
+ }
240
+
241
+ function foldShift(op, a, b) {
242
+ const n = constIntValue(b);
243
+ const x = constIntValue(a);
244
+ if (n === null || x === null) return null;
245
+ if (n < 0n) throw new Error("invalid shift count: negative shift count");
246
+ if (n > MAX_SHIFT) return null;
247
+ return typedInt(op === "<<" ? x << n : x >> n, a.t);
248
+ }
249
+
250
+ function foldBools(op, a, b) {
251
+ if (op === "&&") return { k: BOOL, v: a.v && b.v };
252
+ if (op === "||") return { k: BOOL, v: a.v || b.v };
253
+ const r = op === "==" || op === "!=" ? compare(op, a.v, b.v) : null;
254
+ return r === null ? null : { k: BOOL, v: r };
255
+ }
256
+
257
+ function foldStrings(op, a, b) {
258
+ if (op === "+") return { k: STRING, v: a.v + b.v, t: a.t ?? b.t };
259
+ const r = compare(op, a.v, b.v);
260
+ return r === null ? null : { k: BOOL, v: r };
261
+ }
262
+
263
+ export function foldBinary(op, a, b) {
264
+ if (op === "<<" || op === ">>") return foldShift(op, a, b);
265
+ if (a.k === BOOL && b.k === BOOL) return foldBools(op, a, b);
266
+ if (a.k === STRING && b.k === STRING) return foldStrings(op, a, b);
267
+ const t = a.t ?? b.t;
268
+ const numeric = (x) => x.k === INT || x.k === FLOAT;
269
+ if (!numeric(a) || !numeric(b)) return null;
270
+ if (FLOAT_TYPES.has(t) || (!t && (a.k === FLOAT || b.k === FLOAT)))
271
+ return foldFloats(op, a, b, t);
272
+ const ai = constIntValue(a);
273
+ const bi = constIntValue(b);
274
+ if (ai === null || bi === null) return foldFloats(op, a, b, t);
275
+ return foldInts(op, ai, bi, t);
276
+ }
277
+
278
+ // Converts `val` to the basic type `name`. Returns the typed value, a value
279
+ // with `overflow: true` when an integer does not fit, or null when the
280
+ // conversion is not a constant one this folder handles.
281
+ export function convertConst(val, name) {
282
+ if (INT_RANGES[name]) {
283
+ const i = constIntValue(val);
284
+ return i === null ? null : typedInt(i, name);
285
+ }
286
+ if (FLOAT_TYPES.has(name)) {
287
+ if (val.k !== INT && val.k !== FLOAT) return null;
288
+ return typedFloat(asFloat(val), name);
289
+ }
290
+ if (name === "string") {
291
+ if (val.k === STRING) return { k: STRING, v: val.v, t: name };
292
+ const i = constIntValue(val);
293
+ if (val.k !== INT || i === null) return null;
294
+ const valid = i >= 0n && i <= 0x10ffffn && !(i >= 0xd800n && i <= 0xdfffn);
295
+ return {
296
+ k: STRING,
297
+ v: valid ? String.fromCodePoint(Number(i)) : "�",
298
+ t: name,
299
+ };
300
+ }
301
+ if (name === "bool" && val.k === BOOL) return { k: BOOL, v: val.v, t: name };
302
+ return null;
303
+ }
304
+
305
+ // min/max over constants of one kind.
306
+ export function foldMinMax(isMin, vals) {
307
+ let best = vals[0];
308
+ for (const x of vals.slice(1)) {
309
+ const less = foldBinary("<", x, best);
310
+ if (!less) return null;
311
+ if (less.v === isMin) best = x;
312
+ }
313
+ return best;
314
+ }
315
+
316
+ function foldExpr(expr, resolve) {
317
+ switch (expr?.kind) {
318
+ case "BasicLit":
319
+ return constFromLit(expr);
320
+ case "Ident": {
321
+ if (expr.name === "true" || expr.name === "false")
322
+ return { k: BOOL, v: expr.name === "true" };
323
+ const lit = resolve(expr.name);
324
+ return lit ? constFromLit(lit) : null;
325
+ }
326
+ case "ParenExpr":
327
+ return foldExpr(expr.expr, resolve);
328
+ case "UnaryExpr": {
329
+ const x = foldExpr(expr.operand, resolve);
330
+ return x ? foldUnary(expr.op, x) : null;
331
+ }
332
+ case "BinaryExpr": {
333
+ const a = foldExpr(expr.left, resolve);
334
+ const b = a ? foldExpr(expr.right, resolve) : null;
335
+ return a && b ? foldBinary(expr.op, a, b) : null;
336
+ }
337
+ case "CallExpr": {
338
+ if (expr.func?.kind !== "Ident" || expr.func.name !== "len") return null;
339
+ if (expr.args?.length !== 1) return null;
340
+ const s = foldExpr(expr.args[0], resolve);
341
+ if (s?.k !== STRING) return null;
342
+ // Gofront strings across JS and WASM runtime use UTF-16 code units (s.length).
343
+ return { k: INT, v: BigInt(s.v.length) };
344
+ }
345
+ default:
346
+ return null;
347
+ }
348
+ }
349
+
350
+ // Folds `expr` into a BasicLit, or returns null when it needs the runtime.
351
+ // `resolve(name)` yields the BasicLit of an already-folded constant, if any.
352
+ // Throws on a constant division by zero or a negative shift count.
353
+ export function foldConstExpr(expr, resolve) {
354
+ const val = foldExpr(expr, resolve);
355
+ return val && !val.overflow ? constToLit(val) : null;
356
+ }