gofront 1.8.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 (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  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 +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  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 +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. 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,7 @@ 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 {}";
282
223
  case "func":
283
224
  return this.funcString(t, (x) => this.refl(x));
284
225
  default:
@@ -286,17 +227,6 @@ export class JsonCodecGen {
286
227
  }
287
228
  }
288
229
 
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
230
  // A struct's own name in decode errors.
301
231
  shortName(t) {
302
232
  return t?.kind === "named" ? t.name.split(".").pop() : "";
@@ -309,7 +239,7 @@ export class JsonCodecGen {
309
239
  needsAddr(t, seen = new Set()) {
310
240
  if (!t || seen.has(t)) return false;
311
241
  seen.add(t);
312
- const m = marshalerOf(t);
242
+ const m = marshalerOf(t) ?? textMarshalerOf(t);
313
243
  if (m) return Boolean(m._ptrRecv);
314
244
  const u = under(t);
315
245
  if (u?.kind === "struct")
@@ -346,7 +276,13 @@ export class JsonCodecGen {
346
276
  return [`${this.encFn(t, addr)}(e, ${x})`];
347
277
  }
348
278
 
349
- marshalCall(x, t) {
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
+ ];
350
286
  return [
351
287
  `out, err := ${x}.MarshalJSON()`,
352
288
  `e.Marshaled(out, err, ${q(this.refl(t))})`,
@@ -357,7 +293,7 @@ export class JsonCodecGen {
357
293
  encSpecial(t, addr) {
358
294
  if (isRaw(t)) return ["e.Raw([]byte(v))"];
359
295
  if (isNumber(t)) return ["e.Number(string(v))"];
360
- const m = marshalerOf(t);
296
+ const m = marshalerOf(t) ?? textMarshalerOf(t);
361
297
  if (m && (!m._ptrRecv || addr)) return this.marshalCall("v", t);
362
298
  if (isBytes(t)) return ["e.Bytes([]byte(v))"];
363
299
  if (isInterface(t)) {
@@ -385,7 +321,8 @@ export class JsonCodecGen {
385
321
 
386
322
  encPointer(t, u) {
387
323
  const lines = ["if v == nil {", "\te.Null()", "\treturn", "}"];
388
- if (marshalerOf(u.base)) lines.push(...this.marshalCall("v", t));
324
+ if (marshalerOf(u.base) || textMarshalerOf(u.base))
325
+ lines.push(...this.marshalCall("v", t, u.base));
389
326
  else lines.push(...this.encValue(u.base, "*v", true));
390
327
  return lines;
391
328
  }
@@ -558,24 +495,50 @@ export class JsonCodecGen {
558
495
  genEncAny() {
559
496
  const cases = [];
560
497
  const seen = new Set();
498
+ const f32 = this.enc.get("float32")?.name;
561
499
  for (const [key, e] of this.enc) {
562
500
  if (e.addr || seen.has(key)) continue;
563
501
  seen.add(key);
564
502
  if (isInterface(e.t)) continue;
565
- cases.push(`\tcase ${this.spell(e.t)}:\n\t\t${e.name}(e, x)`);
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
+ });
566
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`;
567
522
  this.out.push(
568
523
  [
569
- `func ${this.prefix}encAny(e *jsonrt.Enc, v any) {`,
524
+ `func ${known}(e *jsonrt.Enc, v any) bool {`,
570
525
  "\tif v == nil {",
571
526
  "\t\te.Null()",
572
- "\t\treturn",
527
+ "\t\treturn true",
573
528
  "\t}",
574
529
  "\tswitch x := v.(type) {",
575
- ...cases,
530
+ ...cases.map((c) => c.src),
576
531
  "\tdefault:",
577
532
  "\t\t_ = x",
578
- '\t\te.FailType("unsupported dynamic type")',
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)",
579
542
  "\t}",
580
543
  "}",
581
544
  ].join("\n"),
@@ -632,7 +595,7 @@ export class JsonCodecGen {
632
595
  if (u.kind === "basic" && !isEmptyIface(t))
633
596
  return this.decScalar(t, u, typ);
634
597
  const byKind = {
635
- pointer: () => this.decPointer(u, typ),
598
+ pointer: () => this.decPointer(u),
636
599
  slice: () => this.decSlice(u, t, typ),
637
600
  array: () => this.decArray(u, typ),
638
601
  map: () => this.decMap(u, t, typ),
@@ -707,7 +670,7 @@ export class JsonCodecGen {
707
670
  ];
708
671
  }
709
672
 
710
- decPointer(u, typ) {
673
+ decPointer(u) {
711
674
  const base = u.base;
712
675
  const lines = [
713
676
  "if d.Null() {",
@@ -717,9 +680,7 @@ export class JsonCodecGen {
717
680
  `\tcur = new(${this.spell(base)})`,
718
681
  "}",
719
682
  ];
720
- if (under(base)?.kind === "pointer") {
721
- lines.push(`d.Mismatch(${typ})`);
722
- } else if (this.decInPlace(base)) {
683
+ if (this.decInPlace(base)) {
723
684
  lines.push(`${this.decFn(base)}(d, cur)`);
724
685
  } else {
725
686
  lines.push(`*cur = ${this.decFn(base)}(d, *cur)`);
@@ -1015,19 +976,11 @@ export class JsonCodecGen {
1015
976
  return name;
1016
977
  }
1017
978
 
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
979
  entryEncode(argType) {
1027
- this.qualified("json.Encoder");
980
+ const json = this.qualifierFor(JSON_PATH);
1028
981
  const name = `${this.prefix}encode${this.entries.length}`;
1029
982
  const lines = [
1030
- `func ${name}(enc *json.Encoder, v any) error {`,
983
+ `func ${name}(enc *${json}.Encoder, v any) error {`,
1031
984
  "\tif enc.Err() != nil {",
1032
985
  "\t\treturn enc.Err()",
1033
986
  "\t}",
@@ -1056,10 +1009,10 @@ export class JsonCodecGen {
1056
1009
  }
1057
1010
 
1058
1011
  entryDecode(argType) {
1059
- this.qualified("json.Decoder");
1012
+ const json = this.qualifierFor(JSON_PATH);
1060
1013
  const name = `${this.prefix}decode${this.entries.length}`;
1061
1014
  const lines = [
1062
- `func ${name}(dec *json.Decoder, v any) error {`,
1015
+ `func ${name}(dec *${json}.Decoder, v any) error {`,
1063
1016
  "\tdata, err := dec.ScanNext()",
1064
1017
  "\tif err != nil {",
1065
1018
  "\t\treturn err",
@@ -1103,7 +1056,13 @@ export class JsonCodecGen {
1103
1056
  (t) =>
1104
1057
  t.kind === "pointer" && (isEmptyIface(t.base) || !isInterface(t.base)),
1105
1058
  );
1106
- for (const pt of [...this.decTargets, ...pointers]) {
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]) {
1107
1066
  const k = this.key(pt);
1108
1067
  if (seen.has(k)) continue;
1109
1068
  seen.add(k);
@@ -1133,21 +1092,21 @@ export class JsonCodecGen {
1133
1092
 
1134
1093
  // Registers what the dynamic encoder handles, until nothing new appears.
1135
1094
  addDynamicTypes() {
1136
- for (const n of DYNAMIC_BASICS) this.encFn({ kind: "basic", name: n });
1137
1095
  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
- });
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
+ }
1144
1105
  for (const t of this.programTypes()) {
1145
1106
  this.encFn(t, false);
1146
1107
  }
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;
1108
+ for (const { type: t, via } of this.collectedTypes()) {
1109
+ if (via !== "declared" || mentionsTypeParam(t)) continue;
1151
1110
  const u = under(t);
1152
1111
  if (!u || u.kind === "interface" || u.kind === "func") continue;
1153
1112
  this.encFn(t, false);
@@ -1155,41 +1114,53 @@ export class JsonCodecGen {
1155
1114
  }
1156
1115
  }
1157
1116
 
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("$$") &&
1117
+ spellableNamed(t, ok) {
1118
+ return (
1119
+ isJsonValue(t) ||
1120
+ (!t.name.includes("$$") &&
1166
1121
  !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;
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;
1177
1142
  }
1178
1143
 
1179
1144
  // Types the program builds with composite literals, `&T{}`, make and new:
1180
1145
  // candidates for a value stored in an interface (an `any` map value, say).
1181
1146
  programTypes() {
1182
1147
  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
- });
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);
1193
1164
  }
1194
1165
  return [...found.values()];
1195
1166
  }
@@ -1212,68 +1183,32 @@ export class JsonCodecGen {
1212
1183
  }
1213
1184
  }
1214
1185
 
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");
1186
+ // The generated declarations (the spine adds the header and imports).
1187
+ body() {
1188
+ return [...this.entries, ...this.out].join("\n\n");
1226
1189
  }
1227
1190
  }
1228
1191
 
1229
- // Calls every `fn(node)` for the AST nodes under `root`.
1230
- function walkNodes(root, fn) {
1231
- if (!root || typeof root !== "object") return;
1232
- if (Array.isArray(root)) {
1233
- for (const n of root) walkNodes(n, fn);
1234
- return;
1235
- }
1236
- fn(root);
1237
- forEachChild(root, (c) => walkNodes(c, fn));
1238
- }
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);
1239
1196
 
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
- ]);
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);
1254
1200
 
1255
- const isCompositeType = (t) => {
1201
+ export const isCompositeType = (t) => {
1256
1202
  const k = under(t)?.kind;
1257
1203
  return (
1258
1204
  (k === "slice" || k === "array" || k === "map") &&
1259
1205
  !holdsFunc(t) &&
1260
- !hasSharedRepr(t)
1206
+ (isTaggedSlice(t) || !hasSharedRepr(t))
1261
1207
  );
1262
1208
  };
1263
1209
 
1264
- function hasSharedRepr(t, depth = 0) {
1265
- if (!t || depth > 8) return false;
1266
- const u = under(t);
1267
- if (u?.kind === "basic") return SHARED_REPR.has(u.name);
1268
- if (u?.kind === "slice" || u?.kind === "array")
1269
- return hasSharedRepr(u.elem, depth + 1);
1270
- if (u?.kind === "map")
1271
- return hasSharedRepr(u.key, depth + 1) || hasSharedRepr(u.value, depth + 1);
1272
- return false;
1273
- }
1274
-
1275
1210
  // Func values are not JSON-encodable, and `case []func():` does not parse.
1276
- function holdsFunc(t, depth = 0) {
1211
+ export function holdsFunc(t, depth = 0) {
1277
1212
  if (!t || depth > 8) return false;
1278
1213
  const u = under(t);
1279
1214
  if (u?.kind === "func") return true;
@@ -1305,12 +1240,17 @@ function jsonAliases(program) {
1305
1240
  return aliases;
1306
1241
  }
1307
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
+
1308
1252
  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);
1253
+ node.kind === "CallExpr" && isJsonRef(node.func, aliases);
1314
1254
 
1315
1255
  function isStreamCall(node) {
1316
1256
  if (node.kind !== "CallExpr" || node.func?.kind !== "SelectorExpr")
@@ -1330,21 +1270,36 @@ function isStreamCall(node) {
1330
1270
  return null;
1331
1271
  }
1332
1272
 
1333
- // Finds `json.Marshal/Unmarshal/Valid` call sites in the checked programs.
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.
1334
1275
  function findSites(programs) {
1335
1276
  const sites = [];
1277
+ const diagnostics = [];
1336
1278
  for (const p of programs) {
1337
1279
  const aliases = jsonAliases(p);
1280
+ const callees = new Set();
1281
+ const refs = [];
1338
1282
  walkNodes(p.decls, (node) => {
1339
1283
  if (isJsonCall(node, aliases)) {
1340
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);
1341
1288
  } else {
1342
1289
  const streamFn = isStreamCall(node);
1343
1290
  if (streamFn) sites.push({ call: node, fn: streamFn, program: p });
1344
1291
  }
1345
1292
  });
1346
- }
1347
- return sites;
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 };
1348
1303
  }
1349
1304
 
1350
1305
  function selectCodecEntry(gen, fn, argType) {
@@ -1353,22 +1308,52 @@ function selectCodecEntry(gen, fn, argType) {
1353
1308
  if (fn === "Unmarshal") return gen.entryUnmarshal(argType(1));
1354
1309
  if (fn === "Encode") return gen.entryEncode(argType(0));
1355
1310
  if (fn === "Decode") return gen.entryDecode(argType(0));
1356
- return gen.entryValid();
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;
1357
1328
  }
1358
1329
 
1359
1330
  // 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();
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();
1366
1339
  const gen = new JsonCodecGen(checker, pkgName, qualifiers, programs);
1340
+ if (registerOnly) {
1341
+ gen.registerOnly = true;
1342
+ gen.flagDynamic();
1343
+ }
1367
1344
  const pending = [];
1368
- const diagnostics = [];
1369
1345
  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")
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
+ )
1372
1357
  diagnostics.push({
1373
1358
  program,
1374
1359
  node: call.func.expr,
@@ -1380,19 +1365,15 @@ export function generateJsonCodecs(programs, checker, pkgName) {
1380
1365
  pending.push([call.func, entry, fn === "Encode" || fn === "Decode"]);
1381
1366
  }
1382
1367
  gen.finish();
1368
+ if (registerOnly && !gen.out.some((o) => o.includes("RegisterEncoder")))
1369
+ return null;
1383
1370
  for (const [sel, entry, isStream] of pending) {
1384
- sel._jsonGen = entry;
1371
+ sel._genEntry = entry;
1385
1372
  if (isStream) sel._jsonStream = true;
1386
1373
  }
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 };
1374
+ return {
1375
+ body: gen.body(),
1376
+ imports: ["internal/jsonrt", ...importsFor(qualifiers)],
1377
+ diagnostics,
1378
+ };
1398
1379
  }