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
@@ -33,7 +33,13 @@ export function hasGenerics(programs) {
33
33
  /**
34
34
  * Returns a stable identifier-safe string key for any Go type or AST type node.
35
35
  */
36
+ // A qualified type argument (`time.Duration`) becomes part of a JS and wasm
37
+ // export identifier, so its dot is replaced.
36
38
  function typeKey(t) {
39
+ return rawTypeKey(t).replaceAll(".", "_");
40
+ }
41
+
42
+ function rawTypeKey(t) {
37
43
  if (!t) return "any";
38
44
  if (typeof t === "string") return t;
39
45
  if (t.kind === "basic") return t.name;
@@ -78,7 +84,7 @@ function typeKey(t) {
78
84
  /**
79
85
  * Converts a Go type representation or AST type node into a concrete AST type node.
80
86
  */
81
- function typeToTypeNode(t) {
87
+ export function typeToTypeNode(t) {
82
88
  if (!t) return { kind: "TypeName", name: "any" };
83
89
  if (typeof t === "string") return { kind: "TypeName", name: t };
84
90
  if (
@@ -133,16 +139,48 @@ function getSpecializedName(baseName, typeArgs) {
133
139
  return `${baseName}$${keys}`;
134
140
  }
135
141
 
142
+ const KEPT_CLONE_KEYS = new Set(["_typeArgs", "_line", "_col"]);
143
+
136
144
  function cloneAst(node) {
137
145
  if (!node || typeof node !== "object") return node;
138
146
  if (Array.isArray(node)) return node.map(cloneAst);
139
147
  const copy = {};
140
148
  for (const [k, v] of Object.entries(node)) {
149
+ // Side tables are dropped, except source positions (for diagnostics in
150
+ // the specialised body) and the inferred type arguments of a generic
151
+ // call inside a generic body: substitution needs them to specialise the
152
+ // callee (`isNaN(x)` inside `Compare[T]`).
153
+ if (k.startsWith("_") && !KEPT_CLONE_KEYS.has(k)) continue;
141
154
  copy[k] = cloneAst(v);
142
155
  }
156
+ // Named results hang off the return type node and alias its types; keep
157
+ // just the names so `rebindNamedResults` can rebuild them after the types
158
+ // are substituted.
159
+ const named = node.returnType?._namedReturns;
160
+ if (named) copy._resultNames = named.map((r) => r.name);
143
161
  return copy;
144
162
  }
145
163
 
164
+ // Re-attaches named results (`(old *T)`) to every function in a cloned and
165
+ // substituted tree, pointing at the substituted type nodes.
166
+ function rebindNamedResults(node) {
167
+ if (!node || typeof node !== "object") return;
168
+ if (Array.isArray(node)) {
169
+ for (const n of node) rebindNamedResults(n);
170
+ return;
171
+ }
172
+ if (node._resultNames && node.returnType) {
173
+ const rt = node.returnType;
174
+ rt._namedReturns = node._resultNames.map((name, i) => ({
175
+ name,
176
+ type: rt.kind === "TupleType" ? rt.types[i] : rt,
177
+ }));
178
+ }
179
+ for (const [k, v] of Object.entries(node)) {
180
+ if (!k.startsWith("_")) rebindNamedResults(v);
181
+ }
182
+ }
183
+
146
184
  /**
147
185
  * Replaces references to type parameters in AST nodes with concrete types.
148
186
  */
@@ -246,6 +284,23 @@ function substituteTypesInAst(
246
284
 
247
285
  // 4. CallExpr
248
286
  if (node.kind === "CallExpr") {
287
+ // `T(x)` with T a type parameter becomes the conversion `int8(x)`.
288
+ const conv =
289
+ node.func?.kind === "Ident" ? typeSubstMap.get(node.func.name) : null;
290
+ if (conv && node.args?.length === 1) {
291
+ return {
292
+ kind: "TypeConversion",
293
+ targetType: cloneAst(conv),
294
+ expr: substituteTypesInAst(
295
+ node.args[0],
296
+ typeSubstMap,
297
+ onGenericUsage,
298
+ genericTypes,
299
+ genericFuncs,
300
+ ),
301
+ _line: node._line,
302
+ };
303
+ }
249
304
  if (node.func?.kind === "Ident") {
250
305
  let typeArgs = node._typeArgs ?? node.func._typeArgs;
251
306
  if (typeArgs) {
@@ -593,6 +648,7 @@ export function monomorphise(programs, checker) {
593
648
  genericTypes,
594
649
  genericFuncs,
595
650
  );
651
+ rebindNamedResults(monoTypeDecl);
596
652
  specializedTypes.set(item.specializedName, monoTypeDecl);
597
653
 
598
654
  // Specialize all methods declared on this generic type
@@ -610,6 +666,7 @@ export function monomorphise(programs, checker) {
610
666
  genericTypes,
611
667
  genericFuncs,
612
668
  );
669
+ rebindNamedResults(monoMethod);
613
670
  const mKey = `${item.specializedName}.${monoMethod.name}`;
614
671
  specializedMethods.set(mKey, monoMethod);
615
672
  }
@@ -634,6 +691,7 @@ export function monomorphise(programs, checker) {
634
691
  genericTypes,
635
692
  genericFuncs,
636
693
  );
694
+ rebindNamedResults(monoFuncDecl);
637
695
  specializedFuncs.set(item.specializedName, monoFuncDecl);
638
696
  }
639
697
  }
@@ -703,9 +761,9 @@ export function monomorphise(programs, checker) {
703
761
  }
704
762
  }
705
763
 
706
- for (const p of progs) {
707
- monoChecker.check(p);
708
- }
764
+ // One pass over all programs: declarations of every linked package are
765
+ // visible to the others (cross-package references are dequalified).
766
+ monoChecker.checkAll(progs);
709
767
 
710
768
  return { programs: progs, checker: monoChecker };
711
769
  }
@@ -0,0 +1,397 @@
1
+ // src/backend/wasm/prescan.js
2
+ // Pre-scan helpers: walk the lowered AST to decide which imports, runtime helpers
3
+ // and interface dispatchers a module needs before emission starts.
4
+
5
+ import { BINARY_MATH_IMPORTS } from "./stdlib/core.js";
6
+ import {
7
+ anonIfaceName,
8
+ getInterfaceMethodList,
9
+ getReceiverTypeName,
10
+ isAnyType,
11
+ isBoolGoType,
12
+ isInterfaceType,
13
+ isNonEmptyInterface,
14
+ isTestingT,
15
+ toWasmType,
16
+ } from "./types.js";
17
+
18
+ export function importsAnyOf(programs, pkgs) {
19
+ const has = (p) => (pkgs instanceof Set ? pkgs.has(p) : pkgs.includes(p));
20
+ return (programs ?? []).some((p) =>
21
+ (p.imports ?? []).some((decl) =>
22
+ (decl.imports ?? [decl]).some((imp) => has(imp.path)),
23
+ ),
24
+ );
25
+ }
26
+
27
+ export function hasErrorUsage(programs) {
28
+ return (programs ?? []).some((p) => {
29
+ let found = false;
30
+ const scan = (n) => {
31
+ if (!n || found || typeof n !== "object") return;
32
+ if (Array.isArray(n)) {
33
+ for (const x of n) scan(x);
34
+ return;
35
+ }
36
+ if (
37
+ (n.kind === "Ident" ||
38
+ n.kind === "TypeName" ||
39
+ n.kind === "basic" ||
40
+ n.kind === "interface") &&
41
+ n.name === "error"
42
+ ) {
43
+ found = true;
44
+ return;
45
+ }
46
+ for (const k of Object.keys(n)) {
47
+ if (!k.startsWith("_")) scan(n[k]);
48
+ }
49
+ };
50
+ scan(p);
51
+ return found;
52
+ });
53
+ }
54
+
55
+ // Depth-first search over AST nodes (skipping `_`-prefixed annotations).
56
+ export function _someNode(node, pred) {
57
+ if (!node || typeof node !== "object") return false;
58
+ if (Array.isArray(node)) return node.some((n) => _someNode(n, pred));
59
+ if (pred(node)) return true;
60
+ for (const key of Object.keys(node)) {
61
+ if (key.startsWith("_")) continue;
62
+ if (_someNode(node[key], pred)) return true;
63
+ }
64
+ return false;
65
+ }
66
+
67
+ export function _usesTestingRun(node) {
68
+ return _someNode(
69
+ node,
70
+ (n) =>
71
+ n.kind === "CallExpr" &&
72
+ n.func?.kind === "SelectorExpr" &&
73
+ n.func.field === "Run" &&
74
+ isTestingT(n.func.expr?._type),
75
+ );
76
+ }
77
+
78
+ export function _usesTestingCleanup(node) {
79
+ return _someNode(
80
+ node,
81
+ (n) =>
82
+ n.kind === "CallExpr" &&
83
+ n.func?.kind === "SelectorExpr" &&
84
+ n.func.field === "Cleanup" &&
85
+ isTestingT(n.func.expr?._type),
86
+ );
87
+ }
88
+
89
+ // `__any_eq` is only needed when some `==`/`!=` (or switch case) compares
90
+ // anyref operands, a struct with an anyref field is compared, or the error
91
+ // runtime (`errors.Is`) is present. `_emitAnyEq` throws if this misses a
92
+ // site, so a gap surfaces as a compile error rather than a bad module.
93
+ export function _usesAnyEq(funcDecls, mod) {
94
+ if (mod.hasErrorType) return true;
95
+ for (const info of mod.mapTypes.values()) {
96
+ if (isInterfaceType(info.keyGoType, mod.checker)) return true;
97
+ }
98
+ for (const info of mod.structTypes.values()) {
99
+ if (info.fields?.some((f) => f.wType === "anyref")) return true;
100
+ }
101
+ const isAnyRef = (n) =>
102
+ Boolean(n?._type) && toWasmType(n._type, mod.checker, mod) === "anyref";
103
+ return funcDecls.some((fn) =>
104
+ _someNode(fn.body, (n) => {
105
+ if (n.kind === "BinaryExpr" && (n.op === "==" || n.op === "!="))
106
+ return isAnyRef(n.left) || isAnyRef(n.right);
107
+ if (n.kind === "SwitchStmt" && n.tag) return isAnyRef(n.tag);
108
+ return false;
109
+ }),
110
+ );
111
+ }
112
+
113
+ // An `fmt` call with an `any`/interface operand or a `xs...` spread needs
114
+ // `__any_fmt`. `emitFmtCall` falls back to scalar-only dispatch without it.
115
+ export function _usesFmtAny(funcDecls, mod) {
116
+ return funcDecls.some((fn) =>
117
+ _someNode(
118
+ fn.body,
119
+ (n) =>
120
+ n.kind === "CallExpr" &&
121
+ n.func?.kind === "SelectorExpr" &&
122
+ n.func.expr?.kind === "Ident" &&
123
+ n.func.expr.name === "fmt" &&
124
+ n.args?.some(
125
+ (a) =>
126
+ a._spread ||
127
+ isAnyType(a._type, mod.checker) ||
128
+ isInterfaceType(a._type, mod.checker),
129
+ ),
130
+ ),
131
+ );
132
+ }
133
+
134
+ // Whether a value of this static type may print a 64-bit unsigned integer
135
+ // (directly, inside a container or struct, or behind `any`/an interface).
136
+ export function typeMayHoldU64(t, mod, seen = new Set(), depth = 0) {
137
+ if (!t || typeof t !== "object" || seen.has(t) || depth > 8) return false;
138
+ seen.add(t);
139
+ if (isAnyType(t, mod.checker) || isInterfaceType(t, mod.checker)) return true;
140
+ const sub = (x) => typeMayHoldU64(x, mod, seen, depth + 1);
141
+ switch (t.kind) {
142
+ case "basic":
143
+ return t.name === "uint64" || t.name === "uint" || t.name === "uintptr";
144
+ case "TypeName":
145
+ case "Ident":
146
+ return sub(
147
+ mod.checker?.resolveType?.(t) ?? { kind: "basic", name: t.name },
148
+ );
149
+ case "named":
150
+ return sub(t.underlying);
151
+ case "tuple":
152
+ return (t.types ?? []).some(sub);
153
+ case "pointer":
154
+ return sub(t.base);
155
+ case "slice":
156
+ case "array":
157
+ return sub(t.elem);
158
+ case "map":
159
+ return sub(t.key) || sub(t.value);
160
+ case "struct":
161
+ return [...(t.fields?.values?.() ?? [])].some(sub);
162
+ default:
163
+ return false;
164
+ }
165
+ }
166
+
167
+ export function _scanImportsInBody(node, mod) {
168
+ if (!node || typeof node !== "object") return;
169
+
170
+ if (node.kind === "CallExpr") {
171
+ const { func, args } = node;
172
+ if ((args ?? []).some((a) => a._spread || typeMayHoldU64(a._type, mod))) {
173
+ mod.getFmtBoxU64Index();
174
+ }
175
+ if (
176
+ func.kind === "Ident" &&
177
+ (func.name === "print" || func.name === "println")
178
+ ) {
179
+ const isPrintln = func.name === "println";
180
+ if (args.length === 0 && isPrintln) {
181
+ mod.getPrintlnEmptyIndex();
182
+ }
183
+ for (let i = 0; i < args.length; i++) {
184
+ const arg = args[i];
185
+ const isLast = i === args.length - 1 && isPrintln;
186
+ const isBool =
187
+ isBoolGoType(arg._type) ||
188
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL");
189
+ const wType = toWasmType(arg._type, mod.checker);
190
+ mod.getLogImportIndex(wType, isLast, isBool);
191
+ }
192
+ } else if (
193
+ func.kind === "SelectorExpr" &&
194
+ func.expr?.name === "math" &&
195
+ func.field
196
+ ) {
197
+ const jsMathMap = {
198
+ Sin: "sin",
199
+ Cos: "cos",
200
+ Tan: "tan",
201
+ Asin: "asin",
202
+ Acos: "acos",
203
+ Atan: "atan",
204
+ Atan2: "atan2",
205
+ Pow: "pow",
206
+ Mod: "mod",
207
+ Hypot: "hypot",
208
+ Cbrt: "cbrt",
209
+ Exp: "exp",
210
+ Log: "log",
211
+ Log1p: "log1p",
212
+ Log2: "log2",
213
+ Log10: "log10",
214
+ Expm1: "expm1",
215
+ Round: "round",
216
+ Sinh: "sinh",
217
+ Cosh: "cosh",
218
+ Tanh: "tanh",
219
+ };
220
+ if (jsMathMap[func.field]) {
221
+ const isBinary = BINARY_MATH_IMPORTS.has(jsMathMap[func.field]);
222
+ mod.getMathImportIndex(jsMathMap[func.field], isBinary);
223
+ }
224
+ } else if (
225
+ func.kind === "SelectorExpr" &&
226
+ isTestingT(func.expr?._type) &&
227
+ func.field
228
+ ) {
229
+ mod.internString(func.field);
230
+ if (func.field === "Name") mod.getTestingNameImportIndex();
231
+ else if (func.field === "Failed" || func.field === "Skipped")
232
+ mod.getTestingFlagImportIndex();
233
+ else if (func.field === "Run") {
234
+ // closure type for func(*testing.T) must exist before bodies are emitted
235
+ mod.getTestingRunClosureType();
236
+ } else if (func.field === "Cleanup") {
237
+ mod.getTestingCleanupClosureType();
238
+ } else {
239
+ mod.getTestingCallImportIndex();
240
+ for (const arg of args) {
241
+ mod.getTestingArgImportIndex(
242
+ toWasmType(arg._type, mod.checker),
243
+ isBoolGoType(arg._type) ||
244
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL"),
245
+ );
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ if (node.kind === "BasicLit" && node.litKind === "STRING") {
252
+ mod.getStringImportIndex();
253
+ const strVal = String(node.value ?? "");
254
+ mod.internString(strVal);
255
+ }
256
+
257
+ // Recurse child properties
258
+ for (const key of Object.keys(node)) {
259
+ if (key.startsWith("_")) continue;
260
+ const child = node[key];
261
+ if (Array.isArray(child)) {
262
+ for (const item of child) _scanImportsInBody(item, mod);
263
+ } else if (child && typeof child === "object") {
264
+ _scanImportsInBody(child, mod);
265
+ }
266
+ }
267
+ }
268
+
269
+ export function _scanInterfaceCallNodes(node, interfaceCalls, checker) {
270
+ if (!node || typeof node !== "object") return;
271
+ if (Array.isArray(node)) {
272
+ for (const item of node)
273
+ _scanInterfaceCallNodes(item, interfaceCalls, checker);
274
+ return;
275
+ }
276
+ if (node.kind === "CallExpr" && node.func?.kind === "SelectorExpr") {
277
+ const recvType = node.func.expr?._type;
278
+ if (isNonEmptyInterface(recvType, checker)) {
279
+ const ifaceName =
280
+ getReceiverTypeName(recvType, node.func.expr) ||
281
+ anonIfaceName(recvType);
282
+ const methodName = node.func.field;
283
+ const key = `${ifaceName}.${methodName}`;
284
+ if (!interfaceCalls.has(key)) {
285
+ interfaceCalls.set(key, {
286
+ ifaceName,
287
+ ifaceType: recvType,
288
+ methodName,
289
+ callNode: node,
290
+ });
291
+ }
292
+ }
293
+ }
294
+ for (const key of Object.keys(node)) {
295
+ if (!key.startsWith("_"))
296
+ _scanInterfaceCallNodes(node[key], interfaceCalls, checker);
297
+ }
298
+ }
299
+
300
+ export function _scanInterfaceTypeDecls(progs, interfaceCalls, checker) {
301
+ for (const p of progs) {
302
+ for (const d of p.decls ?? []) {
303
+ if (d.kind !== "TypeDecl") continue;
304
+ const resolved = checker?.types?.get(d.name) ?? d.type;
305
+ const underlying =
306
+ resolved?.kind === "named"
307
+ ? resolved.underlying
308
+ : d.type?.kind === "InterfaceType"
309
+ ? d.type
310
+ : resolved;
311
+ if (!isNonEmptyInterface(underlying, checker)) continue;
312
+ const methods =
313
+ underlying.methods instanceof Map
314
+ ? Array.from(underlying.methods.entries())
315
+ : Array.isArray(underlying.methods)
316
+ ? underlying.methods.map((m) => [m.name ?? m, m])
317
+ : [];
318
+ for (const [mName, mSig] of methods) {
319
+ const key = `${d.name}.${mName}`;
320
+ if (!interfaceCalls.has(key)) {
321
+ interfaceCalls.set(key, {
322
+ ifaceName: d.name,
323
+ ifaceType: resolved,
324
+ methodName: mName,
325
+ methodSig: mSig,
326
+ });
327
+ }
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ // A qualified interface (`fmt.Stringer`) in a signature is a boundary type
334
+ // when the function is exported, so its dispatchers must exist. Returns true
335
+ // when `t` names such a type.
336
+ function _registerQualifiedIface(t, interfaceCalls, checker) {
337
+ if (t.kind !== "TypeName" || !t.name?.includes(".")) return false;
338
+ const resolved = checker?.types?.get(t.name);
339
+ const iface = resolved?.underlying ?? resolved;
340
+ if (!resolved || !isNonEmptyInterface(iface, checker)) return true;
341
+ for (const m of getInterfaceMethodList(iface)) {
342
+ const key = `${t.name}.${m.name}`;
343
+ if (interfaceCalls.has(key)) continue;
344
+ interfaceCalls.set(key, {
345
+ ifaceName: t.name,
346
+ ifaceType: resolved,
347
+ methodName: m.name,
348
+ methodSig: { kind: "func", params: m.params, returns: m.returns },
349
+ });
350
+ }
351
+ return true;
352
+ }
353
+
354
+ export function _scanFuncSigInterfaces(
355
+ progs,
356
+ funcDecls,
357
+ interfaceCalls,
358
+ checker,
359
+ mod,
360
+ ) {
361
+ const scanType = (t) => {
362
+ if (!t) return;
363
+ if (_registerQualifiedIface(t, interfaceCalls, checker)) return;
364
+ if (t.kind === "InterfaceType" || (t.kind === "interface" && !t.name)) {
365
+ const mList = getInterfaceMethodList(t);
366
+ if (mList.length === 0) return;
367
+ const ifaceName = anonIfaceName(t, checker, mod);
368
+ for (const m of mList) {
369
+ const key = `${ifaceName}.${m.name}`;
370
+ if (!interfaceCalls.has(key)) {
371
+ interfaceCalls.set(key, {
372
+ ifaceName,
373
+ ifaceType: t,
374
+ methodName: m.name,
375
+ methodSig: {
376
+ kind: "func",
377
+ params: m.params,
378
+ returns: m.returns,
379
+ },
380
+ });
381
+ }
382
+ }
383
+ }
384
+ };
385
+ for (const fn of funcDecls) {
386
+ for (const p of fn.params ?? []) scanType(p.type);
387
+ scanType(fn.returnType);
388
+ }
389
+ for (const p of progs) {
390
+ for (const d of p.decls ?? []) {
391
+ if (d.kind === "FuncDecl") {
392
+ for (const param of d.params ?? []) scanType(param.type);
393
+ scanType(d.returnType);
394
+ }
395
+ }
396
+ }
397
+ }