gofront 1.7.0 → 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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  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 +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  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 +10 -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 +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  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 +512 -77
  34. package/src/backend/wasm/encode.js +43 -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/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /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) {
@@ -105,16 +114,25 @@ export function toWasmType(goType, checker = null, mod = null) {
105
114
  ) {
106
115
  return "v128";
107
116
  }
108
- if (mod?.getStructType(goType.name)) {
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) {
109
123
  return {
110
124
  kind: "ref",
111
125
  nullable: true,
112
- typeIndex: mod.getStructType(goType.name).typeIndex,
126
+ typeIndex: struct.typeIndex,
113
127
  };
114
128
  }
115
129
  if (goType.underlying) {
116
130
  return toWasmType(goType.underlying, checker, mod);
117
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);
118
136
  }
119
137
 
120
138
  // Untyped constants
@@ -128,6 +146,17 @@ export function toWasmType(goType, checker = null, mod = null) {
128
146
  return "externref";
129
147
  case "bool":
130
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" };
131
160
  default:
132
161
  return "i64";
133
162
  }
@@ -159,6 +188,32 @@ export function toWasmType(goType, checker = null, mod = null) {
159
188
  case "float64":
160
189
  return "f64";
161
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
+
162
217
  case "string":
163
218
  return "externref";
164
219
 
@@ -181,14 +236,32 @@ export function toWasmType(goType, checker = null, mod = null) {
181
236
  if (
182
237
  typeof baseWType === "object" &&
183
238
  baseWType !== null &&
184
- 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)
185
243
  ) {
186
244
  return baseWType;
187
245
  }
188
- 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) {
189
257
  const box = mod.getBoxType(goType.base);
190
258
  if (box)
191
- 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
+ };
192
265
  }
193
266
  }
194
267
  return { kind: "ref", nullable: true, heapType: "any" };
@@ -262,10 +335,8 @@ export function toWasmType(goType, checker = null, mod = null) {
262
335
  for (const n of names) rawFields.push({ name: n, goType: f.type });
263
336
  }
264
337
  }
265
- if (rawFields.length > 0) {
266
- const info = mod.getAnonStructType(rawFields);
267
- return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
268
- }
338
+ const info = mod.getAnonStructType(rawFields);
339
+ return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
269
340
  }
270
341
  return { kind: "ref", nullable: true, heapType: "struct" };
271
342
  }
@@ -294,14 +365,19 @@ export function getInterfaceMethodList(iface) {
294
365
  if (iface.methods instanceof Map) {
295
366
  return Array.from(iface.methods.entries()).map(([name, sig]) => ({
296
367
  name,
297
- params: (sig.params ?? []).map((p) => p.type ?? p),
368
+ params: variadicAsSlice(
369
+ (sig.params ?? []).map((p) => p.type ?? p),
370
+ sig.variadic,
371
+ ),
298
372
  returns: (sig.returns ?? []).map((r) => r.type ?? r),
299
373
  }));
300
374
  }
301
375
  if (Array.isArray(iface.methods)) {
302
376
  return iface.methods.map((m) => ({
303
377
  name: m.name?.value ?? m.name ?? m,
304
- 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
+ ),
305
381
  returns: m.returnType
306
382
  ? m.returnType.kind === "TupleType" || m.returnType.kind === "tuple"
307
383
  ? m.returnType.types
@@ -312,6 +388,12 @@ export function getInterfaceMethodList(iface) {
312
388
  return [];
313
389
  }
314
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
+
315
397
  export function goTypeKey(t) {
316
398
  if (!t) return "any";
317
399
  if (typeof t === "string") {
@@ -361,6 +443,9 @@ export function goTypeKey(t) {
361
443
  return `struct_${rawFields.join("_")}`;
362
444
  }
363
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)}`;
364
449
  return t.name ?? (t.underlying ? goTypeKey(t.underlying) : "any");
365
450
  }
366
451
  if (t.name) return t.name;
@@ -404,6 +489,43 @@ export function isPointerToStruct(goType, checker = null, mod = null) {
404
489
  return false;
405
490
  }
406
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
+
407
529
  export function isSliceType(goType, checker = null) {
408
530
  if (!goType) return false;
409
531
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
@@ -413,9 +535,38 @@ export function isSliceType(goType, checker = null) {
413
535
  if (goType.kind === "named" && goType.underlying) {
414
536
  return isSliceType(goType.underlying, checker);
415
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
+ }
416
542
  return goType.kind === "slice" || goType.kind === "SliceType";
417
543
  }
418
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
+
419
570
  // `gofront/shared` linear-memory buffer: returns the SHARED_KINDS entry
420
571
  // (elem, ctor, bytes, key) or null.
421
572
  export function getSharedInfo(goType, checker = null) {
@@ -451,6 +602,10 @@ export function isMapType(goType, checker = null) {
451
602
  if (goType.kind === "named" && goType.underlying) {
452
603
  return isMapType(goType.underlying, checker);
453
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
+ }
454
609
  return goType.kind === "map" || goType.kind === "MapType";
455
610
  }
456
611
 
@@ -464,6 +619,11 @@ export function getMapKeyValTypes(goType, checker = null) {
464
619
  if (goType.kind === "named" && goType.underlying) {
465
620
  return getMapKeyValTypes(goType.underlying, checker);
466
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
+ }
467
627
  if (goType.kind === "map" || goType.kind === "MapType") {
468
628
  const key = goType.key ?? goType.keyType;
469
629
  const val =
@@ -483,6 +643,10 @@ export function isStringType(goType, checker = null) {
483
643
  if (goType.kind === "named") {
484
644
  if (goType.name === "string") return true;
485
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);
486
650
  }
487
651
  if (goType.kind === "basic") return goType.name === "string";
488
652
  if (goType.kind === "untyped") return goType.base === "string";
@@ -528,6 +692,14 @@ export function isInterfaceType(goType, checker = null) {
528
692
  return false;
529
693
  }
530
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
+
531
703
  // The built-in `error` interface (checker ERROR type or its name).
532
704
  export function isErrorType(goType) {
533
705
  if (!goType) return false;
@@ -639,7 +811,9 @@ export function getFuncSignature(goType, checker = null) {
639
811
  goType.kind === "FuncLit" ||
640
812
  goType.kind === "FuncDecl"
641
813
  ) {
642
- 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
+ );
643
817
  const returns = [];
644
818
  if (goType.returnType) {
645
819
  if (
@@ -672,11 +846,22 @@ export function getFuncSignature(goType, checker = null) {
672
846
  returns.push(r);
673
847
  }
674
848
  }
675
- return { params: goType.params ?? [], returns };
849
+ return {
850
+ params: variadicAsSlice(goType.params ?? [], goType.variadic),
851
+ returns,
852
+ };
676
853
  }
677
854
  return { params: [], returns: [] };
678
855
  }
679
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
+
680
865
  export function isUnhashableGoType(goType, checker = null, seen = new Set()) {
681
866
  if (!goType) return false;
682
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":
@@ -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":