gofront 1.5.1 → 1.6.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 (60) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +50 -19
  4. package/src/backend/js/index.js +100 -55
  5. package/src/backend/js/runtime.js +102 -0
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +96 -16
  8. package/src/backend/js/stdlib/errors.js +2 -2
  9. package/src/backend/js/stdlib/fmt.js +26 -8
  10. package/src/backend/js/stdlib/index.js +3 -0
  11. package/src/backend/js/stdlib/maps.js +1 -1
  12. package/src/backend/js/stdlib/path.js +2 -2
  13. package/src/backend/js/stdlib/shared.js +17 -0
  14. package/src/backend/js/stdlib/slices.js +1 -1
  15. package/src/backend/js/stdlib/sort.js +1 -1
  16. package/src/backend/js/stdlib/strconv.js +34 -9
  17. package/src/backend/js/stdlib/strings.js +4 -1
  18. package/src/backend/js/stdlib/testing.js +2 -2
  19. package/src/backend/js/stdlib/time.js +3 -3
  20. package/src/backend/js/templ.js +1 -1
  21. package/src/backend/wasm/boundary.js +486 -33
  22. package/src/backend/wasm/emit-builtins.js +1103 -0
  23. package/src/backend/wasm/emit-exprs.js +1908 -0
  24. package/src/backend/wasm/emit-maps.js +1863 -0
  25. package/src/backend/wasm/emit-shared.js +519 -0
  26. package/src/backend/wasm/emit-stdlib.js +3006 -0
  27. package/src/backend/wasm/emit-stmts.js +2472 -0
  28. package/src/backend/wasm/emit.js +606 -5009
  29. package/src/backend/wasm/encode.js +212 -64
  30. package/src/backend/wasm/glue.js +19 -5
  31. package/src/backend/wasm/index.js +1426 -49
  32. package/src/backend/wasm/monomorph.js +711 -0
  33. package/src/backend/wasm/optimize.js +227 -0
  34. package/src/backend/wasm/types.js +159 -35
  35. package/src/backend/wasm/wat.js +49 -2
  36. package/src/cli-core.js +275 -72
  37. package/src/compiler.js +144 -21
  38. package/src/index.js +86 -115
  39. package/src/lower/boxing.js +3 -4
  40. package/src/lower/captures.js +10 -17
  41. package/src/lower/escape.js +4 -9
  42. package/src/lower/functions.js +277 -5
  43. package/src/lower/globals.js +7 -15
  44. package/src/lower/index.js +5 -2
  45. package/src/lower/ownership.js +5 -10
  46. package/src/lower/range.js +2 -5
  47. package/src/lower/walk.js +25 -0
  48. package/src/resolver.js +2 -1
  49. package/src/test-runner.js +20 -0
  50. package/src/typechecker/assignability.js +42 -1
  51. package/src/typechecker/expressions.js +142 -2
  52. package/src/typechecker/index.js +27 -1
  53. package/src/typechecker/resolve.js +35 -6
  54. package/src/typechecker/statements.js +0 -7
  55. package/src/typechecker/stdlib/core.js +17 -19
  56. package/src/typechecker/stdlib/extended.js +18 -3
  57. package/src/typechecker/stdlib/shared.js +53 -0
  58. package/src/typechecker/stdlib.js +2 -0
  59. package/src/typechecker/types.js +206 -7
  60. package/src/vendor.js +2 -2
@@ -10,15 +10,94 @@ import {
10
10
  collectBoundaryMeta,
11
11
  generateFacade,
12
12
  scanBoundaryImports,
13
+ wTypeKey,
13
14
  } from "./boundary.js";
14
15
  import { FunctionEmitter } from "./emit.js";
16
+ import { BINARY_MATH_IMPORTS } from "./emit-stdlib.js";
15
17
  import { encodeModule, isGoFrontWasm } from "./encode.js";
16
- import { getFuncSignature, isTestingT, toWasmType } from "./types.js";
18
+ import { hasGenerics, monomorphise } from "./monomorph.js";
19
+ import { hasNativeWasmOpt, optimizeWasm } from "./optimize.js";
20
+ import {
21
+ ERROR_STRUCT,
22
+ getFuncSignature,
23
+ getMapKeyValTypes,
24
+ getReceiverTypeName,
25
+ isMapType,
26
+ isNonEmptyInterface,
27
+ isTestingT,
28
+ toWasmType,
29
+ } from "./types.js";
17
30
  import { emitWat } from "./wat.js";
18
31
 
19
- export { isGoFrontWasm };
32
+ export { hasNativeWasmOpt, isGoFrontWasm, optimizeWasm };
33
+
34
+ const ERROR_PRODUCING_PKGS = new Set(["errors", "fmt", "strconv"]);
35
+ // Packages whose calls lower to the `env.*` imports in STDLIB_IMPORTS (the
36
+ // matching JS lives in WASM_STDLIB_JS). Only modules importing one of them
37
+ // declare the imports and ship that JS block.
38
+ const STDLIB_ENV_PKGS = new Set([
39
+ "strings",
40
+ "strconv",
41
+ "unicode/utf8",
42
+ "fmt",
43
+ "testing",
44
+ ]);
45
+
46
+ function importsAnyOf(programs, pkgs) {
47
+ return (programs ?? []).some((p) =>
48
+ (p.imports ?? []).some((decl) =>
49
+ (decl.imports ?? [decl]).some((imp) => pkgs.has(imp.path)),
50
+ ),
51
+ );
52
+ }
20
53
 
21
- export class ModuleEmitter {
54
+ // [name, params, results] for `env.*` imports backing stdlib calls; the JS
55
+ // side lives in WASM_STDLIB_JS (boundary.js). All are registered eagerly
56
+ // (when any STDLIB_ENV_PKGS package is imported) because the import table is
57
+ // locked before function bodies are emitted.
58
+ const S = "externref";
59
+ const STDLIB_IMPORTS = [
60
+ // strings
61
+ ["str_trim", [S, S], [S]],
62
+ ["str_trim_left", [S, S], [S]],
63
+ ["str_trim_right", [S, S], [S]],
64
+ ["str_trim_prefix", [S, S], [S]],
65
+ ["str_trim_suffix", [S, S], [S]],
66
+ ["str_replace", [S, S, S, "i32"], [S]],
67
+ ["str_contains_rune", [S, "i32"], ["i32"]],
68
+ ["str_contains_any", [S, S], ["i32"]],
69
+ ["str_index_byte", [S, "i32"], ["i32"]],
70
+ ["str_index_rune", [S, "i32"], ["i32"]],
71
+ ["str_index_any", [S, S], ["i32"]],
72
+ ["str_last_index_byte", [S, "i32"], ["i32"]],
73
+ ["str_title", [S], [S]],
74
+ ["str_split", [S, S], ["i32"]],
75
+ ["str_fields", [S], ["i32"]],
76
+ ["str_part", ["i32"], [S]],
77
+ // strconv
78
+ ["strconv_atoi", [S], ["i64"]],
79
+ ["strconv_parse_int", [S, "i32", "i32"], ["i64"]],
80
+ ["strconv_parse_float", [S], ["f64"]],
81
+ ["strconv_parse_bool", [S], ["i32"]],
82
+ ["strconv_err", [], [S]],
83
+ ["strconv_format_float", ["f64", "i32", "i32", "i32"], [S]],
84
+ ["strconv_format_int", ["i64", "i32"], [S]],
85
+ ["strconv_quote", [S], [S]],
86
+ // unicode/utf8
87
+ ["utf8_rune_count", [S], ["i32"]],
88
+ ["utf8_rune_len", ["i32"], ["i32"]],
89
+ ["utf8_valid_string", [S], ["i32"]],
90
+ ["utf8_decode_rune", [S, "i32"], ["i32"]],
91
+ ["utf8_decode_size", [], ["i32"]],
92
+ ["utf8_full_rune", [S], ["i32"]],
93
+ // fmt (arguments travel through the testing_arg_* buffer)
94
+ ["fmt_sprintf", [S], [S]],
95
+ ["fmt_printf", [S], []],
96
+ // testing.T.Run: the harness calls back into the exported __testing_run_cb
97
+ ["testing_run", [S, S, "anyref"], ["i32"]],
98
+ ];
99
+
100
+ class ModuleEmitter {
22
101
  constructor(checker, lowerResult = null, programs = [], options = {}) {
23
102
  this.checker = checker;
24
103
  this.lowerResult = lowerResult;
@@ -37,6 +116,9 @@ export class ModuleEmitter {
37
116
  this.arrayTypes = new Map(); // wType key -> array info
38
117
  this.sliceTypes = new Map(); // wType key -> slice info
39
118
  this.sliceTypesByIndex = new Map(); // typeIndex -> slice info
119
+ this.mapTypes = new Map(); // wType key -> map info
120
+ this.mapTypesByIndex = new Map(); // typeIndex -> map info
121
+ this.mapBucketsType = null; // shared (array (mut i32))
40
122
  this.closureTypes = new Map(); // key -> closure info
41
123
  this.envTypes = new Map(); // key -> env info
42
124
 
@@ -54,11 +136,18 @@ export class ModuleEmitter {
54
136
  this.funcMap = new Map(); // name -> global func index
55
137
  this.funcParamTypes = new Map(); // name -> param types array
56
138
  this.funcParamGoTypes = new Map(); // name -> param Go types array
139
+ this.funcReturnTypesByIdx = new Map(); // global func index -> result wasm types
57
140
 
58
141
  this.exports = []; // export entries
59
142
  this.stringTable = []; // string literals
60
143
  this.stringCache = new Map();
61
144
 
145
+ // `errors.New` / `fmt.Errorf` / strconv parse failures allocate the
146
+ // runtime's own error struct; it joins the struct rec group so the
147
+ // `error.Error` dispatcher can `ref.test` it like any user type.
148
+ this.hasErrorType = importsAnyOf(programs, ERROR_PRODUCING_PKGS);
149
+ this.usesStdlibEnv = importsAnyOf(programs, STDLIB_ENV_PKGS);
150
+
62
151
  if (programs && programs.length > 0) {
63
152
  this.initTypes(programs);
64
153
  }
@@ -73,6 +162,20 @@ export class ModuleEmitter {
73
162
  const seen = new Set();
74
163
  const aliases = []; // qualified key (pkg.T) -> unqualified struct name
75
164
 
165
+ if (this.hasErrorType) {
166
+ seen.add(ERROR_STRUCT);
167
+ rawStructs.push({
168
+ name: ERROR_STRUCT,
169
+ structType: {
170
+ kind: "struct",
171
+ fields: new Map([
172
+ ["msg", { kind: "basic", name: "string" }],
173
+ ["cause", { kind: "basic", name: "any" }],
174
+ ]),
175
+ },
176
+ });
177
+ }
178
+
76
179
  for (const p of progs) {
77
180
  for (const d of p.decls ?? []) {
78
181
  if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
@@ -136,7 +239,8 @@ export class ModuleEmitter {
136
239
  for (let i = 0; i < rawStructs.length; i++) {
137
240
  const s = rawStructs[i];
138
241
  const info = this.structTypes.get(s.name);
139
- const resolvedStruct = this.checker?.types?.get(s.name)?.underlying;
242
+ const resolvedStruct =
243
+ s.structType ?? this.checker?.types?.get(s.name)?.underlying;
140
244
 
141
245
  const fields = [];
142
246
  const fieldIndexMap = new Map();
@@ -308,6 +412,107 @@ export class ModuleEmitter {
308
412
  return this.sliceTypesByIndex?.get(typeIndex) ?? null;
309
413
  }
310
414
 
415
+ getMapType(keyGoType, valGoType) {
416
+ const keyWType = toWasmType(keyGoType, this.checker, this);
417
+ const valWType = toWasmType(valGoType, this.checker, this);
418
+ const key = `${wTypeKey(keyWType)}:${wTypeKey(valWType)}`;
419
+ if (this.mapTypes.has(key)) {
420
+ return this.mapTypes.get(key);
421
+ }
422
+
423
+ if (!this.mapBucketsType) {
424
+ const typeIndex = this._getTotalTypeCount();
425
+ this._pushType({
426
+ form: "array",
427
+ elemType: "i32",
428
+ mutable: true,
429
+ });
430
+ this.mapBucketsType = { typeIndex };
431
+ }
432
+
433
+ const entryTypeIndex = this._getTotalTypeCount();
434
+ this._pushType({
435
+ form: "struct",
436
+ fields: [
437
+ { type: keyWType, mutable: true }, // 0: key
438
+ { type: valWType, mutable: true }, // 1: val
439
+ { type: "i32", mutable: true }, // 2: next (bucket chain)
440
+ { type: "i32", mutable: true }, // 3: order_prev (insertion order)
441
+ { type: "i32", mutable: true }, // 4: order_next (insertion order)
442
+ { type: "i32", mutable: true }, // 5: active (1 = active, 0 = deleted)
443
+ ],
444
+ });
445
+
446
+ const entriesTypeIndex = this._getTotalTypeCount();
447
+ this._pushType({
448
+ form: "array",
449
+ elemType: { kind: "ref", nullable: true, typeIndex: entryTypeIndex },
450
+ mutable: true,
451
+ });
452
+
453
+ const mapTypeIndex = this._getTotalTypeCount();
454
+ this._pushType({
455
+ form: "struct",
456
+ fields: [
457
+ {
458
+ type: {
459
+ kind: "ref",
460
+ nullable: true,
461
+ typeIndex: this.mapBucketsType.typeIndex,
462
+ },
463
+ mutable: true,
464
+ }, // 0: buckets
465
+ {
466
+ type: {
467
+ kind: "ref",
468
+ nullable: true,
469
+ typeIndex: entriesTypeIndex,
470
+ },
471
+ mutable: true,
472
+ }, // 1: entries
473
+ { type: "i32", mutable: true }, // 2: len
474
+ { type: "i32", mutable: true }, // 3: cap
475
+ { type: "i32", mutable: true }, // 4: count
476
+ { type: "i32", mutable: true }, // 5: head
477
+ { type: "i32", mutable: true }, // 6: tail
478
+ { type: "i32", mutable: true }, // 7: free_head
479
+ { type: "i32", mutable: true }, // 8: num_buckets
480
+ ],
481
+ });
482
+
483
+ const suffix = key.replace(/[^a-zA-Z0-9_]/g, "_");
484
+ const mapInfo = {
485
+ key,
486
+ suffix,
487
+ typeIndex: mapTypeIndex,
488
+ entryTypeIndex,
489
+ entriesTypeIndex,
490
+ bucketsTypeIndex: this.mapBucketsType.typeIndex,
491
+ keyWType,
492
+ valWType,
493
+ keyGoType,
494
+ valGoType,
495
+ wType: { kind: "ref", nullable: true, typeIndex: mapTypeIndex },
496
+ makeFuncName: `__map_make_${suffix}`,
497
+ getFuncName: `__map_get_${suffix}`,
498
+ getOkFuncName: `__map_get_ok_${suffix}`,
499
+ setFuncName: `__map_set_${suffix}`,
500
+ deleteFuncName: `__map_delete_${suffix}`,
501
+ lenFuncName: `__map_len_${suffix}`,
502
+ clearFuncName: `__map_clear_${suffix}`,
503
+ keysFuncName: `__map_keys_${suffix}`,
504
+ valuesFuncName: `__map_values_${suffix}`,
505
+ cloneFuncName: `__map_clone_${suffix}`,
506
+ };
507
+ this.mapTypes.set(key, mapInfo);
508
+ this.mapTypesByIndex.set(mapTypeIndex, mapInfo);
509
+ return mapInfo;
510
+ }
511
+
512
+ getMapTypeByIndex(typeIndex) {
513
+ return this.mapTypesByIndex?.get(typeIndex) ?? null;
514
+ }
515
+
311
516
  getClosureType(goType) {
312
517
  const sig = getFuncSignature(goType, this.checker);
313
518
  const paramWTypes = sig.params
@@ -399,9 +604,11 @@ export class ModuleEmitter {
399
604
 
400
605
  _initRuntimeImports() {
401
606
  this._initPanicTag();
607
+ this._initPanicGlobal();
402
608
  this.getPanicImportIndex(); // env.panic
403
609
  this.internString("runtime error: index out of range");
404
610
  this.internString("runtime error: slice bounds out of range");
611
+ this.internString("assignment to entry in nil map");
405
612
 
406
613
  // String built-ins
407
614
  this.getStringImportIndex(); // env.str
@@ -417,7 +624,35 @@ export class ModuleEmitter {
417
624
  this.getStringSliceImportIndex(); // env.str_slice
418
625
  this.getStringFromCodePointImportIndex(); // env.str_from_code_point
419
626
  this.getStringCodePointAtImportIndex(); // env.str_code_point_at
627
+ this.getStringHashImportIndex(); // env.str_hash
628
+ this.getStringToUpperImportIndex();
629
+ this.getStringToLowerImportIndex();
630
+ this.getStringTrimSpaceImportIndex();
631
+ this.getStringContainsImportIndex();
632
+ this.getStringHasPrefixImportIndex();
633
+ this.getStringHasSuffixImportIndex();
634
+ this.getStringIndexImportIndex();
635
+ this.getStringLastIndexImportIndex();
636
+ this.getStringRepeatImportIndex();
637
+ this.getStringReplaceAllImportIndex();
638
+ this.getStringEqualFoldImportIndex();
639
+ this.getStringCountImportIndex();
640
+ this.getStrFromI64ImportIndex();
641
+ this.getStrFromI32ImportIndex();
642
+ this.getStrFromF64ImportIndex();
420
643
  this.getIsStringImportIndex(); // env.is_string
644
+ this.getTestingCallImportIndex();
645
+ this.getTestingNameImportIndex();
646
+ this.getTestingFlagImportIndex();
647
+ for (const t of ["i32", "i64", "f32", "f64", "externref", "anyref"]) {
648
+ this.getTestingArgImportIndex(t, false);
649
+ }
650
+ this.getTestingArgImportIndex("i32", true);
651
+ if (this.usesStdlibEnv) {
652
+ for (const [name, params, results] of STDLIB_IMPORTS) {
653
+ this.getOrAddFuncImport("env", name, params, results);
654
+ }
655
+ }
421
656
 
422
657
  // Logging built-ins
423
658
  this.getPrintlnEmptyIndex(); // env.println_empty
@@ -442,12 +677,12 @@ export class ModuleEmitter {
442
677
  "log2",
443
678
  "log10",
444
679
  "round",
680
+ "cbrt",
445
681
  ];
446
682
  for (const m of unaryMath) {
447
683
  this.getMathImportIndex(m, false);
448
684
  }
449
- const binaryMath = ["atan2", "pow"];
450
- for (const m of binaryMath) {
685
+ for (const m of BINARY_MATH_IMPORTS) {
451
686
  this.getMathImportIndex(m, true);
452
687
  }
453
688
  }
@@ -461,6 +696,114 @@ export class ModuleEmitter {
461
696
  this.exports.push({ name: "panicTag", kind: "tag", index: 0 });
462
697
  }
463
698
 
699
+ getPanicNodeTypeIndex() {
700
+ if (this.panicNodeTypeIndex !== undefined) {
701
+ return this.panicNodeTypeIndex;
702
+ }
703
+ const typeIndex = this._getTotalTypeCount();
704
+ const typeEntry = {
705
+ form: "rec",
706
+ types: [
707
+ {
708
+ form: "struct",
709
+ fields: [
710
+ {
711
+ type: "anyref",
712
+ mutable: true,
713
+ },
714
+ {
715
+ type: "i32",
716
+ mutable: true,
717
+ },
718
+ {
719
+ type: {
720
+ kind: "ref",
721
+ nullable: true,
722
+ typeIndex,
723
+ },
724
+ mutable: true,
725
+ },
726
+ ],
727
+ },
728
+ ],
729
+ };
730
+ this._pushType(typeEntry);
731
+ this.panicNodeTypeIndex = typeIndex;
732
+ return typeIndex;
733
+ }
734
+
735
+ _initPanicGlobal() {
736
+ const pType = this.getPanicNodeTypeIndex();
737
+ const gIdx = this.globals.length;
738
+ this.globals.push({
739
+ type: { kind: "ref", nullable: true, typeIndex: pType },
740
+ mutable: true,
741
+ init: [{ op: "ref.null", typeIndex: pType }],
742
+ });
743
+ this.panicGlobalIndex = gIdx;
744
+ this.exports.push({ name: "__panic", kind: "global", index: gIdx });
745
+ }
746
+
747
+ getPanicGlobalIndex() {
748
+ return this.panicGlobalIndex;
749
+ }
750
+
751
+ // `__deferArmed` is set to 1 immediately before a deferred closure is
752
+ // invoked and cleared by that closure's prologue. recover() only takes
753
+ // effect when the current frame observed the flag, which gives Go's
754
+ // "recover must be called directly by the deferred function" rule.
755
+ getDeferArmedGlobalIndex() {
756
+ if (this.deferArmedGlobalIndex !== undefined) {
757
+ return this.deferArmedGlobalIndex;
758
+ }
759
+ const gIdx = this.globals.length;
760
+ this.globals.push({
761
+ type: "i32",
762
+ mutable: true,
763
+ init: [{ op: "i32.const", value: 0 }],
764
+ });
765
+ this.deferArmedGlobalIndex = gIdx;
766
+ return gIdx;
767
+ }
768
+
769
+ getDeferNodeTypeIndex() {
770
+ if (this.deferNodeTypeIndex !== undefined) {
771
+ return this.deferNodeTypeIndex;
772
+ }
773
+ const deferClosureSig = { kind: "Signature", params: [], results: [] };
774
+ const deferClosureInfo = this.getClosureType(deferClosureSig);
775
+ const typeIndex = this._getTotalTypeCount();
776
+ const typeEntry = {
777
+ form: "rec",
778
+ types: [
779
+ {
780
+ form: "struct",
781
+ fields: [
782
+ {
783
+ type: {
784
+ kind: "ref",
785
+ nullable: true,
786
+ typeIndex: deferClosureInfo.typeIndex,
787
+ },
788
+ mutable: false,
789
+ },
790
+ {
791
+ type: {
792
+ kind: "ref",
793
+ nullable: true,
794
+ typeIndex,
795
+ },
796
+ mutable: true,
797
+ },
798
+ ],
799
+ },
800
+ ],
801
+ };
802
+ this._pushType(typeEntry);
803
+ this.deferNodeTypeIndex = typeIndex;
804
+ return typeIndex;
805
+ }
806
+
464
807
  getTypeIndex(params, results) {
465
808
  const key = `${params.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${results.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
466
809
  if (this.typeCache.has(key)) {
@@ -518,6 +861,32 @@ export class ModuleEmitter {
518
861
  return this.resolveFuncIndex("__bounds_panic");
519
862
  }
520
863
 
864
+ // `gofront/shared` buffers are `(base i32, len i32)` views over the single
865
+ // exported linear memory, bump-allocated by `__shared_new` during start.
866
+ getSharedType() {
867
+ if (this.sharedType) return this.sharedType;
868
+ const typeIndex = this._getTotalTypeCount();
869
+ const typeEntry = {
870
+ form: "struct",
871
+ fields: [
872
+ { type: "i32", mutable: false }, // byte base
873
+ { type: "i32", mutable: false }, // element count
874
+ ],
875
+ };
876
+ this._pushType(typeEntry);
877
+ this.memory = { min: 1 };
878
+ this.exports.push({ name: "memory", kind: "memory", index: 0 });
879
+ const topGlobalIndex = this.globals.length;
880
+ this.globals.push({
881
+ type: "i32",
882
+ mutable: true,
883
+ init: [{ op: "i32.const", value: 0 }],
884
+ });
885
+ const wType = { kind: "ref", nullable: true, typeIndex };
886
+ this.sharedType = { typeIndex, typeEntry, wType, topGlobalIndex };
887
+ return this.sharedType;
888
+ }
889
+
521
890
  getSliceBoundsPanicFuncIndex() {
522
891
  return this.resolveFuncIndex("__slice_bounds_panic");
523
892
  }
@@ -535,7 +904,7 @@ export class ModuleEmitter {
535
904
  wType === "externref" ? "str" : wType === "anyref" ? "any" : wType;
536
905
  if (!["i32", "i64", "f32", "f64", "str", "any"].includes(suffix)) {
537
906
  throw new Error(
538
- "only primitive, string and any arguments are supported for testing.T methods in wasm packages",
907
+ "only primitive, string, error and any arguments are supported for fmt and testing.T calls in wasm packages (format slices/structs field by field)",
539
908
  );
540
909
  }
541
910
  return this.getOrAddFuncImport(
@@ -594,9 +963,15 @@ export class ModuleEmitter {
594
963
  ["externref"],
595
964
  [],
596
965
  );
597
- case "anyref":
598
- return this.getOrAddFuncImport("env", `${prefix}_any`, ["anyref"], []);
599
966
  default:
967
+ if (typeof wType === "object") {
968
+ return this.getOrAddFuncImport(
969
+ "env",
970
+ `${prefix}_any`,
971
+ ["anyref"],
972
+ [],
973
+ );
974
+ }
600
975
  return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
601
976
  }
602
977
  }
@@ -672,11 +1047,166 @@ export class ModuleEmitter {
672
1047
  return this.getOrAddFuncImport("env", "is_string", ["anyref"], ["i32"]);
673
1048
  }
674
1049
 
1050
+ getStringHashImportIndex() {
1051
+ return this.getOrAddFuncImport("env", "str_hash", ["externref"], ["i32"]);
1052
+ }
1053
+
1054
+ getStringToUpperImportIndex() {
1055
+ return this.getOrAddFuncImport(
1056
+ "env",
1057
+ "str_to_upper",
1058
+ ["externref"],
1059
+ ["externref"],
1060
+ );
1061
+ }
1062
+
1063
+ getStringToLowerImportIndex() {
1064
+ return this.getOrAddFuncImport(
1065
+ "env",
1066
+ "str_to_lower",
1067
+ ["externref"],
1068
+ ["externref"],
1069
+ );
1070
+ }
1071
+
1072
+ getStringTrimSpaceImportIndex() {
1073
+ return this.getOrAddFuncImport(
1074
+ "env",
1075
+ "str_trim_space",
1076
+ ["externref"],
1077
+ ["externref"],
1078
+ );
1079
+ }
1080
+
1081
+ getStringContainsImportIndex() {
1082
+ return this.getOrAddFuncImport(
1083
+ "env",
1084
+ "str_contains",
1085
+ ["externref", "externref"],
1086
+ ["i32"],
1087
+ );
1088
+ }
1089
+
1090
+ getStringHasPrefixImportIndex() {
1091
+ return this.getOrAddFuncImport(
1092
+ "env",
1093
+ "str_has_prefix",
1094
+ ["externref", "externref"],
1095
+ ["i32"],
1096
+ );
1097
+ }
1098
+
1099
+ getStringHasSuffixImportIndex() {
1100
+ return this.getOrAddFuncImport(
1101
+ "env",
1102
+ "str_has_suffix",
1103
+ ["externref", "externref"],
1104
+ ["i32"],
1105
+ );
1106
+ }
1107
+
1108
+ getStringIndexImportIndex() {
1109
+ return this.getOrAddFuncImport(
1110
+ "env",
1111
+ "str_index",
1112
+ ["externref", "externref"],
1113
+ ["i32"],
1114
+ );
1115
+ }
1116
+
1117
+ getStringLastIndexImportIndex() {
1118
+ return this.getOrAddFuncImport(
1119
+ "env",
1120
+ "str_last_index",
1121
+ ["externref", "externref"],
1122
+ ["i32"],
1123
+ );
1124
+ }
1125
+
1126
+ getStringRepeatImportIndex() {
1127
+ return this.getOrAddFuncImport(
1128
+ "env",
1129
+ "str_repeat",
1130
+ ["externref", "i32"],
1131
+ ["externref"],
1132
+ );
1133
+ }
1134
+
1135
+ getStringReplaceAllImportIndex() {
1136
+ return this.getOrAddFuncImport(
1137
+ "env",
1138
+ "str_replace_all",
1139
+ ["externref", "externref", "externref"],
1140
+ ["externref"],
1141
+ );
1142
+ }
1143
+
1144
+ getStringEqualFoldImportIndex() {
1145
+ return this.getOrAddFuncImport(
1146
+ "env",
1147
+ "str_equal_fold",
1148
+ ["externref", "externref"],
1149
+ ["i32"],
1150
+ );
1151
+ }
1152
+
1153
+ getStringCountImportIndex() {
1154
+ return this.getOrAddFuncImport(
1155
+ "env",
1156
+ "str_count",
1157
+ ["externref", "externref"],
1158
+ ["i32"],
1159
+ );
1160
+ }
1161
+
1162
+ getStrFromI64ImportIndex() {
1163
+ return this.getOrAddFuncImport(
1164
+ "env",
1165
+ "str_from_i64",
1166
+ ["i64"],
1167
+ ["externref"],
1168
+ );
1169
+ }
1170
+
1171
+ getStrFromI32ImportIndex() {
1172
+ return this.getOrAddFuncImport(
1173
+ "env",
1174
+ "str_from_i32",
1175
+ ["i32"],
1176
+ ["externref"],
1177
+ );
1178
+ }
1179
+
1180
+ getStrFromF64ImportIndex() {
1181
+ return this.getOrAddFuncImport(
1182
+ "env",
1183
+ "str_from_f64",
1184
+ ["f64"],
1185
+ ["externref"],
1186
+ );
1187
+ }
1188
+
675
1189
  getMathImportIndex(jsName, isBinary = false) {
676
1190
  const params = isBinary ? ["f64", "f64"] : ["f64"];
677
1191
  return this.getOrAddFuncImport("Math", jsName, params, ["f64"]);
678
1192
  }
679
1193
 
1194
+ // Imports from the STDLIB_IMPORTS table (strings/strconv/utf8/fmt/testing.Run).
1195
+ getStdlibImportIndex(name) {
1196
+ const idx = this.importCache.get(`env.${name}`);
1197
+ if (idx === undefined) throw new Error(`Unknown runtime import: ${name}`);
1198
+ return idx;
1199
+ }
1200
+
1201
+ // Closure type of `func(*testing.T)` (the t.Run body).
1202
+ getTestingRunClosureType() {
1203
+ return this.getClosureType({
1204
+ kind: "func",
1205
+ params: [{ kind: "pointer", base: { kind: "named", name: "testing.T" } }],
1206
+ returns: [],
1207
+ });
1208
+ }
1209
+
680
1210
  resolveFuncIndex(name) {
681
1211
  return this.funcMap.get(name) ?? null;
682
1212
  }
@@ -700,9 +1230,220 @@ export class ModuleEmitter {
700
1230
  isNonLiteralConst(name) {
701
1231
  return this.nonLiteralConsts.has(name);
702
1232
  }
1233
+
1234
+ // Interface method dispatch is a `ref.test` chain over every concrete type
1235
+ // that declares `methodName` (no itabs); the static interface type is not
1236
+ // needed to build the candidate list.
1237
+ findInterfaceCandidates(_ifaceType, methodName) {
1238
+ const candidates = [];
1239
+ const seenKeys = new Set();
1240
+
1241
+ // 1. Struct types
1242
+ for (const [sName, sInfo] of this.structTypes.entries()) {
1243
+ const directMethodName = `${sName}.${methodName}`;
1244
+ if (this.funcMap.has(directMethodName)) {
1245
+ const fIdx = this.resolveFuncIndex(directMethodName);
1246
+ const paramGoTypes = this.getFuncParamGoTypes(directMethodName);
1247
+ const paramWTypes = this.getFuncParamTypes(directMethodName);
1248
+ const recvGoType = paramGoTypes[0];
1249
+ const isPtrRecv = recvGoType?.kind === "pointer";
1250
+
1251
+ if (isPtrRecv) {
1252
+ const key = `ptr:${sName}`;
1253
+ if (!seenKeys.has(key)) {
1254
+ seenKeys.add(key);
1255
+ candidates.push({
1256
+ recvTypeName: sName,
1257
+ testTypeIndex: sInfo.typeIndex,
1258
+ isBoxedValue: false,
1259
+ needsValueDeref: false,
1260
+ embedPath: null,
1261
+ funcIndex: fIdx,
1262
+ targetParamTypes: paramWTypes,
1263
+ structInfo: sInfo,
1264
+ });
1265
+ }
1266
+ } else {
1267
+ // Value receiver: both *sName and sName match
1268
+ const ptrKey = `ptr:${sName}`;
1269
+ if (!seenKeys.has(ptrKey)) {
1270
+ seenKeys.add(ptrKey);
1271
+ candidates.push({
1272
+ recvTypeName: sName,
1273
+ testTypeIndex: sInfo.typeIndex,
1274
+ isBoxedValue: false,
1275
+ needsValueDeref: true,
1276
+ embedPath: null,
1277
+ funcIndex: fIdx,
1278
+ targetParamTypes: paramWTypes,
1279
+ targetRecvWType: paramWTypes[0],
1280
+ structInfo: sInfo,
1281
+ });
1282
+ }
1283
+ const box = this.getBoxType({ kind: "named", name: sName });
1284
+ const valKey = `val:${sName}`;
1285
+ if (box && !seenKeys.has(valKey)) {
1286
+ seenKeys.add(valKey);
1287
+ candidates.push({
1288
+ recvTypeName: sName,
1289
+ testTypeIndex: box.typeIndex,
1290
+ isBoxedValue: true,
1291
+ needsValueDeref: false,
1292
+ embedPath: null,
1293
+ funcIndex: fIdx,
1294
+ targetParamTypes: paramWTypes,
1295
+ targetRecvWType: paramWTypes[0],
1296
+ structInfo: sInfo,
1297
+ });
1298
+ }
1299
+ }
1300
+ }
1301
+
1302
+ // Promoted methods on sName
1303
+ if (sInfo.embeds && sInfo.embeds.length > 0) {
1304
+ for (const embed of sInfo.embeds) {
1305
+ const embedMethodName = `${embed.name}.${methodName}`;
1306
+ if (this.funcMap.has(embedMethodName)) {
1307
+ const fIdx = this.resolveFuncIndex(embedMethodName);
1308
+ const paramGoTypes = this.getFuncParamGoTypes(embedMethodName);
1309
+ const paramWTypes = this.getFuncParamTypes(embedMethodName);
1310
+ const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
1311
+ const embedSInfo = this.getStructType(embed.name);
1312
+
1313
+ const ptrKey = `ptr:${sName}`;
1314
+ if (!seenKeys.has(ptrKey)) {
1315
+ seenKeys.add(ptrKey);
1316
+ candidates.push({
1317
+ recvTypeName: sName,
1318
+ testTypeIndex: sInfo.typeIndex,
1319
+ isBoxedValue: false,
1320
+ needsValueDeref: !isPtrRecv,
1321
+ embedPath: [
1322
+ {
1323
+ parentTypeIndex: sInfo.typeIndex,
1324
+ fieldIndex: embed.fieldIndex,
1325
+ },
1326
+ ],
1327
+ funcIndex: fIdx,
1328
+ targetParamTypes: paramWTypes,
1329
+ targetRecvWType: paramWTypes[0],
1330
+ structInfo: embedSInfo,
1331
+ });
1332
+ }
1333
+
1334
+ if (!isPtrRecv) {
1335
+ const box = this.getBoxType({ kind: "named", name: sName });
1336
+ const valKey = `val:${sName}`;
1337
+ if (box && !seenKeys.has(valKey)) {
1338
+ seenKeys.add(valKey);
1339
+ candidates.push({
1340
+ recvTypeName: sName,
1341
+ testTypeIndex: box.typeIndex,
1342
+ isBoxedValue: true,
1343
+ needsValueDeref: false,
1344
+ embedPath: [
1345
+ {
1346
+ parentTypeIndex: sInfo.typeIndex,
1347
+ fieldIndex: embed.fieldIndex,
1348
+ },
1349
+ ],
1350
+ funcIndex: fIdx,
1351
+ targetParamTypes: paramWTypes,
1352
+ targetRecvWType: paramWTypes[0],
1353
+ structInfo: embedSInfo,
1354
+ });
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ // 2. Named non-struct types
1363
+ for (const [fnName, fIdx] of this.funcMap.entries()) {
1364
+ if (fnName.endsWith(`.${methodName}`)) {
1365
+ const rName = fnName.slice(0, fnName.length - methodName.length - 1);
1366
+ if (!this.structTypes.has(rName)) {
1367
+ const box = this.getBoxType({ kind: "named", name: rName });
1368
+ const key = `named:${rName}`;
1369
+ if (box && !seenKeys.has(key)) {
1370
+ seenKeys.add(key);
1371
+ const paramWTypes = this.getFuncParamTypes(fnName);
1372
+ candidates.push({
1373
+ recvTypeName: rName,
1374
+ testTypeIndex: box.typeIndex,
1375
+ isBoxedValue: true,
1376
+ needsValueDeref: false,
1377
+ embedPath: null,
1378
+ funcIndex: fIdx,
1379
+ targetParamTypes: paramWTypes,
1380
+ });
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+
1386
+ return candidates;
1387
+ }
1388
+
1389
+ getTypesImplementingInterface(ifaceType) {
1390
+ const candidates = [];
1391
+ const seen = new Set();
1392
+ const ifaceResolved =
1393
+ this.checker?.resolveType?.(
1394
+ ifaceType?.kind === "named" ? ifaceType.underlying : ifaceType,
1395
+ ) ?? ifaceType;
1396
+
1397
+ // 1. Struct types
1398
+ for (const [sName, sInfo] of this.structTypes.entries()) {
1399
+ const namedType = this.checker?.types?.get(sName) ?? {
1400
+ kind: "named",
1401
+ name: sName,
1402
+ };
1403
+ const ptrType = { kind: "pointer", base: namedType };
1404
+
1405
+ // Check pointer to struct
1406
+ if (this.checker?.implements?.(ptrType, ifaceResolved)) {
1407
+ if (!seen.has(sInfo.typeIndex)) {
1408
+ seen.add(sInfo.typeIndex);
1409
+ candidates.push({ name: `*${sName}`, typeIndex: sInfo.typeIndex });
1410
+ }
1411
+ }
1412
+
1413
+ // Check struct value (boxed)
1414
+ if (this.checker?.implements?.(namedType, ifaceResolved)) {
1415
+ const box = this.getBoxType(namedType);
1416
+ if (box && !seen.has(box.typeIndex)) {
1417
+ seen.add(box.typeIndex);
1418
+ candidates.push({ name: sName, typeIndex: box.typeIndex });
1419
+ }
1420
+ }
1421
+ }
1422
+
1423
+ // 2. Named non-struct types that have methods
1424
+ if (this.checker?.types) {
1425
+ for (const [tName, tVal] of this.checker.types.entries()) {
1426
+ if (
1427
+ tVal?.kind === "named" &&
1428
+ tVal.underlying?.kind !== "struct" &&
1429
+ tVal.underlying?.kind !== "interface"
1430
+ ) {
1431
+ if (this.checker.implements(tVal, ifaceResolved)) {
1432
+ const box = this.getBoxType(tVal);
1433
+ if (box && !seen.has(box.typeIndex)) {
1434
+ seen.add(box.typeIndex);
1435
+ candidates.push({ name: tName, typeIndex: box.typeIndex });
1436
+ }
1437
+ }
1438
+ }
1439
+ }
1440
+ }
1441
+
1442
+ return candidates;
1443
+ }
703
1444
  }
704
1445
 
705
- export function peepholeOptimize(instructions) {
1446
+ function peepholeOptimize(instructions) {
706
1447
  let changed = true;
707
1448
  let current = instructions;
708
1449
  while (changed) {
@@ -768,9 +1509,20 @@ export function compileWasmModule(
768
1509
  lowerResult = null,
769
1510
  options = {},
770
1511
  ) {
771
- const progs = Array.isArray(programs) ? programs : [programs];
772
- const resolvedLowerResult = lowerResult ?? lower(progs, checker);
773
- const mod = new ModuleEmitter(checker, resolvedLowerResult, progs, {
1512
+ let progs = Array.isArray(programs) ? programs : [programs];
1513
+ let resolvedChecker = checker;
1514
+ let resolvedLowerResult = lowerResult;
1515
+
1516
+ if (hasGenerics(progs)) {
1517
+ const mono = monomorphise(progs, checker);
1518
+ progs = mono.programs;
1519
+ resolvedChecker = mono.checker;
1520
+ resolvedLowerResult = lower(progs, resolvedChecker);
1521
+ } else if (!resolvedLowerResult) {
1522
+ resolvedLowerResult = lower(progs, resolvedChecker);
1523
+ }
1524
+
1525
+ const mod = new ModuleEmitter(resolvedChecker, resolvedLowerResult, progs, {
774
1526
  bundledPackages: options.bundledPackages,
775
1527
  });
776
1528
 
@@ -785,6 +1537,7 @@ export function compileWasmModule(
785
1537
  if (d.kind === "FuncDecl") {
786
1538
  d._pkgName = pkgName;
787
1539
  d._pkgTarget = pkgTarget;
1540
+ d._srcFile = p._path ?? p._filename ?? null;
788
1541
  if (d.name === "init" || d.name.startsWith("init$")) {
789
1542
  const renamed = initCount === 0 ? "init" : `init$${initCount}`;
790
1543
  d.name = renamed;
@@ -812,6 +1565,8 @@ export function compileWasmModule(
812
1565
  _sourceDecl: d,
813
1566
  _pkgName: pkgName,
814
1567
  _pkgTarget: pkgTarget,
1568
+ _srcFile: p._path ?? p._filename ?? null,
1569
+ _line: d._line,
815
1570
  };
816
1571
  funcDecls.push(normFn);
817
1572
  }
@@ -846,6 +1601,8 @@ export function compileWasmModule(
846
1601
  _isClosure: true,
847
1602
  _funcLit: node,
848
1603
  _rootFuncDecl: rootFn,
1604
+ _srcFile: rootFn?._srcFile ?? null,
1605
+ _line: typeof node._line === "number" ? node._line : rootFn?._line,
849
1606
  };
850
1607
  node._liftedFn = liftedFn;
851
1608
  closures.push(liftedFn);
@@ -956,7 +1713,352 @@ export function compileWasmModule(
956
1713
  };
957
1714
  funcDecls.push(sliceBoundsPanicFn);
958
1715
 
959
- // 1d. Boundary metadata (exported surface of wasm packages)
1716
+ const pushPanicFn = {
1717
+ kind: "FuncDecl",
1718
+ name: "__push_panic",
1719
+ params: [{ name: "msg", type: { kind: "basic", name: "string" } }],
1720
+ returnType: null,
1721
+ _isPushPanic: true,
1722
+ };
1723
+ funcDecls.push(pushPanicFn);
1724
+
1725
+ // 1d. Interface dispatchers
1726
+ _collectInterfaceDispatchers(progs, funcDecls, mod, checker);
1727
+
1728
+ // 1e. Map helpers
1729
+ function scanMapTypes(node) {
1730
+ if (!node || typeof node !== "object") return;
1731
+ if (Array.isArray(node)) {
1732
+ for (const item of node) scanMapTypes(item);
1733
+ return;
1734
+ }
1735
+ if (node._type && isMapType(node._type, checker)) {
1736
+ const { keyType, valType } = getMapKeyValTypes(node._type, checker);
1737
+ if (keyType && valType) {
1738
+ mod.getMapType(keyType, valType);
1739
+ }
1740
+ }
1741
+ if (node.kind === "MapType") {
1742
+ const keyGoType = node.key;
1743
+ const valGoType = node.value ?? node.elem;
1744
+ if (keyGoType && valGoType) {
1745
+ mod.getMapType(keyGoType, valGoType);
1746
+ }
1747
+ }
1748
+ for (const key of Object.keys(node)) {
1749
+ if (key.startsWith("_")) continue;
1750
+ scanMapTypes(node[key]);
1751
+ }
1752
+ }
1753
+
1754
+ for (const fn of funcDecls) {
1755
+ if (fn.params) scanMapTypes(fn.params);
1756
+ if (fn.returnType) scanMapTypes(fn.returnType);
1757
+ if (fn.body) scanMapTypes(fn.body);
1758
+ }
1759
+ for (const p of progs) {
1760
+ for (const d of p.decls ?? []) {
1761
+ scanMapTypes(d);
1762
+ }
1763
+ }
1764
+
1765
+ for (const mapInfo of mod.mapTypes.values()) {
1766
+ // make
1767
+ funcDecls.push({
1768
+ kind: "FuncDecl",
1769
+ name: mapInfo.makeFuncName,
1770
+ params: [{ name: "cap", type: { kind: "basic", name: "int32" } }],
1771
+ returnType: mapInfo.wType,
1772
+ body: { kind: "Block", stmts: [], list: [] },
1773
+ _isMapHelper: true,
1774
+ _mapHelperKind: "make",
1775
+ _mapInfo: mapInfo,
1776
+ _rootFuncDecl: null,
1777
+ });
1778
+ // get
1779
+ funcDecls.push({
1780
+ kind: "FuncDecl",
1781
+ name: mapInfo.getFuncName,
1782
+ params: [
1783
+ { name: "m", type: mapInfo.wType },
1784
+ { name: "k", type: mapInfo.keyGoType },
1785
+ ],
1786
+ returnType: mapInfo.valGoType,
1787
+ body: { kind: "Block", stmts: [], list: [] },
1788
+ _isMapHelper: true,
1789
+ _mapHelperKind: "get",
1790
+ _mapInfo: mapInfo,
1791
+ _rootFuncDecl: null,
1792
+ });
1793
+ // get_ok
1794
+ funcDecls.push({
1795
+ kind: "FuncDecl",
1796
+ name: mapInfo.getOkFuncName,
1797
+ params: [
1798
+ { name: "m", type: mapInfo.wType },
1799
+ { name: "k", type: mapInfo.keyGoType },
1800
+ ],
1801
+ returnType: {
1802
+ kind: "tuple",
1803
+ types: [mapInfo.valGoType, { kind: "basic", name: "bool" }],
1804
+ },
1805
+ body: { kind: "Block", stmts: [], list: [] },
1806
+ _isMapHelper: true,
1807
+ _mapHelperKind: "get_ok",
1808
+ _mapInfo: mapInfo,
1809
+ _rootFuncDecl: null,
1810
+ });
1811
+ // set
1812
+ funcDecls.push({
1813
+ kind: "FuncDecl",
1814
+ name: mapInfo.setFuncName,
1815
+ params: [
1816
+ { name: "m", type: mapInfo.wType },
1817
+ { name: "k", type: mapInfo.keyGoType },
1818
+ { name: "v", type: mapInfo.valGoType },
1819
+ ],
1820
+ returnType: null,
1821
+ body: { kind: "Block", stmts: [], list: [] },
1822
+ _isMapHelper: true,
1823
+ _mapHelperKind: "set",
1824
+ _mapInfo: mapInfo,
1825
+ _rootFuncDecl: null,
1826
+ });
1827
+ // delete
1828
+ funcDecls.push({
1829
+ kind: "FuncDecl",
1830
+ name: mapInfo.deleteFuncName,
1831
+ params: [
1832
+ { name: "m", type: mapInfo.wType },
1833
+ { name: "k", type: mapInfo.keyGoType },
1834
+ ],
1835
+ returnType: null,
1836
+ body: { kind: "Block", stmts: [], list: [] },
1837
+ _isMapHelper: true,
1838
+ _mapHelperKind: "delete",
1839
+ _mapInfo: mapInfo,
1840
+ _rootFuncDecl: null,
1841
+ });
1842
+ // len
1843
+ funcDecls.push({
1844
+ kind: "FuncDecl",
1845
+ name: mapInfo.lenFuncName,
1846
+ params: [{ name: "m", type: mapInfo.wType }],
1847
+ returnType: { kind: "basic", name: "int32" },
1848
+ body: { kind: "Block", stmts: [], list: [] },
1849
+ _isMapHelper: true,
1850
+ _mapHelperKind: "len",
1851
+ _mapInfo: mapInfo,
1852
+ _rootFuncDecl: null,
1853
+ });
1854
+ // clear
1855
+ funcDecls.push({
1856
+ kind: "FuncDecl",
1857
+ name: mapInfo.clearFuncName,
1858
+ params: [{ name: "m", type: mapInfo.wType }],
1859
+ returnType: null,
1860
+ body: { kind: "Block", stmts: [], list: [] },
1861
+ _isMapHelper: true,
1862
+ _mapHelperKind: "clear",
1863
+ _mapInfo: mapInfo,
1864
+ _rootFuncDecl: null,
1865
+ });
1866
+ // keys
1867
+ funcDecls.push({
1868
+ kind: "FuncDecl",
1869
+ name: mapInfo.keysFuncName,
1870
+ params: [{ name: "m", type: mapInfo.wType }],
1871
+ returnType: { kind: "slice", elem: mapInfo.keyGoType },
1872
+ body: { kind: "Block", stmts: [], list: [] },
1873
+ _isMapHelper: true,
1874
+ _mapHelperKind: "keys",
1875
+ _mapInfo: mapInfo,
1876
+ _rootFuncDecl: null,
1877
+ });
1878
+ // values
1879
+ funcDecls.push({
1880
+ kind: "FuncDecl",
1881
+ name: mapInfo.valuesFuncName,
1882
+ params: [{ name: "m", type: mapInfo.wType }],
1883
+ returnType: { kind: "slice", elem: mapInfo.valGoType },
1884
+ body: { kind: "Block", stmts: [], list: [] },
1885
+ _isMapHelper: true,
1886
+ _mapHelperKind: "values",
1887
+ _mapInfo: mapInfo,
1888
+ _rootFuncDecl: null,
1889
+ });
1890
+ // clone
1891
+ funcDecls.push({
1892
+ kind: "FuncDecl",
1893
+ name: mapInfo.cloneFuncName,
1894
+ params: [{ name: "m", type: mapInfo.wType }],
1895
+ returnType: mapInfo.wType,
1896
+ body: { kind: "Block", stmts: [], list: [] },
1897
+ _isMapHelper: true,
1898
+ _mapHelperKind: "clone",
1899
+ _mapInfo: mapInfo,
1900
+ _rootFuncDecl: null,
1901
+ });
1902
+ }
1903
+
1904
+ // 1e2. `gofront/shared` linear-memory helpers (only when imported).
1905
+ const usesShared = progs.some((p) =>
1906
+ (p.imports ?? []).some((decl) =>
1907
+ (decl.imports ?? [decl]).some((imp) => imp.path === "gofront/shared"),
1908
+ ),
1909
+ );
1910
+ if (usesShared) {
1911
+ const sharedWType = mod.getSharedType().wType;
1912
+ const i32 = { kind: "basic", name: "int32" };
1913
+ funcDecls.push({
1914
+ kind: "FuncDecl",
1915
+ name: "__shared_len_panic",
1916
+ params: [],
1917
+ returnType: null,
1918
+ _isRuntimePanic: true,
1919
+ _panicMsg: "runtime error: shared buffer length out of range",
1920
+ });
1921
+ funcDecls.push({
1922
+ kind: "FuncDecl",
1923
+ name: "__shared_new",
1924
+ params: [
1925
+ { name: "len", type: i32 },
1926
+ { name: "bytes", type: i32 },
1927
+ ],
1928
+ returnType: sharedWType,
1929
+ body: { kind: "Block", stmts: [], list: [] },
1930
+ _isSharedHelper: "new",
1931
+ _rootFuncDecl: null,
1932
+ });
1933
+ funcDecls.push({
1934
+ kind: "FuncDecl",
1935
+ name: "__shared_sub",
1936
+ params: [
1937
+ { name: "buf", type: sharedWType },
1938
+ { name: "lo", type: i32 },
1939
+ { name: "hi", type: i32 },
1940
+ { name: "bytes", type: i32 },
1941
+ ],
1942
+ returnType: sharedWType,
1943
+ body: { kind: "Block", stmts: [], list: [] },
1944
+ _isSharedHelper: "sub",
1945
+ _rootFuncDecl: null,
1946
+ });
1947
+ }
1948
+
1949
+ // 1e3. Runtime helpers: error values, interface equality, t.Run callback.
1950
+ mod.usesTestingRun = funcDecls.some((fn) => _usesTestingRun(fn.body));
1951
+ mod.usesAnyEq = _usesAnyEq(funcDecls, mod);
1952
+ const ANY_T = { kind: "basic", name: "any" };
1953
+ const STR_T = { kind: "basic", name: "string" };
1954
+ const BOOL_T = { kind: "basic", name: "bool" };
1955
+ const helper = (name, params, returnType, kind, extra = {}) => ({
1956
+ kind: "FuncDecl",
1957
+ name,
1958
+ params,
1959
+ returnType,
1960
+ body: { kind: "Block", stmts: [], list: [] },
1961
+ _isRuntimeHelper: kind,
1962
+ _rootFuncDecl: null,
1963
+ ...extra,
1964
+ });
1965
+ if (mod.usesAnyEq) {
1966
+ funcDecls.push(
1967
+ helper(
1968
+ "__any_eq",
1969
+ [
1970
+ { name: "a", type: ANY_T },
1971
+ { name: "b", type: ANY_T },
1972
+ ],
1973
+ BOOL_T,
1974
+ "any_eq",
1975
+ ),
1976
+ );
1977
+ }
1978
+ if (mod.hasErrorType) {
1979
+ const errPtr = {
1980
+ kind: "pointer",
1981
+ base: { kind: "named", name: ERROR_STRUCT },
1982
+ };
1983
+ funcDecls.push(
1984
+ helper(
1985
+ `${ERROR_STRUCT}.Error`,
1986
+ [{ name: "e", type: errPtr }],
1987
+ STR_T,
1988
+ "error_msg",
1989
+ {
1990
+ _isMethod: true,
1991
+ _recvTypeName: ERROR_STRUCT,
1992
+ _methodName: "Error",
1993
+ },
1994
+ ),
1995
+ helper(
1996
+ `${ERROR_STRUCT}.Unwrap`,
1997
+ [{ name: "e", type: errPtr }],
1998
+ ANY_T,
1999
+ "error_cause",
2000
+ {
2001
+ _isMethod: true,
2002
+ _recvTypeName: ERROR_STRUCT,
2003
+ _methodName: "Unwrap",
2004
+ },
2005
+ ),
2006
+ helper(
2007
+ "__mk_error",
2008
+ [
2009
+ { name: "msg", type: STR_T },
2010
+ { name: "cause", type: ANY_T },
2011
+ ],
2012
+ ANY_T,
2013
+ "mk_error",
2014
+ ),
2015
+ helper("__error_str", [{ name: "e", type: ANY_T }], STR_T, "error_str"),
2016
+ {
2017
+ kind: "FuncDecl",
2018
+ name: "__errors_unwrap",
2019
+ params: [{ name: "__recv", type: ANY_T }],
2020
+ returnType: ANY_T,
2021
+ body: { kind: "Block", stmts: [], list: [] },
2022
+ _isInterfaceDispatcher: true,
2023
+ _softDispatch: true,
2024
+ _ifaceName: "error",
2025
+ _ifaceType: null,
2026
+ _methodName: "Unwrap",
2027
+ _rootFuncDecl: null,
2028
+ },
2029
+ helper(
2030
+ "__errors_is",
2031
+ [
2032
+ { name: "err", type: ANY_T },
2033
+ { name: "target", type: ANY_T },
2034
+ ],
2035
+ BOOL_T,
2036
+ "errors_is",
2037
+ ),
2038
+ );
2039
+ }
2040
+ if (mod.usesTestingRun) {
2041
+ funcDecls.push(
2042
+ helper(
2043
+ "__testing_run_cb",
2044
+ [
2045
+ { name: "fn", type: ANY_T },
2046
+ {
2047
+ name: "t",
2048
+ type: {
2049
+ kind: "pointer",
2050
+ base: { kind: "named", name: "testing.T" },
2051
+ },
2052
+ },
2053
+ ],
2054
+ null,
2055
+ "testing_run_cb",
2056
+ { _forceExport: true },
2057
+ ),
2058
+ );
2059
+ }
2060
+
2061
+ // 1f. Boundary metadata (exported surface of wasm packages)
960
2062
  const boundaryMeta = options.boundary
961
2063
  ? collectBoundaryMeta(progs, funcDecls, mod)
962
2064
  : null;
@@ -1009,14 +2111,19 @@ export function compileWasmModule(
1009
2111
  const typeIndex = mod.getTypeIndex(paramTypes, returnTypes);
1010
2112
  fn._typeIndex = typeIndex;
1011
2113
  fn._globalFuncIndex = globalIdx;
2114
+ mod.funcReturnTypesByIdx.set(globalIdx, returnTypes);
1012
2115
 
1013
2116
  // Export if public (capitalized) or main
1014
2117
  if (
1015
- !fn._isGlobalInit &&
1016
- (options.exportAll ||
1017
- fn.name === "main" ||
1018
- (fn.name[0] >= "A" && fn.name[0] <= "Z") ||
1019
- (fn._isMethod && fn._methodName[0] >= "A" && fn._methodName[0] <= "Z"))
2118
+ fn._forceExport ||
2119
+ (!fn._isGlobalInit &&
2120
+ !fn._isRuntimeHelper &&
2121
+ (options.exportAll ||
2122
+ fn.name === "main" ||
2123
+ (fn.name[0] >= "A" && fn.name[0] <= "Z") ||
2124
+ (fn._isMethod &&
2125
+ fn._methodName[0] >= "A" &&
2126
+ fn._methodName[0] <= "Z")))
1020
2127
  ) {
1021
2128
  mod.exports.push({ name: fn.name, kind: "func", index: globalIdx });
1022
2129
  if (fn._isMethod && fn._exportName) {
@@ -1027,6 +2134,16 @@ export function compileWasmModule(
1027
2134
  });
1028
2135
  }
1029
2136
  }
2137
+ if (
2138
+ fn._isPushPanic &&
2139
+ !mod.exports.some((e) => e.name === "__push_panic")
2140
+ ) {
2141
+ mod.exports.push({
2142
+ name: "__push_panic",
2143
+ kind: "func",
2144
+ index: globalIdx,
2145
+ });
2146
+ }
1030
2147
  }
1031
2148
 
1032
2149
  // 4. Emit function bodies
@@ -1037,7 +2154,29 @@ export function compileWasmModule(
1037
2154
  const funcIdx = mod.getStringImportIndex();
1038
2155
  emitter.pushInstruction({ op: "i32.const", value: strIdx });
1039
2156
  emitter.pushInstruction({ op: "call", funcIndex: funcIdx });
2157
+ emitter.pushInstruction("any.convert_extern");
1040
2158
  emitter.emitPanicThrow();
2159
+ } else if (fn._isPushPanic) {
2160
+ const panicNodeTypeIndex = mod.getPanicNodeTypeIndex();
2161
+ const panicGlobal = mod.getPanicGlobalIndex();
2162
+ emitter.pushInstruction({ op: "local.get", index: 0 });
2163
+ emitter.pushInstruction("any.convert_extern");
2164
+ emitter.pushInstruction({ op: "i32.const", value: 0 });
2165
+ emitter.pushInstruction({ op: "global.get", index: panicGlobal });
2166
+ emitter.pushInstruction({
2167
+ op: "struct.new",
2168
+ typeIndex: panicNodeTypeIndex,
2169
+ });
2170
+ emitter.pushInstruction({ op: "global.set", index: panicGlobal });
2171
+ emitter.pushInstruction("return");
2172
+ } else if (fn._isInterfaceDispatcher) {
2173
+ emitter.emitInterfaceDispatcher(fn);
2174
+ } else if (fn._isMapHelper) {
2175
+ emitter.emitMapHelper(fn);
2176
+ } else if (fn._isSharedHelper) {
2177
+ emitter.emitSharedHelper(fn);
2178
+ } else if (fn._isRuntimeHelper) {
2179
+ emitter.emitRuntimeHelper(fn);
1041
2180
  } else if (fn.body) {
1042
2181
  emitter.emitFunctionBody(fn.body);
1043
2182
  }
@@ -1061,6 +2200,10 @@ export function compileWasmModule(
1061
2200
  typeIndex: fn._typeIndex,
1062
2201
  locals: emitter.localTypes,
1063
2202
  body,
2203
+ src:
2204
+ fn._srcFile && typeof fn._line === "number"
2205
+ ? { file: fn._srcFile, line: fn._line }
2206
+ : null,
1064
2207
  });
1065
2208
  }
1066
2209
 
@@ -1071,6 +2214,7 @@ export function compileWasmModule(
1071
2214
  facade = generateFacade(boundaryMeta, {
1072
2215
  stringTable: mod.stringTable,
1073
2216
  callMain: Boolean(options.callMain) && mod.funcMap.has("main"),
2217
+ stdlibEnv: mod.usesStdlibEnv,
1074
2218
  });
1075
2219
  }
1076
2220
 
@@ -1082,18 +2226,41 @@ export function compileWasmModule(
1082
2226
  elements: mod.elements,
1083
2227
  funcs: mod.funcs,
1084
2228
  exports: mod.exports,
2229
+ memory: mod.memory ?? null,
1085
2230
  start: globalInitFn ? globalInitFn._globalFuncIndex : null,
2231
+ sourceMappingURL: options.sourceMappingURL ?? null,
1086
2232
  };
1087
2233
 
1088
2234
  const wasmBytes = encodeModule(moduleIR);
1089
2235
  const watText = options.emitWat ? emitWat(moduleIR) : null;
1090
2236
 
2237
+ // Function-level source positions: `sources` are the absolute Go files,
2238
+ // `mappings` pair each function body's byte offset with its 0-based line.
2239
+ const sources = [];
2240
+ const sourceIndex = new Map();
2241
+ const mappings = [];
2242
+ moduleIR.funcs.forEach((fn, i) => {
2243
+ if (!fn.src) return;
2244
+ let idx = sourceIndex.get(fn.src.file);
2245
+ if (idx === undefined) {
2246
+ idx = sources.length;
2247
+ sources.push(fn.src.file);
2248
+ sourceIndex.set(fn.src.file, idx);
2249
+ }
2250
+ mappings.push({
2251
+ offset: moduleIR.codeOffsets[i],
2252
+ srcFileIdx: idx,
2253
+ srcLine: Math.max(0, fn.src.line - 1),
2254
+ });
2255
+ });
2256
+
1091
2257
  return {
1092
2258
  wasm: wasmBytes,
1093
2259
  wat: watText,
1094
2260
  stringTable: mod.stringTable,
1095
2261
  moduleIR,
1096
2262
  facade,
2263
+ sourceMapInfo: { sources, mappings },
1097
2264
  };
1098
2265
  }
1099
2266
 
@@ -1122,6 +2289,50 @@ function _collectPackageConsts(progs, mod) {
1122
2289
  }
1123
2290
  }
1124
2291
 
2292
+ // Depth-first search over AST nodes (skipping `_`-prefixed annotations).
2293
+ function _someNode(node, pred) {
2294
+ if (!node || typeof node !== "object") return false;
2295
+ if (Array.isArray(node)) return node.some((n) => _someNode(n, pred));
2296
+ if (pred(node)) return true;
2297
+ for (const key of Object.keys(node)) {
2298
+ if (key.startsWith("_")) continue;
2299
+ if (_someNode(node[key], pred)) return true;
2300
+ }
2301
+ return false;
2302
+ }
2303
+
2304
+ function _usesTestingRun(node) {
2305
+ return _someNode(
2306
+ node,
2307
+ (n) =>
2308
+ n.kind === "CallExpr" &&
2309
+ n.func?.kind === "SelectorExpr" &&
2310
+ n.func.field === "Run" &&
2311
+ isTestingT(n.func.expr?._type),
2312
+ );
2313
+ }
2314
+
2315
+ // `__any_eq` is only needed when some `==`/`!=` (or switch case) compares
2316
+ // anyref operands, a struct with an anyref field is compared, or the error
2317
+ // runtime (`errors.Is`) is present. `_emitAnyEq` throws if this misses a
2318
+ // site, so a gap surfaces as a compile error rather than a bad module.
2319
+ function _usesAnyEq(funcDecls, mod) {
2320
+ if (mod.hasErrorType) return true;
2321
+ for (const info of mod.structTypes.values()) {
2322
+ if (info.fields?.some((f) => f.wType === "anyref")) return true;
2323
+ }
2324
+ const isAnyRef = (n) =>
2325
+ Boolean(n?._type) && toWasmType(n._type, mod.checker, mod) === "anyref";
2326
+ return funcDecls.some((fn) =>
2327
+ _someNode(fn.body, (n) => {
2328
+ if (n.kind === "BinaryExpr" && (n.op === "==" || n.op === "!="))
2329
+ return isAnyRef(n.left) || isAnyRef(n.right);
2330
+ if (n.kind === "SwitchStmt" && n.tag) return isAnyRef(n.tag);
2331
+ return false;
2332
+ }),
2333
+ );
2334
+ }
2335
+
1125
2336
  function _scanImportsInBody(node, mod) {
1126
2337
  if (!node || typeof node !== "object") return;
1127
2338
 
@@ -1158,6 +2369,9 @@ function _scanImportsInBody(node, mod) {
1158
2369
  Atan: "atan",
1159
2370
  Atan2: "atan2",
1160
2371
  Pow: "pow",
2372
+ Mod: "mod",
2373
+ Hypot: "hypot",
2374
+ Cbrt: "cbrt",
1161
2375
  Exp: "exp",
1162
2376
  Log: "log",
1163
2377
  Log2: "log2",
@@ -1165,7 +2379,7 @@ function _scanImportsInBody(node, mod) {
1165
2379
  Round: "round",
1166
2380
  };
1167
2381
  if (jsMathMap[func.field]) {
1168
- const isBinary = func.field === "Atan2" || func.field === "Pow";
2382
+ const isBinary = BINARY_MATH_IMPORTS.has(jsMathMap[func.field]);
1169
2383
  mod.getMathImportIndex(jsMathMap[func.field], isBinary);
1170
2384
  }
1171
2385
  } else if (
@@ -1177,7 +2391,10 @@ function _scanImportsInBody(node, mod) {
1177
2391
  if (func.field === "Name") mod.getTestingNameImportIndex();
1178
2392
  else if (func.field === "Failed" || func.field === "Skipped")
1179
2393
  mod.getTestingFlagImportIndex();
1180
- else if (func.field !== "Run") {
2394
+ else if (func.field === "Run") {
2395
+ // closure type for func(*testing.T) must exist before bodies are emitted
2396
+ mod.getTestingRunClosureType();
2397
+ } else {
1181
2398
  mod.getTestingCallImportIndex();
1182
2399
  for (const arg of args) {
1183
2400
  mod.getTestingArgImportIndex(
@@ -1231,6 +2448,46 @@ export function compileWasm(source, options = {}) {
1231
2448
  return { ...res, errors: [] };
1232
2449
  }
1233
2450
 
2451
+ function _isPointerGoType(t, mod) {
2452
+ if (!t) return false;
2453
+ if (t.kind === "pointer" || t.kind === "PointerType" || t.kind === "StarExpr")
2454
+ return true;
2455
+ // `type P *T` used as a field type: look through the name.
2456
+ const named =
2457
+ t.kind === "named" || t.kind === "TypeName" || t.kind === "Ident";
2458
+ const under = named
2459
+ ? (t.underlying ?? mod.checker?.types?.get(t.name)?.underlying)
2460
+ : null;
2461
+ return under ? _isPointerGoType(under, mod) : false;
2462
+ }
2463
+
2464
+ // Constant-expression zero value of a struct global. Nested struct values are
2465
+ // built in place: a null ref would trap on the first field access.
2466
+ function _zeroStructConst(structInfo, mod) {
2467
+ const out = [];
2468
+ for (const f of structInfo.fields) out.push(..._zeroFieldConst(f, mod));
2469
+ out.push({ op: "struct.new", typeIndex: structInfo.typeIndex });
2470
+ return out;
2471
+ }
2472
+
2473
+ function _zeroFieldConst(f, mod) {
2474
+ const w = f.wType;
2475
+ if (w === "i64") return [{ op: "i64.const", value: 0n }];
2476
+ if (w === "f32" || w === "f64") return [{ op: `${w}.const`, value: 0 }];
2477
+ if (w === "externref") return [{ op: "ref.null", heapType: "extern" }];
2478
+ if (w === "anyref") return [{ op: "ref.null", heapType: "any" }];
2479
+ if (typeof w !== "object" || w === null)
2480
+ return [{ op: "i32.const", value: 0 }];
2481
+ const ti = w.typeIndex;
2482
+ const slice = typeof ti === "number" ? mod.getSliceTypeByIndex(ti) : null;
2483
+ if (slice) return [{ op: "global.get", index: slice.emptyGlobalIndex }];
2484
+ if (typeof ti === "number" && !_isPointerGoType(f.goType, mod)) {
2485
+ for (const s of mod.structTypes.values())
2486
+ if (s.typeIndex === ti) return _zeroStructConst(s, mod);
2487
+ }
2488
+ return [{ op: "ref.null", heapType: ti ?? "any" }];
2489
+ }
2490
+
1234
2491
  function _collectPackageGlobals(progs, mod, checker) {
1235
2492
  const pending = []; // non-constant initializers, run by the start function
1236
2493
  for (const p of progs) {
@@ -1270,34 +2527,7 @@ function _collectPackageGlobals(progs, mod, checker) {
1270
2527
  (s) => s.typeIndex === wType.typeIndex,
1271
2528
  );
1272
2529
  if (structInfo) {
1273
- initInsts = [];
1274
- for (const f of structInfo.fields) {
1275
- if (f.wType === "i64")
1276
- initInsts.push({ op: "i64.const", value: 0n });
1277
- else if (f.wType === "f32" || f.wType === "f64")
1278
- initInsts.push({ op: `${f.wType}.const`, value: 0.0 });
1279
- else if (typeof f.wType === "object") {
1280
- const fieldSlice =
1281
- typeof f.wType.typeIndex === "number"
1282
- ? mod.getSliceTypeByIndex(f.wType.typeIndex)
1283
- : null;
1284
- if (fieldSlice) {
1285
- initInsts.push({
1286
- op: "global.get",
1287
- index: fieldSlice.emptyGlobalIndex,
1288
- });
1289
- } else {
1290
- initInsts.push({
1291
- op: "ref.null",
1292
- heapType: f.wType.typeIndex ?? "any",
1293
- });
1294
- }
1295
- } else initInsts.push({ op: "i32.const", value: 0 });
1296
- }
1297
- initInsts.push({
1298
- op: "struct.new",
1299
- typeIndex: structInfo.typeIndex,
1300
- });
2530
+ initInsts = _zeroStructConst(structInfo, mod);
1301
2531
  } else {
1302
2532
  initInsts = [
1303
2533
  { op: "ref.null", heapType: wType.typeIndex ?? "any" },
@@ -1411,3 +2641,150 @@ function _makeGlobalInitFunc(pending, initNames = []) {
1411
2641
  _isGlobalInit: true,
1412
2642
  };
1413
2643
  }
2644
+
2645
+ function _scanInterfaceCallNodes(node, interfaceCalls, checker) {
2646
+ if (!node || typeof node !== "object") return;
2647
+ if (Array.isArray(node)) {
2648
+ for (const item of node)
2649
+ _scanInterfaceCallNodes(item, interfaceCalls, checker);
2650
+ return;
2651
+ }
2652
+ if (node.kind === "CallExpr" && node.func?.kind === "SelectorExpr") {
2653
+ const recvType = node.func.expr?._type;
2654
+ if (isNonEmptyInterface(recvType, checker)) {
2655
+ const ifaceName = getReceiverTypeName(recvType, node.func.expr) || "anon";
2656
+ const methodName = node.func.field;
2657
+ const key = `${ifaceName}.${methodName}`;
2658
+ if (!interfaceCalls.has(key)) {
2659
+ interfaceCalls.set(key, {
2660
+ ifaceName,
2661
+ ifaceType: recvType,
2662
+ methodName,
2663
+ callNode: node,
2664
+ });
2665
+ }
2666
+ }
2667
+ }
2668
+ for (const key of Object.keys(node)) {
2669
+ if (!key.startsWith("_"))
2670
+ _scanInterfaceCallNodes(node[key], interfaceCalls, checker);
2671
+ }
2672
+ }
2673
+
2674
+ function _scanInterfaceTypeDecls(progs, interfaceCalls, checker) {
2675
+ for (const p of progs) {
2676
+ for (const d of p.decls ?? []) {
2677
+ if (d.kind !== "TypeDecl") continue;
2678
+ const resolved = checker?.types?.get(d.name) ?? d.type;
2679
+ const underlying =
2680
+ resolved?.kind === "named"
2681
+ ? resolved.underlying
2682
+ : d.type?.kind === "InterfaceType"
2683
+ ? d.type
2684
+ : resolved;
2685
+ if (!isNonEmptyInterface(underlying, checker)) continue;
2686
+ const methods =
2687
+ underlying.methods instanceof Map
2688
+ ? Array.from(underlying.methods.entries())
2689
+ : Array.isArray(underlying.methods)
2690
+ ? underlying.methods.map((m) => [m.name ?? m, m])
2691
+ : [];
2692
+ for (const [mName, mSig] of methods) {
2693
+ const key = `${d.name}.${mName}`;
2694
+ if (!interfaceCalls.has(key)) {
2695
+ interfaceCalls.set(key, {
2696
+ ifaceName: d.name,
2697
+ ifaceType: resolved,
2698
+ methodName: mName,
2699
+ methodSig: mSig,
2700
+ });
2701
+ }
2702
+ }
2703
+ }
2704
+ }
2705
+ }
2706
+
2707
+ function _makeInterfaceDispatcher(info, checker) {
2708
+ let methodSig = info.methodSig;
2709
+ if (!methodSig) {
2710
+ const ifaceResolved =
2711
+ checker?.resolveType?.(
2712
+ info.ifaceType?.kind === "named"
2713
+ ? info.ifaceType.underlying
2714
+ : info.ifaceType,
2715
+ ) ?? info.ifaceType;
2716
+ if (ifaceResolved?.methods instanceof Map) {
2717
+ methodSig = ifaceResolved.methods.get(info.methodName);
2718
+ }
2719
+ }
2720
+
2721
+ const params = [{ name: "__recv", type: { kind: "basic", name: "any" } }];
2722
+ if (methodSig?.params) {
2723
+ for (let p = 0; p < methodSig.params.length; p++) {
2724
+ params.push({ name: `__arg${p}`, type: methodSig.params[p] });
2725
+ }
2726
+ } else if (info.callNode?.args) {
2727
+ for (let p = 0; p < info.callNode.args.length; p++) {
2728
+ params.push({
2729
+ name: `__arg${p}`,
2730
+ type: info.callNode.args[p]._type ?? {
2731
+ kind: "basic",
2732
+ name: "any",
2733
+ },
2734
+ });
2735
+ }
2736
+ }
2737
+
2738
+ let returnType = info.callNode?._type ?? null;
2739
+ if (methodSig?.returns) {
2740
+ returnType =
2741
+ methodSig.returns.length === 1
2742
+ ? methodSig.returns[0]
2743
+ : methodSig.returns.length > 1
2744
+ ? { kind: "tuple", types: methodSig.returns }
2745
+ : null;
2746
+ }
2747
+
2748
+ return {
2749
+ kind: "FuncDecl",
2750
+ name: `__dispatch_${info.ifaceName}_${info.methodName}`,
2751
+ params,
2752
+ returnType,
2753
+ body: { kind: "Block", stmts: [], list: [] },
2754
+ _isInterfaceDispatcher: true,
2755
+ _ifaceName: info.ifaceName,
2756
+ _ifaceType: info.ifaceType,
2757
+ _methodName: info.methodName,
2758
+ _rootFuncDecl: null,
2759
+ };
2760
+ }
2761
+
2762
+ function _collectInterfaceDispatchers(progs, funcDecls, mod, checker) {
2763
+ const interfaceCalls = new Map();
2764
+ for (const fn of funcDecls) {
2765
+ if (fn.body) _scanInterfaceCallNodes(fn.body, interfaceCalls, checker);
2766
+ }
2767
+ _scanInterfaceTypeDecls(progs, interfaceCalls, checker);
2768
+
2769
+ // `error.Error()` is reached implicitly by fmt verbs and errors.Is, so
2770
+ // the dispatcher must exist even when user code never calls it.
2771
+ if (mod.hasErrorType && !interfaceCalls.has("error.Error")) {
2772
+ interfaceCalls.set("error.Error", {
2773
+ ifaceName: "error",
2774
+ ifaceType: checker?.types?.get("error") ?? {
2775
+ kind: "interface",
2776
+ name: "error",
2777
+ },
2778
+ methodName: "Error",
2779
+ methodSig: {
2780
+ kind: "func",
2781
+ params: [],
2782
+ returns: [{ kind: "basic", name: "string" }],
2783
+ },
2784
+ });
2785
+ }
2786
+
2787
+ for (const info of interfaceCalls.values()) {
2788
+ funcDecls.push(_makeInterfaceDispatcher(info, checker));
2789
+ }
2790
+ }