gofront 1.6.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -2,6 +2,9 @@
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";
@@ -15,8 +18,14 @@ export function isTestingT(goType) {
15
18
  ? goType.base
16
19
  : goType.kind === "StarExpr"
17
20
  ? (goType.expr ?? goType.operand)
18
- : null;
19
- return base?.name === "testing.T";
21
+ : goType;
22
+ const name = base?.name ?? (typeof base === "string" ? base : null);
23
+ return (
24
+ name === "testing.T" ||
25
+ name === "testing.B" ||
26
+ name === "testing.M" ||
27
+ name === "testing.TB"
28
+ );
20
29
  }
21
30
 
22
31
  export function toWasmType(goType, checker = null, mod = null) {
@@ -27,6 +36,7 @@ export function toWasmType(goType, checker = null, mod = null) {
27
36
  goType === "i64" ||
28
37
  goType === "f32" ||
29
38
  goType === "f64" ||
39
+ goType === "v128" ||
30
40
  goType === "anyref" ||
31
41
  goType === "externref" ||
32
42
  goType === "funcref"
@@ -39,6 +49,16 @@ export function toWasmType(goType, checker = null, mod = null) {
39
49
  }
40
50
  if (isTestingT(goType)) return "externref";
41
51
 
52
+ if (
53
+ goType.kind === "simd" ||
54
+ (goType.kind === "named" &&
55
+ (goType.name === "simd.F32x4" ||
56
+ goType.name === "F32x4" ||
57
+ goType.underlying?.kind === "simd"))
58
+ ) {
59
+ return "v128";
60
+ }
61
+
42
62
  // Pointer AST nodes
43
63
  if (goType.kind === "PointerType") {
44
64
  return toWasmType({ kind: "pointer", base: goType.base }, checker, mod);
@@ -68,6 +88,9 @@ export function toWasmType(goType, checker = null, mod = null) {
68
88
 
69
89
  // TypeName AST node
70
90
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
91
+ if (goType.name === "simd.F32x4" || goType.name === "F32x4") {
92
+ return "v128";
93
+ }
71
94
  if (mod?.getStructType(goType.name)) {
72
95
  return {
73
96
  kind: "ref",
@@ -84,16 +107,32 @@ export function toWasmType(goType, checker = null, mod = null) {
84
107
 
85
108
  // Unwrap named types
86
109
  if (goType.kind === "named") {
87
- if (mod?.getStructType(goType.name)) {
110
+ if (
111
+ goType.name === "simd.F32x4" ||
112
+ goType.name === "F32x4" ||
113
+ goType.underlying?.kind === "simd"
114
+ ) {
115
+ return "v128";
116
+ }
117
+ // An instance written `Box[int]` is the monomorphised struct `Box$int`.
118
+ const instance = goType.typeArgs?.length
119
+ ? mod?.getStructType(`${goType.name}$${instanceArgsKey(goType.typeArgs)}`)
120
+ : null;
121
+ const struct = instance ?? mod?.getStructType(goType.name);
122
+ if (struct) {
88
123
  return {
89
124
  kind: "ref",
90
125
  nullable: true,
91
- typeIndex: mod.getStructType(goType.name).typeIndex,
126
+ typeIndex: struct.typeIndex,
92
127
  };
93
128
  }
94
129
  if (goType.underlying) {
95
130
  return toWasmType(goType.underlying, checker, mod);
96
131
  }
132
+ // A bare `{ kind: "named", name }` (method receivers): look it up.
133
+ const resolved = checker?.types?.get(goType.name);
134
+ if (resolved?.underlying)
135
+ return toWasmType(resolved.underlying, checker, mod);
97
136
  }
98
137
 
99
138
  // Untyped constants
@@ -107,6 +146,17 @@ export function toWasmType(goType, checker = null, mod = null) {
107
146
  return "externref";
108
147
  case "bool":
109
148
  return "i32";
149
+ case "complex128":
150
+ if (mod?.getComplexType) {
151
+ const info = mod.getComplexType("complex128");
152
+ return {
153
+ kind: "ref",
154
+ nullable: true,
155
+ typeIndex: info.typeIndex,
156
+ typeName: info.name,
157
+ };
158
+ }
159
+ return { kind: "ref", nullable: true, heapType: "struct" };
110
160
  default:
111
161
  return "i64";
112
162
  }
@@ -138,6 +188,32 @@ export function toWasmType(goType, checker = null, mod = null) {
138
188
  case "float64":
139
189
  return "f64";
140
190
 
191
+ case "complex64": {
192
+ if (mod?.getComplexType) {
193
+ const info = mod.getComplexType("complex64");
194
+ return {
195
+ kind: "ref",
196
+ nullable: true,
197
+ typeIndex: info.typeIndex,
198
+ typeName: info.name,
199
+ };
200
+ }
201
+ return { kind: "ref", nullable: true, heapType: "struct" };
202
+ }
203
+
204
+ case "complex128": {
205
+ if (mod?.getComplexType) {
206
+ const info = mod.getComplexType("complex128");
207
+ return {
208
+ kind: "ref",
209
+ nullable: true,
210
+ typeIndex: info.typeIndex,
211
+ typeName: info.name,
212
+ };
213
+ }
214
+ return { kind: "ref", nullable: true, heapType: "struct" };
215
+ }
216
+
141
217
  case "string":
142
218
  return "externref";
143
219
 
@@ -160,14 +236,32 @@ export function toWasmType(goType, checker = null, mod = null) {
160
236
  if (
161
237
  typeof baseWType === "object" &&
162
238
  baseWType !== null &&
163
- baseWType.kind === "ref"
239
+ baseWType.kind === "ref" &&
240
+ // A pointer to a slice or map goes through the scalar box:
241
+ // the pointee is a replaceable reference value.
242
+ !isReplaceableRef(goType.base, checker)
164
243
  ) {
165
244
  return baseWType;
166
245
  }
167
- if (mod?.getBoxType) {
246
+ if (mod?.getPtrType) {
247
+ const ptr = mod.getPtrType(goType.base);
248
+ if (mod.getBoxType) mod.getBoxType(goType.base);
249
+ if (ptr)
250
+ return {
251
+ kind: "ref",
252
+ nullable: true,
253
+ typeIndex: ptr.typeIndex,
254
+ typeName: ptr.name,
255
+ };
256
+ } else if (mod?.getBoxType) {
168
257
  const box = mod.getBoxType(goType.base);
169
258
  if (box)
170
- return { kind: "ref", nullable: true, typeIndex: box.typeIndex };
259
+ return {
260
+ kind: "ref",
261
+ nullable: true,
262
+ typeIndex: box.typeIndex,
263
+ typeName: box.name,
264
+ };
171
265
  }
172
266
  }
173
267
  return { kind: "ref", nullable: true, heapType: "any" };
@@ -241,10 +335,8 @@ export function toWasmType(goType, checker = null, mod = null) {
241
335
  for (const n of names) rawFields.push({ name: n, goType: f.type });
242
336
  }
243
337
  }
244
- if (rawFields.length > 0) {
245
- const info = mod.getAnonStructType(rawFields);
246
- return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
247
- }
338
+ const info = mod.getAnonStructType(rawFields);
339
+ return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
248
340
  }
249
341
  return { kind: "ref", nullable: true, heapType: "struct" };
250
342
  }
@@ -273,14 +365,19 @@ export function getInterfaceMethodList(iface) {
273
365
  if (iface.methods instanceof Map) {
274
366
  return Array.from(iface.methods.entries()).map(([name, sig]) => ({
275
367
  name,
276
- params: (sig.params ?? []).map((p) => p.type ?? p),
368
+ params: variadicAsSlice(
369
+ (sig.params ?? []).map((p) => p.type ?? p),
370
+ sig.variadic,
371
+ ),
277
372
  returns: (sig.returns ?? []).map((r) => r.type ?? r),
278
373
  }));
279
374
  }
280
375
  if (Array.isArray(iface.methods)) {
281
376
  return iface.methods.map((m) => ({
282
377
  name: m.name?.value ?? m.name ?? m,
283
- params: (m.params ?? []).map((p) => p.type ?? p),
378
+ params: (m.params ?? []).map((p) =>
379
+ p.variadic ? { kind: "SliceType", elem: p.type } : (p.type ?? p),
380
+ ),
284
381
  returns: m.returnType
285
382
  ? m.returnType.kind === "TupleType" || m.returnType.kind === "tuple"
286
383
  ? m.returnType.types
@@ -291,6 +388,12 @@ export function getInterfaceMethodList(iface) {
291
388
  return [];
292
389
  }
293
390
 
391
+ // The type-argument part of a monomorphised name (see monomorph.js typeKey):
392
+ // the dot of a qualified argument is replaced to keep the name an identifier.
393
+ function instanceArgsKey(typeArgs) {
394
+ return typeArgs.map(goTypeKey).join("$").replaceAll(".", "_");
395
+ }
396
+
294
397
  export function goTypeKey(t) {
295
398
  if (!t) return "any";
296
399
  if (typeof t === "string") {
@@ -340,6 +443,9 @@ export function goTypeKey(t) {
340
443
  return `struct_${rawFields.join("_")}`;
341
444
  }
342
445
  if (t.kind === "named") {
446
+ // An instance of a generic type is the monomorphised `Box$int`.
447
+ if (t.name && t.typeArgs?.length && !t.name.includes("$"))
448
+ return `${t.name}$${instanceArgsKey(t.typeArgs)}`;
343
449
  return t.name ?? (t.underlying ? goTypeKey(t.underlying) : "any");
344
450
  }
345
451
  if (t.name) return t.name;
@@ -383,6 +489,43 @@ export function isPointerToStruct(goType, checker = null, mod = null) {
383
489
  return false;
384
490
  }
385
491
 
492
+ // Slices, maps and pointers are reference values that an assignment through a
493
+ // pointer replaces (a nil map becomes a new one, `*pp = q` repoints `p`),
494
+ // unlike structs and arrays, whose pointer is the value's own reference.
495
+ function isReplaceableRef(goType, checker) {
496
+ let t = goType;
497
+ if (t?.kind === "TypeName" && checker?.types?.has(t.name))
498
+ t = checker.types.get(t.name);
499
+ if (t?.kind === "named" && t.underlying) t = t.underlying;
500
+ return (
501
+ isSliceType(goType, checker) ||
502
+ isMapType(goType, checker) ||
503
+ t?.kind === "pointer" ||
504
+ t?.kind === "PointerType"
505
+ );
506
+ }
507
+
508
+ export function isScalarType(goType, checker = null, mod = null) {
509
+ if (!goType) return false;
510
+ // A slice or map is a pointee "scalar": the pointer to it is a
511
+ // box, so the pointee can be replaced (`*p = make(...)`).
512
+ if (isReplaceableRef(goType, checker)) return true;
513
+ if (
514
+ isStructType(goType, checker, mod) ||
515
+ isArrayType(goType, checker) ||
516
+ isMapType(goType, checker) ||
517
+ isFuncType(goType, checker) ||
518
+ isComplex(goType)
519
+ ) {
520
+ return false;
521
+ }
522
+ const wType = toWasmType(goType, checker, mod);
523
+ if (typeof wType === "object" && wType !== null && wType.kind === "ref") {
524
+ return false;
525
+ }
526
+ return true;
527
+ }
528
+
386
529
  export function isSliceType(goType, checker = null) {
387
530
  if (!goType) return false;
388
531
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
@@ -392,9 +535,38 @@ export function isSliceType(goType, checker = null) {
392
535
  if (goType.kind === "named" && goType.underlying) {
393
536
  return isSliceType(goType.underlying, checker);
394
537
  }
538
+ if (goType.kind === "named" && !goType.underlying) {
539
+ const resolved = checker?.types?.get(goType.name);
540
+ if (resolved && resolved !== goType) return isSliceType(resolved, checker);
541
+ }
395
542
  return goType.kind === "slice" || goType.kind === "SliceType";
396
543
  }
397
544
 
545
+ export function getSliceElemType(t, checker = null) {
546
+ if (!t) return null;
547
+ if (t.elem) return t.elem;
548
+ if (t.underlying?.elem) return t.underlying.elem;
549
+ if (t.name && checker?.types?.has(t.name)) {
550
+ const resolved = checker.types.get(t.name);
551
+ return getSliceElemType(resolved, checker);
552
+ }
553
+ return null;
554
+ }
555
+
556
+ // `[]rune` / `[]int32` (also through named types): the target of `[]rune(s)`.
557
+ export function isRuneSlice(goType, checker = null) {
558
+ if (!isSliceType(goType, checker)) return false;
559
+ let t = goType;
560
+ while (t && !t.elem) {
561
+ if (t.kind === "named") t = t.underlying;
562
+ else if (t.kind === "TypeName" || t.kind === "Ident")
563
+ t = checker?.types?.get(t.name);
564
+ else return false;
565
+ }
566
+ const name = t?.elem?.name;
567
+ return name === "rune" || name === "int32";
568
+ }
569
+
398
570
  // `gofront/shared` linear-memory buffer: returns the SHARED_KINDS entry
399
571
  // (elem, ctor, bytes, key) or null.
400
572
  export function getSharedInfo(goType, checker = null) {
@@ -430,6 +602,10 @@ export function isMapType(goType, checker = null) {
430
602
  if (goType.kind === "named" && goType.underlying) {
431
603
  return isMapType(goType.underlying, checker);
432
604
  }
605
+ if (goType.kind === "named" && !goType.underlying) {
606
+ const resolved = checker?.types?.get(goType.name);
607
+ if (resolved && resolved !== goType) return isMapType(resolved, checker);
608
+ }
433
609
  return goType.kind === "map" || goType.kind === "MapType";
434
610
  }
435
611
 
@@ -443,6 +619,11 @@ export function getMapKeyValTypes(goType, checker = null) {
443
619
  if (goType.kind === "named" && goType.underlying) {
444
620
  return getMapKeyValTypes(goType.underlying, checker);
445
621
  }
622
+ if (goType.kind === "named" && !goType.underlying) {
623
+ const resolved = checker?.types?.get(goType.name);
624
+ if (resolved && resolved !== goType)
625
+ return getMapKeyValTypes(resolved, checker);
626
+ }
446
627
  if (goType.kind === "map" || goType.kind === "MapType") {
447
628
  const key = goType.key ?? goType.keyType;
448
629
  const val =
@@ -462,6 +643,10 @@ export function isStringType(goType, checker = null) {
462
643
  if (goType.kind === "named") {
463
644
  if (goType.name === "string") return true;
464
645
  if (goType.underlying) return isStringType(goType.underlying, checker);
646
+ // A bare `{ kind: "named", name }` (interface candidates) resolves by name.
647
+ const resolved = checker?.types?.get(goType.name);
648
+ if (resolved && resolved !== goType && resolved.kind === "named")
649
+ return isStringType(resolved, checker);
465
650
  }
466
651
  if (goType.kind === "basic") return goType.name === "string";
467
652
  if (goType.kind === "untyped") return goType.base === "string";
@@ -507,6 +692,14 @@ export function isInterfaceType(goType, checker = null) {
507
692
  return false;
508
693
  }
509
694
 
695
+ // `bool`, including the untyped boolean of a constant (`const ok = true`).
696
+ export function isBoolGoType(goType) {
697
+ return (
698
+ goType?.name === "bool" ||
699
+ (goType?.kind === "untyped" && goType.base === "bool")
700
+ );
701
+ }
702
+
510
703
  // The built-in `error` interface (checker ERROR type or its name).
511
704
  export function isErrorType(goType) {
512
705
  if (!goType) return false;
@@ -618,7 +811,9 @@ export function getFuncSignature(goType, checker = null) {
618
811
  goType.kind === "FuncLit" ||
619
812
  goType.kind === "FuncDecl"
620
813
  ) {
621
- const params = (goType.params ?? []).map((p) => p.type ?? p);
814
+ const params = (goType.params ?? []).map((p) =>
815
+ p.variadic ? { kind: "SliceType", elem: p.type } : (p.type ?? p),
816
+ );
622
817
  const returns = [];
623
818
  if (goType.returnType) {
624
819
  if (
@@ -651,11 +846,22 @@ export function getFuncSignature(goType, checker = null) {
651
846
  returns.push(r);
652
847
  }
653
848
  }
654
- return { params: goType.params ?? [], returns };
849
+ return {
850
+ params: variadicAsSlice(goType.params ?? [], goType.variadic),
851
+ returns,
852
+ };
655
853
  }
656
854
  return { params: [], returns: [] };
657
855
  }
658
856
 
857
+ // Checker func types keep a variadic parameter's element type; in WASM the
858
+ // parameter is a slice (see variadic.js).
859
+ export function variadicAsSlice(params, variadic) {
860
+ if (!variadic || params.length === 0) return params;
861
+ const last = params[params.length - 1];
862
+ return [...params.slice(0, -1), { kind: "slice", elem: last }];
863
+ }
864
+
659
865
  export function isUnhashableGoType(goType, checker = null, seen = new Set()) {
660
866
  if (!goType) return false;
661
867
  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
+ // `fmt.Sprintf(...)` or a method of an emitter-backed type (`t.Errorf(...)`
17
+ // on `*testing.T`): those emitters take the arguments unpacked. Emitter
18
+ // types are the dotted names; 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":
@@ -98,6 +121,43 @@ function formatInstruction(inst) {
98
121
  case "i32.store8":
99
122
  case "i32.store16":
100
123
  return inst.offset ? `${op} offset=${inst.offset}` : op;
124
+ case "v128.load":
125
+ case "v128.store": {
126
+ const parts = [op];
127
+ if (inst.offset) parts.push(`offset=${inst.offset}`);
128
+ if (inst.align !== undefined) parts.push(`align=${1 << inst.align}`);
129
+ return parts.join(" ");
130
+ }
131
+ case "v128.const": {
132
+ if (inst.values && inst.values.length === 4) {
133
+ return `${op} f32x4 ${inst.values.join(" ")}`;
134
+ }
135
+ if (inst.bytes) {
136
+ const b = new Uint8Array(16);
137
+ b.set(inst.bytes.slice(0, 16));
138
+ const dv = new DataView(b.buffer);
139
+ return `${op} i32x4 ${dv.getInt32(0, true)} ${dv.getInt32(4, true)} ${dv.getInt32(8, true)} ${dv.getInt32(12, true)}`;
140
+ }
141
+ return `${op} i32x4 0 0 0 0`;
142
+ }
143
+ case "i8x16.shuffle": {
144
+ const lanes = (inst.lanes ?? inst.indices ?? []).slice(0, 16);
145
+ while (lanes.length < 16) lanes.push(0);
146
+ return `${op} ${lanes.join(" ")}`;
147
+ }
148
+ case "i8x16.extract_lane_s":
149
+ case "i8x16.extract_lane_u":
150
+ case "i8x16.replace_lane":
151
+ case "i16x8.extract_lane_s":
152
+ case "i16x8.extract_lane_u":
153
+ case "i16x8.replace_lane":
154
+ case "i32x4.extract_lane":
155
+ case "i32x4.replace_lane":
156
+ case "i64x2.extract_lane":
157
+ case "i64x2.replace_lane":
158
+ case "f32x4.extract_lane":
159
+ case "f32x4.replace_lane":
160
+ return `${op} ${inst.lane ?? inst.index ?? 0}`;
101
161
  case "i32.const":
102
162
  case "i64.const":
103
163
  return `${op} ${inst.value ?? 0}`;
@@ -132,12 +192,12 @@ function formatInstruction(inst) {
132
192
  return `${op}${res}${catches ? ` ${catches}` : ""}`;
133
193
  }
134
194
  case "struct.new":
135
- 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}`;
136
196
  case "struct.get":
137
197
  case "struct.get_s":
138
198
  case "struct.get_u":
139
199
  case "struct.set":
140
- 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}`;
141
201
  case "array.new":
142
202
  case "array.new_default":
143
203
  case "array.get":
@@ -156,11 +216,15 @@ function formatInstruction(inst) {
156
216
  case "ref.cast":
157
217
  case "ref.cast_null": {
158
218
  const ht =
159
- inst.typeIndex !== undefined
160
- ? `$t${inst.typeIndex}`
161
- : inst.heapType !== undefined
162
- ? String(inst.heapType)
163
- : 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;
164
228
  return `${op} ${ht}`;
165
229
  }
166
230
  case "any.convert_extern":