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