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
@@ -0,0 +1,1398 @@
1
+ // src/lower/json-codecs.js
2
+ // encoding/json for wasm packages: per-type codecs generated as Go source.
3
+ //
4
+ // The WASM backend has no reflection, so every `json.Marshal` and
5
+ // `json.Unmarshal` call site gets codecs generated from the checked types.
6
+ // The generated functions are ordinary Go that calls internal/jsonrt (the
7
+ // scanner, the encoder state and the decoder state); they are parsed and
8
+ // checked as one extra file of the package and linked into app.wasm, never
9
+ // emitted as JS. JSON field rules come from the checker's resolveJsonFields,
10
+ // which the JS backend uses too, so the two backends cannot drift.
11
+ //
12
+ // A call site whose argument has a concrete type gets an entry point for that
13
+ // type. A call site with an interface-typed argument gets the dynamic entry,
14
+ // which switches over every type the program encodes or decodes anywhere.
15
+
16
+ import { resolveJsonFields } from "../typechecker/resolve.js";
17
+ import { defaultType } from "../typechecker/types.js";
18
+ import { forEachChild } from "./walk.js";
19
+
20
+ const JSON_PATH = "encoding/json";
21
+ const SITE_FUNCS = new Set(["Marshal", "MarshalIndent", "Unmarshal", "Valid"]);
22
+
23
+ const INT_KINDS = new Set(["int", "int8", "int16", "int32", "int64"]);
24
+ const UINT_KINDS = new Set([
25
+ "uint",
26
+ "uint8",
27
+ "uint16",
28
+ "uint32",
29
+ "uint64",
30
+ "uintptr",
31
+ ]);
32
+ const FLOAT_KINDS = new Set(["float32", "float64"]);
33
+ const INT_BITS = {
34
+ int: 64,
35
+ int8: 8,
36
+ int16: 16,
37
+ int32: 32,
38
+ int64: 64,
39
+ uint: 64,
40
+ uint8: 8,
41
+ uint16: 16,
42
+ uint32: 32,
43
+ uint64: 64,
44
+ uintptr: 64,
45
+ };
46
+ const ALIAS = { byte: "uint8", rune: "int32" };
47
+ const REFLECT_NAME = { any: "interface {}" };
48
+ // The types the dynamic entry always handles: what decoding into `any` yields
49
+ // plus the other basic kinds a program may store in an interface.
50
+ const DYNAMIC_BASICS = [
51
+ "bool",
52
+ "string",
53
+ "int",
54
+ "int8",
55
+ "int16",
56
+ "int32",
57
+ "int64",
58
+ "uint",
59
+ "uint8",
60
+ "uint16",
61
+ "uint32",
62
+ "uint64",
63
+ "float32",
64
+ "float64",
65
+ ];
66
+
67
+ const unalias = (n) => ALIAS[n] ?? n;
68
+ const q = (s) => JSON.stringify(s);
69
+
70
+ function under(t) {
71
+ return t?.kind === "named" ? under(t.underlying) : t;
72
+ }
73
+
74
+ function isBasic(t, set) {
75
+ const u = under(t);
76
+ return u?.kind === "basic" && set.has(unalias(u.name));
77
+ }
78
+
79
+ const isRaw = (t) =>
80
+ t?.kind === "named" &&
81
+ (t.name === "json.RawMessage" || t.name === "encoding/json.RawMessage");
82
+
83
+ const isNumber = (t) =>
84
+ t?.kind === "named" &&
85
+ (t.name === "json.Number" ||
86
+ t.name === "encoding/json.Number" ||
87
+ t.name?.startsWith("Number$$") ||
88
+ t.name?.endsWith(".Number") ||
89
+ (t.name === "Number" && under(t)?.name === "string"));
90
+
91
+ function isBytes(t) {
92
+ const u = under(t);
93
+ return (
94
+ u?.kind === "slice" && !isRaw(t) && unalias(under(u.elem)?.name) === "uint8"
95
+ );
96
+ }
97
+
98
+ const isEmptyIface = (t) =>
99
+ (t?.kind === "basic" && t.name === "any") ||
100
+ (under(t)?.kind === "interface" && under(t).methods?.size === 0);
101
+
102
+ const isNilType = (t) =>
103
+ t?.kind === "nil" || (t?.kind === "basic" && t.name === "nil");
104
+
105
+ const isInterface = (t) =>
106
+ isNilType(t) ||
107
+ isEmptyIface(t) ||
108
+ (t?.kind !== "basic" && under(t)?.kind === "interface");
109
+
110
+ function methodOf(t, name) {
111
+ if (t?.kind !== "named") return null;
112
+ return t.methods?.get(name) ?? t.underlying?.methods?.get(name) ?? null;
113
+ }
114
+
115
+ // A method's results; a multi-value result is one tuple type.
116
+ function results(m) {
117
+ const r = (m.returns ?? []).filter(
118
+ (t) => !(t?.kind === "basic" && t.name === "void"),
119
+ );
120
+ return r.length === 1 && r[0]?.kind === "tuple" ? r[0].types : r;
121
+ }
122
+
123
+ // MarshalJSON() ([]byte, error)
124
+ function marshalerOf(t) {
125
+ if (isRaw(t)) return null;
126
+ const m = methodOf(t, "MarshalJSON");
127
+ if (m?.params?.length !== 0 || results(m).length !== 2) return null;
128
+ return m;
129
+ }
130
+
131
+ // UnmarshalJSON([]byte) error
132
+ function unmarshalerOf(t) {
133
+ if (isRaw(t)) return null;
134
+ const m = methodOf(t, "UnmarshalJSON");
135
+ if (m?.params?.length !== 1 || results(m).length !== 1) return null;
136
+ return m;
137
+ }
138
+
139
+ function isSimpleKey(s) {
140
+ return /^[A-Za-z0-9_ .-]*$/.test(s);
141
+ }
142
+
143
+ export class JsonCodecGen {
144
+ constructor(checker, pkgName, qualifiers, programs = []) {
145
+ this.programs = programs;
146
+ this.checker = checker;
147
+ this.pkgName = pkgName;
148
+ this.qualifiers = qualifiers;
149
+ this.prefix = `gfj_${String(pkgName).replace(/[^A-Za-z0-9_]/g, "_")}_`;
150
+ this.enc = new Map(); // key -> { name, t, addr }
151
+ this.dec = new Map();
152
+ this.queue = [];
153
+ this.out = [];
154
+ this.entries = [];
155
+ this.dynamic = false;
156
+ this.dynamicDone = false;
157
+ this.decTargets = [];
158
+ this.counter = 0;
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
+ };
179
+ return (byKind[t.kind] ?? (() => `?${t.kind}`))();
180
+ }
181
+
182
+ structKey(t) {
183
+ const fields = (t._structFields ?? []).map(
184
+ (f) =>
185
+ `${(f.names ?? []).join(",")}:${this.key(f.type)}:${f.tag ?? ""}:${f.embedded ? 1 : 0}`,
186
+ );
187
+ return `struct{${fields.join(";")}}`;
188
+ }
189
+
190
+ // Go source for the type.
191
+ spell(t) {
192
+ const sp = (x) => this.spell(x);
193
+ const byKind = {
194
+ basic: () => t.name,
195
+ named: () => this.spellNamed(t),
196
+ pointer: () => `*${sp(t.base)}`,
197
+ slice: () => `[]${sp(t.elem)}`,
198
+ array: () => `[${t.size}]${sp(t.elem)}`,
199
+ map: () => `map[${sp(t.key)}]${sp(t.value)}`,
200
+ struct: () => this.spellStruct(t),
201
+ interface: () => "any",
202
+ func: () => this.funcString(t, sp),
203
+ };
204
+ if (!byKind[t.kind])
205
+ throw new Error(`json: cannot spell type kind ${t.kind}`);
206
+ return byKind[t.kind]();
207
+ }
208
+
209
+ resolveImportAlias(modPath) {
210
+ for (const p of this.programs) {
211
+ for (const imp of p.imports ?? []) {
212
+ for (const spec of imp.imports) {
213
+ if (spec.path === modPath) {
214
+ return spec.alias ?? spec.path.split("/").pop();
215
+ }
216
+ }
217
+ }
218
+ }
219
+ return modPath.split("/").pop();
220
+ }
221
+
222
+ spellNamed(t) {
223
+ let name = t.name;
224
+ const at = name.indexOf("$$");
225
+ if (at >= 0) {
226
+ const orig = name.slice(0, at);
227
+ const modPath = name.slice(at + 2).replaceAll("$", "/");
228
+ const alias = this.resolveImportAlias(modPath);
229
+ this.qualifiers.add(alias);
230
+ name = `${alias}.${orig}`;
231
+ }
232
+ const base = this.qualified(name);
233
+ return t.typeArgs?.length
234
+ ? `${base}[${t.typeArgs.map((a) => this.spell(a)).join(", ")}]`
235
+ : base;
236
+ }
237
+
238
+ spellStruct(t) {
239
+ const fields = (t._structFields ?? []).map((f) => {
240
+ const tag = f.tag ? ` \`${f.tag}\`` : "";
241
+ if (f.embedded) return `${this.spell(f.type)}${tag}`;
242
+ return `${f.names.join(", ")} ${this.spell(f.type)}${tag}`;
243
+ });
244
+ return `struct { ${fields.join("; ")} }`;
245
+ }
246
+
247
+ qualified(name) {
248
+ const dot = name.indexOf(".");
249
+ if (dot > 0) this.qualifiers.add(name.slice(0, dot));
250
+ return name;
251
+ }
252
+
253
+ // reflect's Type.String() of the type, for error messages.
254
+ refl(t) {
255
+ switch (t.kind) {
256
+ case "basic":
257
+ return REFLECT_NAME[t.name] ?? unalias(t.name);
258
+ case "named": {
259
+ let name = t.name;
260
+ const at = name.indexOf("$$");
261
+ if (at >= 0) {
262
+ const orig = name.slice(0, at);
263
+ const modPath = name.slice(at + 2).replaceAll("$", "/");
264
+ name = `${modPath}.${orig}`;
265
+ } else if (!name.includes(".")) {
266
+ name = `${this.pkgName}.${name}`;
267
+ }
268
+ return t.typeArgs?.length
269
+ ? `${name}[${t.typeArgs.map((a) => this.refl(a)).join(",")}]`
270
+ : name;
271
+ }
272
+ case "pointer":
273
+ return `*${this.refl(t.base)}`;
274
+ case "slice":
275
+ return `[]${this.refl(t.elem)}`;
276
+ case "array":
277
+ return `[${t.size}]${this.refl(t.elem)}`;
278
+ case "map":
279
+ return `map[${this.refl(t.key)}]${this.refl(t.value)}`;
280
+ case "interface":
281
+ return "interface {}";
282
+ case "func":
283
+ return this.funcString(t, (x) => this.refl(x));
284
+ default:
285
+ return "struct {}";
286
+ }
287
+ }
288
+
289
+ funcString(t, name) {
290
+ const rs = results(t);
291
+ const res =
292
+ rs.length === 0
293
+ ? ""
294
+ : rs.length === 1
295
+ ? ` ${name(rs[0])}`
296
+ : ` (${rs.map(name).join(", ")})`;
297
+ return `func(${t.params.map(name).join(", ")})${res}`;
298
+ }
299
+
300
+ // A struct's own name in decode errors.
301
+ shortName(t) {
302
+ return t?.kind === "named" ? t.name.split(".").pop() : "";
303
+ }
304
+
305
+ // ── Encoders ────────────────────────────────────────────────
306
+
307
+ // True when encoding an addressable `t` differs from a plain value: it, or
308
+ // a value field inside it, has a pointer-receiver MarshalJSON.
309
+ needsAddr(t, seen = new Set()) {
310
+ if (!t || seen.has(t)) return false;
311
+ seen.add(t);
312
+ const m = marshalerOf(t);
313
+ if (m) return Boolean(m._ptrRecv);
314
+ const u = under(t);
315
+ if (u?.kind === "struct")
316
+ return resolveJsonFields(u).some((f) => this.needsAddr(f.type, seen));
317
+ if (u?.kind === "array") return this.needsAddr(u.elem, seen);
318
+ return false;
319
+ }
320
+
321
+ encFn(t, addr = false) {
322
+ const a = addr && this.needsAddr(t);
323
+ const key = this.key(t) + (a ? "&" : "");
324
+ let e = this.enc.get(key);
325
+ if (!e) {
326
+ e = { name: `${this.prefix}e${this.counter++}`, t, addr: a };
327
+ this.enc.set(key, e);
328
+ this.queue.push(() => this.genEnc(e));
329
+ }
330
+ return e.name;
331
+ }
332
+
333
+ flagDynamic() {
334
+ this.dynamic = true;
335
+ }
336
+
337
+ genEnc({ name, t, addr }) {
338
+ const body = this.encBody(t, addr).map((l) => `\t${l}`);
339
+ this.out.push(
340
+ `func ${name}(e *jsonrt.Enc, v ${this.spell(t)}) {\n${body.join("\n")}\n}`,
341
+ );
342
+ }
343
+
344
+ // Statements that encode the Go expression `x` of type `t`.
345
+ encValue(t, x, addr) {
346
+ return [`${this.encFn(t, addr)}(e, ${x})`];
347
+ }
348
+
349
+ marshalCall(x, t) {
350
+ return [
351
+ `out, err := ${x}.MarshalJSON()`,
352
+ `e.Marshaled(out, err, ${q(this.refl(t))})`,
353
+ ];
354
+ }
355
+
356
+ // Raw messages, marshalers, byte slices and interfaces encode specially.
357
+ encSpecial(t, addr) {
358
+ if (isRaw(t)) return ["e.Raw([]byte(v))"];
359
+ if (isNumber(t)) return ["e.Number(string(v))"];
360
+ const m = marshalerOf(t);
361
+ if (m && (!m._ptrRecv || addr)) return this.marshalCall("v", t);
362
+ if (isBytes(t)) return ["e.Bytes([]byte(v))"];
363
+ if (isInterface(t)) {
364
+ this.flagDynamic();
365
+ return [`${this.prefix}encAny(e, v)`];
366
+ }
367
+ return null;
368
+ }
369
+
370
+ encBody(t, addr) {
371
+ const special = this.encSpecial(t, addr);
372
+ if (special) return special;
373
+ const u = under(t);
374
+ if (u.kind === "basic") return this.encScalar(t, "v");
375
+ const byKind = {
376
+ pointer: () => this.encPointer(t, u),
377
+ slice: () => this.encSlice(u),
378
+ array: () => this.encArray(u, addr),
379
+ map: () => this.encMap(u),
380
+ struct: () => this.encStruct(u, addr),
381
+ };
382
+ if (byKind[u.kind]) return byKind[u.kind]();
383
+ return ["_ = v", `e.FailType(${q(this.refl(t))})`];
384
+ }
385
+
386
+ encPointer(t, u) {
387
+ const lines = ["if v == nil {", "\te.Null()", "\treturn", "}"];
388
+ if (marshalerOf(u.base)) lines.push(...this.marshalCall("v", t));
389
+ else lines.push(...this.encValue(u.base, "*v", true));
390
+ return lines;
391
+ }
392
+
393
+ encSlice(u) {
394
+ return [
395
+ "if v == nil {",
396
+ "\te.Null()",
397
+ "\treturn",
398
+ "}",
399
+ "e.Byte('[')",
400
+ "for i := 0; i < len(v); i++ {",
401
+ "\tif i > 0 {",
402
+ "\t\te.Byte(',')",
403
+ "\t}",
404
+ ...this.encValue(u.elem, "v[i]", true).map((l) => `\t${l}`),
405
+ "}",
406
+ "e.Byte(']')",
407
+ ];
408
+ }
409
+
410
+ encArray(u, addr) {
411
+ return [
412
+ "e.Byte('[')",
413
+ `for i := 0; i < ${u.size}; i++ {`,
414
+ "\tif i > 0 {",
415
+ "\t\te.Byte(',')",
416
+ "\t}",
417
+ ...this.encValue(u.elem, "v[i]", addr).map((l) => `\t${l}`),
418
+ "}",
419
+ "e.Byte(']')",
420
+ ];
421
+ }
422
+
423
+ // Scalars encode their value; `x` has type `t` (basic underlying).
424
+ encScalar(t, x) {
425
+ const u = under(t);
426
+ const n = unalias(u.name);
427
+ if (n === "bool") return [`e.Bool(bool(${x}))`];
428
+ if (n === "string") return [`e.Str(string(${x}))`];
429
+ if (INT_KINDS.has(n)) return [`e.Int(int64(${x}))`];
430
+ if (UINT_KINDS.has(n)) return [`e.Uint(uint64(${x}))`];
431
+ if (n === "float32") return [`e.Float(float64(${x}), 32)`];
432
+ if (n === "float64") return [`e.Float(float64(${x}), 64)`];
433
+ return [`_ = ${x}`, `e.FailType(${q(this.refl(t))})`];
434
+ }
435
+
436
+ encMap(u) {
437
+ const kt = u.key;
438
+ const strKey = isBasic(kt, new Set(["string"]));
439
+ const intKey = isBasic(kt, INT_KINDS);
440
+ const uintKey = isBasic(kt, UINT_KINDS);
441
+ if (!strKey && !intKey && !uintKey) {
442
+ return ["_ = v", `e.FailType(${q(this.refl(u))})`];
443
+ }
444
+ const conv = strKey
445
+ ? "string(k)"
446
+ : intKey
447
+ ? "jsonrt.IntKey(int64(k))"
448
+ : "jsonrt.UintKey(uint64(k))";
449
+ return [
450
+ "if v == nil {",
451
+ "\te.Null()",
452
+ "\treturn",
453
+ "}",
454
+ "keys := make([]string, 0, len(v))",
455
+ `vals := make(map[string]${this.spell(u.value)}, len(v))`,
456
+ "for k, x := range v {",
457
+ `\tks := ${conv}`,
458
+ "\tkeys = append(keys, ks)",
459
+ "\tvals[ks] = x",
460
+ "}",
461
+ "jsonrt.SortStrings(keys)",
462
+ "e.Byte('{')",
463
+ "for i, k := range keys {",
464
+ "\te.KeyStr(i, k)",
465
+ ...this.encValue(u.value, "vals[k]", false).map((l) => `\t${l}`),
466
+ "}",
467
+ "e.Byte('}')",
468
+ ];
469
+ }
470
+
471
+ // The expression for "x has its zero value" under omitempty.
472
+ emptyExpr(t, x) {
473
+ const u = under(t);
474
+ if (t.kind === "pointer" || u.kind === "pointer" || isInterface(t))
475
+ return `${x} == nil`;
476
+ if (u.kind === "slice" || u.kind === "map") return `len(${x}) == 0`;
477
+ if (u.kind === "array") return u.size === 0 ? "true" : "false";
478
+ if (u.kind === "struct") return "false";
479
+ const n = unalias(u.name);
480
+ if (n === "bool") return `!${x}`;
481
+ if (n === "string") return `${x} == ""`;
482
+ return `${x} == 0`;
483
+ }
484
+
485
+ quotable(f) {
486
+ if (!f.quoted) return false;
487
+ const t = f.type.kind === "pointer" ? f.type.base : f.type;
488
+ if (marshalerOf(t)) return false;
489
+ const u = under(t);
490
+ return u?.kind === "basic" && DYNAMIC_BASICS.includes(unalias(u.name));
491
+ }
492
+
493
+ encQuoted(f, x) {
494
+ const isPtr = f.type.kind === "pointer";
495
+ const t = isPtr ? f.type.base : f.type;
496
+ const scalar =
497
+ unalias(under(t).name) === "string"
498
+ ? [`e.QStr(string(${isPtr ? `*${x}` : x}))`]
499
+ : [
500
+ "e.Byte('\"')",
501
+ ...this.encScalar(t, isPtr ? `*${x}` : x),
502
+ "e.Byte('\"')",
503
+ ];
504
+ if (!isPtr) return scalar;
505
+ return [
506
+ `if ${x} == nil {`,
507
+ "\te.Null()",
508
+ "} else {",
509
+ ...scalar.map((l) => `\t${l}`),
510
+ "}",
511
+ ];
512
+ }
513
+
514
+ encStruct(u, addr) {
515
+ const fields = resolveJsonFields(u);
516
+ if (fields.length === 0) return ["_ = v", "e.Byte('{')", "e.Byte('}')"];
517
+ const lines = ["e.Byte('{')", "n := 0"];
518
+ for (const f of fields) {
519
+ const path = f.path ?? [{ name: f.goName, isPtr: false }];
520
+ const guards = [];
521
+ let cur = "v";
522
+ for (let i = 0; i < path.length - 1; i++) {
523
+ cur += `.${path[i].name}`;
524
+ if (path[i].isPtr) guards.push(`${cur} != nil`);
525
+ }
526
+ const access = `${cur}.${path[path.length - 1].name}`;
527
+ const inner = [`x := ${access}`];
528
+ const emit = [];
529
+ const keyName = f.jsonKey;
530
+ emit.push(
531
+ isSimpleKey(keyName)
532
+ ? `e.Key(n, ${q(keyName)})`
533
+ : `e.KeyStr(n, ${q(keyName)})`,
534
+ );
535
+ emit.push("n++");
536
+ emit.push(
537
+ ...(this.quotable(f)
538
+ ? this.encQuoted(f, "x")
539
+ : this.encValue(f.type, "x", addr)),
540
+ );
541
+ if (f.omitempty) {
542
+ inner.push(`if !(${this.emptyExpr(f.type, "x")}) {`);
543
+ inner.push(...emit.map((l) => `\t${l}`));
544
+ inner.push("}");
545
+ } else {
546
+ inner.push(...emit);
547
+ }
548
+ const block = guards.length
549
+ ? [`if ${guards.join(" && ")} {`, ...inner.map((l) => `\t${l}`), "}"]
550
+ : ["{", ...inner.map((l) => `\t${l}`), "}"];
551
+ lines.push(...block);
552
+ }
553
+ lines.push("e.Byte('}')");
554
+ return lines;
555
+ }
556
+
557
+ // The dynamic encoder: a type switch over every type the program encodes.
558
+ genEncAny() {
559
+ const cases = [];
560
+ const seen = new Set();
561
+ for (const [key, e] of this.enc) {
562
+ if (e.addr || seen.has(key)) continue;
563
+ seen.add(key);
564
+ if (isInterface(e.t)) continue;
565
+ cases.push(`\tcase ${this.spell(e.t)}:\n\t\t${e.name}(e, x)`);
566
+ }
567
+ this.out.push(
568
+ [
569
+ `func ${this.prefix}encAny(e *jsonrt.Enc, v any) {`,
570
+ "\tif v == nil {",
571
+ "\t\te.Null()",
572
+ "\t\treturn",
573
+ "\t}",
574
+ "\tswitch x := v.(type) {",
575
+ ...cases,
576
+ "\tdefault:",
577
+ "\t\t_ = x",
578
+ '\t\te.FailType("unsupported dynamic type")',
579
+ "\t}",
580
+ "}",
581
+ ].join("\n"),
582
+ );
583
+ }
584
+
585
+ // ── Decoders ────────────────────────────────────────────────
586
+ //
587
+ // A struct decodes in place through its pointer (`func(d, p *S)`); every
588
+ // other type is a value-style `func(d, cur T) T` that returns the decoded
589
+ // value, because the WASM backend cannot take the address of a field or
590
+ // element that holds a pointer.
591
+
592
+ decInPlace(t) {
593
+ return under(t)?.kind === "struct";
594
+ }
595
+
596
+ decFn(t) {
597
+ const key = this.key(t);
598
+ let d = this.dec.get(key);
599
+ if (!d) {
600
+ d = { name: `${this.prefix}d${this.counter++}`, t };
601
+ this.dec.set(key, d);
602
+ this.queue.push(() => this.genDec(d));
603
+ }
604
+ return d.name;
605
+ }
606
+
607
+ genDec({ name, t }) {
608
+ const body = this.decBody(t).map((l) => `\t${l}`);
609
+ const sp = this.spell(t);
610
+ const sig = this.decInPlace(t)
611
+ ? `(d *jsonrt.Dec, p *${sp})`
612
+ : `(d *jsonrt.Dec, cur ${sp}) ${sp}`;
613
+ this.out.push(`func ${name}${sig} {\n${body.join("\n")}\n}`);
614
+ }
615
+
616
+ // Statements that decode a value of type `t` into the addressable
617
+ // expression `lv`, reading from decoder `dv`.
618
+ decInto(t, lv, dv = "d") {
619
+ const fn = this.decFn(t);
620
+ return this.decInPlace(t)
621
+ ? [`${fn}(${dv}, &${lv})`]
622
+ : [`${lv} = ${fn}(${dv}, ${lv})`];
623
+ }
624
+
625
+ // The scalar-or-reference forms return `cur` unchanged when the value does
626
+ // not apply (null, or a type error that was recorded).
627
+ decBody(t) {
628
+ const typ = q(this.refl(t));
629
+ const special = this.decSpecial(t, typ);
630
+ if (special) return special;
631
+ const u = under(t);
632
+ if (u.kind === "basic" && !isEmptyIface(t))
633
+ return this.decScalar(t, u, typ);
634
+ const byKind = {
635
+ pointer: () => this.decPointer(u, typ),
636
+ slice: () => this.decSlice(u, t, typ),
637
+ array: () => this.decArray(u, typ),
638
+ map: () => this.decMap(u, t, typ),
639
+ struct: () => this.decStruct(u, t, typ),
640
+ interface: () =>
641
+ isEmptyIface(t)
642
+ ? ["return d.Any()"]
643
+ : [`d.Mismatch(${typ})`, "return cur"],
644
+ basic: () => ["return d.Any()"],
645
+ };
646
+ return (byKind[u.kind] ?? (() => [`d.Mismatch(${typ})`, "return cur"]))();
647
+ }
648
+
649
+ decSpecial(t, typ) {
650
+ if (isRaw(t)) return [`return ${this.spell(t)}(d.Raw())`];
651
+ if (isNumber(t))
652
+ return [
653
+ `s, ok := d.Number(${typ})`,
654
+ "if !ok {",
655
+ "\treturn cur",
656
+ "}",
657
+ `return ${this.spell(t)}(s)`,
658
+ ];
659
+ if (unmarshalerOf(t)) return this.decUnmarshaler(t);
660
+ if (isBytes(t)) return this.decBytes(t, typ);
661
+ return null;
662
+ }
663
+
664
+ decUnmarshaler(t) {
665
+ const inPlace = this.decInPlace(t);
666
+ const recv = inPlace ? "p" : "x";
667
+ const call = [
668
+ "raw := d.Raw()",
669
+ `if err := ${recv}.UnmarshalJSON(raw); err != nil {`,
670
+ "\td.Fatal(err)",
671
+ "}",
672
+ ];
673
+ return inPlace ? call : ["x := cur", ...call, "return x"];
674
+ }
675
+
676
+ decBytes(t, typ) {
677
+ return [
678
+ "if d.Null() {",
679
+ "\treturn nil",
680
+ "}",
681
+ "if d.IsArray() {",
682
+ ...this.decElems(under(t), t, typ),
683
+ "}",
684
+ `if b, ok := d.Bytes(${typ}); ok {`,
685
+ `\treturn ${this.spell(t)}(b)`,
686
+ "}",
687
+ "return cur",
688
+ ];
689
+ }
690
+
691
+ decScalar(t, u, typ) {
692
+ const n = unalias(u.name);
693
+ const sp = this.spell(t);
694
+ let call;
695
+ if (n === "bool") call = `d.Bool(${typ})`;
696
+ else if (n === "string") call = `d.String(${typ})`;
697
+ else if (INT_KINDS.has(n)) call = `d.Int(${INT_BITS[n]}, ${typ})`;
698
+ else if (UINT_KINDS.has(n)) call = `d.Uint(${INT_BITS[n]}, ${typ})`;
699
+ else if (FLOAT_KINDS.has(n))
700
+ call = `d.Float(${n === "float32" ? 32 : 64}, ${typ})`;
701
+ else return [`d.Mismatch(${typ})`, "return cur"];
702
+ return [
703
+ `if v, ok := ${call}; ok {`,
704
+ `\treturn ${sp}(v)`,
705
+ "}",
706
+ "return cur",
707
+ ];
708
+ }
709
+
710
+ decPointer(u, typ) {
711
+ const base = u.base;
712
+ const lines = [
713
+ "if d.Null() {",
714
+ "\treturn nil",
715
+ "}",
716
+ "if cur == nil {",
717
+ `\tcur = new(${this.spell(base)})`,
718
+ "}",
719
+ ];
720
+ if (under(base)?.kind === "pointer") {
721
+ lines.push(`d.Mismatch(${typ})`);
722
+ } else if (this.decInPlace(base)) {
723
+ lines.push(`${this.decFn(base)}(d, cur)`);
724
+ } else {
725
+ lines.push(`*cur = ${this.decFn(base)}(d, *cur)`);
726
+ }
727
+ lines.push("return cur");
728
+ return lines;
729
+ }
730
+
731
+ // Decodes the elements of the array at the cursor into a new slice `s`.
732
+ decElems(u, t, typ) {
733
+ return [
734
+ `if !d.ArrBegin(${typ}) {`,
735
+ "\treturn cur",
736
+ "}",
737
+ `s := make(${this.spell(t)}, 0)`,
738
+ "for d.NextElem() {",
739
+ `\tvar x ${this.spell(u.elem)}`,
740
+ ...this.decInto(u.elem, "x").map((l) => `\t${l}`),
741
+ "\ts = append(s, x)",
742
+ "}",
743
+ "return s",
744
+ ];
745
+ }
746
+
747
+ decSlice(u, t, typ) {
748
+ return ["if d.Null() {", "\treturn nil", "}", ...this.decElems(u, t, typ)];
749
+ }
750
+
751
+ decArray(u, typ) {
752
+ return [
753
+ "if d.Null() {",
754
+ "\treturn cur",
755
+ "}",
756
+ `if !d.ArrBegin(${typ}) {`,
757
+ "\treturn cur",
758
+ "}",
759
+ "i := 0",
760
+ "for d.NextElem() {",
761
+ `\tif i < ${u.size} {`,
762
+ ...this.decInto(u.elem, "cur[i]").map((l) => `\t\t${l}`),
763
+ "\t} else {",
764
+ "\t\td.Skip()",
765
+ "\t}",
766
+ "\ti++",
767
+ "}",
768
+ `for ; i < ${u.size}; i++ {`,
769
+ `\tvar zero ${this.spell(u.elem)}`,
770
+ "\tcur[i] = zero",
771
+ "}",
772
+ "return cur",
773
+ ];
774
+ }
775
+
776
+ decMap(u, t, typ) {
777
+ const kt = u.key;
778
+ const strKey = isBasic(kt, new Set(["string"]));
779
+ const intKey = isBasic(kt, INT_KINDS);
780
+ const uintKey = isBasic(kt, UINT_KINDS);
781
+ if (!strKey && !intKey && !uintKey)
782
+ return [`d.Mismatch(${typ})`, "return cur"];
783
+ const ks = this.spell(kt);
784
+ let keyLines;
785
+ if (strKey) {
786
+ keyLines = [`k := ${ks}(d.Key())`];
787
+ } else {
788
+ const n = unalias(under(kt).name);
789
+ keyLines = [
790
+ `kv, ok := d.KeyInt(${INT_BITS[n]}, ${intKey}, ${q(this.refl(kt))})`,
791
+ "if !ok {",
792
+ "\td.Skip()",
793
+ "\tcontinue",
794
+ "}",
795
+ `k := ${ks}(kv)`,
796
+ ];
797
+ }
798
+ return [
799
+ "if d.Null() {",
800
+ "\treturn nil",
801
+ "}",
802
+ `if !d.ObjBegin(${typ}) {`,
803
+ "\treturn cur",
804
+ "}",
805
+ "m := cur",
806
+ "if m == nil {",
807
+ `\tm = make(${this.spell(t)})`,
808
+ "}",
809
+ "for d.NextKey() {",
810
+ ...keyLines.map((l) => `\t${l}`),
811
+ `\tvar x ${this.spell(u.value)}`,
812
+ ...this.decInto(u.value, "x").map((l) => `\t${l}`),
813
+ "\tm[k] = x",
814
+ "}",
815
+ "return m",
816
+ ];
817
+ }
818
+
819
+ decStruct(u, t, typ) {
820
+ const fields = resolveJsonFields(u);
821
+ const sname = this.shortName(t);
822
+ const lines = [
823
+ "if d.Null() {",
824
+ "\treturn",
825
+ "}",
826
+ `if !d.ObjBegin(${typ}) {`,
827
+ "\treturn",
828
+ "}",
829
+ "for d.NextKey() {",
830
+ ];
831
+ if (fields.length === 0) {
832
+ lines.push(
833
+ "\tif d.DisallowUnknownFields {",
834
+ "\t\td.UnknownField(d.Key())",
835
+ "\t} else {",
836
+ "\t\td.Skip()",
837
+ "\t}",
838
+ "}",
839
+ );
840
+ return lines;
841
+ }
842
+ lines.push("\tk := d.Key()", "\ti := -1", "\tswitch k {");
843
+ fields.forEach((f, i) => {
844
+ lines.push(`\tcase ${q(f.jsonKey)}:`, `\t\ti = ${i}`);
845
+ });
846
+ lines.push("\tdefault:");
847
+ fields.forEach((f, i) => {
848
+ lines.push(
849
+ `\t\t${i > 0 ? "} else " : ""}if jsonrt.EqualFold(k, ${q(f.jsonKey)}) {`,
850
+ `\t\t\ti = ${i}`,
851
+ );
852
+ });
853
+ lines.push("\t\t}", "\t}", "\tswitch i {");
854
+ fields.forEach((f, i) => {
855
+ lines.push(`\tcase ${i}:`);
856
+ lines.push(`\t\td.Enter(${q(sname)}, ${q(f.jsonKey)})`);
857
+ lines.push(...this.decFieldLines(u, f).map((l) => `\t\t${l}`));
858
+ lines.push("\t\td.Leave()");
859
+ });
860
+ lines.push(
861
+ "\tdefault:",
862
+ "\t\tif d.DisallowUnknownFields {",
863
+ "\t\t\td.UnknownField(k)",
864
+ "\t\t} else {",
865
+ "\t\t\td.Skip()",
866
+ "\t\t}",
867
+ "\t}",
868
+ "}",
869
+ );
870
+ return lines;
871
+ }
872
+
873
+ // Allocates the embedded pointers on the path to the field, then decodes it.
874
+ decFieldLines(u, f) {
875
+ const path = f.path ?? [{ name: f.goName, isPtr: false }];
876
+ const lines = [];
877
+ let cur = "p";
878
+ for (let j = 0; j < path.length - 1; j++) {
879
+ cur += `.${path[j].name}`;
880
+ if (!path[j].isPtr) continue;
881
+ const segType = this.embedType(u, path.slice(0, j + 1));
882
+ lines.push(
883
+ `if ${cur} == nil {`,
884
+ `\t${cur} = new(${this.spell(segType)})`,
885
+ "}",
886
+ );
887
+ }
888
+ lines.push(...this.decField(f, `${cur}.${path[path.length - 1].name}`));
889
+ return lines;
890
+ }
891
+
892
+ // The base type of the embedded pointer reached by `path`.
893
+ embedType(u, path) {
894
+ let cur = u;
895
+ let ty = null;
896
+ for (const seg of path) {
897
+ const fl = (cur._structFields ?? []).find(
898
+ (f) => f.embedded && this.embeddedName(f) === seg.name,
899
+ );
900
+ ty = fl.type.kind === "pointer" ? fl.type.base : fl.type;
901
+ cur = under(ty);
902
+ }
903
+ return ty;
904
+ }
905
+
906
+ embeddedName(f) {
907
+ const t = f.type.kind === "pointer" ? f.type.base : f.type;
908
+ return (f.astType?.name ?? f.astType?.base?.name ?? t.name ?? "")
909
+ .split(".")
910
+ .pop();
911
+ }
912
+
913
+ decField(f, lv) {
914
+ if (!this.quotable(f)) return this.decInto(f.type, lv);
915
+ const t = f.type.kind === "pointer" ? f.type.base : f.type;
916
+ const un = unalias(under(t).name);
917
+ const kind = un === "string" ? "string" : un === "bool" ? "bool" : "number";
918
+ return [
919
+ `if sub := d.Unquote(${q(this.refl(t))}, ${q(kind)}); sub != nil {`,
920
+ ...this.decInto(f.type, lv, "sub").map((l) => `\t${l}`),
921
+ "\td.Merge(sub)",
922
+ "}",
923
+ ];
924
+ }
925
+
926
+ // ── Entry points ────────────────────────────────────────────
927
+
928
+ entryMarshal(argType) {
929
+ const name = `${this.prefix}marshal${this.entries.length}`;
930
+ const lines = [
931
+ `func ${name}(v any) ([]byte, error) {`,
932
+ "\te := jsonrt.NewEnc()",
933
+ ];
934
+ if (!argType || isInterface(argType)) {
935
+ this.flagDynamic();
936
+ lines.push(`\t${this.prefix}encAny(e, v)`);
937
+ } else {
938
+ lines.push(
939
+ `\tx, _ := v.(${this.spell(argType)})`,
940
+ `\t${this.encFn(argType, false)}(e, x)`,
941
+ );
942
+ }
943
+ lines.push("\tb, err := e.Result()", "\treturn b, err", "}");
944
+ this.entries.push(lines.join("\n"));
945
+ return name;
946
+ }
947
+
948
+ // Statements that decode into the pointer `x` of pointer type `pt`.
949
+ rootDecode(pt, x) {
950
+ const base = pt.base;
951
+ return this.decInPlace(base)
952
+ ? [`${this.decFn(base)}(d, ${x})`]
953
+ : [`*${x} = ${this.decFn(base)}(d, *${x})`];
954
+ }
955
+
956
+ entryMarshalIndent(argType) {
957
+ const name = `${this.prefix}marshal_indent${this.entries.length}`;
958
+ const lines = [
959
+ `func ${name}(v any, prefix, indent string) ([]byte, error) {`,
960
+ "\te := jsonrt.NewEnc()",
961
+ ];
962
+ if (!argType || isInterface(argType)) {
963
+ this.flagDynamic();
964
+ lines.push(`\t${this.prefix}encAny(e, v)`);
965
+ } else {
966
+ lines.push(
967
+ `\tx, _ := v.(${this.spell(argType)})`,
968
+ `\t${this.encFn(argType, false)}(e, x)`,
969
+ );
970
+ }
971
+ lines.push(
972
+ "\tb, err := e.Result()",
973
+ "\tif err != nil {",
974
+ "\t\treturn nil, err",
975
+ "\t}",
976
+ "\tout, err := jsonrt.AppendIndent(nil, b, prefix, indent)",
977
+ "\treturn out, err",
978
+ "}",
979
+ );
980
+ this.entries.push(lines.join("\n"));
981
+ return name;
982
+ }
983
+
984
+ entryUnmarshal(argType) {
985
+ const name = `${this.prefix}unmarshal${this.entries.length}`;
986
+ const lines = [
987
+ `func ${name}(data []byte, v any) error {`,
988
+ "\td, err := jsonrt.NewDec(data)",
989
+ "\tif err != nil {",
990
+ "\t\treturn err",
991
+ "\t}",
992
+ ];
993
+ if (argType?.kind === "pointer" && !isInterface(argType)) {
994
+ this.decTargets.push(argType);
995
+ lines.push(
996
+ `\tp, _ := v.(${this.spell(argType)})`,
997
+ "\tif p == nil {",
998
+ `\t\treturn &jsonrt.InvalidUnmarshalError{Type: ${q(this.refl(argType))}, IsPtr: true}`,
999
+ "\t}",
1000
+ ...this.rootDecode(argType, "p").map((l) => `\t${l}`),
1001
+ "\treturn d.Done()",
1002
+ );
1003
+ } else if (!argType || isInterface(argType)) {
1004
+ lines.push(`\treturn ${this.prefix}decAny(d, v)`);
1005
+ this.dynamicDecode = true;
1006
+ } else {
1007
+ lines.push(
1008
+ "\t_ = d",
1009
+ "\t_ = v",
1010
+ `\treturn &jsonrt.InvalidUnmarshalError{Type: ${q(this.refl(argType))}}`,
1011
+ );
1012
+ }
1013
+ lines.push("}");
1014
+ this.entries.push(lines.join("\n"));
1015
+ return name;
1016
+ }
1017
+
1018
+ entryValid() {
1019
+ const name = `${this.prefix}valid${this.entries.length}`;
1020
+ this.entries.push(
1021
+ `func ${name}(data []byte) bool {\n\treturn jsonrt.Valid(data)\n}`,
1022
+ );
1023
+ return name;
1024
+ }
1025
+
1026
+ entryEncode(argType) {
1027
+ this.qualified("json.Encoder");
1028
+ const name = `${this.prefix}encode${this.entries.length}`;
1029
+ const lines = [
1030
+ `func ${name}(enc *json.Encoder, v any) error {`,
1031
+ "\tif enc.Err() != nil {",
1032
+ "\t\treturn enc.Err()",
1033
+ "\t}",
1034
+ "\te := jsonrt.NewEnc()",
1035
+ "\te.EscapeHTML = enc.EscapeHTML()",
1036
+ ];
1037
+ if (!argType || isInterface(argType)) {
1038
+ this.flagDynamic();
1039
+ lines.push(`\t${this.prefix}encAny(e, v)`);
1040
+ } else {
1041
+ lines.push(
1042
+ `\tx, _ := v.(${this.spell(argType)})`,
1043
+ `\t${this.encFn(argType, false)}(e, x)`,
1044
+ );
1045
+ }
1046
+ lines.push(
1047
+ "\tb, err := e.Result()",
1048
+ "\tif err != nil {",
1049
+ "\t\treturn err",
1050
+ "\t}",
1051
+ "\treturn enc.EncodeRaw(b)",
1052
+ "}",
1053
+ );
1054
+ this.entries.push(lines.join("\n"));
1055
+ return name;
1056
+ }
1057
+
1058
+ entryDecode(argType) {
1059
+ this.qualified("json.Decoder");
1060
+ const name = `${this.prefix}decode${this.entries.length}`;
1061
+ const lines = [
1062
+ `func ${name}(dec *json.Decoder, v any) error {`,
1063
+ "\tdata, err := dec.ScanNext()",
1064
+ "\tif err != nil {",
1065
+ "\t\treturn err",
1066
+ "\t}",
1067
+ "\td, err := jsonrt.NewDec(data)",
1068
+ "\tif err != nil {",
1069
+ "\t\treturn err",
1070
+ "\t}",
1071
+ "\tdec.SetupDec(d)",
1072
+ ];
1073
+ if (argType?.kind === "pointer" && !isInterface(argType)) {
1074
+ this.decTargets.push(argType);
1075
+ lines.push(
1076
+ `\tp, _ := v.(${this.spell(argType)})`,
1077
+ "\tif p == nil {",
1078
+ `\t\treturn &jsonrt.InvalidUnmarshalError{Type: ${q(this.refl(argType))}, IsPtr: true}`,
1079
+ "\t}",
1080
+ ...this.rootDecode(argType, "p").map((l) => `\t${l}`),
1081
+ "\treturn d.Done()",
1082
+ );
1083
+ } else if (!argType || isInterface(argType)) {
1084
+ lines.push(`\treturn ${this.prefix}decAny(d, v)`);
1085
+ this.dynamicDecode = true;
1086
+ } else {
1087
+ lines.push(
1088
+ "\t_ = d",
1089
+ "\t_ = v",
1090
+ `\treturn &jsonrt.InvalidUnmarshalError{Type: ${q(this.refl(argType))}}`,
1091
+ );
1092
+ }
1093
+ lines.push("}");
1094
+ this.entries.push(lines.join("\n"));
1095
+ return name;
1096
+ }
1097
+
1098
+ // The dynamic decode entry: every pointer type some call site decodes into.
1099
+ genDecAny() {
1100
+ const seen = new Set();
1101
+ const cases = [];
1102
+ const pointers = this.programTypes().filter(
1103
+ (t) =>
1104
+ t.kind === "pointer" && (isEmptyIface(t.base) || !isInterface(t.base)),
1105
+ );
1106
+ for (const pt of [...this.decTargets, ...pointers]) {
1107
+ const k = this.key(pt);
1108
+ if (seen.has(k)) continue;
1109
+ seen.add(k);
1110
+ cases.push(
1111
+ `\tcase ${this.spell(pt)}:`,
1112
+ "\t\tif x == nil {",
1113
+ `\t\t\treturn &jsonrt.InvalidUnmarshalError{Type: ${q(this.refl(pt))}, IsPtr: true}`,
1114
+ "\t\t} else {",
1115
+ ...this.rootDecode(pt, "x").map((l) => `\t\t\t${l}`),
1116
+ "\t\t}",
1117
+ );
1118
+ }
1119
+ this.out.push(
1120
+ [
1121
+ `func ${this.prefix}decAny(d *jsonrt.Dec, v any) error {`,
1122
+ "\tswitch x := v.(type) {",
1123
+ ...cases,
1124
+ "\tdefault:",
1125
+ "\t\t_ = x",
1126
+ "\t\treturn jsonrt.InvalidUnmarshal(v)",
1127
+ "\t}",
1128
+ "\treturn d.Done()",
1129
+ "}",
1130
+ ].join("\n"),
1131
+ );
1132
+ }
1133
+
1134
+ // Registers what the dynamic encoder handles, until nothing new appears.
1135
+ addDynamicTypes() {
1136
+ for (const n of DYNAMIC_BASICS) this.encFn({ kind: "basic", name: n });
1137
+ const anyT = { kind: "basic", name: "any" };
1138
+ this.encFn({ kind: "slice", elem: anyT });
1139
+ this.encFn({
1140
+ kind: "map",
1141
+ key: { kind: "basic", name: "string" },
1142
+ value: anyT,
1143
+ });
1144
+ for (const t of this.programTypes()) {
1145
+ this.encFn(t, false);
1146
+ }
1147
+ for (const [name, t] of this.checker.types) {
1148
+ if (t?.kind !== "named" || name.includes(".") || name.includes("$$"))
1149
+ continue;
1150
+ if (t.name !== name || t.typeArgs?.length || t._genericOf) continue;
1151
+ const u = under(t);
1152
+ if (!u || u.kind === "interface" || u.kind === "func") continue;
1153
+ this.encFn(t, false);
1154
+ this.encFn({ kind: "pointer", base: t }, false);
1155
+ }
1156
+ }
1157
+
1158
+ // True when the type can be written as Go source in the generated file.
1159
+ spellable(t, depth = 0) {
1160
+ if (!t || depth > 8) return false;
1161
+ const ok = (x) => this.spellable(x, depth + 1);
1162
+ const byKind = {
1163
+ basic: () => t.name !== "void" && t.name !== "nil",
1164
+ named: () =>
1165
+ !t.name.includes("$$") &&
1166
+ !t.name.includes("[") &&
1167
+ (t.typeArgs ?? []).every(ok),
1168
+ pointer: () => ok(t.base),
1169
+ slice: () => ok(t.elem),
1170
+ array: () => ok(t.elem),
1171
+ map: () => ok(t.key) && ok(t.value),
1172
+ interface: () => true,
1173
+ struct: () => (t._structFields ?? []).every((f) => ok(f.type)),
1174
+ func: () => t.params.every(ok) && results(t).every(ok),
1175
+ };
1176
+ return byKind[t.kind]?.() ?? false;
1177
+ }
1178
+
1179
+ // Types the program builds with composite literals, `&T{}`, make and new:
1180
+ // candidates for a value stored in an interface (an `any` map value, say).
1181
+ programTypes() {
1182
+ const found = new Map();
1183
+ for (const p of this.programs) {
1184
+ walkNodes(p.decls, (node) => {
1185
+ if (!node._type) return;
1186
+ const t = defaultType(node._type);
1187
+ // Slices, arrays and maps reach an interface from any expression
1188
+ // (a function result, a variable), not only from a literal
1189
+ if (!buildsValue(node) && !isCompositeType(t)) return;
1190
+ if (this.spellable(t) && under(t)?.kind !== "interface")
1191
+ found.set(this.key(t), t);
1192
+ });
1193
+ }
1194
+ return [...found.values()];
1195
+ }
1196
+
1197
+ drain() {
1198
+ while (this.queue.length > 0) this.queue.shift()();
1199
+ }
1200
+
1201
+ finish() {
1202
+ this.drain();
1203
+ while (this.dynamic && !this.dynamicDone) {
1204
+ this.dynamicDone = true;
1205
+ this.addDynamicTypes();
1206
+ this.drain();
1207
+ }
1208
+ if (this.dynamic) this.genEncAny();
1209
+ if (this.dynamicDecode) {
1210
+ this.genDecAny();
1211
+ this.drain();
1212
+ }
1213
+ }
1214
+
1215
+ source(pkgName, imports) {
1216
+ const importLines = imports.map((p) => `\t${q(p)}`).join("\n");
1217
+ return [
1218
+ `package ${pkgName}`,
1219
+ "",
1220
+ `import (\n\t"internal/jsonrt"\n${importLines}\n)`,
1221
+ "",
1222
+ ...this.entries,
1223
+ ...this.out,
1224
+ "",
1225
+ ].join("\n\n");
1226
+ }
1227
+ }
1228
+
1229
+ // Calls every `fn(node)` for the AST nodes under `root`.
1230
+ function walkNodes(root, fn) {
1231
+ if (!root || typeof root !== "object") return;
1232
+ if (Array.isArray(root)) {
1233
+ for (const n of root) walkNodes(n, fn);
1234
+ return;
1235
+ }
1236
+ fn(root);
1237
+ forEachChild(root, (c) => walkNodes(c, fn));
1238
+ }
1239
+
1240
+ // Slices and maps of these share one WebAssembly representation (and so one
1241
+ // type-switch case), so only literals may register them: a `[]byte` conversion
1242
+ // must not shadow a `[]int32` that is stored in an interface.
1243
+ const SHARED_REPR = new Set([
1244
+ "bool",
1245
+ "int8",
1246
+ "int16",
1247
+ "int32",
1248
+ "uint8",
1249
+ "uint16",
1250
+ "uint32",
1251
+ "byte",
1252
+ "rune",
1253
+ ]);
1254
+
1255
+ const isCompositeType = (t) => {
1256
+ const k = under(t)?.kind;
1257
+ return (
1258
+ (k === "slice" || k === "array" || k === "map") &&
1259
+ !holdsFunc(t) &&
1260
+ !hasSharedRepr(t)
1261
+ );
1262
+ };
1263
+
1264
+ function hasSharedRepr(t, depth = 0) {
1265
+ if (!t || depth > 8) return false;
1266
+ const u = under(t);
1267
+ if (u?.kind === "basic") return SHARED_REPR.has(u.name);
1268
+ if (u?.kind === "slice" || u?.kind === "array")
1269
+ return hasSharedRepr(u.elem, depth + 1);
1270
+ if (u?.kind === "map")
1271
+ return hasSharedRepr(u.key, depth + 1) || hasSharedRepr(u.value, depth + 1);
1272
+ return false;
1273
+ }
1274
+
1275
+ // Func values are not JSON-encodable, and `case []func():` does not parse.
1276
+ function holdsFunc(t, depth = 0) {
1277
+ if (!t || depth > 8) return false;
1278
+ const u = under(t);
1279
+ if (u?.kind === "func") return true;
1280
+ if (u?.kind === "slice" || u?.kind === "array")
1281
+ return holdsFunc(u.elem, depth + 1);
1282
+ if (u?.kind === "map")
1283
+ return holdsFunc(u.key, depth + 1) || holdsFunc(u.value, depth + 1);
1284
+ return false;
1285
+ }
1286
+
1287
+ // A composite literal, `&x`, make or new: an expression that builds a value.
1288
+ function buildsValue(node) {
1289
+ if (node.kind === "CompositeLit") return true;
1290
+ if (node.kind === "UnaryExpr") return node.op === "&";
1291
+ return (
1292
+ node.kind === "CallExpr" &&
1293
+ node.func?.kind === "Ident" &&
1294
+ (node.func.name === "make" || node.func.name === "new")
1295
+ );
1296
+ }
1297
+
1298
+ // The names the file imports `encoding/json` as.
1299
+ function jsonAliases(program) {
1300
+ const aliases = new Set();
1301
+ for (const imp of program.imports ?? [])
1302
+ for (const spec of imp.imports)
1303
+ if (spec.path === JSON_PATH && spec.alias !== "_")
1304
+ aliases.add(spec.alias ?? "json");
1305
+ return aliases;
1306
+ }
1307
+
1308
+ const isJsonCall = (node, aliases) =>
1309
+ node.kind === "CallExpr" &&
1310
+ node.func?.kind === "SelectorExpr" &&
1311
+ node.func.expr?.kind === "Ident" &&
1312
+ aliases.has(node.func.expr.name) &&
1313
+ SITE_FUNCS.has(node.func.field);
1314
+
1315
+ function isStreamCall(node) {
1316
+ if (node.kind !== "CallExpr" || node.func?.kind !== "SelectorExpr")
1317
+ return null;
1318
+ const recvType = node.func.expr?._type;
1319
+ const recvName = recvType?.name ?? recvType?.base?.name ?? "";
1320
+ const isEnc =
1321
+ recvName === "json.Encoder" ||
1322
+ recvName === "encoding/json.Encoder" ||
1323
+ recvName.startsWith("Encoder$$");
1324
+ if (isEnc && node.func.field === "Encode") return "Encode";
1325
+ const isDec =
1326
+ recvName === "json.Decoder" ||
1327
+ recvName === "encoding/json.Decoder" ||
1328
+ recvName.startsWith("Decoder$$");
1329
+ if (isDec && node.func.field === "Decode") return "Decode";
1330
+ return null;
1331
+ }
1332
+
1333
+ // Finds `json.Marshal/Unmarshal/Valid` call sites in the checked programs.
1334
+ function findSites(programs) {
1335
+ const sites = [];
1336
+ for (const p of programs) {
1337
+ const aliases = jsonAliases(p);
1338
+ walkNodes(p.decls, (node) => {
1339
+ if (isJsonCall(node, aliases)) {
1340
+ sites.push({ call: node, fn: node.func.field, program: p });
1341
+ } else {
1342
+ const streamFn = isStreamCall(node);
1343
+ if (streamFn) sites.push({ call: node, fn: streamFn, program: p });
1344
+ }
1345
+ });
1346
+ }
1347
+ return sites;
1348
+ }
1349
+
1350
+ function selectCodecEntry(gen, fn, argType) {
1351
+ if (fn === "Marshal") return gen.entryMarshal(argType(0));
1352
+ if (fn === "MarshalIndent") return gen.entryMarshalIndent(argType(0));
1353
+ if (fn === "Unmarshal") return gen.entryUnmarshal(argType(1));
1354
+ if (fn === "Encode") return gen.entryEncode(argType(0));
1355
+ if (fn === "Decode") return gen.entryDecode(argType(0));
1356
+ return gen.entryValid();
1357
+ }
1358
+
1359
+ // Generates the codec file for a package's `json` call sites. Returns null
1360
+ // when it has none; otherwise { source, imports } and marks each call's
1361
+ // SelectorExpr with `_jsonGen`, the entry point the WASM link calls instead.
1362
+ export function generateJsonCodecs(programs, checker, pkgName) {
1363
+ const sites = findSites(programs);
1364
+ if (sites.length === 0) return null;
1365
+ const qualifiers = new Set();
1366
+ const gen = new JsonCodecGen(checker, pkgName, qualifiers, programs);
1367
+ const pending = [];
1368
+ const diagnostics = [];
1369
+ for (const { call, fn, program } of sites) {
1370
+ const argType = (i) => defaultType(call.args[i]?._type);
1371
+ if (fn === "Unmarshal" && argType(1)?.base?.kind === "pointer")
1372
+ diagnostics.push({
1373
+ program,
1374
+ node: call.func.expr,
1375
+ message:
1376
+ "json.Unmarshal into a pointer to a pointer is not yet available in wasm packages",
1377
+ hint: "declare the value and pass its address, or pass the pointer itself",
1378
+ });
1379
+ const entry = selectCodecEntry(gen, fn, argType);
1380
+ pending.push([call.func, entry, fn === "Encode" || fn === "Decode"]);
1381
+ }
1382
+ gen.finish();
1383
+ for (const [sel, entry, isStream] of pending) {
1384
+ sel._jsonGen = entry;
1385
+ if (isStream) sel._jsonStream = true;
1386
+ }
1387
+ const importPaths = new Map();
1388
+ for (const p of programs)
1389
+ for (const imp of p.imports ?? [])
1390
+ for (const spec of imp.imports) {
1391
+ const last = spec.path.split("/").pop();
1392
+ importPaths.set(spec.alias ?? last, spec.path);
1393
+ }
1394
+ const imports = [...qualifiers]
1395
+ .map((qn) => importPaths.get(qn))
1396
+ .filter(Boolean);
1397
+ return { source: gen.source(pkgName, imports), diagnostics };
1398
+ }