gofront 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -14,11 +14,22 @@
14
14
  // which switches over every type the program encodes or decodes anywhere.
15
15
 
16
16
  import { resolveJsonFields } from "../typechecker/resolve.js";
17
- import { defaultType } from "../typechecker/types.js";
18
- import { forEachChild } from "./walk.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";
19
30
 
20
31
  const JSON_PATH = "encoding/json";
21
- const SITE_FUNCS = new Set(["Marshal", "MarshalIndent", "Unmarshal", "Valid"]);
32
+ const SITE_FUNCS = new Set(["Marshal", "MarshalIndent", "Unmarshal"]);
22
33
 
23
34
  const INT_KINDS = new Set(["int", "int8", "int16", "int32", "int64"]);
24
35
  const UINT_KINDS = new Set([
@@ -67,18 +78,13 @@ const DYNAMIC_BASICS = [
67
78
  const unalias = (n) => ALIAS[n] ?? n;
68
79
  const q = (s) => JSON.stringify(s);
69
80
 
70
- function under(t) {
71
- return t?.kind === "named" ? under(t.underlying) : t;
72
- }
73
-
74
81
  function isBasic(t, set) {
75
82
  const u = under(t);
76
83
  return u?.kind === "basic" && set.has(unalias(u.name));
77
84
  }
78
85
 
79
86
  const isRaw = (t) =>
80
- t?.kind === "named" &&
81
- (t.name === "json.RawMessage" || t.name === "encoding/json.RawMessage");
87
+ t?.kind === "named" && t.name === "RawMessage$$encoding$json";
82
88
 
83
89
  const isNumber = (t) =>
84
90
  t?.kind === "named" &&
@@ -95,10 +101,6 @@ function isBytes(t) {
95
101
  );
96
102
  }
97
103
 
98
- const isEmptyIface = (t) =>
99
- (t?.kind === "basic" && t.name === "any") ||
100
- (under(t)?.kind === "interface" && under(t).methods?.size === 0);
101
-
102
104
  const isNilType = (t) =>
103
105
  t?.kind === "nil" || (t?.kind === "basic" && t.name === "nil");
104
106
 
@@ -112,14 +114,6 @@ function methodOf(t, name) {
112
114
  return t.methods?.get(name) ?? t.underlying?.methods?.get(name) ?? null;
113
115
  }
114
116
 
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
117
  // MarshalJSON() ([]byte, error)
124
118
  function marshalerOf(t) {
125
119
  if (isRaw(t)) return null;
@@ -128,6 +122,14 @@ function marshalerOf(t) {
128
122
  return m;
129
123
  }
130
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
+
131
133
  // UnmarshalJSON([]byte) error
132
134
  function unmarshalerOf(t) {
133
135
  if (isRaw(t)) return null;
@@ -140,12 +142,13 @@ function isSimpleKey(s) {
140
142
  return /^[A-Za-z0-9_ .-]*$/.test(s);
141
143
  }
142
144
 
143
- export class JsonCodecGen {
145
+ const isNamedOrPointer = (t) =>
146
+ t.kind === "named" || (t.kind === "pointer" && t.base?.kind === "named");
147
+
148
+ export class JsonCodecGen extends TypeSpeller {
144
149
  constructor(checker, pkgName, qualifiers, programs = []) {
145
- this.programs = programs;
150
+ super(pkgName, qualifiers, programs);
146
151
  this.checker = checker;
147
- this.pkgName = pkgName;
148
- this.qualifiers = qualifiers;
149
152
  this.prefix = `gfj_${String(pkgName).replace(/[^A-Za-z0-9_]/g, "_")}_`;
150
153
  this.enc = new Map(); // key -> { name, t, addr }
151
154
  this.dec = new Map();
@@ -175,6 +178,7 @@ export class JsonCodecGen {
175
178
  map: () => `map[${k(t.key)}]${k(t.value)}`,
176
179
  struct: () => this.structKey(t),
177
180
  interface: () => (isEmptyIface(t) ? "any" : `iface:${t.name ?? ""}`),
181
+ func: () => this.funcString(t, k),
178
182
  };
179
183
  return (byKind[t.kind] ?? (() => `?${t.kind}`))();
180
184
  }
@@ -187,83 +191,20 @@ export class JsonCodecGen {
187
191
  return `struct{${fields.join(";")}}`;
188
192
  }
189
193
 
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
194
  // reflect's Type.String() of the type, for error messages.
254
195
  refl(t) {
255
196
  switch (t.kind) {
256
197
  case "basic":
257
198
  return REFLECT_NAME[t.name] ?? unalias(t.name);
258
199
  case "named": {
259
- let name = t.name;
200
+ let name = t.name.replace(LOCAL_TYPE_SUFFIX, "");
260
201
  const at = name.indexOf("$$");
261
202
  if (at >= 0) {
262
203
  const orig = name.slice(0, at);
263
204
  const modPath = name.slice(at + 2).replaceAll("$", "/");
264
205
  name = `${modPath}.${orig}`;
265
206
  } else if (!name.includes(".")) {
266
- name = `${this.pkgName}.${name}`;
207
+ name = `${this.declaringPackage(t)}.${name}`;
267
208
  }
268
209
  return t.typeArgs?.length
269
210
  ? `${name}[${t.typeArgs.map((a) => this.refl(a)).join(",")}]`
@@ -278,7 +219,9 @@ export class JsonCodecGen {
278
219
  case "map":
279
220
  return `map[${this.refl(t.key)}]${this.refl(t.value)}`;
280
221
  case "interface":
281
- return "interface {}";
222
+ return t.name === "error" ? "error" : "interface {}";
223
+ case "namespace":
224
+ return t.name;
282
225
  case "func":
283
226
  return this.funcString(t, (x) => this.refl(x));
284
227
  default:
@@ -286,17 +229,6 @@ export class JsonCodecGen {
286
229
  }
287
230
  }
288
231
 
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
232
  // A struct's own name in decode errors.
301
233
  shortName(t) {
302
234
  return t?.kind === "named" ? t.name.split(".").pop() : "";
@@ -309,7 +241,7 @@ export class JsonCodecGen {
309
241
  needsAddr(t, seen = new Set()) {
310
242
  if (!t || seen.has(t)) return false;
311
243
  seen.add(t);
312
- const m = marshalerOf(t);
244
+ const m = marshalerOf(t) ?? textMarshalerOf(t);
313
245
  if (m) return Boolean(m._ptrRecv);
314
246
  const u = under(t);
315
247
  if (u?.kind === "struct")
@@ -343,10 +275,56 @@ export class JsonCodecGen {
343
275
 
344
276
  // Statements that encode the Go expression `x` of type `t`.
345
277
  encValue(t, x, addr) {
278
+ if (holdsInterop(t)) return this.encInterop(t, x);
346
279
  return [`${this.encFn(t, addr)}(e, ${x})`];
347
280
  }
348
281
 
349
- marshalCall(x, t) {
282
+ // A JS-interop value has no Go source to spell, so it is encoded where it
283
+ // sits: nil is `null` (an empty slice, map or array is `[]` or `{}`), and
284
+ // anything else fails with the type's name.
285
+ encInterop(t, x) {
286
+ const u = under(t);
287
+ const fail = (name) => `\te.FailType(${q(name)})`;
288
+ if (u.kind === "namespace")
289
+ return [`if ${x} == nil {`, "\te.Null()", "} else {", fail(u.name), "}"];
290
+ if (u.kind === "pointer")
291
+ return [
292
+ `if ${x} == nil {`,
293
+ "\te.Null()",
294
+ "} else {",
295
+ fail(this.refl(t)),
296
+ "}",
297
+ ];
298
+ const [empty, elem] =
299
+ u.kind === "map"
300
+ ? ["{}", holdsInterop(u.key) ? u.key : u.value]
301
+ : ["[]", u.elem];
302
+ if (u.kind === "array")
303
+ return [
304
+ `if ${u.size} == 0 {`,
305
+ `\te.Lit(${q(empty)})`,
306
+ "} else {",
307
+ fail(this.refl(elem)),
308
+ "}",
309
+ ];
310
+ return [
311
+ `if ${x} == nil {`,
312
+ "\te.Null()",
313
+ `} else if len(${x}) == 0 {`,
314
+ `\te.Lit(${q(empty)})`,
315
+ "} else {",
316
+ fail(this.refl(elem)),
317
+ "}",
318
+ ];
319
+ }
320
+
321
+ // `mt` is the type that declares the method (the pointee for a pointer).
322
+ marshalCall(x, t, mt = t) {
323
+ if (!marshalerOf(mt) && textMarshalerOf(mt))
324
+ return [
325
+ `out, err := ${x}.MarshalText()`,
326
+ `e.TextMarshaled(out, err, ${q(this.refl(t))})`,
327
+ ];
350
328
  return [
351
329
  `out, err := ${x}.MarshalJSON()`,
352
330
  `e.Marshaled(out, err, ${q(this.refl(t))})`,
@@ -357,7 +335,7 @@ export class JsonCodecGen {
357
335
  encSpecial(t, addr) {
358
336
  if (isRaw(t)) return ["e.Raw([]byte(v))"];
359
337
  if (isNumber(t)) return ["e.Number(string(v))"];
360
- const m = marshalerOf(t);
338
+ const m = marshalerOf(t) ?? textMarshalerOf(t);
361
339
  if (m && (!m._ptrRecv || addr)) return this.marshalCall("v", t);
362
340
  if (isBytes(t)) return ["e.Bytes([]byte(v))"];
363
341
  if (isInterface(t)) {
@@ -385,7 +363,8 @@ export class JsonCodecGen {
385
363
 
386
364
  encPointer(t, u) {
387
365
  const lines = ["if v == nil {", "\te.Null()", "\treturn", "}"];
388
- if (marshalerOf(u.base)) lines.push(...this.marshalCall("v", t));
366
+ if (marshalerOf(u.base) || textMarshalerOf(u.base))
367
+ lines.push(...this.marshalCall("v", t, u.base));
389
368
  else lines.push(...this.encValue(u.base, "*v", true));
390
369
  return lines;
391
370
  }
@@ -476,6 +455,7 @@ export class JsonCodecGen {
476
455
  if (u.kind === "slice" || u.kind === "map") return `len(${x}) == 0`;
477
456
  if (u.kind === "array") return u.size === 0 ? "true" : "false";
478
457
  if (u.kind === "struct") return "false";
458
+ if (u.kind === "namespace") return `${x} == nil`;
479
459
  const n = unalias(u.name);
480
460
  if (n === "bool") return `!${x}`;
481
461
  if (n === "string") return `${x} == ""`;
@@ -558,24 +538,50 @@ export class JsonCodecGen {
558
538
  genEncAny() {
559
539
  const cases = [];
560
540
  const seen = new Set();
541
+ const f32 = this.enc.get("float32")?.name;
561
542
  for (const [key, e] of this.enc) {
562
543
  if (e.addr || seen.has(key)) continue;
563
544
  seen.add(key);
564
545
  if (isInterface(e.t)) continue;
565
- cases.push(`\tcase ${this.spell(e.t)}:\n\t\t${e.name}(e, x)`);
546
+ // A package that only lends its encoder knows its named types.
547
+ if (this.registerOnly && !isNamedOrPointer(e.t)) continue;
548
+ // Where one run-time shape serves both float sizes, a float64 that a
549
+ // float32 holds exactly encodes as the float32 it most likely is.
550
+ const body =
551
+ f32 && e.t.kind === "basic" && e.t.name === "float64"
552
+ ? `\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}`
553
+ : `\t\t${e.name}(e, x)`;
554
+ cases.push({
555
+ t: e.t,
556
+ src: `\tcase ${this.spell(e.t)}:\n${body}\n\t\treturn true`,
557
+ });
566
558
  }
559
+ // JS numbers do not tell an int from a float64 or float32: whole numbers
560
+ // take the int cases, then float64, then float32.
561
+ const rank = ({ t }) =>
562
+ t.name === "float64" ? 1 : t.name === "float32" ? 2 : 0;
563
+ cases.sort((a, b) => rank(a) - rank(b));
564
+ const known = `${this.prefix}encKnown`;
567
565
  this.out.push(
568
566
  [
569
- `func ${this.prefix}encAny(e *jsonrt.Enc, v any) {`,
567
+ `func ${known}(e *jsonrt.Enc, v any) bool {`,
570
568
  "\tif v == nil {",
571
569
  "\t\te.Null()",
572
- "\t\treturn",
570
+ "\t\treturn true",
573
571
  "\t}",
574
572
  "\tswitch x := v.(type) {",
575
- ...cases,
573
+ ...cases.map((c) => c.src),
576
574
  "\tdefault:",
577
575
  "\t\t_ = x",
578
- '\t\te.FailType("unsupported dynamic type")',
576
+ "\t\treturn false",
577
+ "\t}",
578
+ "}",
579
+ // Every package with codecs lends its dynamic encoder to the others,
580
+ // so a value of a type another package declares still encodes.
581
+ `var _ = jsonrt.RegisterEncoder(${known})`,
582
+ `func ${this.prefix}encAny(e *jsonrt.Enc, v any) {`,
583
+ `\tif !${known}(e, v) {`,
584
+ "\t\te.EncodeDynamic(v)",
579
585
  "\t}",
580
586
  "}",
581
587
  ].join("\n"),
@@ -632,7 +638,7 @@ export class JsonCodecGen {
632
638
  if (u.kind === "basic" && !isEmptyIface(t))
633
639
  return this.decScalar(t, u, typ);
634
640
  const byKind = {
635
- pointer: () => this.decPointer(u, typ),
641
+ pointer: () => this.decPointer(u),
636
642
  slice: () => this.decSlice(u, t, typ),
637
643
  array: () => this.decArray(u, typ),
638
644
  map: () => this.decMap(u, t, typ),
@@ -707,7 +713,7 @@ export class JsonCodecGen {
707
713
  ];
708
714
  }
709
715
 
710
- decPointer(u, typ) {
716
+ decPointer(u) {
711
717
  const base = u.base;
712
718
  const lines = [
713
719
  "if d.Null() {",
@@ -717,9 +723,7 @@ export class JsonCodecGen {
717
723
  `\tcur = new(${this.spell(base)})`,
718
724
  "}",
719
725
  ];
720
- if (under(base)?.kind === "pointer") {
721
- lines.push(`d.Mismatch(${typ})`);
722
- } else if (this.decInPlace(base)) {
726
+ if (this.decInPlace(base)) {
723
727
  lines.push(`${this.decFn(base)}(d, cur)`);
724
728
  } else {
725
729
  lines.push(`*cur = ${this.decFn(base)}(d, *cur)`);
@@ -818,6 +822,9 @@ export class JsonCodecGen {
818
822
 
819
823
  decStruct(u, t, typ) {
820
824
  const fields = resolveJsonFields(u);
825
+ for (const f of fields)
826
+ if (holdsInterop(f.type))
827
+ throw new Error(`json: cannot unmarshal into ${interopName(f.type)}`);
821
828
  const sname = this.shortName(t);
822
829
  const lines = [
823
830
  "if d.Null() {",
@@ -1015,19 +1022,11 @@ export class JsonCodecGen {
1015
1022
  return name;
1016
1023
  }
1017
1024
 
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
1025
  entryEncode(argType) {
1027
- this.qualified("json.Encoder");
1026
+ const json = this.qualifierFor(JSON_PATH);
1028
1027
  const name = `${this.prefix}encode${this.entries.length}`;
1029
1028
  const lines = [
1030
- `func ${name}(enc *json.Encoder, v any) error {`,
1029
+ `func ${name}(enc *${json}.Encoder, v any) error {`,
1031
1030
  "\tif enc.Err() != nil {",
1032
1031
  "\t\treturn enc.Err()",
1033
1032
  "\t}",
@@ -1056,10 +1055,10 @@ export class JsonCodecGen {
1056
1055
  }
1057
1056
 
1058
1057
  entryDecode(argType) {
1059
- this.qualified("json.Decoder");
1058
+ const json = this.qualifierFor(JSON_PATH);
1060
1059
  const name = `${this.prefix}decode${this.entries.length}`;
1061
1060
  const lines = [
1062
- `func ${name}(dec *json.Decoder, v any) error {`,
1061
+ `func ${name}(dec *${json}.Decoder, v any) error {`,
1063
1062
  "\tdata, err := dec.ScanNext()",
1064
1063
  "\tif err != nil {",
1065
1064
  "\t\treturn err",
@@ -1103,7 +1102,13 @@ export class JsonCodecGen {
1103
1102
  (t) =>
1104
1103
  t.kind === "pointer" && (isEmptyIface(t.base) || !isInterface(t.base)),
1105
1104
  );
1106
- for (const pt of [...this.decTargets, ...pointers]) {
1105
+ // A pointer to a type parameter's variable names no type in JavaScript:
1106
+ // it decodes by its pointee's shape (see __isty), so these always exist.
1107
+ const shapes = ["any", "float64", "string", "bool", "int64"].map((n) => ({
1108
+ kind: "pointer",
1109
+ base: { kind: "basic", name: n },
1110
+ }));
1111
+ for (const pt of [...this.decTargets, ...pointers, ...shapes]) {
1107
1112
  const k = this.key(pt);
1108
1113
  if (seen.has(k)) continue;
1109
1114
  seen.add(k);
@@ -1133,21 +1138,22 @@ export class JsonCodecGen {
1133
1138
 
1134
1139
  // Registers what the dynamic encoder handles, until nothing new appears.
1135
1140
  addDynamicTypes() {
1136
- for (const n of DYNAMIC_BASICS) this.encFn({ kind: "basic", name: n });
1137
1141
  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
- });
1142
+ if (!this.registerOnly) {
1143
+ for (const n of DYNAMIC_BASICS) this.encFn({ kind: "basic", name: n });
1144
+ this.encFn({ kind: "slice", elem: anyT });
1145
+ this.encFn({
1146
+ kind: "map",
1147
+ key: { kind: "basic", name: "string" },
1148
+ value: anyT,
1149
+ });
1150
+ }
1144
1151
  for (const t of this.programTypes()) {
1145
1152
  this.encFn(t, false);
1146
1153
  }
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;
1154
+ for (const { type: t, via } of this.collectedTypes()) {
1155
+ if (via !== "boxed" || !t || mentionsTypeParam(t)) continue;
1156
+ if (!this.spellable(t)) continue;
1151
1157
  const u = under(t);
1152
1158
  if (!u || u.kind === "interface" || u.kind === "func") continue;
1153
1159
  this.encFn(t, false);
@@ -1155,41 +1161,61 @@ export class JsonCodecGen {
1155
1161
  }
1156
1162
  }
1157
1163
 
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;
1164
+ // A struct holding a JS-interop value (a `.d.ts` interface) cannot be
1165
+ // written as a dynamic case, so it is not spellable either.
1166
+ spellableNamed(t, ok) {
1167
+ if (isJsonValue(t)) return true;
1168
+ return (
1169
+ !t.name.includes("$$") &&
1170
+ !t.name.includes("[") &&
1171
+ (t.typeArgs ?? []).every(ok) &&
1172
+ !reachesInterop(t)
1173
+ );
1174
+ }
1175
+
1176
+ addTypeArg(found, a) {
1177
+ const t = defaultType(a);
1178
+ if (!t || mentionsTypeParam(t) || !this.spellable(t) || isInterface(t))
1179
+ return;
1180
+ // An array or map of small integers would shadow its look-alikes in the
1181
+ // type switch (see SHARED_REPR).
1182
+ const ambiguous = !isCompositeType(t) && hasSharedRepr(t);
1183
+ if (holdsFunc(t) || ambiguous) return;
1184
+ found.set(this.key(t), t);
1185
+ const p = { kind: "pointer", base: t };
1186
+ found.set(this.key(p), p);
1187
+ }
1188
+
1189
+ collectedTypes() {
1190
+ this.collected ??= collectProgramTypes(this.programs, this.checker);
1191
+ return this.collected;
1177
1192
  }
1178
1193
 
1179
1194
  // Types the program builds with composite literals, `&T{}`, make and new:
1180
1195
  // candidates for a value stored in an interface (an `any` map value, say).
1181
1196
  programTypes() {
1182
1197
  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
- });
1198
+ for (const { type: t, node, via } of this.collectedTypes()) {
1199
+ // A generic call's type arguments are what its body encodes and
1200
+ // decodes through `T` and `&v`.
1201
+ if (via === "typeArg") this.addTypeArg(found, t);
1202
+ if (via !== "expr" || !t) continue;
1203
+ // A func literal in an interface reports its own type.
1204
+ if (node.kind === "FuncLit" && this.spellable(t)) {
1205
+ found.set(this.key(t), t);
1206
+ continue;
1207
+ }
1208
+ // Slices, arrays and maps reach an interface from any expression
1209
+ // (a function result, a variable), not only from a literal
1210
+ if (
1211
+ !buildsValue(node) &&
1212
+ !isCompositeType(t) &&
1213
+ !isFuncComposite(t) &&
1214
+ !isJsonValue(t)
1215
+ )
1216
+ continue;
1217
+ if (this.spellable(t) && under(t)?.kind !== "interface")
1218
+ found.set(this.key(t), t);
1193
1219
  }
1194
1220
  return [...found.values()];
1195
1221
  }
@@ -1212,68 +1238,85 @@ export class JsonCodecGen {
1212
1238
  }
1213
1239
  }
1214
1240
 
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");
1241
+ // The generated declarations (the spine adds the header and imports).
1242
+ body() {
1243
+ return [...this.entries, ...this.out].join("\n\n");
1226
1244
  }
1227
1245
  }
1228
1246
 
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;
1247
+ // A slice of booleans or small integers carries a tag of its Go type inside an
1248
+ // interface, so it never shadows another slice type in the type switch.
1249
+ const isTaggedSlice = (t) =>
1250
+ under(t)?.kind === "slice" && isBasic(under(t).elem, SHARED_REPR);
1251
+
1252
+ // json.RawMessage and json.Number values can reach an interface from any
1253
+ // expression (a conversion, a field read).
1254
+ // True when `t` is, or holds through pointers, elements and struct fields, a
1255
+ // JS-interop type (a `.d.ts` interface) that has no Go source to spell.
1256
+ export function reachesInterop(t, seen = new Set(), viaStruct = true) {
1257
+ if (!t || seen.has(t)) return false;
1258
+ seen.add(t);
1259
+ const u = under(t) ?? t;
1260
+ switch (u.kind) {
1261
+ case "namespace":
1262
+ return true;
1263
+ case "pointer":
1264
+ return reachesInterop(u.base, seen, viaStruct);
1265
+ case "slice":
1266
+ case "array":
1267
+ return reachesInterop(u.elem, seen, viaStruct);
1268
+ case "map":
1269
+ return (
1270
+ reachesInterop(u.key, seen, viaStruct) ||
1271
+ reachesInterop(u.value, seen, viaStruct)
1272
+ );
1273
+ case "struct":
1274
+ return (
1275
+ viaStruct &&
1276
+ (u._structFields ?? []).some((f) => reachesInterop(f.type, seen))
1277
+ );
1278
+ default:
1279
+ return false;
1235
1280
  }
1236
- fn(root);
1237
- forEachChild(root, (c) => walkNodes(c, fn));
1238
1281
  }
1239
1282
 
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
- ]);
1283
+ // True when a value of type `t` is, or holds through pointers and elements
1284
+ // (not through a named struct, which has its own codec), a JS-interop value.
1285
+ const holdsInterop = (t) => reachesInterop(t, new Set(), false);
1286
+
1287
+ // The name of the JS-interop type that `t` is or wraps.
1288
+ function interopName(t) {
1289
+ const u = under(t);
1290
+ if (u.kind === "namespace") return u.name;
1291
+ if (u.kind === "map")
1292
+ return interopName(holdsInterop(u.key) ? u.key : u.value);
1293
+ return interopName(u.base ?? u.elem);
1294
+ }
1254
1295
 
1255
- const isCompositeType = (t) => {
1296
+ const isJsonValue = (t) => isRaw(t) || isNumber(t);
1297
+
1298
+ export const isCompositeType = (t) => {
1256
1299
  const k = under(t)?.kind;
1257
1300
  return (
1258
1301
  (k === "slice" || k === "array" || k === "map") &&
1259
1302
  !holdsFunc(t) &&
1260
- !hasSharedRepr(t)
1303
+ (isTaggedSlice(t) || !hasSharedRepr(t))
1261
1304
  );
1262
1305
  };
1263
1306
 
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
- }
1307
+ // A slice, array or map holding funcs: it never encodes, but a nil one in an
1308
+ // interface still does, as `null`, and a non-nil one names the func.
1309
+ export const isFuncComposite = (t) => {
1310
+ const k = under(t)?.kind;
1311
+ return (
1312
+ (k === "slice" || k === "array" || k === "map") &&
1313
+ holdsFunc(t) &&
1314
+ !hasSharedRepr(t)
1315
+ );
1316
+ };
1274
1317
 
1275
1318
  // Func values are not JSON-encodable, and `case []func():` does not parse.
1276
- function holdsFunc(t, depth = 0) {
1319
+ export function holdsFunc(t, depth = 0) {
1277
1320
  if (!t || depth > 8) return false;
1278
1321
  const u = under(t);
1279
1322
  if (u?.kind === "func") return true;
@@ -1305,12 +1348,17 @@ function jsonAliases(program) {
1305
1348
  return aliases;
1306
1349
  }
1307
1350
 
1351
+ // `json.Marshal`-style selector on the imported package (not a local that
1352
+ // shadows its name).
1353
+ const isJsonRef = (node, aliases) =>
1354
+ node.kind === "SelectorExpr" &&
1355
+ node.expr?.kind === "Ident" &&
1356
+ node.expr._type?.kind === "namespace" &&
1357
+ aliases.has(node.expr.name) &&
1358
+ SITE_FUNCS.has(node.field);
1359
+
1308
1360
  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);
1361
+ node.kind === "CallExpr" && isJsonRef(node.func, aliases);
1314
1362
 
1315
1363
  function isStreamCall(node) {
1316
1364
  if (node.kind !== "CallExpr" || node.func?.kind !== "SelectorExpr")
@@ -1330,21 +1378,36 @@ function isStreamCall(node) {
1330
1378
  return null;
1331
1379
  }
1332
1380
 
1333
- // Finds `json.Marshal/Unmarshal/Valid` call sites in the checked programs.
1381
+ // Finds `json.Marshal/MarshalIndent/Unmarshal` call sites in the checked
1382
+ // programs, and a diagnostic for each use of them that is not a call.
1334
1383
  function findSites(programs) {
1335
1384
  const sites = [];
1385
+ const diagnostics = [];
1336
1386
  for (const p of programs) {
1337
1387
  const aliases = jsonAliases(p);
1388
+ const callees = new Set();
1389
+ const refs = [];
1338
1390
  walkNodes(p.decls, (node) => {
1339
1391
  if (isJsonCall(node, aliases)) {
1340
1392
  sites.push({ call: node, fn: node.func.field, program: p });
1393
+ callees.add(node.func);
1394
+ } else if (isJsonRef(node, aliases)) {
1395
+ refs.push(node);
1341
1396
  } else {
1342
1397
  const streamFn = isStreamCall(node);
1343
1398
  if (streamFn) sites.push({ call: node, fn: streamFn, program: p });
1344
1399
  }
1345
1400
  });
1346
- }
1347
- return sites;
1401
+ for (const node of refs)
1402
+ if (!callees.has(node))
1403
+ diagnostics.push({
1404
+ program: p,
1405
+ node: node.expr,
1406
+ message: `json.${node.field} cannot be used as a value`,
1407
+ hint: `call json.${node.field} directly`,
1408
+ });
1409
+ }
1410
+ return { sites, diagnostics };
1348
1411
  }
1349
1412
 
1350
1413
  function selectCodecEntry(gen, fn, argType) {
@@ -1353,22 +1416,52 @@ function selectCodecEntry(gen, fn, argType) {
1353
1416
  if (fn === "Unmarshal") return gen.entryUnmarshal(argType(1));
1354
1417
  if (fn === "Encode") return gen.entryEncode(argType(0));
1355
1418
  if (fn === "Decode") return gen.entryDecode(argType(0));
1356
- return gen.entryValid();
1419
+ }
1420
+
1421
+ // Records that the build uses `json` when this package calls it; returns
1422
+ // whether the package has call sites (or misuses) of its own. One without
1423
+ // them still lends its types to the dynamic encoder once `json` is in use.
1424
+ // The flag is tracked per target so WASM-only use does not pull `json` into
1425
+ // JS output (and vice versa).
1426
+ function jsonTargets(checker) {
1427
+ return checker.target === "both" || !checker.target
1428
+ ? ["js", "wasm"]
1429
+ : [checker.target];
1430
+ }
1431
+
1432
+ function noteJsonUse(options, sites, diagnostics, checker) {
1433
+ if (sites.length > 0 && options.jsonBuild)
1434
+ for (const t of jsonTargets(checker)) options.jsonBuild[t] = true;
1435
+ return sites.length + diagnostics.length > 0;
1357
1436
  }
1358
1437
 
1359
1438
  // 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();
1439
+ // when it has none; otherwise { body, imports, diagnostics } and marks each call's
1440
+ // SelectorExpr with `_genEntry`, the entry point the WASM link calls instead.
1441
+ export function generateJsonCodecs(programs, checker, pkgName, options = {}) {
1442
+ const { sites, diagnostics } = findSites(programs);
1443
+ const registerOnly = !noteJsonUse(options, sites, diagnostics, checker);
1444
+ if (registerOnly && !jsonTargets(checker).some((t) => options.jsonBuild?.[t]))
1445
+ return null;
1446
+ const qualifiers = new Map();
1366
1447
  const gen = new JsonCodecGen(checker, pkgName, qualifiers, programs);
1448
+ if (registerOnly) {
1449
+ gen.registerOnly = true;
1450
+ gen.flagDynamic();
1451
+ }
1367
1452
  const pending = [];
1368
- const diagnostics = [];
1369
1453
  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")
1454
+ // A site inside a generic function is shared by every instance, so a type
1455
+ // that mentions a type parameter takes the dynamic entry.
1456
+ const argType = (i) => {
1457
+ const t = defaultType(call.args[i]?._type);
1458
+ return mentionsTypeParam(t) ? null : t;
1459
+ };
1460
+ if (
1461
+ fn === "Unmarshal" &&
1462
+ checker.target !== "js" &&
1463
+ argType(1)?.base?.kind === "pointer"
1464
+ )
1372
1465
  diagnostics.push({
1373
1466
  program,
1374
1467
  node: call.func.expr,
@@ -1380,19 +1473,15 @@ export function generateJsonCodecs(programs, checker, pkgName) {
1380
1473
  pending.push([call.func, entry, fn === "Encode" || fn === "Decode"]);
1381
1474
  }
1382
1475
  gen.finish();
1476
+ if (registerOnly && !gen.out.some((o) => o.includes("RegisterEncoder")))
1477
+ return null;
1383
1478
  for (const [sel, entry, isStream] of pending) {
1384
- sel._jsonGen = entry;
1479
+ sel._genEntry = entry;
1385
1480
  if (isStream) sel._jsonStream = true;
1386
1481
  }
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 };
1482
+ return {
1483
+ body: gen.body(),
1484
+ imports: ["internal/jsonrt", ...importsFor(qualifiers)],
1485
+ diagnostics,
1486
+ };
1398
1487
  }