gofront 1.8.0 → 1.9.1

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 (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -0,0 +1,929 @@
1
+ // src/lower/fmt-formatters.js
2
+ // The compile-time half of package fmt, as generated Go source.
3
+ //
4
+ // Go's fmt asks reflection for the type of every operand. GoFront knows it
5
+ // statically, so for each call of the Print family this generator writes the
6
+ // description package fmt reads (`fmtrt.Arg`, see stdlib/internal/fmtrt):
7
+ //
8
+ // - a call with an operand whose runtime representation does not tell its
9
+ // type (a float64 that holds 3, an int8, a named type, a struct) is
10
+ // redirected to a wrapper `gff_<pkg>_<Fn>_<n>` taking the operands with
11
+ // their static types and passing each as a description;
12
+ // - a package that stores values in interfaces that fmt prints registers a
13
+ // lookup, `gff_<pkg>_value`, a type switch over the types it can build,
14
+ // with `fmtrt.Register`.
15
+ //
16
+ // The functions are ordinary Go, parsed and checked as part of the package's
17
+ // generated file (src/link/stdlib.js), so both backends compile the same
18
+ // code.
19
+
20
+ import {
21
+ ANY,
22
+ defaultType,
23
+ embeddedFieldName,
24
+ FMT_PRINT_FUNCS,
25
+ LOCAL_TYPE_SUFFIX,
26
+ } from "../typechecker/types.js";
27
+ import {
28
+ collectProgramTypes,
29
+ importsFor,
30
+ isEmptyIface,
31
+ mentionsTypeParam,
32
+ results,
33
+ TypeSpeller,
34
+ under,
35
+ walkNodes,
36
+ } from "./gen-types.js";
37
+ import { holdsFunc } from "./json-codecs.js";
38
+
39
+ const FMT_PATH = "fmt";
40
+ const RT_PATH = "internal/fmtrt";
41
+ const JSON_PATH = "encoding/json";
42
+
43
+ // The Print family: fn -> what precedes the operands.
44
+ const PRINT_FUNCS = new Map([
45
+ ["Sprint", null],
46
+ ["Sprintln", null],
47
+ ["Print", null],
48
+ ["Println", null],
49
+ ["Fprint", "io.Writer"],
50
+ ["Fprintln", "io.Writer"],
51
+ ["Append", "[]byte"],
52
+ ["Appendln", "[]byte"],
53
+ ["Sprintf", null],
54
+ ["Printf", null],
55
+ ["Errorf", null],
56
+ ["Fprintf", "io.Writer"],
57
+ ["Appendf", "[]byte"],
58
+ ]);
59
+
60
+ // The functions that take a format string after the prefix.
61
+ const isFormatted = (fn) => fn.endsWith("f");
62
+
63
+ const RESULT = {
64
+ Sprint: "string",
65
+ Sprintln: "string",
66
+ Print: "(int, error)",
67
+ Println: "(int, error)",
68
+ Fprint: "(int, error)",
69
+ Fprintln: "(int, error)",
70
+ Append: "[]byte",
71
+ Appendln: "[]byte",
72
+ Sprintf: "string",
73
+ Printf: "(int, error)",
74
+ Errorf: "error",
75
+ Fprintf: "(int, error)",
76
+ Appendf: "[]byte",
77
+ };
78
+
79
+ // Where a wrapper registers the package's lookup (when it has one) before it
80
+ // formats: a package-level initialiser that prints runs before the file's own.
81
+ const ENSURE = "{{ensure}}";
82
+ // The key the lookup registers under: a hash of the generated source.
83
+ const KEY = "{{key}}";
84
+
85
+ const ALIAS = { byte: "uint8", rune: "int32" };
86
+ const unalias = (n) => ALIAS[n] ?? n;
87
+ const q = (s) => JSON.stringify(s);
88
+
89
+ const INT_KINDS = new Set(["int", "int8", "int16", "int32", "int64"]);
90
+ const UINT_KINDS = new Set([
91
+ "uint",
92
+ "uint8",
93
+ "uint16",
94
+ "uint32",
95
+ "uint64",
96
+ "uintptr",
97
+ ]);
98
+ const FORMAT_METHODS = { Err: "Error", Str: "String", GoStr: "GoString" };
99
+
100
+ // A type of another package that has no exported name: the generated file
101
+ // cannot spell it.
102
+ function isUnexportedForeign(name) {
103
+ const at = name.indexOf("$$");
104
+ return at > 0 && !/^\p{Lu}/u.test(name);
105
+ }
106
+
107
+ // The types of package testing: the lookup of a package that logs through a
108
+ // *testing.T never describes the test state itself.
109
+ const HOST_TYPE = /^testing\.|\$\$testing$/;
110
+
111
+ const isInterface = (t) =>
112
+ t?.kind === "nil" ||
113
+ (t?.kind === "basic" && (t.name === "nil" || t.name === "any")) ||
114
+ (t?.kind !== "basic" && under(t)?.kind === "interface");
115
+
116
+ // An interface whose dynamic values fmt formats through their own methods
117
+ // (error, Stringer), so no description of their type is needed.
118
+ function isMethodOnly(t) {
119
+ const u = under(t);
120
+ if (u?.kind !== "interface" || !u.methods || u.methods.size === 0)
121
+ return false;
122
+ return [...u.methods.keys()].every((m) => m in { Error: 1, String: 1 });
123
+ }
124
+
125
+ function methodOf(t, name) {
126
+ if (t?.kind !== "named") return null;
127
+ return t.methods?.get(name) ?? t.underlying?.methods?.get(name) ?? null;
128
+ }
129
+
130
+ const returnsString = (m) => {
131
+ const r = results(m);
132
+ return (
133
+ m.params?.length === 0 &&
134
+ r.length === 1 &&
135
+ under(r[0])?.kind === "basic" &&
136
+ under(r[0]).name === "string"
137
+ );
138
+ };
139
+
140
+ // A map, slice or array type: one that generic code boxes into an interface
141
+ // is not spelled in the package, so the formatter lookup must describe it.
142
+ function isMapSliceOrArray(t) {
143
+ return t?.kind === "map" || t?.kind === "slice" || t?.kind === "array";
144
+ }
145
+
146
+ class FmtGen extends TypeSpeller {
147
+ constructor(checker, pkgName, qualifiers, programs, formatted = false) {
148
+ super(pkgName, qualifiers, programs);
149
+ this.checker = checker;
150
+ // A package with a format call: %d and %+v call no methods, so an
151
+ // error or Stringer in an interface is described by its type too.
152
+ this.formatted = formatted;
153
+ this.prefix = `gff_${String(pkgName).replace(/[^A-Za-z0-9_]/g, "_")}_`;
154
+ this.builders = new Map(); // key -> function name
155
+ this.wrappers = new Map(); // signature -> function name
156
+ this.queue = [];
157
+ this.out = [];
158
+ this.counter = 0;
159
+ this.needsLookup = false;
160
+ this.uses = new Set(); // imports the generated code spells itself
161
+ this.topLevel = new Set();
162
+ for (const p of programs)
163
+ for (const d of p.decls ?? [])
164
+ if (d.kind === "TypeDecl") this.topLevel.add(d.name);
165
+ }
166
+
167
+ // ── Type naming ─────────────────────────────────────────────
168
+
169
+ key(t) {
170
+ if (!t) return "?";
171
+ const k = (x) => this.key(x);
172
+ const byKind = {
173
+ basic: () => unalias(t.name),
174
+ named: () =>
175
+ t.typeArgs?.length
176
+ ? `${t.name}[${t.typeArgs.map(k).join(",")}]`
177
+ : t.name,
178
+ pointer: () => `*${k(t.base)}`,
179
+ slice: () => `[]${k(t.elem)}`,
180
+ array: () => `[${t.size}]${k(t.elem)}`,
181
+ map: () => `map[${k(t.key)}]${k(t.value)}`,
182
+ struct: () => this.structKey(t),
183
+ interface: () => (isEmptyIface(t) ? "any" : `iface:${t.name ?? ""}`),
184
+ func: () => this.funcString(t, k),
185
+ };
186
+ return (byKind[t.kind] ?? (() => `?${t.kind}`))();
187
+ }
188
+
189
+ structKey(t) {
190
+ const fields = (t._structFields ?? []).map(
191
+ (f) =>
192
+ `${(f.names ?? []).join(",")}:${this.key(f.type)}:${f.embedded ? 1 : 0}`,
193
+ );
194
+ return `struct{${fields.join(";")}}`;
195
+ }
196
+
197
+ // How %T prints the type.
198
+ typeName(t) {
199
+ const n = (x) => this.typeName(x);
200
+ switch (t?.kind) {
201
+ case "basic":
202
+ return t.name === "any" ? "interface {}" : unalias(t.name);
203
+ case "named": {
204
+ let name = t.name.replace(LOCAL_TYPE_SUFFIX, "");
205
+ const at = name.indexOf("$$");
206
+ if (at >= 0)
207
+ name = `${name
208
+ .slice(at + 2)
209
+ .split("$")
210
+ .at(-1)}.${name.slice(0, at)}`;
211
+ else if (!name.includes("."))
212
+ name = `${this.declaringPackage(t)}.${name}`;
213
+ return t.typeArgs?.length
214
+ ? `${name}[${t.typeArgs.map(n).join(",")}]`
215
+ : name;
216
+ }
217
+ case "pointer":
218
+ return `*${n(t.base)}`;
219
+ case "slice":
220
+ return `[]${n(t.elem)}`;
221
+ case "array":
222
+ return `[${t.size}]${n(t.elem)}`;
223
+ case "map":
224
+ return `map[${n(t.key)}]${n(t.value)}`;
225
+ case "interface":
226
+ return t.name === "error" || t.name ? t.name : "interface {}";
227
+ case "func":
228
+ return this.funcString(t, n).replace(/^func\(/, "func(");
229
+ case "struct": {
230
+ const fs = (t._structFields ?? []).map((f) =>
231
+ f.embedded ? n(f.type) : `${f.names.join(", ")} ${n(f.type)}`,
232
+ );
233
+ return fs.length ? `struct { ${fs.join("; ")} }` : "struct {}";
234
+ }
235
+ default:
236
+ return "?";
237
+ }
238
+ }
239
+
240
+ // A type the generated file can name: declared at package level or by
241
+ // another package, or a function-local type, which the checker keeps in
242
+ // its type table under its name.
243
+ isNamedInScope(t) {
244
+ return (
245
+ t.name.includes("$$") ||
246
+ t.name.includes(".") ||
247
+ this.topLevel.has(t.name) ||
248
+ this.checker.types.get(t.name) === t
249
+ );
250
+ }
251
+
252
+ spellableNamed(t, ok, seen) {
253
+ if (
254
+ t.name.includes("[") ||
255
+ !(t.typeArgs ?? []).every(ok) ||
256
+ !this.isNamedInScope(t) ||
257
+ isUnexportedForeign(t.name) ||
258
+ HOST_TYPE.test(t.name)
259
+ )
260
+ return false;
261
+ // A named struct is as spellable as its fields (a type may hold itself).
262
+ const u = under(t);
263
+ if (u?.kind === "namespace") return false;
264
+ if (u?.kind !== "struct" || seen.has(t)) return true;
265
+ seen.add(t);
266
+ return this.spellableStruct(u, ok);
267
+ }
268
+
269
+ spellableInterface(t) {
270
+ return t.name === "error" || isEmptyIface(t);
271
+ }
272
+
273
+ // A field of an interface type is described by its value.
274
+ spellableStruct(t, ok) {
275
+ return (t._structFields ?? []).every(
276
+ (f) => isInterface(f.type) || ok(f.type),
277
+ );
278
+ }
279
+
280
+ // ── Descriptions ────────────────────────────────────────────
281
+
282
+ // Whether a method declared on a generic type has a pointer receiver:
283
+ // instances hold substituted copies that lose the flag.
284
+ ptrRecv(base, m, name) {
285
+ if (Object.hasOwn(m, "_ptrRecv")) return Boolean(m._ptrRecv);
286
+ return Boolean(
287
+ this.checker.types.get(base.name)?._generic?.methods?.get(name)?._ptrRecv,
288
+ );
289
+ }
290
+
291
+ // How the method set of `t` (a type or a pointer to one) holds the
292
+ // string-valued method `name`: null when it does not, otherwise the chain
293
+ // of embedded fields to the interface the method is promoted from (the
294
+ // checker does not treat such a type as implementing the interface), or
295
+ // null in `path` when the type declares or promotes it from a struct.
296
+ hasFormatMethod(t, name) {
297
+ const isPtr = t?.kind === "pointer";
298
+ return this.methodSetHas(isPtr ? t.base : t, name, isPtr, 0);
299
+ }
300
+
301
+ methodSetHas(base, name, isPtr, depth) {
302
+ if (depth > 4 || !base) return null;
303
+ const u = under(base);
304
+ if (u?.kind === "interface") {
305
+ const im = u.methods?.get(name);
306
+ return im && returnsString(im) ? { path: [] } : null;
307
+ }
308
+ const m = methodOf(base, name);
309
+ if (m) {
310
+ if (!isPtr && this.ptrRecv(base, m, name)) return null;
311
+ return returnsString(m) ? { path: null } : null;
312
+ }
313
+ if (u?.kind !== "struct") return null;
314
+ for (const f of u._structFields ?? []) {
315
+ if (!f.embedded) continue;
316
+ const viaPtr = f.type?.kind === "pointer";
317
+ const inner = viaPtr ? f.type.base : f.type;
318
+ const found = this.methodSetHas(inner, name, isPtr || viaPtr, depth + 1);
319
+ if (!found) continue;
320
+ // Through an interface: the field names lead to it.
321
+ return found.path
322
+ ? { path: [embeddedFieldName(f), ...found.path] }
323
+ : found;
324
+ }
325
+ return null;
326
+ }
327
+
328
+ // The `Err`, `Str` and `GoStr` fields for a value of type `t`.
329
+ methodFields(t, x) {
330
+ let out = "";
331
+ for (const [field, method] of Object.entries(FORMAT_METHODS)) {
332
+ const found = this.hasFormatMethod(t, method);
333
+ if (!found) continue;
334
+ out += `, ${field}: ${found.path ? [x, ...found.path].join(".") : x}`;
335
+ }
336
+ return out;
337
+ }
338
+
339
+ // A Go expression of type fmtrt.Arg describing the value `x` of type `t`.
340
+ describe(t, x) {
341
+ t = defaultType(t);
342
+ const T = q(this.typeName(t));
343
+ if (isInterface(t) || t?.kind === "typeParam") {
344
+ if (this.formatted || !isMethodOnly(t)) this.needsLookup = true;
345
+ return `fmtrt.Arg{K: fmtrt.KInterface, V: ${x}}`;
346
+ }
347
+ const m = this.methodFields(t, x);
348
+ const u = under(t);
349
+ if (u?.kind === "basic") return this.describeBasic(u.name, T, x, m);
350
+ return `${this.builder(t)}(${x})`;
351
+ }
352
+
353
+ describeBasic(name, T, x, m) {
354
+ name = unalias(name);
355
+ const head = (k, rest) => `fmtrt.Arg{K: fmtrt.${k}, T: ${T}, ${rest}${m}}`;
356
+ if (name === "bool") return head("KBool", `B: bool(${x})`);
357
+ if (name === "string") return head("KString", `S: string(${x})`);
358
+ if (INT_KINDS.has(name)) return head("KInt", `I: int64(${x})`);
359
+ if (UINT_KINDS.has(name)) return head("KUint", `U: uint64(${x})`);
360
+ if (name === "float32" || name === "float64")
361
+ return head(
362
+ "KFloat",
363
+ `Size: ${name === "float32" ? 32 : 64}, R: float64(${x})`,
364
+ );
365
+ if (name === "complex64" || name === "complex128")
366
+ return head(
367
+ "KComplex",
368
+ `Size: ${name === "complex64" ? 64 : 128}, R: float64(real(${x})), Im: float64(imag(${x}))`,
369
+ );
370
+ return `fmtrt.Arg{K: fmtrt.KInterface, V: ${x}}`;
371
+ }
372
+
373
+ // The function describing values of the composite type `t`.
374
+ builder(t) {
375
+ const key = this.key(t);
376
+ let name = this.builders.get(key);
377
+ if (!name) {
378
+ name = `${this.prefix}a${this.counter++}`;
379
+ this.builders.set(key, name);
380
+ this.queue.push(() => this.genBuilder(name, t));
381
+ }
382
+ return name;
383
+ }
384
+
385
+ genBuilder(name, t) {
386
+ const T = q(this.typeName(t));
387
+ const m = this.methodFields(t, "x");
388
+ const u = under(t);
389
+ const body = {
390
+ struct: () => this.structArg(u, T, m),
391
+ array: () => this.elemsArg("KArray", u, T, m, `${u.size}`),
392
+ slice: () => this.elemsArg("KSlice", u, T, m, "len(x)"),
393
+ simd: () => this.simdArg(u, T, m),
394
+ map: () => this.mapArg(u, T, m),
395
+ pointer: () => this.pointerArg(u, T, m),
396
+ func: () =>
397
+ `fmtrt.Arg{K: fmtrt.KFunc, T: ${T}, Nil: x == nil, Ptr: x${m}}`,
398
+ }[u.kind];
399
+ const expr = body ? body() : `fmtrt.Arg{K: fmtrt.KInterface, V: x}`;
400
+ this.out.push(
401
+ `func ${name}(x ${this.spell(t)}) fmtrt.Arg {\n\treturn ${expr}\n}`,
402
+ );
403
+ }
404
+
405
+ structArg(u, T, m) {
406
+ const cases = [];
407
+ let n = 0;
408
+ for (const f of u._structFields ?? []) {
409
+ const names = f.embedded ? [embeddedFieldName(f)] : f.names;
410
+ for (const name of names) {
411
+ const sel = `x.${name}`;
412
+ const desc = this.describe(f.type, sel);
413
+ cases.push(`\t\tcase ${n}:\n\t\t\treturn ${q(name)}, ${desc}`);
414
+ n++;
415
+ }
416
+ }
417
+ const field =
418
+ n === 0
419
+ ? "nil"
420
+ : `func(i int) (string, fmtrt.Arg) {\n\t\tswitch i {\n${cases.join("\n")}\n\t\t}\n\t\treturn "", fmtrt.Arg{}\n\t}`;
421
+ return `fmtrt.Arg{K: fmtrt.KStruct, T: ${T}, N: ${n}, Field: ${field}${m}}`;
422
+ }
423
+
424
+ // A vector prints as its lanes; Lane takes a constant index.
425
+ simdArg(u, T, m) {
426
+ const cases = [];
427
+ for (let i = 0; i < u.lanes; i++)
428
+ cases.push(
429
+ `\t\tcase ${i}:\n\t\t\treturn ${this.describe(u.elem, `x.Lane(${i})`)}`,
430
+ );
431
+ return `fmtrt.Arg{K: fmtrt.KArray, T: ${T}, N: ${u.lanes}, Elem: func(i int) fmtrt.Arg {\n\t\tswitch i {\n${cases.join("\n")}\n\t\t}\n\t\treturn fmtrt.Arg{}\n\t}${m}}`;
432
+ }
433
+
434
+ elemsArg(kind, u, T, m, len) {
435
+ const isBytes = u.elem.kind === "basic" && unalias(u.elem.name) === "uint8";
436
+ const bytes = isBytes
437
+ ? kind === "KSlice"
438
+ ? ", IsBytes: true, Bytes: []byte(x)"
439
+ : ", IsBytes: true, Bytes: x[:]"
440
+ : "";
441
+ const nil = kind === "KSlice" ? ", Nil: x == nil, Ptr: x" : "";
442
+ return `fmtrt.Arg{K: fmtrt.${kind}, T: ${T}, N: ${len}${nil}${bytes}, Elem: func(i int) fmtrt.Arg {\n\t\treturn ${this.describe(u.elem, "x[i]")}\n\t}${m}}`;
443
+ }
444
+
445
+ mapArg(u, T, m) {
446
+ const key = this.describe(u.key, "k");
447
+ const val = this.describe(u.value, "v");
448
+ return `fmtrt.Arg{K: fmtrt.KMap, T: ${T}, Nil: x == nil, Ptr: x, N: len(x), Entries: func() []fmtrt.Entry {\n\t\tif x == nil {\n\t\t\treturn nil\n\t\t}\n\t\tes := make([]fmtrt.Entry, 0, len(x))\n\t\tfor k, v := range x {\n\t\t\tes = append(es, fmtrt.Entry{Key: ${key}, Val: ${val}})\n\t\t}\n\t\treturn es\n\t}${m}}`;
449
+ }
450
+
451
+ pointerArg(u, T, m) {
452
+ const k = under(u.base)?.kind;
453
+ const inner =
454
+ k === "struct" || k === "array" || k === "slice" || k === "map"
455
+ ? `, Inner: func() fmtrt.Arg {\n\t\treturn ${this.describe(u.base, "*x")}\n\t}`
456
+ : "";
457
+ const boxed = k !== "struct" && k !== "array" ? ", Boxed: true" : "";
458
+ return `fmtrt.Arg{K: fmtrt.KPtr, T: ${T}, Nil: x == nil, Ptr: x${boxed}${inner}${m}}`;
459
+ }
460
+
461
+ // ── Call sites ──────────────────────────────────────────────
462
+
463
+ // Whether an operand of static type `t` needs a description, or reaches
464
+ // fmt in a form it understands: { wrap } or { bare, lookup }.
465
+ classify(t, formatted = false) {
466
+ t = defaultType(t);
467
+ if (!t) return { bare: true, lookup: true };
468
+ if (t.kind === "nil" || (t.kind === "basic" && t.name === "nil"))
469
+ return { bare: true };
470
+ if (mentionsTypeParam(t) || t.kind === "typeParam")
471
+ return { bare: true, lookup: true };
472
+ if (isInterface(t))
473
+ return { bare: true, lookup: formatted || !isMethodOnly(t) };
474
+ if (t.kind === "basic" && !t.alias) {
475
+ if (t.name === "string" || t.name === "bool") return { bare: true };
476
+ // A JS number holds an int exactly only up to 2^53: past it the
477
+ // number reads as a float, so ints are described unless the
478
+ // package is WebAssembly only.
479
+ if (t.name === "int" && this.checker.target === "wasm")
480
+ return { bare: true };
481
+ }
482
+ if (!this.spellable(t)) return { bare: true, lookup: true };
483
+ // A named type whose value has String or Error answers to it as it is:
484
+ // fmt asks the value.
485
+ // (A string type is described: Print spaces only between operands that
486
+ // are not strings, and a bare value does not say its kind. So are
487
+ // slices and maps, which WebAssembly does not box with their methods.)
488
+ const declared = (m) => this.hasFormatMethod(t, m)?.path === null;
489
+ // A format call may print it with %d or %+v, which call no methods, so
490
+ // there it is described.
491
+ if (
492
+ !formatted &&
493
+ t.kind === "named" &&
494
+ (under(t)?.kind === "struct" ||
495
+ (under(t)?.kind === "basic" && under(t).name !== "string")) &&
496
+ (declared("Error") || declared("String"))
497
+ )
498
+ return { bare: true };
499
+ return { wrap: true };
500
+ }
501
+
502
+ // The wrapper for a call with these operands. A call that forwards the
503
+ // results of another (`fmt.Println(f())`) is wrapped with every operand
504
+ // typed exactly; null when one cannot be spelled.
505
+ wrapper(fn, prefixType, operandTypes, kinds, exact) {
506
+ const spelled = (t) => this.spell(defaultType(t));
507
+ if (exact && !operandTypes.every((t) => this.spellable(defaultType(t))))
508
+ return null;
509
+ const sig = `${fn}|${prefixType ?? ""}|${exact ? "!" : ""}${operandTypes.map((t, i) => (exact || kinds[i].wrap ? this.key(defaultType(t)) : "any")).join(",")}`;
510
+ let name = this.wrappers.get(sig);
511
+ if (name) return name;
512
+ name = `${this.prefix}${fn}_${this.wrappers.size}`;
513
+ this.wrappers.set(sig, name);
514
+ const params = [];
515
+ if (prefixType) params.push(`p ${prefixType}`);
516
+ if (isFormatted(fn)) params.push("format string");
517
+ const args = [];
518
+ operandTypes.forEach((t, i) => {
519
+ const a = `a${i}`;
520
+ if (kinds[i].wrap) {
521
+ params.push(`${a} ${spelled(t)}`);
522
+ args.push(this.describe(t, a));
523
+ } else {
524
+ params.push(`${a} ${exact ? spelled(t) : "any"}`);
525
+ args.push(a);
526
+ }
527
+ });
528
+ const callArgs = [
529
+ ...(prefixType ? ["p"] : []),
530
+ ...(isFormatted(fn) ? ["format"] : []),
531
+ ...args,
532
+ ].join(", ");
533
+ this.out.push(
534
+ `func ${name}(${params.join(", ")}) ${RESULT[fn]} {${ENSURE}\n\treturn fmt.${fn}(${callArgs})\n}`,
535
+ );
536
+ if (prefixType === "io.Writer") this.uses.add("io");
537
+ return name;
538
+ }
539
+
540
+ drain() {
541
+ while (this.queue.length > 0) this.queue.shift()();
542
+ }
543
+
544
+ // ── The lookup ──────────────────────────────────────────────
545
+
546
+ // Types an interface may hold that fmt prints: the types of the
547
+ // package's expressions, its named types, and a pointer to each.
548
+ candidateTypes() {
549
+ const found = new Map();
550
+ const add = (t) => {
551
+ if (!t || !this.usable(t)) return;
552
+ found.set(this.key(t), t);
553
+ if (t.kind === "named" && under(t)?.kind !== "func")
554
+ found.set(this.key({ kind: "pointer", base: t }), {
555
+ kind: "pointer",
556
+ base: t,
557
+ });
558
+ };
559
+ for (const { type, node, via } of collectProgramTypes(
560
+ this.programs,
561
+ this.checker,
562
+ )) {
563
+ // A member of a package (fmt.Println) is no value of its own.
564
+ if (via === "expr" && node.expr?._type?.kind === "namespace") continue;
565
+ if (via === "declared" && !this.topLevel.has(type.name)) continue;
566
+ add(type);
567
+ }
568
+ // Only instances of generic types (and pointers to them), and maps, slices and
569
+ // arrays a generic body boxes into an interface: they are the types a package
570
+ // never spells, and any other type here would take the place of the
571
+ // dispatch a type parameter value already has.
572
+ for (const { type, boxed } of this.genericBodyTypes()) {
573
+ const base = type.kind === "pointer" ? type.base : type;
574
+ if (
575
+ (boxed && isMapSliceOrArray(type)) ||
576
+ (base.kind === "named" && base.typeArgs?.length > 0)
577
+ )
578
+ add(type);
579
+ }
580
+ return [...found.values()];
581
+ }
582
+
583
+ // The types the bodies of generic functions build for each instantiation
584
+ // the package makes: `Box[T]{v}` inside `wrap[T]` is a `Box[int]` once
585
+ // `wrap(2)` is called, a type no expression of the package spells.
586
+ genericBodyTypes() {
587
+ const decls = new Map();
588
+ for (const program of this.programs)
589
+ for (const d of program.decls ?? [])
590
+ if (d.kind === "FuncDecl" && !d.recv && d.typeParams?.length)
591
+ decls.set(d.name, d);
592
+ if (decls.size === 0) return [];
593
+ const out = [];
594
+ const seen = new Set();
595
+ const queue = [];
596
+ const request = (call, map) => {
597
+ const inst = this.genericInstance(call, map, decls);
598
+ if (!inst || seen.has(inst.id) || seen.size >= 512) return;
599
+ seen.add(inst.id);
600
+ queue.push(inst);
601
+ };
602
+ for (const program of this.programs)
603
+ walkNodes(program.decls, (node) => request(node, new Map()));
604
+ while (queue.length > 0) {
605
+ const { decl, map } = queue.shift();
606
+ walkNodes(decl.body, (node) => {
607
+ out.push(...this.bodyNodeTypes(node, map));
608
+ request(node, map);
609
+ });
610
+ }
611
+ return out;
612
+ }
613
+
614
+ // The types a node of an instantiated generic body adds: the node's own
615
+ // type once substituted (a type without a type parameter is found by the
616
+ // walk of the package already), and a map it boxes into an interface,
617
+ // which is a type of its own (a type parameter's value keeps its dispatch).
618
+ bodyNodeTypes(node, map) {
619
+ const sub = (t) => defaultType(this.checker.substituteType(t, map));
620
+ const out = [];
621
+ if (node._type && mentionsTypeParam(node._type))
622
+ out.push({ type: sub(node._type) });
623
+ const src = node._ifaceSrc;
624
+ if (isMapSliceOrArray(src) && mentionsTypeParam(src))
625
+ out.push({ type: sub(src), boxed: true });
626
+ return out;
627
+ }
628
+
629
+ // The instantiation of a generic function that `call` makes, with its
630
+ // type arguments substituted through `map` (the instantiation of the
631
+ // function that contains the call), or null for any other node or one
632
+ // whose type arguments are still open.
633
+ genericInstance(call, map, decls) {
634
+ const generic = call._genericFnType;
635
+ const decl = generic && decls.get(generic.name);
636
+ const args = call._typeArgs ?? call.func?._typeArgs;
637
+ if (!decl || args?.length !== generic.typeParams?.length) return null;
638
+ const sub = args.map((a) => this.checker.substituteType(a, map));
639
+ if (sub.some((a) => mentionsTypeParam(a))) return null;
640
+ return {
641
+ decl,
642
+ id: `${decl.name}|${sub.map((a) => this.key(a)).join(",")}`,
643
+ map: new Map(generic.typeParams.map((tp, i) => [tp.name, sub[i]])),
644
+ };
645
+ }
646
+
647
+ usable(t) {
648
+ if (!this.spellable(t) || mentionsTypeParam(t)) return false;
649
+ if (isInterface(t) || under(t)?.kind === "interface") return false;
650
+ if (t.kind === "basic") return false;
651
+ if (t.kind === "func") return true;
652
+ // An array, map or slice is boxed by its Go type, so one with small
653
+ // integer or boolean keys or elements no longer shadows its look-alikes in
654
+ // the type switch. One holding funcs is described too, so `%T` names it.
655
+ if (t.kind === "array" || t.kind === "map" || t.kind === "slice")
656
+ return true;
657
+ if (holdsFunc(t)) return false;
658
+ return true;
659
+ }
660
+
661
+ genLookup() {
662
+ const cases = [];
663
+ const seen = new Set();
664
+ for (const t of this.candidateTypes()) {
665
+ const spelled = this.spell(t);
666
+ if (seen.has(spelled)) continue;
667
+ seen.add(spelled);
668
+ cases.push(
669
+ `\tcase ${spelled}:\n\t\treturn ${this.describe(t, "x")}, true`,
670
+ );
671
+ }
672
+ this.drain();
673
+ const name = `${this.prefix}value`;
674
+ this.out.push(
675
+ [
676
+ `func ${name}(v any) (fmtrt.Arg, bool) {`,
677
+ "\tswitch x := v.(type) {",
678
+ ...cases,
679
+ "\t}",
680
+ "\treturn fmtrt.Arg{}, false",
681
+ "}",
682
+ "",
683
+ `func ${this.prefix}ensure() {`,
684
+ `\tfmtrt.Register(${KEY}, ${name})`,
685
+ "}",
686
+ "",
687
+ `var _ = fmtrt.Register(${KEY}, ${name})`,
688
+ ].join("\n"),
689
+ );
690
+ }
691
+ }
692
+
693
+ // FNV-1a of a string, in hex.
694
+ function hash(text) {
695
+ let h = 0x811c9dc5;
696
+ for (let i = 0; i < text.length; i++)
697
+ h = Math.imul(h ^ text.charCodeAt(i), 0x01000193) >>> 0;
698
+ return h.toString(16);
699
+ }
700
+
701
+ // The names the file imports `fmt` as.
702
+ function fmtAliases(program) {
703
+ const aliases = new Set();
704
+ for (const imp of program.imports ?? [])
705
+ for (const spec of imp.imports)
706
+ if (spec.path === FMT_PATH && spec.alias !== "_")
707
+ aliases.add(spec.alias ?? "fmt");
708
+ return aliases;
709
+ }
710
+
711
+ const isFmtRef = (node, aliases) =>
712
+ node.kind === "SelectorExpr" &&
713
+ node.expr?.kind === "Ident" &&
714
+ node.expr._type?.kind === "namespace" &&
715
+ aliases.has(node.expr.name) &&
716
+ FMT_PRINT_FUNCS.has(node.field);
717
+
718
+ // The methods of *testing.T, *testing.B and testing.TB that format their
719
+ // operands like Print (Log, Error, Fatal, Skip) or like Printf (the f forms).
720
+ const TESTING_FORMATS = new Set(
721
+ ["Log", "Error", "Fatal", "Skip"].flatMap((m) => [m, `${m}f`]),
722
+ );
723
+ const TESTING_RECEIVER = /^(T|B|TB)\$\$testing$|^testing\.(T|B|TB)$/;
724
+
725
+ // Whether the package logs operands through testing's methods, and whether
726
+ // any of those calls is formatted. The operands reach fmt as `any` values, so
727
+ // the package needs the lookup (and, for a format call, the description of
728
+ // Stringers and errors) as for a forwarded Print call.
729
+ function findTestingCalls(programs) {
730
+ const found = { calls: false, formatted: false };
731
+ for (const program of programs)
732
+ walkNodes(program.decls, (node) => {
733
+ if (node.kind !== "CallExpr" || node.func?.kind !== "SelectorExpr")
734
+ return;
735
+ if (!TESTING_FORMATS.has(node.func.field)) return;
736
+ let recv = node.func.expr?._type;
737
+ if (recv?.kind === "pointer") recv = recv.base;
738
+ if (!TESTING_RECEIVER.test(recv?.name ?? "")) return;
739
+ found.calls = true;
740
+ if (isFormatted(node.func.field)) found.formatted = true;
741
+ });
742
+ return found;
743
+ }
744
+
745
+ function findSites(programs) {
746
+ const sites = [];
747
+ const values = []; // uses that are not calls: the operands are unknown
748
+ for (const program of programs) {
749
+ const aliases = fmtAliases(program);
750
+ if (aliases.size === 0) continue;
751
+ const callees = new Set();
752
+ const refs = [];
753
+ walkNodes(program.decls, (node) => {
754
+ if (node.kind === "CallExpr" && isFmtRef(node.func, aliases)) {
755
+ sites.push({ call: node, program });
756
+ callees.add(node.func);
757
+ } else if (isFmtRef(node, aliases)) {
758
+ refs.push(node);
759
+ }
760
+ });
761
+ for (const node of refs) if (!callees.has(node)) values.push(node);
762
+ }
763
+ return { sites, values };
764
+ }
765
+
766
+ // `fmt.Println(any(x))` formats x: the conversion hides the static type the
767
+ // description is made from, and drops nothing else.
768
+ function unwrapInterfaceConversions(call, skip) {
769
+ const args = call.args ?? [];
770
+ for (let i = skip; i < args.length; i++) {
771
+ const a = args[i];
772
+ if (a?.kind !== "TypeConversion" || !isInterface(a._type)) continue;
773
+ const inner = a.expr;
774
+ const t = defaultType(inner?._type);
775
+ if (t && t.kind !== "tuple" && !isInterface(t) && !inner._spread)
776
+ args[i] = inner;
777
+ }
778
+ }
779
+
780
+ // Whether the package passes json.Unmarshal or a Decode call a target that is
781
+ // not statically a pointer (an `any` that may hold a value): the decoder then
782
+ // reports the target's type, which only a registered lookup can name.
783
+ function hasJsonTargetNotPointer(programs) {
784
+ let found = false;
785
+ for (const program of programs) {
786
+ if (
787
+ !(program.imports ?? []).some((imp) =>
788
+ imp.imports.some((spec) => spec.path === JSON_PATH),
789
+ )
790
+ )
791
+ continue;
792
+ walkNodes(program.decls, (node) => {
793
+ if (found || node.kind !== "CallExpr") return;
794
+ const f = node.func;
795
+ if (f?.kind !== "SelectorExpr") return;
796
+ const at = f.field === "Unmarshal" ? 1 : f.field === "Decode" ? 0 : -1;
797
+ const arg = node.args?.[at];
798
+ if (at < 0 || !arg?._type) return;
799
+ if (under(defaultType(arg._type))?.kind !== "pointer") found = true;
800
+ });
801
+ }
802
+ return found;
803
+ }
804
+
805
+ // The operand types of a call, or null when it cannot be wrapped.
806
+ function operandTypes(call, skip) {
807
+ const args = call.args ?? [];
808
+ if (args.some((a) => a?._spread)) return null;
809
+ if (call._multiForward) {
810
+ const types = args[0]?._type?.types;
811
+ return types ? types.slice(skip) : null;
812
+ }
813
+ return args.slice(skip).map((a) => a?._type);
814
+ }
815
+
816
+ // Marks `call` to call the generated wrapper `name`, which takes the
817
+ // operands with their static types (every operand, for a forwarded call) and
818
+ // is not variadic.
819
+ function markCall(call, { name, kinds, skip, types, exact }) {
820
+ const orig = call.func._type;
821
+ const operand = (t, i) => (exact || kinds[i].wrap ? defaultType(t) : ANY);
822
+ call.func._genEntry = name;
823
+ call.func._type = {
824
+ kind: "func",
825
+ params: [...orig.params.slice(0, skip), ...types.map(operand)],
826
+ returns: orig.returns,
827
+ variadic: false,
828
+ };
829
+ if (exact) return;
830
+ kinds.forEach((k, i) => {
831
+ if (!k.wrap) return;
832
+ const arg = call.args[skip + i];
833
+ arg._ifaceBox = undefined;
834
+ arg._ifaceSrc = undefined;
835
+ arg._ifaceNamedType = undefined;
836
+ arg._ifacePtrFields = undefined;
837
+ });
838
+ }
839
+
840
+ // Generates the formatters for a package's Print-family calls. Returns null
841
+ // when it needs none; otherwise { body, imports, diagnostics } and redirects
842
+ // each call that needs a wrapper with `_genEntry`.
843
+ export function generateFmtFormatters(programs, checker, pkgName) {
844
+ const { sites, values } = findSites(programs);
845
+ // encoding/json names the type of a target that is not a pointer with %T,
846
+ // an operand that arrives through an interface.
847
+ const usesJson = hasJsonTargetNotPointer(programs);
848
+ const testing = findTestingCalls(programs);
849
+ if (sites.length + values.length === 0 && !usesJson && !testing.calls)
850
+ return null;
851
+ const qualifiers = new Map();
852
+ const gen = new FmtGen(
853
+ checker,
854
+ pkgName,
855
+ qualifiers,
856
+ programs,
857
+ testing.formatted || sites.some((s) => isFormatted(s.call.func.field)),
858
+ );
859
+ const diagnostics = [];
860
+ const marks = [];
861
+ let lookup = values.length > 0 || usesJson || testing.calls;
862
+ for (const { call, program } of sites) {
863
+ // Parsed trees outlive a build: forget how an earlier build wrapped it.
864
+ delete call.func._genEntry;
865
+ for (const a of call.args ?? []) if (a) delete a._boxTypeParam;
866
+ const fn = call.func.field;
867
+ const prefix = PRINT_FUNCS.get(fn);
868
+ const formatted = isFormatted(fn);
869
+ const skip = (prefix ? 1 : 0) + (formatted ? 1 : 0);
870
+ if (!call._multiForward) unwrapInterfaceConversions(call, skip);
871
+ const types = operandTypes(call, skip);
872
+ if (!types) {
873
+ lookup = true;
874
+ continue;
875
+ }
876
+ const kinds = types.map((t) => gen.classify(t, formatted));
877
+ if (kinds.some((k) => k.lookup)) lookup = true;
878
+ // A type parameter value is boxed by its type argument (JS backend).
879
+ if (!call._multiForward)
880
+ types.forEach((t, i) => {
881
+ if (t?.kind === "typeParam") call.args[skip + i]._boxTypeParam = true;
882
+ });
883
+ if (!kinds.some((k) => k.wrap || k.lookup) && !call._multiForward) continue;
884
+ const exact = Boolean(call._multiForward);
885
+ let prefixType = prefix;
886
+ if (exact && prefix) {
887
+ const t = defaultType(call.args[0]._type.types[0]);
888
+ prefixType = gen.spellable(t) ? gen.spell(t) : null;
889
+ }
890
+ const name =
891
+ exact && prefix && !prefixType
892
+ ? null
893
+ : gen.wrapper(fn, prefixType, types, kinds, exact);
894
+ if (!name) {
895
+ diagnostics.push({
896
+ program,
897
+ node: call.func.expr,
898
+ message:
899
+ "passing multiple return values to a variadic function is not supported in wasm packages; assign them to variables first",
900
+ });
901
+ continue;
902
+ }
903
+ marks.push({ call, name, kinds, skip, types, exact });
904
+ }
905
+ if (lookup) gen.needsLookup = true;
906
+ gen.drain();
907
+ if (gen.needsLookup) gen.genLookup();
908
+ gen.drain();
909
+ if (gen.out.length === 0 && diagnostics.length === 0) return null;
910
+ for (const mark of marks) markCall(mark.call, mark);
911
+ const ensure = gen.needsLookup ? `\n\t${gen.prefix}ensure()` : "";
912
+ const source = gen.out.join("\n\n").replaceAll(ENSURE, ensure);
913
+ const body = source.replaceAll(
914
+ KEY,
915
+ JSON.stringify(`${pkgName}:${hash(source)}`),
916
+ );
917
+ const imports = new Set(gen.uses);
918
+ if (
919
+ body.includes("fmt.Sprint") ||
920
+ body.includes("fmt.Print") ||
921
+ body.includes("fmt.Fprint") ||
922
+ body.includes("fmt.Append") ||
923
+ body.includes("fmt.Errorf")
924
+ )
925
+ imports.add(FMT_PATH);
926
+ if (body.includes("fmtrt.")) imports.add(RT_PATH);
927
+ for (const p of importsFor(qualifiers)) imports.add(p);
928
+ return { body, imports: [...imports], diagnostics };
929
+ }