gofront 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  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 +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  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 +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -10,11 +10,12 @@
10
10
  // TemplText { kind, value }
11
11
  // TemplExpr { kind, tokens } ← raw Go token array
12
12
  // TemplComponent { kind, tokens } ← raw Go token array for call
13
- // TemplChildren { kind }
13
+ // TemplChildren { kind, name } ← `{ name... }`, name of a variadic slot
14
14
  // TemplIf { kind, condTokens, then: TemplNode[], else_: TemplNode[] | null }
15
15
  // TemplFor { kind, stmtTokens, body: TemplNode[] }
16
16
  // TemplSwitch { kind, exprTokens, cases: TemplCase[] }
17
17
  // TemplCase { kind, caseTokens: tokens | null (null = default), body: TemplNode[] }
18
+ // (a `case a, b:` also carries `exprs`, one expression per value)
18
19
  //
19
20
  // Go expressions embedded in templ (`{ expr }`, attribute values, `if`
20
21
  // conditions, `switch` and `case` expressions, `@Comp(...)` calls) and `for`
@@ -71,12 +72,45 @@ function parseExprTokens(tokens, allowEmpty = true) {
71
72
  if (allowEmpty && (!tokens || tokens.length === 0)) return { expr: null };
72
73
  try {
73
74
  const eofTok = new Token(T.EOF, "", 1, 1);
74
- return { expr: new Parser([...tokens, eofTok]).parseExpr() };
75
+ const p = new Parser([...tokens, eofTok]);
76
+ const expr = p.parseExpr();
77
+ while (p.check(T.SEMICOLON)) p.advance();
78
+ if (!p.check(T.EOF))
79
+ p.err(`unexpected '${p.peek().value}' after expression`);
80
+ return { expr };
75
81
  } catch (exprError) {
76
82
  return { expr: null, exprError };
77
83
  }
78
84
  }
79
85
 
86
+ // Parses `tokens` as a comma-separated expression list (`case 1, 2:`). `expr`
87
+ // and `exprError` describe the first expression (or the first error); `exprs`
88
+ // holds every parsed expression.
89
+ function parseExprListTokens(tokens) {
90
+ if (!tokens || tokens.length === 0) return { expr: null };
91
+ const parts = [[]];
92
+ let depth = 0;
93
+ for (const t of trimSemicolons(tokens ?? [])) {
94
+ if (t.type === T.LPAREN || t.type === T.LBRACKET || t.type === T.LBRACE)
95
+ depth++;
96
+ else if (
97
+ t.type === T.RPAREN ||
98
+ t.type === T.RBRACKET ||
99
+ t.type === T.RBRACE
100
+ )
101
+ depth--;
102
+ if (t.type === T.COMMA && depth === 0) parts.push([]);
103
+ else parts[parts.length - 1].push(t);
104
+ }
105
+ const exprs = [];
106
+ for (const part of parts) {
107
+ const parsed = parseExprTokens(part, false);
108
+ if (parsed.exprError) return { expr: null, exprError: parsed.exprError };
109
+ exprs.push(parsed.expr);
110
+ }
111
+ return { expr: exprs[0] ?? null, exprs };
112
+ }
113
+
80
114
  // Parses an `if` condition; the lexer may leave the `if` keyword in front.
81
115
  function parseCondTokens(tokens) {
82
116
  if (!tokens || tokens.length === 0) return { expr: null };
@@ -220,7 +254,10 @@ export class TemplParser extends Parser {
220
254
  return this.templComponent(t.value);
221
255
  case TT.TEMPL_CHILDREN:
222
256
  this.advance();
223
- return { kind: "TemplChildren" };
257
+ return attach(
258
+ { kind: "TemplChildren", name: t.value },
259
+ parseExprTokens([new Token(T.IDENT, t.value, t.line, t.col)]),
260
+ );
224
261
  case TT.TEMPL_IF: {
225
262
  this.advance();
226
263
  return this.parseTemplIf(t);
@@ -243,7 +280,9 @@ export class TemplParser extends Parser {
243
280
  // `@templ.Raw(arg)` parses `arg`; any other component parses the whole call.
244
281
  templComponent(tokens) {
245
282
  const raw = isTemplRaw(tokens);
246
- const parsed = parseExprTokens(raw ? tokens.slice(4, -1) : tokens);
283
+ const parsed = parseExprTokens(
284
+ raw ? trimSemicolons(tokens).slice(4, -1) : tokens,
285
+ );
247
286
  return attach({ kind: "TemplComponent", tokens }, { raw, ...parsed });
248
287
  }
249
288
 
@@ -331,7 +370,7 @@ export class TemplParser extends Parser {
331
370
  cases.push(
332
371
  attach(
333
372
  { kind: "TemplCase", caseTokens, body },
334
- isDefault ? { expr: null } : parseExprTokens(caseTokens),
373
+ isDefault ? { expr: null } : parseExprListTokens(caseTokens),
335
374
  ),
336
375
  );
337
376
  } else {
@@ -269,6 +269,7 @@ export const typeParserMethods = {
269
269
  names: [],
270
270
  type: { kind: "TypeName", name: typeName },
271
271
  embedded: true,
272
+ embeddedName: typeName,
272
273
  tag,
273
274
  });
274
275
  this.semi();
@@ -328,6 +329,17 @@ export const typeParserMethods = {
328
329
  this.semi();
329
330
  continue;
330
331
  }
332
+ // Type-set element that is not a bare name: *T, []int, map[K]V, ...
333
+ if (!this.check(T.IDENT)) {
334
+ const terms = [{ approx: false, type: this.parseType() }];
335
+ while (this.match(T.PIPE)) {
336
+ const approx = this.match(T.TILDE);
337
+ terms.push({ approx, type: this.parseType() });
338
+ }
339
+ unionConstraint = { kind: "UnionConstraint", terms };
340
+ this.semi();
341
+ continue;
342
+ }
331
343
  const name = this.expect(T.IDENT).value;
332
344
  if (this.check(T.LPAREN)) {
333
345
  // Method signature: Name(params) returnType
package/src/resolver.js CHANGED
@@ -39,8 +39,7 @@ export function isBuiltinPackage(importPath) {
39
39
  }
40
40
 
41
41
  // Go-source stdlib: `stdlib/<import path>/*.go`, written in GoFront Go
42
- // and compiled into the caller's target. A directory for a path that is also
43
- // in STDLIB_PACKAGES is an overlay: its declarations extend that namespace.
42
+ // and compiled into the caller's target.
44
43
  const STDLIB_SOURCE_ROOT = fileURLToPath(
45
44
  new URL("../stdlib/", import.meta.url),
46
45
  );
@@ -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,7 +10,9 @@ import {
9
10
  CMP_OPS,
10
11
  COMPLEX128,
11
12
  FLOAT64,
13
+ is64BitInt,
12
14
  isAny,
15
+ isBool,
13
16
  isComplex,
14
17
  isComplex64,
15
18
  isComplexOrNumeric,
@@ -25,9 +28,11 @@ import {
25
28
  SIZED_INT_NAMES,
26
29
  STRING,
27
30
  typeStr,
31
+ UNTYPED_BOOL,
28
32
  UNTYPED_COMPLEX,
29
33
  UNTYPED_FLOAT,
30
34
  UNTYPED_INT,
35
+ UNTYPED_RUNE,
31
36
  } from "./types.js";
32
37
 
33
38
  // Unnamed type literal kinds for Go's "identical underlying types" assignability rule.
@@ -61,6 +66,23 @@ const UNTYPED_COMPAT = {
61
66
  complex128: new Set(["complex128", "complex64"]),
62
67
  };
63
68
 
69
+ // `&&` and `||` of two untyped bools stay untyped; a typed operand (including
70
+ // a defined bool type) gives its type, as in Go.
71
+ // Two typed operands must have the same type, and the result has it.
72
+ function logicalResultType(lt, rt) {
73
+ if (lt?.kind === "untyped" && rt?.kind === "untyped") return UNTYPED_BOOL;
74
+ if (rt?.kind === "untyped" && isBool(lt)) return lt;
75
+ if (lt?.kind === "untyped" && isBool(rt)) return rt;
76
+ if (isBool(lt) && isBool(rt) && typeStr(lt) === typeStr(rt)) return lt;
77
+ return BOOL;
78
+ }
79
+
80
+ // Two untyped numeric operands combine to the later of int, rune, float.
81
+ function untypedIntegerKind(isFloat, lt, rt) {
82
+ if (isFloat) return UNTYPED_FLOAT;
83
+ return lt.rune || rt.rune ? UNTYPED_RUNE : UNTYPED_INT;
84
+ }
85
+
64
86
  /** @type {ThisType<TypeChecker>} */
65
87
  export const assignabilityMethods = {
66
88
  // ── Binary result types ───────────────────────────────────────────
@@ -69,12 +91,26 @@ export const assignabilityMethods = {
69
91
  if (lt?.kind === "typeParam" || rt?.kind === "typeParam")
70
92
  return this._binaryResultTypeTypeParam(op, lt, rt);
71
93
  if (CMP_OPS.has(op)) return this._binaryResultTypeCmp(op, lt, rt, node);
72
- if (LOG_OPS.has(op)) return BOOL;
94
+ if (LOG_OPS.has(op)) {
95
+ this._checkLogicalOperands(lt, rt, node);
96
+ return logicalResultType(lt, rt);
97
+ }
73
98
  return this._binaryResultTypeNonComplex(op, lt, rt, node);
74
99
  },
75
100
 
101
+ // `B && bool` mixes two distinct bool types, which Go rejects.
102
+ _checkLogicalOperands(lt, rt, node) {
103
+ if (!node || lt?.kind === "untyped" || rt?.kind === "untyped") return;
104
+ if (!isBool(lt) || !isBool(rt) || typeStr(lt) === typeStr(rt)) return;
105
+ this.err(
106
+ `invalid operation: ${exprText(node)} (mismatched types ${typeStr(lt)} and ${typeStr(rt)})`,
107
+ node,
108
+ );
109
+ },
110
+
76
111
  _binaryResultTypeTypeParam(op, lt, rt) {
77
- if (CMP_OPS.has(op) || LOG_OPS.has(op)) return BOOL;
112
+ if (CMP_OPS.has(op)) return UNTYPED_BOOL;
113
+ if (LOG_OPS.has(op)) return BOOL;
78
114
  return lt?.kind === "typeParam" ? lt : rt;
79
115
  },
80
116
 
@@ -133,8 +169,10 @@ export const assignabilityMethods = {
133
169
 
134
170
  _binaryResultTypeCmp(op, lt, rt, node) {
135
171
  if (this._checkComplexCmpOp(op, lt, rt, node)) return ANY;
172
+ this._checkIntegerCmpKinds(op, lt, rt, node);
136
173
  this._checkEqualityComparable(op, lt, rt, node);
137
- return BOOL;
174
+ // A comparison is an untyped bool: it also fits a defined bool type.
175
+ return UNTYPED_BOOL;
138
176
  },
139
177
 
140
178
  _binaryResultTypeComplex(op, lt, rt, node) {
@@ -160,7 +198,7 @@ export const assignabilityMethods = {
160
198
  const rFloat = rName === "float32" || rName === "float64";
161
199
  const isFloat = lFloat || rFloat;
162
200
  if (lt.kind === "untyped" && rt.kind === "untyped")
163
- return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
201
+ return untypedIntegerKind(isFloat, lt, rt);
164
202
  if (lt.kind === "untyped")
165
203
  return isFloat && rt.name !== "float64" && rt.name !== "float32"
166
204
  ? FLOAT64
@@ -173,6 +211,17 @@ export const assignabilityMethods = {
173
211
  return isFloat ? FLOAT64 : lt;
174
212
  },
175
213
 
214
+ // Comparing two typed integers of different types (`byte != int`) is a
215
+ // mismatch in Go; untyped constants adapt to either side.
216
+ _checkIntegerCmpKinds(op, lt, rt, node) {
217
+ if (!node || lt.kind === "untyped" || rt.kind === "untyped") return;
218
+ if (!isInteger(lt) || !isInteger(rt) || typeStr(lt) === typeStr(rt)) return;
219
+ this.err(
220
+ `invalid operation: ${exprText(node)} (mismatched types ${typeStr(lt)} and ${typeStr(rt)})`,
221
+ node,
222
+ );
223
+ },
224
+
176
225
  _checkComplexCmpOp(op, lt, rt, node) {
177
226
  if (isComplex(lt) || isComplex(rt)) {
178
227
  if (op !== "==" && op !== "!=") {
@@ -204,13 +253,93 @@ export const assignabilityMethods = {
204
253
 
205
254
  // ── Assignability ─────────────────────────────────────────────────
206
255
 
207
- assertAssignable(target, source, node, ctx = "assignment") {
256
+ _tag64BitNode(target, node) {
257
+ if (node && typeof node === "object" && is64BitInt(target)) {
258
+ node._is64Bit = true;
259
+ if (node.kind === "KeyValueExpr" && node.value)
260
+ node.value._is64Bit = true;
261
+ if (node.kind === "UnaryExpr" && node.operand)
262
+ node.operand._is64Bit = true;
263
+ if (node.kind === "ParenExpr" && node.expr) node.expr._is64Bit = true;
264
+ }
265
+ },
266
+
267
+ // An untyped numeric constant that takes type float32 is rounded to
268
+ // float32 in JS output (`var c float32 = 0.01`, `v == 0.01`).
269
+ _tagFloat32Const(target, source, node) {
270
+ if (!node || typeof node !== "object" || !node._const) return;
271
+ if (!isUntyped(source) || this._float32Base(target) !== "float32") return;
272
+ node._f32Const = true;
273
+ },
274
+
275
+ _float32Base(t) {
276
+ const r = t?.kind === "named" ? t.underlying : t;
277
+ return r?.kind === "basic" || r?.kind === "untyped" ? r.name : null;
278
+ },
279
+
280
+ // `var m uint64 = (1 << k) - 1`: a non-constant shift whose left operand is
281
+ // an untyped constant takes the type its context gives it, so codegen
282
+ // computes it in that type (BigInt for 64-bit integers).
283
+ _retypeUntypedShift(target, source, valueNode) {
284
+ if (!isUntyped(source) || !isInteger(target)) return;
285
+ this._retypeShiftTree(valueNode, target);
286
+ },
287
+
288
+ _retypeShiftTree(e, target) {
289
+ if (!e || typeof e !== "object" || e._type?.kind !== "untyped") return;
290
+ if (e.kind === "ParenExpr") this._retypeShiftTree(e.expr, target);
291
+ else if (e.kind === "UnaryExpr") this._retypeShiftTree(e.operand, target);
292
+ else if (e.kind === "BinaryExpr" && !this._constValue(e)) {
293
+ const isShift = e.op === "<<" || e.op === ">>";
294
+ this._retypeShiftTree(e.left, target);
295
+ if (isShift && e.left._type?.kind === "untyped") {
296
+ e.left._type = target;
297
+ if (is64BitInt(target)) e.left._is64Bit = true;
298
+ } else if (!isShift) this._retypeShiftTree(e.right, target);
299
+ } else return;
300
+ if (this._constValue(e)) return;
301
+ e._type = target;
302
+ if (is64BitInt(target)) e._is64Bit = true;
303
+ },
304
+
305
+ // An untyped real constant flowing into a complex type becomes a complex
306
+ // value in codegen.
307
+ _tagComplexConst(target, source, valueNode) {
308
+ if (
309
+ valueNode &&
310
+ typeof valueNode === "object" &&
311
+ isComplex(target) &&
312
+ isUntyped(source) &&
313
+ !isComplex(source)
314
+ )
315
+ valueNode._asComplex = true;
316
+ this._tagComplex64Const(target, source, valueNode);
317
+ },
318
+
319
+ // An untyped constant that takes type complex64 is rounded to float32 in
320
+ // JS output: a real constant through `_f32Const`, a complex constant
321
+ // (`1 + 0.01i`) through `_c64Const`.
322
+ _tagComplex64Const(target, source, node) {
323
+ if (!node || typeof node !== "object") return;
324
+ if (!isComplex64(target) || !isUntyped(source)) return;
325
+ if (isComplex(source)) node._c64Const = true;
326
+ else if (node._const) node._f32Const = true;
327
+ },
328
+
329
+ // `operand` is the expression whose value flows in, when `node` (the
330
+ // position and codegen tag) is not that expression.
331
+ assertAssignable(target, source, node, ctx = "assignment", operand = node) {
208
332
  if (!target || !source) return;
209
333
  if (this._checkConstOverflow(target, source, node, ctx)) return;
210
334
  target = this.resolveType(target);
211
335
  source = this.resolveType(source);
336
+ this._tag64BitNode(target, node);
337
+ this._tagFloat32Const(target, source, operand ?? node);
338
+ this._retypeUntypedShift(target, source, operand ?? node);
339
+ this._tagComplexConst(target, source, operand ?? node);
212
340
  this._markIfaceBox(target, source, node);
213
341
  this._checkWasmIfaceProvider(target, source, node);
342
+ if (this._checkIfaceToConcrete(target, source, node, ctx, operand)) return;
214
343
  if (this._assertAssignableEarlyReturn(target, source)) return;
215
344
  if (isPointer(target) && isPointer(source)) {
216
345
  this.assertAssignable(
@@ -221,14 +350,7 @@ export const assignabilityMethods = {
221
350
  return;
222
351
  }
223
352
  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
- }
353
+ if (this._checkNumericCoercion(target, source, node, ctx, operand)) return;
232
354
  if (this._checkArrayAssignable(target, source, node)) return;
233
355
  if (this._checkTypedArrayAssignable(target, source, node)) return;
234
356
  if (this._checkFuncAssignable(target, source, node)) return;
@@ -300,7 +422,7 @@ export const assignabilityMethods = {
300
422
  return { type: t, ptr: isPtr };
301
423
  if (!methods || methods.size === 0) return null;
302
424
  // Only user-declared types (methods carry _ptrRecv); stdlib named types
303
- // such as time.Duration have no box class.
425
+ // such as strings.Builder have no box class.
304
426
  if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
305
427
  return null;
306
428
  return { type: t, ptr: isPtr };
@@ -379,6 +501,55 @@ export const assignabilityMethods = {
379
501
  );
380
502
  },
381
503
 
504
+ // An interface-typed operand needs an explicit type assertion before it can
505
+ // flow into a concrete (non-interface) type, or into an interface with
506
+ // methods (`var e error = b` with `b any`).
507
+ _checkIfaceToConcrete(target, source, node, ctx, operand) {
508
+ if (!source?._declared || !operand || typeof operand !== "object")
509
+ return false;
510
+ if (!this._isConcreteTarget(target) && !this._isMethodIfaceTarget(target))
511
+ return false;
512
+ this.err(
513
+ `cannot use ${exprText(operand)} (${operandKind(operand, typeStr(source))}) as ${typeStr(target)} value in ${ctx}: need type assertion`,
514
+ node,
515
+ );
516
+ return true;
517
+ },
518
+
519
+ // `int(x)`, `(*T)(x)` and `I(x)` (I with methods) with a source-written
520
+ // `any` operand need an assertion.
521
+ _checkIfaceConversion(srcType, targetType, expr, operand) {
522
+ if (!srcType?._declared || !operand) return false;
523
+ if (
524
+ !this._isConcreteTarget(targetType) &&
525
+ !this._isMethodIfaceTarget(targetType)
526
+ )
527
+ return false;
528
+ this.err(
529
+ `cannot convert ${exprText(operand)} (${operandKind(operand, typeStr(srcType))}) to type ${typeStr(targetType)}: need type assertion`,
530
+ expr,
531
+ );
532
+ return true;
533
+ },
534
+
535
+ // An interface type (named or literal) with at least one method.
536
+ _isMethodIfaceTarget(target) {
537
+ if (!target || isAny(target)) return false;
538
+ const base = this.resolveType(
539
+ target.kind === "named" ? target.underlying : target,
540
+ );
541
+ return base?.kind === "interface" && base.methods?.size > 0;
542
+ },
543
+
544
+ _isConcreteTarget(target) {
545
+ if (!target || target.kind === "typeParam" || isAny(target)) return false;
546
+ if (isNil(target) || isUntyped(target)) return false;
547
+ const base = this.resolveType(
548
+ target.kind === "named" ? target.underlying : target,
549
+ );
550
+ return !!base && base.kind !== "interface" && base.kind !== "typeParam";
551
+ },
552
+
382
553
  _assertAssignableEarlyReturn(target, source) {
383
554
  return (
384
555
  target?.kind === "typeParam" ||
@@ -500,12 +671,36 @@ export const assignabilityMethods = {
500
671
  _isNumericCoercible(target, source) {
501
672
  if (target.kind !== "basic" || source.kind !== "basic") return false;
502
673
  const pair = `${target.name}:${source.name}`;
674
+ return pair === "float64:int";
675
+ },
676
+
677
+ // The lenient numeric pairs: accepted, except that a generic call result
678
+ // is a mismatch and a float64 never reaches an int implicitly.
679
+ // Returns true when the pair was handled.
680
+ _checkNumericCoercion(target, source, node, ctx, operand) {
681
+ const floatToInt = this._isFloatToInt(target, source);
682
+ if (!floatToInt && !this._isNumericCoercible(target, source)) return false;
683
+ if (this._isGenericCallResult(node))
684
+ this.err(
685
+ `mismatched types ${typeStr(source)} and ${typeStr(target)} in ${ctx}`,
686
+ node,
687
+ );
688
+ else if (floatToInt)
689
+ this.err(
690
+ `cannot use ${exprText(operand)} (${operandKind(operand, typeStr(source))}) as ${typeStr(target)} value in ${ctx}`,
691
+ node,
692
+ );
693
+ return true;
694
+ },
695
+
696
+ // Go has no implicit float64 -> int conversion; only an untyped constant
697
+ // (handled earlier) or an explicit `int(x)` reaches an int.
698
+ _isFloatToInt(target, source) {
503
699
  return (
504
- pair === "float64:int" ||
505
- pair === "int:float64" ||
506
- // rune was historically int in GoFront; keep rune/int interchangeable.
507
- pair === "int:int32" ||
508
- pair === "int32:int"
700
+ target.kind === "basic" &&
701
+ source.kind === "basic" &&
702
+ target.name === "int" &&
703
+ source.name === "float64"
509
704
  );
510
705
  },
511
706
 
@@ -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) {