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.
- package/README.md +3 -3
- package/package.json +6 -5
- package/src/backend/js/expressions.js +1157 -408
- package/src/backend/js/index.js +327 -153
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +559 -2552
- package/src/backend/js/statements.js +670 -158
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +298 -19
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +91 -170
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +884 -256
- package/src/backend/wasm/emit-exprs.js +185 -77
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +524 -240
- package/src/backend/wasm/emit.js +176 -38
- package/src/backend/wasm/globals.js +48 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +44 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +299 -770
- package/src/backend/wasm/monomorph.js +139 -8
- package/src/backend/wasm/natives.js +600 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +97 -20
- package/src/backend/wasm/variadic.js +9 -4
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +113 -130
- package/src/lexer/lexer.js +106 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +355 -132
- package/src/link/type-names.js +204 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +62 -5
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +929 -0
- package/src/lower/functions.js +225 -7
- package/src/lower/gen-types.js +293 -0
- package/src/lower/index.js +14 -2
- package/src/lower/json-codecs.js +334 -245
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +30 -5
- package/src/parser/expressions.js +111 -57
- package/src/parser/statements.js +108 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +12 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +214 -19
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +156 -8
- package/src/typechecker/expressions.js +490 -108
- package/src/typechecker/index.js +346 -42
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +41 -0
- package/src/typechecker/resolve.js +490 -65
- package/src/typechecker/statements.js +288 -27
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +287 -575
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +391 -452
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +235 -50
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
package/src/lower/json-codecs.js
CHANGED
|
@@ -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 {
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
1139
|
-
|
|
1140
|
-
kind: "
|
|
1141
|
-
|
|
1142
|
-
|
|
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
|
|
1148
|
-
if (
|
|
1149
|
-
|
|
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
|
-
//
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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
|
-
|
|
1216
|
-
|
|
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
|
-
//
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
-
//
|
|
1241
|
-
//
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
"
|
|
1248
|
-
"
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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
|
|
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
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
1361
|
-
// SelectorExpr with `
|
|
1362
|
-
export function generateJsonCodecs(programs, checker, pkgName) {
|
|
1363
|
-
const sites = findSites(programs);
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
-
|
|
1371
|
-
|
|
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.
|
|
1479
|
+
sel._genEntry = entry;
|
|
1385
1480
|
if (isStream) sel._jsonStream = true;
|
|
1386
1481
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
}
|