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