gofront 1.7.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 (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -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 +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  // Parser type-expression methods — installed as a mixin on Parser.prototype.
2
2
 
3
- import { T } from "../tokens.js";
3
+ import { T } from "../lexer/tokens.js";
4
4
 
5
5
  // Token types that directly map to parseTypeName().
6
6
  const TYPE_NAME_TOKENS = new Set([
@@ -76,8 +76,10 @@ export const typeParserMethods = {
76
76
  i++;
77
77
  continue;
78
78
  }
79
- // Reject literals — it's an index/array expression
80
- if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
79
+ // Reject literals — it's an index/array expression. A literal nested
80
+ // deeper is an array length inside a type argument: Stack[[2]int].
81
+ if (depth === 1 && (tt === T.INT || tt === T.FLOAT || tt === T.STRING))
82
+ return false;
81
83
  i++;
82
84
  }
83
85
  return depth === 0;
@@ -86,11 +88,17 @@ export const typeParserMethods = {
86
88
  parseTypeParamList() {
87
89
  this.expect(T.LBRACKET);
88
90
  const params = [];
89
- do {
90
- const name = this.expect(T.IDENT).value;
91
+ while (!this.check(T.RBRACKET) && !this.check(T.EOF)) {
92
+ const names = [this.expect(T.IDENT).value];
93
+ while (this.match(T.COMMA)) {
94
+ names.push(this.expect(T.IDENT).value);
95
+ }
91
96
  const constraint = this.parseConstraint();
92
- params.push({ kind: "TypeParam", name, constraint });
93
- } while (this.match(T.COMMA));
97
+ for (const name of names) {
98
+ params.push({ kind: "TypeParam", name, constraint });
99
+ }
100
+ if (!this.match(T.COMMA)) break;
101
+ }
94
102
  this.expect(T.RBRACKET);
95
103
  return params;
96
104
  },
@@ -185,7 +193,7 @@ export const typeParserMethods = {
185
193
  }
186
194
  const type = {
187
195
  kind: "PointerType",
188
- base: { kind: "TypeName", name: typeName },
196
+ base: this._embeddedTypeName(typeName, true),
189
197
  };
190
198
  let tag = null;
191
199
  if (this.check(T.STRING)) {
@@ -195,6 +203,7 @@ export const typeParserMethods = {
195
203
  names: [],
196
204
  type,
197
205
  embedded: true,
206
+ embeddedName: type.base.name,
198
207
  tag,
199
208
  });
200
209
  this.semi();
@@ -206,14 +215,38 @@ export const typeParserMethods = {
206
215
  if (names.length === 1 && this.check(T.DOT)) {
207
216
  this.advance();
208
217
  const typeName = `${names[0]}.${this.expect(T.IDENT).value}`;
218
+ const embeddedType = this._embeddedTypeName(typeName, true);
209
219
  let tag = null;
210
220
  if (this.check(T.STRING)) {
211
221
  tag = this.advance().value;
212
222
  }
213
223
  fields.push({
214
224
  names: [],
215
- type: { kind: "TypeName", name: typeName },
225
+ type: embeddedType,
216
226
  embedded: true,
227
+ embeddedName: typeName,
228
+ tag,
229
+ });
230
+ this.semi();
231
+ continue;
232
+ }
233
+
234
+ // Embedded generic instance: Inner[T] (not an array field `a [N]T`)
235
+ if (
236
+ names.length === 1 &&
237
+ this.check(T.LBRACKET) &&
238
+ this._bracketEndsEmbeddedField()
239
+ ) {
240
+ const embeddedType = this._embeddedTypeName(names[0], true);
241
+ let tag = null;
242
+ if (this.check(T.STRING)) {
243
+ tag = this.advance().value;
244
+ }
245
+ fields.push({
246
+ names: [],
247
+ type: embeddedType,
248
+ embedded: true,
249
+ embeddedName: names[0],
217
250
  tag,
218
251
  });
219
252
  this.semi();
@@ -236,6 +269,7 @@ export const typeParserMethods = {
236
269
  names: [],
237
270
  type: { kind: "TypeName", name: typeName },
238
271
  embedded: true,
272
+ embeddedName: typeName,
239
273
  tag,
240
274
  });
241
275
  this.semi();
@@ -255,6 +289,33 @@ export const typeParserMethods = {
255
289
  return { kind: "StructType", fields };
256
290
  },
257
291
 
292
+ // True when the `[...]` at the cursor is followed by the end of a field
293
+ // (`Inner[T]` embedded), rather than an element type (`a [N]T`).
294
+ _bracketEndsEmbeddedField() {
295
+ let depth = 0;
296
+ for (let i = this.pos; i < this.tokens.length; i++) {
297
+ const tt = this.tokens[i].type;
298
+ if (tt === T.LBRACKET) depth++;
299
+ else if (tt === T.RBRACKET && --depth === 0) {
300
+ const next = this.tokens[i + 1]?.type;
301
+ return next === T.SEMICOLON || next === T.RBRACE || next === T.STRING;
302
+ }
303
+ }
304
+ return false;
305
+ },
306
+
307
+ // Embedded field type: `name`, or `name[Args]` when type arguments follow.
308
+ _embeddedTypeName(name, allowArgs) {
309
+ if (allowArgs && this.check(T.LBRACKET)) {
310
+ this.expect(T.LBRACKET);
311
+ const typeArgs = [this.parseType()];
312
+ while (this.match(T.COMMA)) typeArgs.push(this.parseType());
313
+ this.expect(T.RBRACKET);
314
+ return { kind: "GenericTypeName", name, typeArgs };
315
+ }
316
+ return { kind: "TypeName", name };
317
+ },
318
+
258
319
  parseInterfaceType() {
259
320
  this.expect(T.INTERFACE);
260
321
  this.expect(T.LBRACE);
package/src/resolver.js CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { existsSync, readdirSync, readFileSync } from "node:fs";
14
14
  import { dirname, join, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
15
16
 
16
17
  // Walk up from startDir looking for a node_modules directory
17
18
  function findNodeModules(startDir) {
@@ -25,6 +26,7 @@ function findNodeModules(startDir) {
25
26
  }
26
27
  }
27
28
 
29
+ import { EXCLUDED_STDLIB_PACKAGES } from "./typechecker/stdlib/excluded.js";
28
30
  import { STDLIB_PACKAGES } from "./typechecker/types.js";
29
31
 
30
32
  // Returns true for local relative paths (start with ./ or ../)
@@ -36,6 +38,22 @@ export function isBuiltinPackage(importPath) {
36
38
  return STDLIB_PACKAGES.has(importPath);
37
39
  }
38
40
 
41
+ // Go-source stdlib: `stdlib/<import path>/*.go`, written in GoFront Go
42
+ // and compiled into the caller's target.
43
+ const STDLIB_SOURCE_ROOT = fileURLToPath(
44
+ new URL("../stdlib/", import.meta.url),
45
+ );
46
+ const STDLIB_PATH_RE = /^[a-z0-9]+(\/[a-z0-9]+)*$/;
47
+
48
+ // Returns the source directory for a Go-source stdlib import path, or null.
49
+ // `root` replaces stdlib/ (tests use a temporary tree).
50
+ export function resolveStdlibSource(importPath, root = null) {
51
+ if (!STDLIB_PATH_RE.test(importPath)) return null;
52
+ const dir = join(root ?? STDLIB_SOURCE_ROOT, importPath);
53
+ if (!existsSync(dir)) return null;
54
+ return readdirSync(dir).some((f) => f.endsWith(".go")) ? dir : null;
55
+ }
56
+
39
57
  // Resolve a local relative import path to an absolute directory of .go files.
40
58
  // Returns the absolute dir path, or null if it doesn't contain any .go files.
41
59
  export function resolveGwDir(importPath, fromFile) {
@@ -86,14 +104,16 @@ function resolveImport(importPath, fromFile) {
86
104
 
87
105
  // Given an AST's import list, resolve all imports.
88
106
  // Returns Map<importPath, { dtsPath, importPath, types, values }>
89
- export function resolveAll(imports, fromFile, parseDts) {
107
+ export function resolveAll(imports, fromFile, parseDts, stdlibRoot = null) {
90
108
  const resolved = new Map();
91
109
 
92
110
  for (const imp of imports) {
93
111
  for (const { path } of imp.imports) {
94
112
  if (path.startsWith("js:")) continue; // handled separately
95
113
  if (isLocalPath(path)) continue; // local .go packages — handled by compiler
114
+ if (EXCLUDED_STDLIB_PACKAGES.has(path)) continue; // the checker reports it
96
115
  if (isBuiltinPackage(path)) continue; // built-in stdlib namespaces — always available
116
+ if (resolveStdlibSource(path, stdlibRoot)) continue; // Go-source stdlib — handled by compiler
97
117
 
98
118
  if (resolved.has(path)) continue;
99
119
 
@@ -1,6 +1,8 @@
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";
5
+ import { exprText, operandKind } from "./operand-text.js";
4
6
  import { sharedInfo } from "./stdlib/shared.js";
5
7
  import {
6
8
  ANY,
@@ -8,9 +10,12 @@ import {
8
10
  CMP_OPS,
9
11
  COMPLEX128,
10
12
  FLOAT64,
13
+ is64BitInt,
11
14
  isAny,
12
15
  isComplex,
16
+ isComplex64,
13
17
  isComplexOrNumeric,
18
+ isInteger,
14
19
  isNil,
15
20
  isNumeric,
16
21
  isPointer,
@@ -79,6 +84,8 @@ export const assignabilityMethods = {
79
84
  if (isAny(lt) || isAny(rt)) return ANY;
80
85
  if (isComplex(lt) || isComplex(rt))
81
86
  return this._binaryResultTypeComplex(op, lt, rt, node);
87
+ if ((op === "<<" || op === ">>") && isNumeric(lt) && isNumeric(rt))
88
+ return this._binaryResultTypeShift(op, lt, rt, node);
82
89
  if (isNumeric(lt) && isNumeric(rt))
83
90
  return this._binaryResultTypeNumeric(lt, rt);
84
91
  if (isString(lt) && isString(rt) && op === "+")
@@ -88,6 +95,37 @@ export const assignabilityMethods = {
88
95
  return ANY;
89
96
  },
90
97
 
98
+ // A shift has the type of its left operand; the count may be any integer
99
+ // type or an untyped constant representable as uint.
100
+ _binaryResultTypeShift(_op, lt, rt, node) {
101
+ if (node) this.checkShiftCount(rt, node.right);
102
+ if (node && lt.kind !== "untyped" && !isInteger(lt))
103
+ this.err(`shifted operand ${typeStr(lt)} must be integer`, node.left);
104
+ return lt;
105
+ },
106
+
107
+ // Reports a shift count that is not an integer or is a negative constant.
108
+ checkShiftCount(rt, countExpr) {
109
+ const v = this._constValue(countExpr);
110
+ if (v) {
111
+ const n = constIntValue(v);
112
+ if (n === null) {
113
+ this.err(
114
+ `invalid shift count ${typeStr(rt)} constant: not an integer`,
115
+ countExpr,
116
+ );
117
+ } else if (n < 0n) {
118
+ this.err(`invalid shift count: ${n} is negative`, countExpr);
119
+ }
120
+ return;
121
+ }
122
+ if (!isInteger(rt))
123
+ this.err(
124
+ `invalid shift count: ${typeStr(rt)} is not an integer type`,
125
+ countExpr,
126
+ );
127
+ },
128
+
91
129
  _binaryResultTypeString(lt, rt) {
92
130
  if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
93
131
  if (lt.kind === "untyped") return rt;
@@ -97,6 +135,7 @@ export const assignabilityMethods = {
97
135
 
98
136
  _binaryResultTypeCmp(op, lt, rt, node) {
99
137
  if (this._checkComplexCmpOp(op, lt, rt, node)) return ANY;
138
+ this._checkIntegerCmpKinds(op, lt, rt, node);
100
139
  this._checkEqualityComparable(op, lt, rt, node);
101
140
  return BOOL;
102
141
  },
@@ -111,6 +150,9 @@ export const assignabilityMethods = {
111
150
  return ANY;
112
151
  }
113
152
  if (lt.kind === "untyped" && rt.kind === "untyped") return UNTYPED_COMPLEX;
153
+ if (lt.kind === "untyped") return rt;
154
+ if (rt.kind === "untyped") return lt;
155
+ if (isComplex64(lt) && isComplex64(rt)) return lt;
114
156
  return COMPLEX128;
115
157
  },
116
158
 
@@ -134,6 +176,17 @@ export const assignabilityMethods = {
134
176
  return isFloat ? FLOAT64 : lt;
135
177
  },
136
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
+
137
190
  _checkComplexCmpOp(op, lt, rt, node) {
138
191
  if (isComplex(lt) || isComplex(rt)) {
139
192
  if (op !== "==" && op !== "!=") {
@@ -165,12 +218,68 @@ export const assignabilityMethods = {
165
218
 
166
219
  // ── Assignability ─────────────────────────────────────────────────
167
220
 
168
- assertAssignable(target, source, node) {
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) {
169
273
  if (!target || !source) return;
274
+ if (this._checkConstOverflow(target, source, node, ctx)) return;
170
275
  target = this.resolveType(target);
171
276
  source = this.resolveType(source);
277
+ this._tag64BitNode(target, node);
278
+ this._retypeUntypedShift(target, source, operand ?? node);
279
+ this._tagComplexConst(target, source, operand ?? node);
172
280
  this._markIfaceBox(target, source, node);
173
281
  this._checkWasmIfaceProvider(target, source, node);
282
+ if (this._checkIfaceToConcrete(target, source, node, ctx, operand)) return;
174
283
  if (this._assertAssignableEarlyReturn(target, source)) return;
175
284
  if (isPointer(target) && isPointer(source)) {
176
285
  this.assertAssignable(
@@ -181,7 +290,7 @@ export const assignabilityMethods = {
181
290
  return;
182
291
  }
183
292
  if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
184
- if (this._isNumericCoercible(target, source)) return;
293
+ if (this._checkNumericCoercion(target, source, node, ctx, operand)) return;
185
294
  if (this._checkArrayAssignable(target, source, node)) return;
186
295
  if (this._checkTypedArrayAssignable(target, source, node)) return;
187
296
  if (this._checkFuncAssignable(target, source, node)) return;
@@ -201,23 +310,32 @@ export const assignabilityMethods = {
201
310
  )
202
311
  return;
203
312
  if (target.kind === "typeParam") return;
204
- const tBase = this.resolveType(
205
- target.kind === "named" ? target.underlying : target,
206
- );
313
+ let tBase = this.resolveType(target);
314
+ if (tBase?.kind === "named") tBase = this.resolveType(tBase.underlying);
207
315
  if (!isAny(target) && tBase?.kind !== "interface") return;
316
+ // The static type behind the interface value: codegen derives the
317
+ // fmt metadata (Stringer elements of slices and maps) from it.
318
+ node._ifaceSrc = source;
319
+ const meta = this._ifaceBoxMeta(source);
320
+ if (!meta) return;
321
+ node._ifaceBox = meta.box;
322
+ if (meta.type) node._ifaceNamedType = meta.type;
323
+ if (meta.ptrFields) node._ifacePtrFields = meta.ptrFields;
324
+ },
325
+
326
+ // How a value of static type `source` is boxed on entering an interface:
327
+ // { box, type? (named kinds), ptrFields? }, or null when it stays raw.
328
+ // Shared by _markIfaceBox and the JS backend's multi-value fmt operands.
329
+ _ifaceBoxMeta(source) {
208
330
  const named = this._namedMethodBoxSource(source);
209
- if (named) {
210
- node._ifaceBox = named.ptr ? "named_ptr" : "named";
211
- node._ifaceNamedType = named.type;
212
- return;
213
- }
331
+ if (named)
332
+ return { box: named.ptr ? "named_ptr" : "named", type: named.type };
214
333
  const sBase = this.resolveType(
215
334
  source.kind === "named" ? source.underlying : source,
216
335
  );
217
336
  const info = this._classifyIfaceBox(sBase);
218
- if (!info) return;
219
- node._ifaceBox = info.box;
220
- if (info.ptrFields) node._ifacePtrFields = info.ptrFields;
337
+ if (!info) return null;
338
+ return { box: info.box, ptrFields: info.ptrFields };
221
339
  },
222
340
 
223
341
  // A named non-struct type with methods (or a pointer to one) entering an
@@ -225,14 +343,26 @@ export const assignabilityMethods = {
225
343
  // dynamic method dispatch works.
226
344
  _namedMethodBoxSource(source) {
227
345
  const isPtr = source?.kind === "pointer";
228
- const t = isPtr ? source.base : source;
346
+ let t = isPtr ? source.base : source;
347
+ if (t?.kind === "TypeName" || t?.kind === "Ident") t = this.resolveType(t);
229
348
  if (t?.kind !== "named") return null;
230
349
  const u = this.resolveType(t.underlying);
231
350
  if (!u || u.kind === "struct" || u.kind === "interface") return null;
232
351
  const methods = t.methods ?? t._generic?.methods;
352
+ // A user-declared named basic type without methods is boxed as well, so
353
+ // a type assertion to its underlying type fails like Go.
354
+ if (
355
+ u.kind === "basic" &&
356
+ !t._generic &&
357
+ t.methods instanceof Map &&
358
+ t.methods.size === 0 &&
359
+ t._target !== undefined &&
360
+ t._target !== "wasm"
361
+ )
362
+ return { type: t, ptr: isPtr };
233
363
  if (!methods || methods.size === 0) return null;
234
364
  // Only user-declared types (methods carry _ptrRecv); stdlib named types
235
- // such as time.Duration have no box class.
365
+ // such as strings.Builder have no box class.
236
366
  if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
237
367
  return null;
238
368
  return { type: t, ptr: isPtr };
@@ -311,6 +441,28 @@ export const assignabilityMethods = {
311
441
  );
312
442
  },
313
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
+
314
466
  _assertAssignableEarlyReturn(target, source) {
315
467
  return (
316
468
  target?.kind === "typeParam" ||
@@ -351,7 +503,45 @@ export const assignabilityMethods = {
351
503
  return;
352
504
  }
353
505
  if (this._identicalUnderlyingLiteral(target, source, tBase, sBase)) return;
354
- this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
506
+ this.err(
507
+ `Cannot assign ${typeStr(source)} to ${typeStr(target)}`,
508
+ node,
509
+ this._seqToSliceHint(source, tBase, node),
510
+ );
511
+ },
512
+
513
+ // maps.Keys/Values (and every iter.Seq) no longer return a slice: point the
514
+ // slice-expecting caller at slices.Collect.
515
+ _seqToSliceHint(source, tBase, node) {
516
+ if (tBase?.kind !== "slice" || !this._isIterSeq(source)) return null;
517
+ return this._seqCollectHint(node);
518
+ },
519
+
520
+ _isIterSeq(t) {
521
+ return t?.kind === "named" && /^iter\.Seq\d*\[/.test(typeStr(t));
522
+ },
523
+
524
+ _seqCollectHint(node) {
525
+ const f = node?.kind === "CallExpr" ? node.func : null;
526
+ if (
527
+ f?.kind === "SelectorExpr" &&
528
+ f.expr?.kind === "Ident" &&
529
+ f.expr.name === "maps" &&
530
+ (f.field === "Keys" || f.field === "Values")
531
+ )
532
+ return `use slices.Collect(maps.${f.field}(m)) for a slice`;
533
+ return "use slices.Collect(seq) for a slice";
534
+ },
535
+
536
+ // len/cap, spread and slice conversion are lenient on func types, and an
537
+ // iter.Seq value is one: reject it there instead of miscompiling.
538
+ _rejectSeqAsSlice(type, node, use) {
539
+ if (!this._isIterSeq(type)) return;
540
+ this.err(
541
+ `cannot use ${typeStr(type)} in ${use}`,
542
+ node,
543
+ this._seqCollectHint(node),
544
+ );
355
545
  },
356
546
 
357
547
  // Identical underlying types are assignable when one side is an unnamed
@@ -377,18 +567,61 @@ export const assignabilityMethods = {
377
567
  );
378
568
  },
379
569
 
570
+ // A value computed from a generic call (`3 + Sum[float64](1, 2)`) has the
571
+ // exact instantiated type, so the loose int/float64 promotion that plain
572
+ // code enjoys does not apply to it.
573
+ _isGenericCallResult(node) {
574
+ if (!node || typeof node !== "object") return false;
575
+ if (node.kind === "ParenExpr") return this._isGenericCallResult(node.expr);
576
+ if (node.kind === "BinaryExpr")
577
+ return (
578
+ this._isGenericCallResult(node.left) ||
579
+ this._isGenericCallResult(node.right)
580
+ );
581
+ return node.kind === "CallExpr" && Boolean(node.func?._genericFnType);
582
+ },
583
+
380
584
  _isNumericCoercible(target, source) {
381
585
  if (target.kind !== "basic" || source.kind !== "basic") return false;
382
586
  const pair = `${target.name}:${source.name}`;
383
587
  return (
384
588
  pair === "float64:int" ||
385
- pair === "int:float64" ||
386
589
  // rune was historically int in GoFront; keep rune/int interchangeable.
387
590
  pair === "int:int32" ||
388
591
  pair === "int32:int"
389
592
  );
390
593
  },
391
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
+
392
625
  _checkArrayAssignable(target, source, node) {
393
626
  if (target.kind === "array" && source.kind === "array") {
394
627
  if (
@@ -465,10 +698,47 @@ export const assignabilityMethods = {
465
698
 
466
699
  // ── Interface satisfaction ────────────────────────────────────────
467
700
 
701
+ // `interface{}` and `any` are the same type, but print differently.
702
+ _sigTypeStr(t) {
703
+ switch (t?.kind) {
704
+ case "interface":
705
+ if (
706
+ !t.name &&
707
+ t.methods.size === 0 &&
708
+ !t.unionConstraint &&
709
+ !t._typeSetEmbeds
710
+ )
711
+ return "any";
712
+ return typeStr(t);
713
+ case "slice":
714
+ return `[]${this._sigTypeStr(t.elem)}`;
715
+ case "array":
716
+ return `[${t.size ?? "..."}]${this._sigTypeStr(t.elem)}`;
717
+ case "pointer":
718
+ return `*${this._sigTypeStr(t.base)}`;
719
+ case "map":
720
+ return `map[${this._sigTypeStr(t.key)}]${this._sigTypeStr(t.value)}`;
721
+ case "tuple":
722
+ return `(${t.types.map((x) => this._sigTypeStr(x)).join(", ")})`;
723
+ case "func": {
724
+ const ret =
725
+ t.returns.length > 1
726
+ ? `(${t.returns.map((x) => this._sigTypeStr(x)).join(", ")})`
727
+ : t.returns[0]
728
+ ? this._sigTypeStr(t.returns[0])
729
+ : "void";
730
+ return `func(${t.params.map((x) => this._sigTypeStr(x)).join(", ")}) ${ret}`;
731
+ }
732
+ default:
733
+ return typeStr(t);
734
+ }
735
+ },
736
+
468
737
  _sigParamsMatch(reqList, actList) {
469
738
  if (reqList.length !== actList.length) return false;
470
739
  for (let i = 0; i < reqList.length; i++) {
471
- if (typeStr(reqList[i]) !== typeStr(actList[i])) return false;
740
+ if (this._sigTypeStr(reqList[i]) !== this._sigTypeStr(actList[i]))
741
+ return false;
472
742
  }
473
743
  return true;
474
744
  },
@@ -511,7 +781,9 @@ export const assignabilityMethods = {
511
781
  }
512
782
  const methodMap =
513
783
  base?.kind === "struct"
514
- ? base.methods || srcType.methods
784
+ ? base.methods?.size
785
+ ? base.methods
786
+ : srcType.methods || base.methods
515
787
  : srcType.kind === "named"
516
788
  ? srcType.methods
517
789
  : null;