gofront 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -0,0 +1,40 @@
1
+ // Source-like text of an expression for diagnostics ("cannot use y (variable
2
+ // of type float64) as int value"). Only the common shapes are printed in full;
3
+ // anything else falls back to "expression".
4
+
5
+ function litText(node) {
6
+ switch (node.litKind) {
7
+ case "STRING":
8
+ return JSON.stringify(node.value);
9
+ case "NIL":
10
+ return "nil";
11
+ default:
12
+ return String(node.value);
13
+ }
14
+ }
15
+
16
+ const PRINTERS = {
17
+ Ident: (n) => n.name,
18
+ BasicLit: litText,
19
+ ParenExpr: (n) => `(${exprText(n.expr)})`,
20
+ SelectorExpr: (n) => `${exprText(n.expr)}.${n.field}`,
21
+ UnaryExpr: (n) => `${n.op}${exprText(n.operand)}`,
22
+ BinaryExpr: (n) => `${exprText(n.left)} ${n.op} ${exprText(n.right)}`,
23
+ CallExpr: (n) =>
24
+ `${exprText(n.func)}(${(n.args ?? []).map(exprText).join(", ")})`,
25
+ IndexExpr: (n) => `${exprText(n.expr)}[${exprText(n.index)}]`,
26
+ };
27
+
28
+ export function exprText(node) {
29
+ const print = node && PRINTERS[node.kind];
30
+ return print ? print(node) : "expression";
31
+ }
32
+
33
+ // "variable of type float64", "untyped int constant", "value of type float64".
34
+ export function operandKind(node, typeName, isUntypedConst) {
35
+ if (isUntypedConst)
36
+ return `untyped ${typeName === "float64" ? "float" : typeName} constant`;
37
+ return node?.kind === "Ident"
38
+ ? `variable of type ${typeName}`
39
+ : `value of type ${typeName}`;
40
+ }
@@ -8,6 +8,7 @@ import {
8
8
  ANY,
9
9
  BASIC_TYPES,
10
10
  COMPARABLE,
11
+ DECLARED_ANY,
11
12
  defaultType,
12
13
  displayTypeName,
13
14
  isAny,
@@ -71,6 +72,30 @@ function typeSize(t) {
71
72
  }
72
73
  }
73
74
 
75
+ // Whether `t` mentions one of the type parameters `params` below its root.
76
+ function nestsTypeParam(t, params) {
77
+ const mentions = (u) => {
78
+ switch (u?.kind) {
79
+ case "typeParam":
80
+ return params.includes(u);
81
+ case "pointer":
82
+ return mentions(u.base);
83
+ case "slice":
84
+ case "array":
85
+ return mentions(u.elem);
86
+ case "map":
87
+ return mentions(u.key) || mentions(u.value);
88
+ case "func":
89
+ return [...(u.params ?? []), ...(u.returns ?? [])].some(mentions);
90
+ case "named":
91
+ return (u.typeArgs ?? []).some(mentions);
92
+ default:
93
+ return false;
94
+ }
95
+ };
96
+ return t?.kind !== "typeParam" && mentions(t);
97
+ }
98
+
74
99
  function instanceKey(t) {
75
100
  switch (t?.kind) {
76
101
  case "named":
@@ -133,13 +158,34 @@ const SUBSTITUTE_DISPATCH = {
133
158
  fields,
134
159
  methods,
135
160
  _embeds: type._embeds,
136
- _structFields: type._structFields,
161
+ // The JSON codecs read the field types from here.
162
+ _structFields: type._structFields?.map((f) => ({
163
+ ...f,
164
+ type: s.substituteType(f.type, map),
165
+ })),
137
166
  };
138
167
  },
139
168
  interface: (s, type, map) => {
140
169
  const methods = new Map();
141
170
  for (const [k, v] of type.methods) methods.set(k, s.substituteType(v, map));
142
- return { kind: "interface", name: type.name, methods };
171
+ const out = { kind: "interface", name: type.name, methods };
172
+ // The type set follows the substitution (`interface{ ~[]E }` with E=int).
173
+ if (type.unionConstraint)
174
+ out.unionConstraint = {
175
+ ...type.unionConstraint,
176
+ terms: type.unionConstraint.terms.map((term) => ({
177
+ ...term,
178
+ resolved: s.substituteType(
179
+ term.resolved ?? s.resolveTypeNode(term.type, type._unionScope),
180
+ map,
181
+ ),
182
+ })),
183
+ };
184
+ if (type._typeSetEmbeds)
185
+ out._typeSetEmbeds = type._typeSetEmbeds.map((e) =>
186
+ s.substituteType(e, map),
187
+ );
188
+ return out;
143
189
  },
144
190
  named: (s, type, map) => {
145
191
  // An instance of a generic type: re-instantiate with the substituted
@@ -167,7 +213,8 @@ export const resolveMethods = {
167
213
  if (!node) return ANY;
168
214
  switch (node.kind) {
169
215
  case "TypeName": {
170
- if (BASIC_TYPES[node.name]) return BASIC_TYPES[node.name];
216
+ if (BASIC_TYPES[node.name])
217
+ return node.name === "any" ? DECLARED_ANY : BASIC_TYPES[node.name];
171
218
  if (node.name === "comparable") return COMPARABLE;
172
219
  if (scope) {
173
220
  const fromScope = scope.lookup(node.name);
@@ -176,11 +223,11 @@ export const resolveMethods = {
176
223
  }
177
224
  const named = this.types.get(node.name);
178
225
  if (named) {
179
- const wasmErr = this._checkWasmStdlibTypeMember(node);
180
- if (wasmErr) return wasmErr;
181
226
  this._useQualifiedTypeName(node, named);
182
227
  return named;
183
228
  }
229
+ const jsOnly = this._jsOnlyMember(node.name);
230
+ if (jsOnly) return this._errJsOnly(jsOnly, node);
184
231
  if (EXCLUDED_STDLIB_MEMBERS.has(node.name)) {
185
232
  return this.err(
186
233
  `${node.name} is not available: ${EXCLUDED_STDLIB_MEMBERS.get(node.name)}`,
@@ -218,6 +265,13 @@ export const resolveMethods = {
218
265
  return this._resolveStructType(node, scope);
219
266
  case "InterfaceType":
220
267
  return this._resolveInterfaceType(node, scope);
268
+ case "UnaryExpr":
269
+ // `new(*T)`: the parser reads the pointer type as a dereference.
270
+ if (node.op !== "*") return ANY;
271
+ return {
272
+ kind: "pointer",
273
+ base: this.resolveTypeNode(node.operand, scope),
274
+ };
221
275
  case "Ident":
222
276
  return this.resolveTypeNode(
223
277
  { kind: "TypeName", name: node.name },
@@ -252,23 +306,23 @@ export const resolveMethods = {
252
306
  }
253
307
  },
254
308
 
255
- _checkWasmStdlibTypeMember(node) {
309
+ // `pkg.Name` when `pkg` is a Go-source package whose `js` files declare
310
+ // Name and this package is wasm or both (those files are not loaded).
311
+ _jsOnlyMember(qualified) {
256
312
  if (this.target !== "wasm" && this.target !== "both") return null;
257
- const dot = node.name.indexOf(".");
258
- if (dot === -1) return null;
259
- const pkg = node.name.slice(0, dot);
260
- if (pkg !== "time") return null;
261
- const field = node.name.slice(dot + 1);
262
- const allowed = WASM_STDLIB_MEMBERS.get(pkg);
263
- if (allowed && !allowed.has(field)) {
264
- this.recordBlocker("unsupported stdlib member", node.name);
265
- return this.err(
266
- `'${node.name}' is not yet available in wasm packages`,
267
- node,
268
- "move this call to a JS package, or use a supported alternative",
269
- );
270
- }
271
- return null;
313
+ const dot = qualified.indexOf(".");
314
+ if (dot < 0) return null;
315
+ const ns = this.globals.lookup(qualified.slice(0, dot));
316
+ return ns?._jsOnly?.has(qualified.slice(dot + 1)) ? qualified : null;
317
+ },
318
+
319
+ _errJsOnly(qualified, node) {
320
+ this.recordBlocker("unsupported stdlib member", qualified);
321
+ return this.err(
322
+ `'${qualified}' is only available in js packages`,
323
+ node,
324
+ "move this call to a JS package",
325
+ );
272
326
  },
273
327
 
274
328
  // `pkg.T` in a type position counts as using the import; a Go-source
@@ -291,10 +345,25 @@ export const resolveMethods = {
291
345
  try {
292
346
  const lit = foldConstExpr(size, resolve);
293
347
  const n = lit ? Number(lit.value) : Number.NaN;
294
- return Number.isInteger(n) && n >= 0 ? n : size;
348
+ if (Number.isInteger(n) && n >= 0) return n;
295
349
  } catch {
296
- return size;
350
+ // fall through to the checker's evaluator
297
351
  }
352
+ // `[math.MaxInt8]T`: a package constant the AST folder cannot see.
353
+ const q = this._constIntValue(size, scope);
354
+ if (q === null || !Number.isInteger(q) || q < 0) return size;
355
+ this._useQualifiedConsts(size);
356
+ return q;
357
+ },
358
+
359
+ // `pkg.K` inside a folded array length counts as using the import.
360
+ _useQualifiedConsts(node) {
361
+ if (!node || typeof node !== "object") return;
362
+ if (node.kind === "SelectorExpr" && node.expr?.kind === "Ident")
363
+ this.globals.lookup(node.expr.name);
364
+ for (const key of ["expr", "left", "right", "operand"])
365
+ this._useQualifiedConsts(node[key]);
366
+ for (const arg of node.args ?? []) this._useQualifiedConsts(arg);
298
367
  },
299
368
 
300
369
  _resolveArrayTypeNode(node, scope) {
@@ -337,8 +406,11 @@ export const resolveMethods = {
337
406
  ? this.resolveTypeNode(node.expr, scope)
338
407
  : null;
339
408
  if (!base) return this.err(`Unknown type '${name ?? "?"}'`, node);
340
- if (qualified) this.globals.lookup(sel.expr.name);
341
- else if (name)
409
+ if (qualified) {
410
+ this.globals.lookup(sel.expr.name);
411
+ // `iter.Seq[int](f)`: the selector names the mangled stdlib type.
412
+ this._useQualifiedTypeName(sel, base);
413
+ } else if (name)
342
414
  this._useQualifiedTypeName(node.name ? node : node.expr, base);
343
415
  if (base.kind === "named" && base._generic?.isAlias) {
344
416
  const aliasArgs = typeArgs.map((n) => this.resolveTypeNode(n, scope));
@@ -452,7 +524,10 @@ export const resolveMethods = {
452
524
  }
453
525
  }
454
526
  const result = { kind: "interface", methods };
455
- if (node.unionConstraint) result.unionConstraint = node.unionConstraint;
527
+ if (node.unionConstraint) {
528
+ result.unionConstraint = node.unionConstraint;
529
+ result._unionScope = scope;
530
+ }
456
531
  if (typeSetEmbeds.length > 0) result._typeSetEmbeds = typeSetEmbeds;
457
532
  return result;
458
533
  },
@@ -501,8 +576,15 @@ export const resolveMethods = {
501
576
  // settles (`T[X]` containing `T[[]X]`), and doubling forms such as
502
577
  // `T[Pair[X, X]]` blow up long before a depth limit is reached.
503
578
  const top = generic._buildSizes[generic._buildSizes.length - 1];
579
+ // Inside a method of `G[T]`, `G[[]T]` makes every instance ask for a
580
+ // larger one, even if the method is never called.
581
+ const growsReceiver =
582
+ this._recvGeneric === generic &&
583
+ typeArgs.some((a) => nestsTypeParam(a, generic.typeParams));
504
584
  if (
585
+ growsReceiver ||
505
586
  building.length >= MAX_INSTANTIATION_DEPTH ||
587
+ (generic._syncDepth ?? 0) >= MAX_INSTANTIATION_DEPTH ||
506
588
  (top !== undefined && size > top)
507
589
  ) {
508
590
  if (!generic._cycleReported) {
@@ -557,13 +639,20 @@ export const resolveMethods = {
557
639
  const methods = named.methods;
558
640
  if (!methods || !named._instMap || named._syncing) return;
559
641
  // A method signature that names the instance reaches this again.
642
+ // `func (s Stack[T]) Wrap() Stack[[]T]` makes every instance's methods
643
+ // ask for a larger instance: count the nesting to diagnose it.
560
644
  named._syncing = true;
561
- for (const [mName, mType] of generic.methods ?? [])
562
- if (!methods.has(mName))
563
- methods.set(mName, this.substituteType(mType, named._instMap));
564
- // Methods promoted from embedded structs (`Outer[T]` embedding `Base`).
565
- this._promoteEmbeddedInto(named.underlying);
566
- named._syncing = false;
645
+ generic._syncDepth = (generic._syncDepth ?? 0) + 1;
646
+ try {
647
+ for (const [mName, mType] of generic.methods ?? [])
648
+ if (!methods.has(mName))
649
+ methods.set(mName, this.substituteType(mType, named._instMap));
650
+ // Methods promoted from embedded structs (`Outer[T]` embedding `Base`).
651
+ this._promoteEmbeddedInto(named.underlying);
652
+ } finally {
653
+ generic._syncDepth--;
654
+ named._syncing = false;
655
+ }
567
656
  },
568
657
 
569
658
  // A[X] for `type A[P any] = T`: resolve T with each P bound to the
@@ -591,19 +680,53 @@ export const resolveMethods = {
591
680
 
592
681
  // `explicit` seeds the leading type arguments of a partial instantiation
593
682
  // (`F[int](x)`); the rest are inferred from the call arguments.
594
- inferTypeArgs(genericType, argTypes, explicit = []) {
683
+ // `scope` resolves the type parameters of the calling function, so an
684
+ // argument of type `S ~[]E` unifies through its core type.
685
+ inferTypeArgs(genericType, argTypes, explicit = [], scope = null) {
595
686
  const map = new Map();
687
+ const saved = this._inferScope;
688
+ this._inferScope = scope;
689
+ try {
690
+ return this._inferTypeArgs(genericType, argTypes, explicit, map);
691
+ } finally {
692
+ this._inferScope = saved;
693
+ }
694
+ },
695
+
696
+ _inferTypeArgs(genericType, argTypes, explicit, map) {
596
697
  for (let i = 0; i < explicit.length; i++)
597
698
  map.set(genericType.typeParams[i].name, explicit[i]);
598
699
  const params = genericType.underlying.params;
599
700
  for (let i = 0; i < params.length && i < argTypes.length; i++)
600
701
  this._inferFromPair(params[i], argTypes[i], map);
702
+ this._inferFromCoreTypes(genericType.typeParams, map);
601
703
  for (const tp of genericType.typeParams) {
602
704
  if (!map.has(tp.name)) return null;
603
705
  }
604
706
  return genericType.typeParams.map((tp) => defaultType(map.get(tp.name)));
605
707
  },
606
708
 
709
+ // Constraint type inference: a type parameter `S` constrained by `~[]E`
710
+ // whose argument is known unifies `E` with that argument's element type.
711
+ _inferFromCoreTypes(typeParams, map) {
712
+ const scope = new Scope(this.globals);
713
+ for (const tp of typeParams) scope.define(tp.name, tp);
714
+ for (let round = 0; round < typeParams.length; round++) {
715
+ const before = map.size;
716
+ for (const tp of typeParams) {
717
+ const arg = map.get(tp.name);
718
+ if (!arg || !tp.constraint) continue;
719
+ const terms = this._constraintTerms(tp.constraint, new Set(), scope);
720
+ if (terms?.length !== 1) continue;
721
+ const argUnder =
722
+ this._coreOfTypeParam(arg) ??
723
+ this._deepUnderlying(this.resolveType(arg));
724
+ if (argUnder) this._inferFromPair(terms[0], argUnder, map);
725
+ }
726
+ if (map.size === before) break;
727
+ }
728
+ },
729
+
607
730
  _inferFromFuncPair(paramType, argType, map) {
608
731
  for (
609
732
  let i = 0;
@@ -622,6 +745,8 @@ export const resolveMethods = {
622
745
  _inferFromStructuredPair(paramType, argType, map) {
623
746
  if (paramType.kind === "slice" && argType.kind === "slice")
624
747
  this._inferFromPair(paramType.elem, argType.elem, map);
748
+ if (paramType.kind === "pointer" && argType.kind === "pointer")
749
+ this._inferFromPair(paramType.base, argType.base, map);
625
750
  if (paramType.kind === "map" && argType.kind === "map") {
626
751
  this._inferFromPair(paramType.key, argType.key, map);
627
752
  this._inferFromPair(paramType.value, argType.value, map);
@@ -638,8 +763,19 @@ export const resolveMethods = {
638
763
  }
639
764
  },
640
765
 
766
+ // An argument whose type is a type parameter of the calling function
767
+ // (`S ~[]E`) unifies through its core type: that type, or null.
768
+ // `paramType` (optional) is the parameter it is matched against; a bare
769
+ // type parameter takes the argument as it is.
770
+ _coreOfTypeParam(argType, paramType = null) {
771
+ if (argType.kind !== "typeParam" || !this._inferScope) return null;
772
+ if (paramType?.kind === "typeParam") return null;
773
+ return this._coreType(argType, this._inferScope);
774
+ },
775
+
641
776
  _inferFromPair(paramType, argType, map) {
642
777
  if (!paramType || !argType) return;
778
+ argType = this._coreOfTypeParam(argType, paramType) ?? argType;
643
779
  if (paramType.kind === "typeParam") {
644
780
  const prev = map.get(paramType.name);
645
781
  if (!prev || untypedWins(prev, argType)) map.set(paramType.name, argType);
@@ -664,7 +800,8 @@ export const resolveMethods = {
664
800
  _checkUnionConstraint(typeArg, constraint, node) {
665
801
  for (const term of constraint.terms) {
666
802
  if (term.approx) return;
667
- const termType = this.resolveTypeNode(term.type, this.globals);
803
+ const termType =
804
+ term.resolved ?? this.resolveTypeNode(term.type, this.globals);
668
805
  if (typeStr(typeArg) === typeStr(termType)) return;
669
806
  }
670
807
  this.err(`type ${typeStr(typeArg)} does not satisfy constraint`, node);
@@ -758,7 +895,7 @@ export const resolveMethods = {
758
895
  return (
759
896
  baseType.kind === "pointer" &&
760
897
  field !== "value" &&
761
- baseType.base?.kind === "named"
898
+ (baseType.base?.kind === "named" || baseType.base?.kind === "struct")
762
899
  );
763
900
  },
764
901
 
@@ -891,16 +1028,35 @@ export const resolveMethods = {
891
1028
  return ANY;
892
1029
  },
893
1030
 
894
- _fieldTypeNamespace(base, field, node) {
1031
+ // An error for `field` when the namespace cannot offer it to this package:
1032
+ // an unexported name, or a member of a `js` file in a wasm or both package.
1033
+ _namespaceMemberError(base, field, node) {
895
1034
  if (base._gofront && field.length > 0 && field[0] >= "a" && field[0] <= "z")
896
1035
  return this.err(
897
1036
  `cannot refer to unexported name ${base.name}.${field}`,
898
1037
  node,
899
1038
  );
1039
+ const wasm = this.target === "wasm" || this.target === "both";
1040
+ return wasm && base._jsOnly?.has(field)
1041
+ ? this._errJsOnly(`${base.name}.${field}`, node)
1042
+ : null;
1043
+ },
1044
+
1045
+ _fieldTypeNamespace(base, field, node) {
1046
+ const bad = this._namespaceMemberError(base, field, node);
1047
+ if (bad) return bad;
900
1048
  // Go-source stdlib member: backends emit the mangled top-level name.
901
1049
  const mangled = base._mangle?.get(field);
902
1050
  if (mangled) {
903
1051
  node._stdName = mangled;
1052
+ // Lazy stdlib bodies: referencing a member checks what it reaches.
1053
+ const failure = base._reach?.(mangled);
1054
+ if (failure)
1055
+ this.err(
1056
+ `internal stdlib error in ${base._path}; please report`,
1057
+ node,
1058
+ failure,
1059
+ );
904
1060
  return base.members[field];
905
1061
  }
906
1062
  if (field in base.members) {
@@ -15,15 +15,20 @@ const CHECK_STMT_DELEGATE = {
15
15
  BranchStmt: "_checkBranchStmt",
16
16
  };
17
17
 
18
+ import { exprText, operandKind } from "./operand-text.js";
18
19
  import {
19
20
  ANY,
20
21
  BOOL,
21
22
  defaultType,
23
+ is64BitInt,
22
24
  isAny,
23
25
  isBool,
26
+ isNil,
27
+ isUntyped,
24
28
  isVoid,
25
29
  iteratorYieldParams,
26
30
  Scope,
31
+ typeStr,
27
32
  } from "./types.js";
28
33
 
29
34
  /** @typedef {import('./index.js').TypeChecker} TypeChecker */
@@ -67,9 +72,9 @@ export const statementCheckMethods = {
67
72
 
68
73
  _checkDefineCommaOkAssert(stmt, rhs, scope) {
69
74
  if (!stmt.rhs[0]?._commaOk) return false;
70
- const lhsNames = stmt.lhs.map((e) => e.name ?? e);
71
- if (lhsNames[0] !== "_") scope.defineLocal(lhsNames[0], rhs[0]);
72
- if (lhsNames[1] !== "_") scope.defineLocal(lhsNames[1], BOOL);
75
+ this._declareDefineVars(stmt, [rhs[0], BOOL], scope);
76
+ if (stmt.lhs.every((e) => e._redecl || (e.name ?? e) === "_"))
77
+ this.err("no new variables on left side of :=", stmt.lhs[0]);
73
78
  stmt._rhsTypes = [rhs[0], BOOL];
74
79
  return true;
75
80
  },
@@ -97,10 +102,10 @@ export const statementCheckMethods = {
97
102
  )
98
103
  return false;
99
104
  const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
100
- const resolvedBase =
101
- mapBaseType?.kind === "named" ? mapBaseType.underlying : mapBaseType;
105
+ const resolvedBase = this._underlyingOrCore(mapBaseType, scope);
102
106
  if (resolvedBase?.kind !== "map") return false;
103
107
  const keyType = this.checkExpr(stmt.rhs[0].index, scope);
108
+ this.assertAssignable(resolvedBase.key, keyType, stmt.rhs[0].index);
104
109
  this._markIfaceKey(resolvedBase, stmt.rhs[0].index, keyType);
105
110
  stmt._commaOkMap = true;
106
111
  stmt.rhs[0]._mapValueType = resolvedBase.value;
@@ -129,7 +134,8 @@ export const statementCheckMethods = {
129
134
  if (this._checkDefineCommaOkAssert(stmt, rhs, scope)) return;
130
135
  if (
131
136
  stmt.rhs[0]?.kind !== "RangeExpr" &&
132
- stmt.lhs.length !== rhsFlat.length
137
+ stmt.lhs.length !== rhsFlat.length &&
138
+ !rhsFlat.some((t) => t?._tainted)
133
139
  ) {
134
140
  const vars =
135
141
  stmt.lhs.length === 1 ? "1 variable" : `${stmt.lhs.length} variables`;
@@ -156,10 +162,10 @@ export const statementCheckMethods = {
156
162
  )
157
163
  return null;
158
164
  const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
159
- const resolvedBase =
160
- mapBaseType?.kind === "named" ? mapBaseType.underlying : mapBaseType;
165
+ const resolvedBase = this._underlyingOrCore(mapBaseType, scope);
161
166
  if (resolvedBase?.kind !== "map") return null;
162
167
  const keyType = this.checkExpr(stmt.rhs[0].index, scope);
168
+ this.assertAssignable(resolvedBase.key, keyType, stmt.rhs[0].index);
163
169
  this._markIfaceKey(resolvedBase, stmt.rhs[0].index, keyType);
164
170
  stmt._commaOkMap = true;
165
171
  stmt.rhs[0]._mapValueType = resolvedBase.value;
@@ -211,7 +217,13 @@ export const statementCheckMethods = {
211
217
  continue;
212
218
  if (!isAny(lhsTypes[i]) && !isAny(r)) {
213
219
  this._skipIfaceMark = true;
214
- this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
220
+ this.assertAssignable(
221
+ lhsTypes[i],
222
+ r,
223
+ stmt.lhs[i],
224
+ "assignment",
225
+ stmt.rhs.length === lhsTypes.length ? stmt.rhs[i] : undefined,
226
+ );
215
227
  this._skipIfaceMark = false;
216
228
  }
217
229
  }
@@ -231,7 +243,8 @@ export const statementCheckMethods = {
231
243
  if (
232
244
  !stmt.rhs[0]?._commaOk &&
233
245
  stmt.rhs[0]?.kind !== "RangeExpr" &&
234
- stmt.lhs.length !== rhsFlat.length
246
+ stmt.lhs.length !== rhsFlat.length &&
247
+ !rhsFlat.some((t) => t?._tainted)
235
248
  ) {
236
249
  const vars =
237
250
  stmt.lhs.length === 1 ? "1 variable" : `${stmt.lhs.length} variables`;
@@ -321,6 +334,8 @@ export const statementCheckMethods = {
321
334
  this.err("For condition must be bool", stmt);
322
335
  } else {
323
336
  const ct = this.checkExpr(stmt.cond.expr, inner);
337
+ const core = ct?.kind === "typeParam" ? this._coreType(ct, inner) : null;
338
+ if (core) stmt.cond.expr._coreType = core;
324
339
  const info = iteratorYieldParams(ct);
325
340
  if (info) {
326
341
  stmt.cond._isIterator = true;
@@ -359,10 +374,17 @@ export const statementCheckMethods = {
359
374
  const inner = new Scope(scope);
360
375
  if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
361
376
  if (stmt.tag) this.checkExpr(stmt.tag, inner);
377
+ const tagIs64 = stmt.tag && is64BitInt(stmt.tag._type);
362
378
  this._switchDepth++;
363
379
  for (const c of stmt.cases) {
364
380
  const caseScope = new Scope(inner);
365
- if (c.list) for (const e of c.list) this.checkExpr(e, caseScope);
381
+ if (c.list) {
382
+ for (const e of c.list) {
383
+ this.checkExpr(e, caseScope);
384
+ this._checkCaseValue(stmt.tag, e);
385
+ if (tagIs64 && e._type?.kind === "untyped") e._is64Bit = true;
386
+ }
387
+ }
366
388
  for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
367
389
  this._reportUnused(caseScope, stmt);
368
390
  }
@@ -370,6 +392,76 @@ export const statementCheckMethods = {
370
392
  this._reportUnused(inner, stmt);
371
393
  },
372
394
 
395
+ // `case e:` compares `tag == e`, so `e` must convert to the tag's type
396
+ // (or the other way round for typed values).
397
+ _checkCaseValue(tag, e) {
398
+ const caseType = this.resolveType(e._type);
399
+ if (!tag) {
400
+ this._checkTaglessCase(e, caseType);
401
+ return;
402
+ }
403
+ let tagType = this.resolveType(tag._type);
404
+ // `switch "foo" {` compares as the constant's default type.
405
+ if (isUntyped(tagType) && !isNil(tagType) && tagType.base !== "bool")
406
+ tagType = defaultType(tagType);
407
+ if (this._caseValueUnchecked(tagType, caseType)) return;
408
+ if (isUntyped(caseType)) {
409
+ if (this._isUntypedAssignable(tagType, caseType)) return;
410
+ this.err(
411
+ `cannot convert ${exprText(e)} (${operandKind(e, caseType.base, true)}) to type ${typeStr(tagType)}`,
412
+ e,
413
+ );
414
+ return;
415
+ }
416
+ if (
417
+ this._assignableQuietly(tagType, caseType) ||
418
+ this._assignableQuietly(caseType, tagType)
419
+ )
420
+ return;
421
+ this.err(
422
+ `invalid case ${exprText(e)} in switch on ${exprText(tag)} (mismatched types ${typeStr(caseType)} and ${typeStr(tagType)})`,
423
+ e,
424
+ );
425
+ },
426
+
427
+ // A tagless `switch` is `switch true`, so every case must be boolean.
428
+ _checkTaglessCase(e, caseType) {
429
+ if (!caseType || caseType._tainted || caseType.kind === "typeParam") return;
430
+ if (isAny(caseType) || isNil(caseType) || isBool(caseType)) return;
431
+ if (isUntyped(caseType) && caseType.base === "bool") return;
432
+ const under = this.resolveType(caseType.underlying ?? caseType);
433
+ if (isBool(under)) return;
434
+ this.err(
435
+ `invalid case ${exprText(e)} in switch (mismatched types ${typeStr(caseType)} and bool)`,
436
+ e,
437
+ );
438
+ },
439
+
440
+ // Operands whose comparison the checker cannot (or need not) judge: unknown
441
+ // or recovered types, `any`/interface tags, nil cases, type parameters.
442
+ _caseValueUnchecked(tagType, caseType) {
443
+ if (!tagType || !caseType || tagType._tainted || caseType._tainted)
444
+ return true;
445
+ if (isAny(tagType) || isAny(caseType) || isNil(caseType)) return true;
446
+ if (isUntyped(tagType) || tagType.kind === "typeParam") return true;
447
+ return (
448
+ this.resolveType(tagType.underlying ?? tagType)?.kind === "interface"
449
+ );
450
+ },
451
+
452
+ // Whether assertAssignable(target, source) reports nothing.
453
+ _assignableQuietly(target, source) {
454
+ const saved = this.errors;
455
+ const savedSkip = this._skipIfaceMark;
456
+ this.errors = [];
457
+ this._skipIfaceMark = true;
458
+ this.assertAssignable(target, source, null);
459
+ const ok = this.errors.length === 0;
460
+ this.errors = saved;
461
+ this._skipIfaceMark = savedSkip;
462
+ return ok;
463
+ },
464
+
373
465
  // A case naming an interface (literal or declared) records the resolved
374
466
  // type so backends test its method set.
375
467
  _markInterfaceCaseType(t, scope) {
@@ -383,6 +475,23 @@ export const statementCheckMethods = {
383
475
  if (u?.kind === "interface") t._type = resolved;
384
476
  },
385
477
 
478
+ // Records the resolved type of a composite type node used as an assertion
479
+ // or type-switch target (`[]int`, `map[string]any`, `*int`, `iter.Seq[int]`)
480
+ // so the backend can match it against the dynamic type of an interface value.
481
+ _markDynamicType(node, resolved) {
482
+ const kind = node?.kind;
483
+ if (
484
+ kind === "SliceType" ||
485
+ kind === "ArrayType" ||
486
+ kind === "MapType" ||
487
+ kind === "PointerType" ||
488
+ kind === "FuncType" ||
489
+ kind === "StructType" ||
490
+ kind === "GenericTypeName"
491
+ )
492
+ node._assertType = resolved;
493
+ },
494
+
386
495
  _checkTypeSwitchStmt(stmt, scope, returnType) {
387
496
  const inner = new Scope(scope);
388
497
  this.checkExpr(stmt.expr, inner);
@@ -396,6 +505,8 @@ export const statementCheckMethods = {
396
505
  const base = t?.kind === "PointerType" ? t.base : t;
397
506
  if (base?.kind === "GenericTypeName") this.resolveTypeNode(t, inner);
398
507
  this._markInterfaceCaseType(t, inner);
508
+ if (t && t.kind !== "TypeName" && t.kind !== "InterfaceType")
509
+ this._markDynamicType(t, this.resolveTypeNode(t, inner));
399
510
  }
400
511
  if (stmt.assign) {
401
512
  // Bind the variable to the single case type, or any for multi/default.