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
@@ -2,23 +2,13 @@
2
2
  // Maps GoFront types to WasmGC types and manages type registry.
3
3
 
4
4
  import { SHARED_KINDS } from "../../typechecker/stdlib/shared.js";
5
+ import { isComplex, isComplex64 } from "../../typechecker/types.js";
6
+
7
+ export { isComplex, isComplex64 };
5
8
 
6
9
  // Name of the runtime's error struct (`errors.New`, `fmt.Errorf`, strconv).
7
10
  export const ERROR_STRUCT = "__errorString";
8
11
 
9
- // `*testing.T` is a JS-side object (the harness's __GoFront_T) passed through
10
- // the boundary as an opaque externref.
11
- export function isTestingT(goType) {
12
- if (!goType) return false;
13
- const base =
14
- goType.kind === "pointer" || goType.kind === "PointerType"
15
- ? goType.base
16
- : goType.kind === "StarExpr"
17
- ? (goType.expr ?? goType.operand)
18
- : null;
19
- return base?.name === "testing.T";
20
- }
21
-
22
12
  export function toWasmType(goType, checker = null, mod = null) {
23
13
  if (!goType) return "i32";
24
14
  if (typeof goType === "string") {
@@ -38,7 +28,6 @@ export function toWasmType(goType, checker = null, mod = null) {
38
28
  if (typeof goType === "object" && goType.kind === "ref") {
39
29
  return goType;
40
30
  }
41
- if (isTestingT(goType)) return "externref";
42
31
 
43
32
  if (
44
33
  goType.kind === "simd" ||
@@ -105,16 +94,25 @@ export function toWasmType(goType, checker = null, mod = null) {
105
94
  ) {
106
95
  return "v128";
107
96
  }
108
- if (mod?.getStructType(goType.name)) {
97
+ // An instance written `Box[int]` is the monomorphised struct `Box$int`.
98
+ const instance = goType.typeArgs?.length
99
+ ? mod?.getStructType(`${goType.name}$${instanceArgsKey(goType.typeArgs)}`)
100
+ : null;
101
+ const struct = instance ?? mod?.getStructType(goType.name);
102
+ if (struct) {
109
103
  return {
110
104
  kind: "ref",
111
105
  nullable: true,
112
- typeIndex: mod.getStructType(goType.name).typeIndex,
106
+ typeIndex: struct.typeIndex,
113
107
  };
114
108
  }
115
109
  if (goType.underlying) {
116
110
  return toWasmType(goType.underlying, checker, mod);
117
111
  }
112
+ // A bare `{ kind: "named", name }` (method receivers): look it up.
113
+ const resolved = checker?.types?.get(goType.name);
114
+ if (resolved?.underlying)
115
+ return toWasmType(resolved.underlying, checker, mod);
118
116
  }
119
117
 
120
118
  // Untyped constants
@@ -128,6 +126,17 @@ export function toWasmType(goType, checker = null, mod = null) {
128
126
  return "externref";
129
127
  case "bool":
130
128
  return "i32";
129
+ case "complex128":
130
+ if (mod?.getComplexType) {
131
+ const info = mod.getComplexType("complex128");
132
+ return {
133
+ kind: "ref",
134
+ nullable: true,
135
+ typeIndex: info.typeIndex,
136
+ typeName: info.name,
137
+ };
138
+ }
139
+ return { kind: "ref", nullable: true, heapType: "struct" };
131
140
  default:
132
141
  return "i64";
133
142
  }
@@ -159,6 +168,32 @@ export function toWasmType(goType, checker = null, mod = null) {
159
168
  case "float64":
160
169
  return "f64";
161
170
 
171
+ case "complex64": {
172
+ if (mod?.getComplexType) {
173
+ const info = mod.getComplexType("complex64");
174
+ return {
175
+ kind: "ref",
176
+ nullable: true,
177
+ typeIndex: info.typeIndex,
178
+ typeName: info.name,
179
+ };
180
+ }
181
+ return { kind: "ref", nullable: true, heapType: "struct" };
182
+ }
183
+
184
+ case "complex128": {
185
+ if (mod?.getComplexType) {
186
+ const info = mod.getComplexType("complex128");
187
+ return {
188
+ kind: "ref",
189
+ nullable: true,
190
+ typeIndex: info.typeIndex,
191
+ typeName: info.name,
192
+ };
193
+ }
194
+ return { kind: "ref", nullable: true, heapType: "struct" };
195
+ }
196
+
162
197
  case "string":
163
198
  return "externref";
164
199
 
@@ -181,14 +216,32 @@ export function toWasmType(goType, checker = null, mod = null) {
181
216
  if (
182
217
  typeof baseWType === "object" &&
183
218
  baseWType !== null &&
184
- baseWType.kind === "ref"
219
+ baseWType.kind === "ref" &&
220
+ // A pointer to a slice or map goes through the scalar box:
221
+ // the pointee is a replaceable reference value.
222
+ !isReplaceableRef(goType.base, checker)
185
223
  ) {
186
224
  return baseWType;
187
225
  }
188
- if (mod?.getBoxType) {
226
+ if (mod?.getPtrType) {
227
+ const ptr = mod.getPtrType(goType.base);
228
+ if (mod.getBoxType) mod.getBoxType(goType.base);
229
+ if (ptr)
230
+ return {
231
+ kind: "ref",
232
+ nullable: true,
233
+ typeIndex: ptr.typeIndex,
234
+ typeName: ptr.name,
235
+ };
236
+ } else if (mod?.getBoxType) {
189
237
  const box = mod.getBoxType(goType.base);
190
238
  if (box)
191
- return { kind: "ref", nullable: true, typeIndex: box.typeIndex };
239
+ return {
240
+ kind: "ref",
241
+ nullable: true,
242
+ typeIndex: box.typeIndex,
243
+ typeName: box.name,
244
+ };
192
245
  }
193
246
  }
194
247
  return { kind: "ref", nullable: true, heapType: "any" };
@@ -262,10 +315,8 @@ export function toWasmType(goType, checker = null, mod = null) {
262
315
  for (const n of names) rawFields.push({ name: n, goType: f.type });
263
316
  }
264
317
  }
265
- if (rawFields.length > 0) {
266
- const info = mod.getAnonStructType(rawFields);
267
- return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
268
- }
318
+ const info = mod.getAnonStructType(rawFields);
319
+ return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
269
320
  }
270
321
  return { kind: "ref", nullable: true, heapType: "struct" };
271
322
  }
@@ -294,14 +345,19 @@ export function getInterfaceMethodList(iface) {
294
345
  if (iface.methods instanceof Map) {
295
346
  return Array.from(iface.methods.entries()).map(([name, sig]) => ({
296
347
  name,
297
- params: (sig.params ?? []).map((p) => p.type ?? p),
348
+ params: variadicAsSlice(
349
+ (sig.params ?? []).map((p) => p.type ?? p),
350
+ sig.variadic,
351
+ ),
298
352
  returns: (sig.returns ?? []).map((r) => r.type ?? r),
299
353
  }));
300
354
  }
301
355
  if (Array.isArray(iface.methods)) {
302
356
  return iface.methods.map((m) => ({
303
357
  name: m.name?.value ?? m.name ?? m,
304
- params: (m.params ?? []).map((p) => p.type ?? p),
358
+ params: (m.params ?? []).map((p) =>
359
+ p.variadic ? { kind: "SliceType", elem: p.type } : (p.type ?? p),
360
+ ),
305
361
  returns: m.returnType
306
362
  ? m.returnType.kind === "TupleType" || m.returnType.kind === "tuple"
307
363
  ? m.returnType.types
@@ -312,6 +368,12 @@ export function getInterfaceMethodList(iface) {
312
368
  return [];
313
369
  }
314
370
 
371
+ // The type-argument part of a monomorphised name (see monomorph.js typeKey):
372
+ // the dot of a qualified argument is replaced to keep the name an identifier.
373
+ function instanceArgsKey(typeArgs) {
374
+ return typeArgs.map(goTypeKey).join("$").replaceAll(".", "_");
375
+ }
376
+
315
377
  export function goTypeKey(t) {
316
378
  if (!t) return "any";
317
379
  if (typeof t === "string") {
@@ -361,6 +423,9 @@ export function goTypeKey(t) {
361
423
  return `struct_${rawFields.join("_")}`;
362
424
  }
363
425
  if (t.kind === "named") {
426
+ // An instance of a generic type is the monomorphised `Box$int`.
427
+ if (t.name && t.typeArgs?.length && !t.name.includes("$"))
428
+ return `${t.name}$${instanceArgsKey(t.typeArgs)}`;
364
429
  return t.name ?? (t.underlying ? goTypeKey(t.underlying) : "any");
365
430
  }
366
431
  if (t.name) return t.name;
@@ -404,6 +469,43 @@ export function isPointerToStruct(goType, checker = null, mod = null) {
404
469
  return false;
405
470
  }
406
471
 
472
+ // Slices, maps and pointers are reference values that an assignment through a
473
+ // pointer replaces (a nil map becomes a new one, `*pp = q` repoints `p`),
474
+ // unlike structs and arrays, whose pointer is the value's own reference.
475
+ function isReplaceableRef(goType, checker) {
476
+ let t = goType;
477
+ if (t?.kind === "TypeName" && checker?.types?.has(t.name))
478
+ t = checker.types.get(t.name);
479
+ if (t?.kind === "named" && t.underlying) t = t.underlying;
480
+ return (
481
+ isSliceType(goType, checker) ||
482
+ isMapType(goType, checker) ||
483
+ t?.kind === "pointer" ||
484
+ t?.kind === "PointerType"
485
+ );
486
+ }
487
+
488
+ export function isScalarType(goType, checker = null, mod = null) {
489
+ if (!goType) return false;
490
+ // A slice or map is a pointee "scalar": the pointer to it is a
491
+ // box, so the pointee can be replaced (`*p = make(...)`).
492
+ if (isReplaceableRef(goType, checker)) return true;
493
+ if (
494
+ isStructType(goType, checker, mod) ||
495
+ isArrayType(goType, checker) ||
496
+ isMapType(goType, checker) ||
497
+ isFuncType(goType, checker) ||
498
+ isComplex(goType)
499
+ ) {
500
+ return false;
501
+ }
502
+ const wType = toWasmType(goType, checker, mod);
503
+ if (typeof wType === "object" && wType !== null && wType.kind === "ref") {
504
+ return false;
505
+ }
506
+ return true;
507
+ }
508
+
407
509
  export function isSliceType(goType, checker = null) {
408
510
  if (!goType) return false;
409
511
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
@@ -413,9 +515,79 @@ export function isSliceType(goType, checker = null) {
413
515
  if (goType.kind === "named" && goType.underlying) {
414
516
  return isSliceType(goType.underlying, checker);
415
517
  }
518
+ if (goType.kind === "named" && !goType.underlying) {
519
+ const resolved = checker?.types?.get(goType.name);
520
+ if (resolved && resolved !== goType) return isSliceType(resolved, checker);
521
+ }
416
522
  return goType.kind === "slice" || goType.kind === "SliceType";
417
523
  }
418
524
 
525
+ // Element kinds whose slices share one WebAssembly representation (a slice of
526
+ // i32), so `[]bool`, `[]byte` and `[]rune` cannot be told apart by `ref.test`.
527
+ const TAGGED_SLICE_ELEMS = new Set([
528
+ "bool",
529
+ "int8",
530
+ "int16",
531
+ "int32",
532
+ "uint8",
533
+ "uint16",
534
+ "uint32",
535
+ "byte",
536
+ "rune",
537
+ ]);
538
+
539
+ // True for a defined slice type or a slice of booleans or integers of at
540
+ // most 32 bits. In an
541
+ // interface such a value carries a per-Go-type tag (see getTaggedSliceType).
542
+ export function isTaggedSliceType(goType, checker = null) {
543
+ if (!isSliceType(goType, checker)) return false;
544
+ // A defined slice type (`type S []int`) is tagged whatever its element, so
545
+ // `case S` does not match an unnamed `[]int`.
546
+ const declared =
547
+ goType.kind === "TypeName" || goType.kind === "Ident"
548
+ ? (checker?.types?.get(goType.name) ?? goType)
549
+ : goType;
550
+ if (declared?.kind === "named") return true;
551
+ let elem = getSliceElemType(goType, checker);
552
+ // A source-level element (`uint8` in a case list) is a TypeName node.
553
+ for (let i = 0; i < 16 && elem; i++) {
554
+ if (elem.kind === "named")
555
+ elem = elem.underlying ?? checker?.types?.get(elem.name);
556
+ else if (elem.kind === "TypeName" || elem.kind === "Ident")
557
+ elem = checker?.types?.get(elem.name) ?? {
558
+ kind: "basic",
559
+ name: elem.name,
560
+ };
561
+ else break;
562
+ }
563
+ return elem?.kind === "basic" && TAGGED_SLICE_ELEMS.has(elem.name);
564
+ }
565
+
566
+ export function getSliceElemType(t, checker = null) {
567
+ if (!t) return null;
568
+ if (t.elem) return t.elem;
569
+ if (t.underlying?.elem) return t.underlying.elem;
570
+ if (t.name && checker?.types?.has(t.name)) {
571
+ const resolved = checker.types.get(t.name);
572
+ return getSliceElemType(resolved, checker);
573
+ }
574
+ return null;
575
+ }
576
+
577
+ // `[]rune` / `[]int32` (also through named types): the target of `[]rune(s)`.
578
+ export function isRuneSlice(goType, checker = null) {
579
+ if (!isSliceType(goType, checker)) return false;
580
+ let t = goType;
581
+ while (t && !t.elem) {
582
+ if (t.kind === "named") t = t.underlying;
583
+ else if (t.kind === "TypeName" || t.kind === "Ident")
584
+ t = checker?.types?.get(t.name);
585
+ else return false;
586
+ }
587
+ const name = t?.elem?.name;
588
+ return name === "rune" || name === "int32";
589
+ }
590
+
419
591
  // `gofront/shared` linear-memory buffer: returns the SHARED_KINDS entry
420
592
  // (elem, ctor, bytes, key) or null.
421
593
  export function getSharedInfo(goType, checker = null) {
@@ -451,6 +623,10 @@ export function isMapType(goType, checker = null) {
451
623
  if (goType.kind === "named" && goType.underlying) {
452
624
  return isMapType(goType.underlying, checker);
453
625
  }
626
+ if (goType.kind === "named" && !goType.underlying) {
627
+ const resolved = checker?.types?.get(goType.name);
628
+ if (resolved && resolved !== goType) return isMapType(resolved, checker);
629
+ }
454
630
  return goType.kind === "map" || goType.kind === "MapType";
455
631
  }
456
632
 
@@ -464,6 +640,11 @@ export function getMapKeyValTypes(goType, checker = null) {
464
640
  if (goType.kind === "named" && goType.underlying) {
465
641
  return getMapKeyValTypes(goType.underlying, checker);
466
642
  }
643
+ if (goType.kind === "named" && !goType.underlying) {
644
+ const resolved = checker?.types?.get(goType.name);
645
+ if (resolved && resolved !== goType)
646
+ return getMapKeyValTypes(resolved, checker);
647
+ }
467
648
  if (goType.kind === "map" || goType.kind === "MapType") {
468
649
  const key = goType.key ?? goType.keyType;
469
650
  const val =
@@ -483,6 +664,10 @@ export function isStringType(goType, checker = null) {
483
664
  if (goType.kind === "named") {
484
665
  if (goType.name === "string") return true;
485
666
  if (goType.underlying) return isStringType(goType.underlying, checker);
667
+ // A bare `{ kind: "named", name }` (interface candidates) resolves by name.
668
+ const resolved = checker?.types?.get(goType.name);
669
+ if (resolved && resolved !== goType && resolved.kind === "named")
670
+ return isStringType(resolved, checker);
486
671
  }
487
672
  if (goType.kind === "basic") return goType.name === "string";
488
673
  if (goType.kind === "untyped") return goType.base === "string";
@@ -528,6 +713,14 @@ export function isInterfaceType(goType, checker = null) {
528
713
  return false;
529
714
  }
530
715
 
716
+ // `bool`, including the untyped boolean of a constant (`const ok = true`).
717
+ export function isBoolGoType(goType) {
718
+ return (
719
+ goType?.name === "bool" ||
720
+ (goType?.kind === "untyped" && goType.base === "bool")
721
+ );
722
+ }
723
+
531
724
  // The built-in `error` interface (checker ERROR type or its name).
532
725
  export function isErrorType(goType) {
533
726
  if (!goType) return false;
@@ -593,6 +786,33 @@ export function isSigned(goType) {
593
786
  return true;
594
787
  }
595
788
 
789
+ // How a builtin `print`/`println` operand is handed to the host. The host
790
+ // prints an i32 or i64 as signed, so a uint32 widens to i64 and a uint64 uses
791
+ // its own unsigned import (`wType` "u64").
792
+ export function printOperandKind(arg, checker) {
793
+ const isBool =
794
+ isBoolGoType(arg._type) ||
795
+ _namedBasic(arg._type) === "bool" ||
796
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL");
797
+ let wType = toWasmType(arg._type, checker);
798
+ let widen = false;
799
+ if (!isBool && (wType === "i32" || wType === "i64") && !isSigned(arg._type)) {
800
+ if (wType === "i64") wType = "u64";
801
+ else if (_namedBasic(arg._type) === "uint32") {
802
+ wType = "i64";
803
+ widen = true;
804
+ }
805
+ }
806
+ return { wType, isBool, widen };
807
+ }
808
+
809
+ function _namedBasic(t) {
810
+ while (t && (t.kind === "named" || t.kind === "TypeName") && t.underlying) {
811
+ t = t.underlying;
812
+ }
813
+ return t?.name;
814
+ }
815
+
596
816
  export function isFloat(goType) {
597
817
  if (!goType) return false;
598
818
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
@@ -639,7 +859,9 @@ export function getFuncSignature(goType, checker = null) {
639
859
  goType.kind === "FuncLit" ||
640
860
  goType.kind === "FuncDecl"
641
861
  ) {
642
- const params = (goType.params ?? []).map((p) => p.type ?? p);
862
+ const params = (goType.params ?? []).map((p) =>
863
+ p.variadic ? { kind: "SliceType", elem: p.type } : (p.type ?? p),
864
+ );
643
865
  const returns = [];
644
866
  if (goType.returnType) {
645
867
  if (
@@ -672,11 +894,22 @@ export function getFuncSignature(goType, checker = null) {
672
894
  returns.push(r);
673
895
  }
674
896
  }
675
- return { params: goType.params ?? [], returns };
897
+ return {
898
+ params: variadicAsSlice(goType.params ?? [], goType.variadic),
899
+ returns,
900
+ };
676
901
  }
677
902
  return { params: [], returns: [] };
678
903
  }
679
904
 
905
+ // Checker func types keep a variadic parameter's element type; in WASM the
906
+ // parameter is a slice (see variadic.js).
907
+ export function variadicAsSlice(params, variadic) {
908
+ if (!variadic || params.length === 0) return params;
909
+ const last = params[params.length - 1];
910
+ return [...params.slice(0, -1), { kind: "slice", elem: last }];
911
+ }
912
+
680
913
  export function isUnhashableGoType(goType, checker = null, seen = new Set()) {
681
914
  if (!goType) return false;
682
915
  if (seen.has(goType)) return false;
@@ -0,0 +1,93 @@
1
+ // src/backend/wasm/variadic.js
2
+ // WASM has no rest parameters. A variadic `xs ...T` becomes an ordinary
3
+ // `[]T` parameter, and every call to a GoFront function, method or closure
4
+ // packs its trailing arguments into a `[]T` literal (`f(s...)` passes `s`
5
+ // through). Emitter-backed stdlib calls keep their raw arguments. Runs
6
+ // after monomorphisation, so callee types are concrete, on the WASM
7
+ // pipeline's own copy of the programs.
8
+
9
+ import { typeToTypeNode } from "./monomorph.js";
10
+
11
+ function variadicFuncType(t) {
12
+ const fn = t?.kind === "named" ? t.underlying : t;
13
+ return fn?.kind === "func" && fn.variadic ? fn : null;
14
+ }
15
+
16
+ // A call of a package member or a method of an emitter-backed type: those
17
+ // emitters take the arguments unpacked. Emitter types are the dotted names;
18
+ // GoFront and Go-source types are not.
19
+ function isStdlibCall(call) {
20
+ const f = call.func;
21
+ if (f?.kind !== "SelectorExpr") return false;
22
+ let recv = f.expr?._type;
23
+ if (recv?.kind === "namespace") return true;
24
+ if (recv?.kind === "pointer") recv = recv.base;
25
+ return typeof recv?.name === "string" && recv.name.includes(".");
26
+ }
27
+
28
+ function packCallArgs(call) {
29
+ if (call._multiForward || isStdlibCall(call)) return false;
30
+ const fn = variadicFuncType(call.func?._type);
31
+ if (!fn) return false;
32
+ const n = fn.params.length;
33
+ const args = call.args ?? [];
34
+ if (args.length === n && args[n - 1]?._spread) {
35
+ args[n - 1]._spread = false;
36
+ return true;
37
+ }
38
+ if (args.length < n - 1) return false;
39
+ const elem = fn.params[n - 1];
40
+ const sliceType = { kind: "slice", elem };
41
+ // No variadic arguments: the parameter is the nil slice, as in Go.
42
+ const rest = args.slice(n - 1);
43
+ const packed =
44
+ rest.length === 0
45
+ ? {
46
+ kind: "TypeConversion",
47
+ targetType: sliceType,
48
+ expr: { kind: "Ident", name: "nil" },
49
+ _type: sliceType,
50
+ _line: call._line,
51
+ _col: call._col,
52
+ }
53
+ : null;
54
+ call.args = [
55
+ ...args.slice(0, n - 1),
56
+ packed ?? {
57
+ kind: "CompositeLit",
58
+ typeExpr: { kind: "SliceType", elem: typeToTypeNode(elem) },
59
+ elems: args.slice(n - 1),
60
+ _type: sliceType,
61
+ _line: call._line,
62
+ _col: call._col,
63
+ },
64
+ ];
65
+ return true;
66
+ }
67
+
68
+ function sliceVariadicParam(params) {
69
+ const last = params?.[params.length - 1];
70
+ if (!last?.variadic) return false;
71
+ last.type = { kind: "SliceType", elem: last.type };
72
+ last.variadic = false;
73
+ last._variadic = true; // the JS facade keeps a rest parameter
74
+ return true;
75
+ }
76
+
77
+ /** Rewrites variadic declarations and calls in place; true if anything changed. */
78
+ export function desugarVariadics(node) {
79
+ if (!node || typeof node !== "object") return false;
80
+ if (Array.isArray(node)) {
81
+ let changed = false;
82
+ for (const n of node) changed = desugarVariadics(n) || changed;
83
+ return changed;
84
+ }
85
+ let changed = false;
86
+ if (Array.isArray(node.params)) changed = sliceVariadicParam(node.params);
87
+ if (node.kind === "CallExpr") changed = packCallArgs(node) || changed;
88
+ for (const key of Object.keys(node)) {
89
+ if (key.charCodeAt(0) === 95) continue; // "_" side tables
90
+ changed = desugarVariadics(node[key]) || changed;
91
+ }
92
+ return changed;
93
+ }
@@ -8,7 +8,13 @@ function formatValType(t) {
8
8
  if (t.kind === "ref") {
9
9
  const nullStr = t.nullable ? "null " : "";
10
10
  const target =
11
- t.typeIndex !== undefined ? `$t${t.typeIndex}` : (t.heapType ?? "any");
11
+ t.typeName !== undefined
12
+ ? t.typeName.startsWith("$")
13
+ ? t.typeName
14
+ : `$${t.typeName}`
15
+ : t.typeIndex !== undefined
16
+ ? `$t${t.typeIndex}`
17
+ : (t.heapType ?? "any");
12
18
  return `(ref ${nullStr}${target})`;
13
19
  }
14
20
  }
@@ -16,7 +22,11 @@ function formatValType(t) {
16
22
  }
17
23
 
18
24
  function formatTypeEntry(t, id) {
19
- const typeId = id !== undefined ? ` $t${id}` : "";
25
+ const typeId = t.name
26
+ ? ` ${t.name.startsWith("$") ? t.name : `$${t.name}`}`
27
+ : id !== undefined
28
+ ? ` $t${id}`
29
+ : "";
20
30
  if (t.form === "rec") {
21
31
  const inners = t.types
22
32
  .map(
@@ -26,6 +36,7 @@ function formatTypeEntry(t, id) {
26
36
  .join("\n");
27
37
  return `(rec\n${inners}\n )`;
28
38
  }
39
+ let body;
29
40
  if (t.form === "struct") {
30
41
  const fields = (t.fields ?? [])
31
42
  .map((f) => {
@@ -33,19 +44,29 @@ function formatTypeEntry(t, id) {
33
44
  return f.mutable ? `(field (mut ${inner}))` : `(field ${inner})`;
34
45
  })
35
46
  .join(" ");
36
- return `(type${typeId} (struct ${fields}))`;
37
- }
38
- if (t.form === "array") {
47
+ body = fields ? `(struct ${fields})` : `(struct)`;
48
+ } else if (t.form === "array") {
39
49
  const inner = formatValType(t.elemType ?? t.type);
40
50
  const field = t.mutable ? `(mut ${inner})` : inner;
41
- return `(type${typeId} (array ${field}))`;
51
+ body = `(array ${field})`;
52
+ } else {
53
+ // func
54
+ const params = (t.params ?? []).map(formatValType).join(" ");
55
+ const results = (t.results ?? []).map(formatValType).join(" ");
56
+ const paramStr = params ? ` (param ${params})` : "";
57
+ const resultStr = results ? ` (result ${results})` : "";
58
+ body = `(func${paramStr}${resultStr})`;
59
+ }
60
+
61
+ if (t.sub) {
62
+ const superStr = t.superType
63
+ ? ` ${t.superType.startsWith("$") ? t.superType : `$${t.superType}`}`
64
+ : t.superTypeIndex !== undefined
65
+ ? ` $t${t.superTypeIndex}`
66
+ : "";
67
+ return `(type${typeId} (sub${superStr} ${body}))`;
42
68
  }
43
- // func
44
- const params = (t.params ?? []).map(formatValType).join(" ");
45
- const results = (t.results ?? []).map(formatValType).join(" ");
46
- const paramStr = params ? ` (param ${params})` : "";
47
- const resultStr = results ? ` (result ${results})` : "";
48
- return `(type${typeId} (func${paramStr}${resultStr}))`;
69
+ return `(type${typeId} ${body})`;
49
70
  }
50
71
 
51
72
  function formatInstruction(inst) {
@@ -56,11 +77,13 @@ function formatInstruction(inst) {
56
77
  case "loop":
57
78
  case "if": {
58
79
  const bt = inst.blockType ?? inst.resultType;
80
+ if (typeof bt === "number") return `${op} (type $t${bt})`;
59
81
  const res = bt && bt !== "void" ? ` (result ${formatValType(bt)})` : "";
60
82
  return `${op}${res}`;
61
83
  }
62
84
  case "br":
63
85
  case "br_if":
86
+ case "br_on_null":
64
87
  return `${op} ${inst.depth ?? inst.index ?? 0}`;
65
88
  case "br_table": {
66
89
  const targets = (inst.targets ?? []).join(" ");
@@ -73,7 +96,7 @@ function formatInstruction(inst) {
73
96
  return `${op} (type $t${inst.typeIndex ?? 0})`;
74
97
  case "call_ref":
75
98
  case "return_call_ref":
76
- return `${op} (type $t${inst.typeIndex ?? 0})`;
99
+ return `${op} ${inst.typeName ? (inst.typeName.startsWith("$") ? inst.typeName : `$${inst.typeName}`) : `$t${inst.typeIndex ?? 0}`}`;
77
100
  case "ref.func":
78
101
  return `ref.func ${inst.funcIndex ?? inst.index ?? 0}`;
79
102
  case "local.get":
@@ -169,12 +192,12 @@ function formatInstruction(inst) {
169
192
  return `${op}${res}${catches ? ` ${catches}` : ""}`;
170
193
  }
171
194
  case "struct.new":
172
- return `struct.new ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
195
+ return `struct.new ${inst.typeName ? (inst.typeName.startsWith("$") ? inst.typeName : `$${inst.typeName}`) : inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
173
196
  case "struct.get":
174
197
  case "struct.get_s":
175
198
  case "struct.get_u":
176
199
  case "struct.set":
177
- return `${op} ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0} ${inst.fieldIndex ?? 0}`;
200
+ return `${op} ${inst.typeName ? (inst.typeName.startsWith("$") ? inst.typeName : `$${inst.typeName}`) : inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0} ${inst.fieldIndex ?? 0}`;
178
201
  case "array.new":
179
202
  case "array.new_default":
180
203
  case "array.get":
@@ -193,11 +216,15 @@ function formatInstruction(inst) {
193
216
  case "ref.cast":
194
217
  case "ref.cast_null": {
195
218
  const ht =
196
- inst.typeIndex !== undefined
197
- ? `$t${inst.typeIndex}`
198
- : inst.heapType !== undefined
199
- ? String(inst.heapType)
200
- : 0;
219
+ inst.typeName !== undefined
220
+ ? inst.typeName.startsWith("$")
221
+ ? inst.typeName
222
+ : `$${inst.typeName}`
223
+ : inst.typeIndex !== undefined
224
+ ? `$t${inst.typeIndex}`
225
+ : inst.heapType !== undefined
226
+ ? String(inst.heapType)
227
+ : 0;
201
228
  return `${op} ${ht}`;
202
229
  }
203
230
  case "any.convert_extern":