gofront 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
// src/backend/js/stdlib/json.js
|
|
2
|
+
// CodeGen for Go encoding/json package with compile-time static encoders and decoders.
|
|
3
|
+
|
|
4
|
+
import { someChild } from "../../../lower/walk.js";
|
|
5
|
+
import { typedArrayConstructorForElem } from "../../../typechecker/types.js";
|
|
6
|
+
|
|
7
|
+
function safeIdent(name) {
|
|
8
|
+
return String(name || "").replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function declareCodec(fnName, params, body) {
|
|
12
|
+
return `var ${fnName} = typeof ${fnName} !== "undefined" ? ${fnName} : function ${fnName}(${params}) {\n${body}\n};`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function typeCodecName(t, fallback = "val") {
|
|
16
|
+
if (!t) return fallback;
|
|
17
|
+
if (t.kind === "pointer" && t.base) {
|
|
18
|
+
return `${typeCodecName(t.base, fallback)}_ptr`;
|
|
19
|
+
}
|
|
20
|
+
if (t.name) return t.name;
|
|
21
|
+
if (t.kind === "slice") return `slice_${typeCodecName(t.elem, "elem")}`;
|
|
22
|
+
if (t.kind === "map")
|
|
23
|
+
return `map_${typeCodecName(t.value ?? t.elem, "elem")}`;
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function unwrapNamed(t) {
|
|
28
|
+
return t?.kind === "named" ? t.underlying : t;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isRawMessageType(t) {
|
|
32
|
+
return (
|
|
33
|
+
t?.name === "json.RawMessage" || t?.name === "encoding/json.RawMessage"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const NUMBER_NAMES = new Set(["json.Number", "encoding/json.Number"]);
|
|
38
|
+
|
|
39
|
+
function isNumberType(t) {
|
|
40
|
+
if (!t) return false;
|
|
41
|
+
const name = t.name ?? "";
|
|
42
|
+
if (NUMBER_NAMES.has(name)) return true;
|
|
43
|
+
if (name.startsWith("Number$$") || name.endsWith(".Number")) return true;
|
|
44
|
+
return (
|
|
45
|
+
t.kind === "named" && name === "Number" && unwrapNamed(t)?.name === "string"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isByteSliceType(t) {
|
|
50
|
+
const base = unwrapNamed(t);
|
|
51
|
+
return (
|
|
52
|
+
base?.kind === "slice" &&
|
|
53
|
+
(base.elem?.name === "byte" || base.elem?.name === "uint8") &&
|
|
54
|
+
!isRawMessageType(t)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isJsonNullAssignable(type) {
|
|
59
|
+
const base = unwrapNamed(type);
|
|
60
|
+
return (
|
|
61
|
+
base?.kind === "pointer" ||
|
|
62
|
+
base?.kind === "slice" ||
|
|
63
|
+
base?.kind === "map" ||
|
|
64
|
+
base?.kind === "interface" ||
|
|
65
|
+
(base?.kind === "basic" && base.name === "any")
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Field kinds the `,string` tag option applies to (Go: bool, ints, uints, floats, string).
|
|
70
|
+
const QUOTABLE_KINDS = new Set([
|
|
71
|
+
"string",
|
|
72
|
+
"bool",
|
|
73
|
+
"int",
|
|
74
|
+
"int8",
|
|
75
|
+
"int16",
|
|
76
|
+
"int32",
|
|
77
|
+
"int64",
|
|
78
|
+
"uint",
|
|
79
|
+
"uint8",
|
|
80
|
+
"uint16",
|
|
81
|
+
"uint32",
|
|
82
|
+
"uint64",
|
|
83
|
+
"uintptr",
|
|
84
|
+
"byte",
|
|
85
|
+
"rune",
|
|
86
|
+
"float32",
|
|
87
|
+
"float64",
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
// reflect's names for GoFront's basic aliases, used in decode error messages.
|
|
91
|
+
const BASIC_DISPLAY = { any: "interface {}", byte: "uint8", rune: "int32" };
|
|
92
|
+
|
|
93
|
+
// Unmarshal targets whose argument errors depend on the static type alone:
|
|
94
|
+
// pointers can be nil, interfaces are judged by their dynamic value.
|
|
95
|
+
// Only statically concrete non-pointer kinds are rejected up front; anything
|
|
96
|
+
// else (type parameters, unknown kinds) is judged by its dynamic value.
|
|
97
|
+
const TARGET_KIND = { pointer: "ptr" };
|
|
98
|
+
const VALUE_TARGET_KINDS = new Set([
|
|
99
|
+
"slice",
|
|
100
|
+
"array",
|
|
101
|
+
"map",
|
|
102
|
+
"struct",
|
|
103
|
+
"func",
|
|
104
|
+
"chan",
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
const COMPOSITE_KINDS = new Set(["slice", "array", "map"]);
|
|
108
|
+
const COMPOSITE_DECODERS = {
|
|
109
|
+
slice: "_getSliceDecoder",
|
|
110
|
+
array: "_getArrayDecoder",
|
|
111
|
+
map: "_getMapDecoder",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Basic kinds decoded by the typed scalar decoders.
|
|
115
|
+
const SCALAR_KINDS = QUOTABLE_KINDS;
|
|
116
|
+
|
|
117
|
+
// Methods live on the named type, or on its underlying struct for struct types.
|
|
118
|
+
function lookupMethod(type, name) {
|
|
119
|
+
if (type?.kind !== "named") return null;
|
|
120
|
+
return type.methods?.get(name) ?? type.underlying?.methods?.get(name) ?? null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const jsonMethods = {
|
|
124
|
+
_genJson(fn, a, expr) {
|
|
125
|
+
this.useHelper("json");
|
|
126
|
+
this.useHelper("base64");
|
|
127
|
+
this.useHelper("error");
|
|
128
|
+
this._markJsonPtrRecvMarshalers();
|
|
129
|
+
const args = a();
|
|
130
|
+
if (fn === "Marshal") {
|
|
131
|
+
this._markJsonAnyEncoders();
|
|
132
|
+
const argType = expr.args[0]?._type;
|
|
133
|
+
const enc = this._getJsonEncoder(argType);
|
|
134
|
+
return `__json_marshal(${enc}, ${args[0]})`;
|
|
135
|
+
}
|
|
136
|
+
if (fn === "MarshalIndent") {
|
|
137
|
+
this._markJsonAnyEncoders();
|
|
138
|
+
const argType = expr.args[0]?._type;
|
|
139
|
+
const enc = this._getJsonEncoder(argType);
|
|
140
|
+
return `__json_marshal_indent(${enc}, ${args[0]}, ${args[1]}, ${args[2]})`;
|
|
141
|
+
}
|
|
142
|
+
if (fn === "Unmarshal") {
|
|
143
|
+
const targetType = expr.args[1]?._type;
|
|
144
|
+
const dec = this._getJsonDecoder(targetType);
|
|
145
|
+
return `__json_unmarshal(${dec}, ${args[0]}, ${args[1]}, ${this._jsonTargetArgs(targetType)})`;
|
|
146
|
+
}
|
|
147
|
+
if (fn === "Valid") {
|
|
148
|
+
return `__json_valid(${args[0]})`;
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Name and static kind of an Unmarshal/Decode target, for the argument
|
|
154
|
+
// errors Go reports from the type alone ("ptr", "val" or none for interfaces).
|
|
155
|
+
_jsonTargetArgs(targetType) {
|
|
156
|
+
const base = unwrapNamed(targetType);
|
|
157
|
+
const key = base?.kind === "basic" ? base.name : base?.kind;
|
|
158
|
+
if (!targetType || key === "nil") return '"", ""';
|
|
159
|
+
const isValue =
|
|
160
|
+
base?.kind === "basic" ? key !== "any" : VALUE_TARGET_KINDS.has(key);
|
|
161
|
+
const kind = TARGET_KIND[key] ?? (isValue ? "val" : "");
|
|
162
|
+
return `${JSON.stringify(this._jsonDisplayType(targetType))}, ${JSON.stringify(kind)}`;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
_getJsonEncoder(type, addr = false) {
|
|
166
|
+
if (!type) return "__json_enc_any";
|
|
167
|
+
if (isNumberType(type)) return "__json_enc_number";
|
|
168
|
+
if (isRawMessageType(type)) {
|
|
169
|
+
const fnName = "__json_enc_RawMessage";
|
|
170
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
171
|
+
this._jsonCodecs.set(
|
|
172
|
+
fnName,
|
|
173
|
+
declareCodec(fnName, "v", ` return __json_enc_raw(v);`),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return fnName;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Go calls a pointer-receiver MarshalJSON only on addressable values.
|
|
180
|
+
const marshaler = this._jsonMarshalerInfo(type);
|
|
181
|
+
if (marshaler && (!marshaler.ptrRecv || addr)) {
|
|
182
|
+
return this._getMarshalerEncoder(type, marshaler);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const base = unwrapNamed(type);
|
|
186
|
+
if (isByteSliceType(type)) return "__json_b64";
|
|
187
|
+
|
|
188
|
+
if (base?.kind === "struct") {
|
|
189
|
+
const needsAddr = addr && this._jsonNeedsAddr(type);
|
|
190
|
+
const typeName = safeIdent((type.name ?? "AnonStruct").split(".").pop());
|
|
191
|
+
const fnName = `__json_enc_${typeName}${needsAddr ? "_addr" : ""}`;
|
|
192
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
193
|
+
this._jsonCodecs.set(fnName, "");
|
|
194
|
+
const code = this._buildStructEncoder(type, fnName, needsAddr);
|
|
195
|
+
this._jsonCodecs.set(fnName, code);
|
|
196
|
+
}
|
|
197
|
+
return fnName;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (type.kind === "pointer" && type.base) {
|
|
201
|
+
const innerEnc = this._getJsonEncoder(type.base, true);
|
|
202
|
+
// Pointers to structs are the struct object; other pointers are { value } boxes.
|
|
203
|
+
const deref = unwrapNamed(type.base)?.kind === "struct" ? "v" : "v.value";
|
|
204
|
+
const fnName = `__json_enc_ptr_${safeIdent(typeCodecName(type.base, "val").split(".").pop())}`;
|
|
205
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
206
|
+
this._jsonCodecs.set(
|
|
207
|
+
fnName,
|
|
208
|
+
declareCodec(
|
|
209
|
+
fnName,
|
|
210
|
+
"v",
|
|
211
|
+
` return (v === null || v === undefined) ? "null" : ${innerEnc}(${deref});`,
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return fnName;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (base?.kind === "slice" || base?.kind === "array") {
|
|
219
|
+
const elemEnc = this._getJsonEncoder(base.elem, true);
|
|
220
|
+
const fnName = `__json_enc_slice_${safeIdent(typeCodecName(base.elem, "elem").split(".").pop())}`;
|
|
221
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
222
|
+
this._jsonCodecs.set(
|
|
223
|
+
fnName,
|
|
224
|
+
declareCodec(
|
|
225
|
+
fnName,
|
|
226
|
+
"v",
|
|
227
|
+
` return __json_enc_slice(v, ${elemEnc});`,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return fnName;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (base?.kind === "map") {
|
|
235
|
+
const valType = base.value ?? base.elem;
|
|
236
|
+
const valEnc = this._getJsonEncoder(valType, false);
|
|
237
|
+
const fnName = `__json_enc_map_${safeIdent(typeCodecName(valType, "val").split(".").pop())}`;
|
|
238
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
239
|
+
this._jsonCodecs.set(
|
|
240
|
+
fnName,
|
|
241
|
+
declareCodec(fnName, "v", ` return __json_enc_map(v, ${valEnc});`),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return fnName;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (base?.kind === "basic") {
|
|
248
|
+
return this._getBasicEncoder(base.name);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return "__json_enc_any";
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// Tags pointer-receiver MarshalJSON methods so the dynamic `any` encoder can skip
|
|
255
|
+
// them for non-addressable values, as Go does.
|
|
256
|
+
_markJsonPtrRecvMarshalers() {
|
|
257
|
+
if (this._jsonPtrRecvMarked) return;
|
|
258
|
+
this._jsonPtrRecvMarked = true;
|
|
259
|
+
for (const [name, type] of this.checker?.types ?? []) {
|
|
260
|
+
if (name.includes(".") || type?.kind !== "named") continue;
|
|
261
|
+
if (!lookupMethod(type, "MarshalJSON")?._ptrRecv) continue;
|
|
262
|
+
const cls = safeIdent(name);
|
|
263
|
+
this._jsonCodecs.set(
|
|
264
|
+
`__json_ptrrecv_${cls}`,
|
|
265
|
+
`if (typeof ${cls} === "function" && ${cls}.prototype.MarshalJSON) ${cls}.prototype.MarshalJSON.__ptrRecv = true;`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
// Structs reach the dynamic `any` encoder as bare instances, so each package
|
|
271
|
+
// struct without a MarshalJSON method gets a hidden hook that runs its static
|
|
272
|
+
// encoder (tags, omitempty, promoted fields) instead of listing its properties.
|
|
273
|
+
// A non-generic struct declared in this package that has no MarshalJSON.
|
|
274
|
+
_isPlainPackageStruct(name, type) {
|
|
275
|
+
const isStruct =
|
|
276
|
+
type?.kind === "named" && type.underlying?.kind === "struct";
|
|
277
|
+
const generic = type?.typeArgs?.length || type?._genericOf;
|
|
278
|
+
if (!isStruct || generic || /[.$[]/.test(name) || type.name !== name)
|
|
279
|
+
return false;
|
|
280
|
+
return !this._jsonMarshalerInfo(type) && this.structNames?.has(name);
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
_markJsonAnyEncoders() {
|
|
284
|
+
this._jsonAnyMarked = true;
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
_isJsonEncoderName(recvName) {
|
|
288
|
+
return (
|
|
289
|
+
recvName === "json.Encoder" ||
|
|
290
|
+
recvName === "encoding/json.Encoder" ||
|
|
291
|
+
recvName?.startsWith("Encoder$$")
|
|
292
|
+
);
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
// True when `node` contains a call that marks the any-encoders when
|
|
296
|
+
// generated (`json.Marshal`, `json.MarshalIndent`, `Encoder.Encode`).
|
|
297
|
+
// Decided before any body is generated, so a function-local struct can get
|
|
298
|
+
// its hook right after its class even when the call comes later.
|
|
299
|
+
_usesJsonAnyMarshal(node) {
|
|
300
|
+
if (Array.isArray(node))
|
|
301
|
+
return node.some((n) => this._usesJsonAnyMarshal(n));
|
|
302
|
+
if (!node || typeof node !== "object") return false;
|
|
303
|
+
if (node.kind === "CallExpr" && node.func?.kind === "SelectorExpr") {
|
|
304
|
+
const { expr, field } = node.func;
|
|
305
|
+
if (
|
|
306
|
+
expr?.kind === "Ident" &&
|
|
307
|
+
expr.name === "json" &&
|
|
308
|
+
(field === "Marshal" || field === "MarshalIndent")
|
|
309
|
+
)
|
|
310
|
+
return true;
|
|
311
|
+
if (
|
|
312
|
+
field === "Encode" &&
|
|
313
|
+
this._isJsonEncoderName(this._resolveRecvName(expr?._type))
|
|
314
|
+
)
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
return someChild(node, (n) => this._usesJsonAnyMarshal(n));
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
_jsonAnyHook(cls, enc) {
|
|
321
|
+
return `Object.defineProperty(${cls}.prototype, "__json_enc", { value: function() { return ${enc}(this); }, configurable: true, writable: true });`;
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
// Runs once all function bodies are generated; package-level structs get
|
|
325
|
+
// their hook at top level (function-local ones get it in _noteLocalTypeDecl).
|
|
326
|
+
_emitJsonAnyHooks() {
|
|
327
|
+
if (!this._jsonAnyMarked || this._jsonAnyHooked) return;
|
|
328
|
+
this._jsonAnyHooked = true;
|
|
329
|
+
const local = this._localTypeNames;
|
|
330
|
+
for (const [name, type] of this.checker?.types ?? []) {
|
|
331
|
+
if (local.has(name) || !this._isPlainPackageStruct(name, type)) continue;
|
|
332
|
+
const cls = safeIdent(name);
|
|
333
|
+
this._jsonCodecs.set(
|
|
334
|
+
`__json_anyenc_${cls}`,
|
|
335
|
+
this._jsonAnyHook(cls, this._getJsonEncoder(type, false)),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// A function-local struct class is only in scope inside its function, so
|
|
341
|
+
// its hook goes right after the class, and only when the program marshals.
|
|
342
|
+
_noteLocalTypeDecl(stmt) {
|
|
343
|
+
this._localTypeNames.add(stmt.name);
|
|
344
|
+
if (!this._jsonAnyUsed || stmt.type.kind !== "StructType") return;
|
|
345
|
+
const type = this.checker?.types.get(stmt.name);
|
|
346
|
+
if (!this._isPlainPackageStruct(stmt.name, type)) return;
|
|
347
|
+
const enc = this._getJsonEncoder(type, false);
|
|
348
|
+
this.line(this._jsonAnyHook(safeIdent(stmt.name), enc));
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
_jsonTypeName(type) {
|
|
352
|
+
const name = type?.name ?? "value";
|
|
353
|
+
const at = name.indexOf("$$");
|
|
354
|
+
if (at >= 0) {
|
|
355
|
+
const orig = name.slice(0, at);
|
|
356
|
+
const modPath = name.slice(at + 2).replaceAll("$", "/");
|
|
357
|
+
return `${modPath}.${orig}`;
|
|
358
|
+
}
|
|
359
|
+
if (name.includes(".")) return name;
|
|
360
|
+
const pkg = this.checker?.pkgName;
|
|
361
|
+
return pkg ? `${pkg}.${name}` : name;
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
_jsonClassName(type) {
|
|
365
|
+
return String(type?.name ?? "Object")
|
|
366
|
+
.split(".")
|
|
367
|
+
.pop();
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
// How to call `name` (MarshalJSON / UnmarshalJSON) on values of `type`, or null.
|
|
371
|
+
// Struct types dispatch on the instance; named non-struct types (raw values)
|
|
372
|
+
// call the static `T.$name(recv, ...)` with a { value } box for pointer receivers.
|
|
373
|
+
_jsonMethodInfo(type, name) {
|
|
374
|
+
const method = lookupMethod(type, name);
|
|
375
|
+
if (!method || isRawMessageType(type)) return null;
|
|
376
|
+
const isStruct = type.underlying?.kind === "struct";
|
|
377
|
+
if (!isStruct && !this._namedBoxInfo(type)) return null;
|
|
378
|
+
return {
|
|
379
|
+
ptrRecv: Boolean(method._ptrRecv),
|
|
380
|
+
isStruct,
|
|
381
|
+
cls: this._jsonClassName(type),
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
_jsonMarshalerInfo(type) {
|
|
386
|
+
return this._jsonMethodInfo(type, "MarshalJSON");
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
_jsonUnmarshalerInfo(type) {
|
|
390
|
+
return this._jsonMethodInfo(type, "UnmarshalJSON");
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
// UnmarshalJSON call for a target reachable through `addrExpr`: the struct
|
|
394
|
+
// instance for struct types, a { value } box otherwise.
|
|
395
|
+
_jsonUnmarshalCall(info, addrExpr, bytesExpr) {
|
|
396
|
+
if (info.isStruct) return `${addrExpr}.UnmarshalJSON(${bytesExpr})`;
|
|
397
|
+
const recv = info.ptrRecv ? addrExpr : `${addrExpr}.value`;
|
|
398
|
+
return `${info.cls}.$UnmarshalJSON(${recv}, ${bytesExpr})`;
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
// True when encoding an addressable `type` differs from a non-addressable one,
|
|
402
|
+
// i.e. it (or a value field inside it) has a pointer-receiver MarshalJSON.
|
|
403
|
+
_jsonNeedsAddr(type, seen = new Set()) {
|
|
404
|
+
if (!type || seen.has(type)) return false;
|
|
405
|
+
seen.add(type);
|
|
406
|
+
const marshaler = this._jsonMarshalerInfo(type);
|
|
407
|
+
if (marshaler) return marshaler.ptrRecv;
|
|
408
|
+
const base = unwrapNamed(type);
|
|
409
|
+
if (base?.kind !== "struct") return false;
|
|
410
|
+
const fields = this.checker?.resolveJsonFields(base) ?? [];
|
|
411
|
+
return fields.some((f) => this._jsonNeedsAddr(f.type, seen));
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
_getMarshalerEncoder(type, info) {
|
|
415
|
+
const fnName = `__json_enc_${safeIdent(info.cls)}_marshal`;
|
|
416
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
417
|
+
let call;
|
|
418
|
+
if (info.isStruct) {
|
|
419
|
+
call = ` if (v === null || v === undefined) return "null";\n var res = v.MarshalJSON();`;
|
|
420
|
+
} else {
|
|
421
|
+
const recv = info.ptrRecv ? "{ value: v }" : "v";
|
|
422
|
+
call = ` var res = ${info.cls}.$MarshalJSON(${recv});`;
|
|
423
|
+
}
|
|
424
|
+
this._jsonCodecs.set(
|
|
425
|
+
fnName,
|
|
426
|
+
declareCodec(
|
|
427
|
+
fnName,
|
|
428
|
+
"v",
|
|
429
|
+
`${call}\n return __json_marshaler_out(res, ${JSON.stringify(this._jsonTypeName(type))});`,
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
return fnName;
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
_getUnmarshalerDecoder(type) {
|
|
437
|
+
const info = this._jsonUnmarshalerInfo(type);
|
|
438
|
+
const fnName = `__json_dec_${safeIdent(info.cls)}_unmarshal`;
|
|
439
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
440
|
+
const bytes =
|
|
441
|
+
"__json_raw_bytes(rawStr !== undefined ? rawStr : JSON.stringify(parsed))";
|
|
442
|
+
const body = info.isStruct
|
|
443
|
+
? ` var inst = target && typeof target === "object" ? target : new ${info.cls}();\n var err = ${this._jsonUnmarshalCall(info, "inst", bytes)};\n if (err) return __json_user_err(err);\n if (box) box.value = inst;\n return null;`
|
|
444
|
+
: ` var addr = box || { value: target };\n var err = ${this._jsonUnmarshalCall(info, "addr", bytes)};\n if (err) return __json_user_err(err);\n return null;`;
|
|
445
|
+
this._jsonCodecs.set(
|
|
446
|
+
fnName,
|
|
447
|
+
declareCodec(fnName, "parsed, target, box, rawData, rawStr", body),
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
return fnName;
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
_unmarshalerElemDecoder(elemType) {
|
|
454
|
+
const isPtr = elemType?.kind === "pointer";
|
|
455
|
+
const valueType = isPtr ? elemType.base : elemType;
|
|
456
|
+
const info = this._jsonUnmarshalerInfo(valueType);
|
|
457
|
+
if (!info) return null;
|
|
458
|
+
let make;
|
|
459
|
+
if (info.isStruct) {
|
|
460
|
+
make = (rawExpr) =>
|
|
461
|
+
`(() => { var inst = new ${info.cls}(); var uerr = ${this._jsonUnmarshalCall(info, "inst", `__json_raw_bytes(${rawExpr})`)}; return uerr ? __json_user_err(uerr) : inst; })()`;
|
|
462
|
+
} else {
|
|
463
|
+
// Pointer elements are the { value } box itself; value elements its value.
|
|
464
|
+
const zero = this.zeroValueForType(valueType);
|
|
465
|
+
const result = isPtr ? "__b" : "__b.value";
|
|
466
|
+
make = (rawExpr) =>
|
|
467
|
+
`(() => { var __b = { value: ${zero} }; var uerr = ${this._jsonUnmarshalCall(info, "__b", `__json_raw_bytes(${rawExpr})`)}; return uerr ? __json_user_err(uerr) : ${result}; })()`;
|
|
468
|
+
}
|
|
469
|
+
if (isPtr)
|
|
470
|
+
return (v, rawExpr) => `(${v} === null ? null : ${make(rawExpr)})`;
|
|
471
|
+
return (_v, rawExpr) => make(rawExpr);
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
// Returns the basic kind a `,string` field is quoted as, or null if the option does not apply.
|
|
475
|
+
_jsonQuotedKind(f) {
|
|
476
|
+
if (!f.quoted) return null;
|
|
477
|
+
const t = f.type?.kind === "pointer" ? f.type.base : f.type;
|
|
478
|
+
if (this._jsonMarshalerInfo(t)) return null;
|
|
479
|
+
const base = unwrapNamed(t);
|
|
480
|
+
return base?.kind === "basic" && QUOTABLE_KINDS.has(base.name)
|
|
481
|
+
? base.name
|
|
482
|
+
: null;
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
_genQuotedFieldEncoderExpr(f, kind) {
|
|
486
|
+
const isPtr = f.type?.kind === "pointer";
|
|
487
|
+
const basic = this._genBasicFieldExpr(kind);
|
|
488
|
+
const quote = (x) => {
|
|
489
|
+
const inner = basic(x);
|
|
490
|
+
return kind === "string"
|
|
491
|
+
? `__json_esc_noh(${inner})`
|
|
492
|
+
: `('"' + ${inner} + '"')`;
|
|
493
|
+
};
|
|
494
|
+
if (!isPtr) return quote;
|
|
495
|
+
return (v) =>
|
|
496
|
+
`(${v} === null || ${v} === undefined ? "null" : ${quote(`${v}.value`)})`;
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
_getBasicEncoder(basicName) {
|
|
500
|
+
switch (basicName) {
|
|
501
|
+
case "string":
|
|
502
|
+
return "__json_esc";
|
|
503
|
+
case "bool":
|
|
504
|
+
return "(v => v ? 'true' : 'false')";
|
|
505
|
+
case "int":
|
|
506
|
+
case "int8":
|
|
507
|
+
case "int16":
|
|
508
|
+
case "int32":
|
|
509
|
+
case "uint":
|
|
510
|
+
case "uint8":
|
|
511
|
+
case "uint16":
|
|
512
|
+
case "uint32":
|
|
513
|
+
case "byte":
|
|
514
|
+
case "rune":
|
|
515
|
+
return "(v => String(Math.trunc(v)))";
|
|
516
|
+
case "int64":
|
|
517
|
+
case "uint64":
|
|
518
|
+
return "(v => typeof v === 'bigint' ? v.toString() : String(v))";
|
|
519
|
+
case "float32":
|
|
520
|
+
return "__json_float32";
|
|
521
|
+
case "float64":
|
|
522
|
+
return "__json_float64";
|
|
523
|
+
default:
|
|
524
|
+
return "__json_enc_any";
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
_buildStructEncoder(structType, fnName, addr = false) {
|
|
529
|
+
const underlying = unwrapNamed(structType);
|
|
530
|
+
const fields = this.checker?.resolveJsonFields(underlying) ?? [];
|
|
531
|
+
const lines = [];
|
|
532
|
+
lines.push(
|
|
533
|
+
`var ${fnName} = typeof ${fnName} !== "undefined" ? ${fnName} : function ${fnName}(v) {`,
|
|
534
|
+
);
|
|
535
|
+
lines.push(` if (v === null || v === undefined) return "null";`);
|
|
536
|
+
lines.push(` var parts = [];`);
|
|
537
|
+
|
|
538
|
+
for (const f of fields) {
|
|
539
|
+
const keyLiteral = JSON.stringify(`${JSON.stringify(f.jsonKey)}:`);
|
|
540
|
+
const encodeVal = this._genFieldEncoderExpr(f, addr);
|
|
541
|
+
const isStruct = unwrapNamed(f.type)?.kind === "struct";
|
|
542
|
+
const isMap = unwrapNamed(f.type)?.kind === "map";
|
|
543
|
+
|
|
544
|
+
this._emitFieldEncoderAccess(f, lines, (valName) => {
|
|
545
|
+
const encoded = encodeVal(valName);
|
|
546
|
+
if (f.omitempty) {
|
|
547
|
+
return `if (!__json_is_zero(${valName}, ${isStruct}, ${isMap})) parts.push(${keyLiteral} + ${encoded});`;
|
|
548
|
+
}
|
|
549
|
+
return `parts.push(${keyLiteral} + ${encoded});`;
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
lines.push(` return "{" + parts.join(",") + "}";`);
|
|
554
|
+
lines.push(`};`);
|
|
555
|
+
return lines.join("\n");
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
_emitFieldEncoderAccess(f, lines, onVal) {
|
|
559
|
+
const path = f.path ?? [{ name: f.goName, isPtr: false }];
|
|
560
|
+
if (f.isEmbedded && f.tagged) {
|
|
561
|
+
lines.push(
|
|
562
|
+
` { var val = (v.${f.goName} !== undefined ? v.${f.goName} : v); ${onVal("val")} }`,
|
|
563
|
+
);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (path.length === 1) {
|
|
567
|
+
lines.push(` { var val = v.${path[0].name}; ${onVal("val")} }`);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Promoted fields from embedded struct
|
|
572
|
+
const ptrSegs = path.slice(0, -1).filter((s) => s.isPtr);
|
|
573
|
+
const nilChecks = ptrSegs.map((s) => `v.${s.name} === null`);
|
|
574
|
+
|
|
575
|
+
let cur = "v";
|
|
576
|
+
const conds = [];
|
|
577
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
578
|
+
const seg = path[i];
|
|
579
|
+
conds.push(`${cur}.${seg.name} != null`);
|
|
580
|
+
cur = `${cur}.${seg.name}`;
|
|
581
|
+
}
|
|
582
|
+
const leaf = `${cur}.${path[path.length - 1].name}`;
|
|
583
|
+
|
|
584
|
+
if (nilChecks.length > 0) {
|
|
585
|
+
lines.push(` if (!(${nilChecks.join(" || ")})) {`);
|
|
586
|
+
lines.push(
|
|
587
|
+
` var val = (${conds.join(" && ")} ? ${leaf} : v.${f.goName});`,
|
|
588
|
+
);
|
|
589
|
+
lines.push(` if (val !== undefined) { ${onVal("val")} }`);
|
|
590
|
+
lines.push(` }`);
|
|
591
|
+
} else {
|
|
592
|
+
lines.push(
|
|
593
|
+
` { var val = (${conds.join(" && ")} ? ${leaf} : v.${f.goName}); if (val !== undefined) { ${onVal("val")} } }`,
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
|
|
598
|
+
_genFieldEncoderExpr(f, addr = false) {
|
|
599
|
+
const quotedKind = this._jsonQuotedKind(f);
|
|
600
|
+
if (quotedKind) return this._genQuotedFieldEncoderExpr(f, quotedKind);
|
|
601
|
+
if (isNumberType(f.type)) return (v) => `__json_enc_number(${v})`;
|
|
602
|
+
if (isRawMessageType(f.type)) {
|
|
603
|
+
return (v) => `__json_enc_raw(${v})`;
|
|
604
|
+
}
|
|
605
|
+
// Marshalers and pointers go through the shared encoders.
|
|
606
|
+
const special =
|
|
607
|
+
f.type?.kind === "pointer" ||
|
|
608
|
+
(f.type?.kind === "named" && this._jsonMarshalerInfo(f.type));
|
|
609
|
+
if (special) {
|
|
610
|
+
const enc = this._getJsonEncoder(f.type, addr);
|
|
611
|
+
return (v) => `${enc}(${v})`;
|
|
612
|
+
}
|
|
613
|
+
if (isByteSliceType(f.type)) {
|
|
614
|
+
return (v) => `__json_b64(${v})`;
|
|
615
|
+
}
|
|
616
|
+
const base = unwrapNamed(f.type);
|
|
617
|
+
if (base?.kind === "basic") {
|
|
618
|
+
return this._genBasicFieldExpr(base.name);
|
|
619
|
+
}
|
|
620
|
+
if (base?.kind === "struct") {
|
|
621
|
+
const enc = this._getJsonEncoder(f.type, addr);
|
|
622
|
+
return (v) => `${enc}(${v})`;
|
|
623
|
+
}
|
|
624
|
+
if (base?.kind === "slice" || base?.kind === "array") {
|
|
625
|
+
const enc = this._getJsonEncoder(base.elem, true);
|
|
626
|
+
return (v) => `__json_enc_slice(${v}, ${enc})`;
|
|
627
|
+
}
|
|
628
|
+
if (base?.kind === "map") {
|
|
629
|
+
const enc = this._getJsonEncoder(base.value ?? base.elem, false);
|
|
630
|
+
return (v) => `__json_enc_map(${v}, ${enc})`;
|
|
631
|
+
}
|
|
632
|
+
return (v) => `__json_enc_any(${v})`;
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
_genBasicFieldExpr(name) {
|
|
636
|
+
switch (name) {
|
|
637
|
+
case "string":
|
|
638
|
+
return (v) => `__json_esc(${v})`;
|
|
639
|
+
case "bool":
|
|
640
|
+
return (v) => `(${v} ? "true" : "false")`;
|
|
641
|
+
case "int":
|
|
642
|
+
case "int8":
|
|
643
|
+
case "int16":
|
|
644
|
+
case "int32":
|
|
645
|
+
case "uint":
|
|
646
|
+
case "uint8":
|
|
647
|
+
case "uint16":
|
|
648
|
+
case "uint32":
|
|
649
|
+
case "byte":
|
|
650
|
+
case "rune":
|
|
651
|
+
return (v) => `String(Math.trunc(${v}))`;
|
|
652
|
+
case "int64":
|
|
653
|
+
case "uint64":
|
|
654
|
+
return (v) =>
|
|
655
|
+
`(typeof ${v} === "bigint" ? ${v}.toString() : String(${v}))`;
|
|
656
|
+
case "float32":
|
|
657
|
+
return (v) => `__json_float32(${v})`;
|
|
658
|
+
case "float64":
|
|
659
|
+
return (v) => `__json_float64(${v})`;
|
|
660
|
+
default:
|
|
661
|
+
return (v) => `__json_enc_any(${v})`;
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
|
|
665
|
+
_getJsonDecoder(targetType) {
|
|
666
|
+
if (!targetType) return "__json_dec_any_target";
|
|
667
|
+
const baseType =
|
|
668
|
+
targetType.kind === "pointer" ? targetType.base : targetType;
|
|
669
|
+
if (!baseType) return "__json_dec_any_target";
|
|
670
|
+
|
|
671
|
+
if (isNumberType(baseType)) {
|
|
672
|
+
return "__json_dec_number";
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (this._jsonUnmarshalerInfo(baseType)) {
|
|
676
|
+
return this._getUnmarshalerDecoder(baseType);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (isRawMessageType(baseType)) {
|
|
680
|
+
const fnName = "__json_dec_RawMessage";
|
|
681
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
682
|
+
this._jsonCodecs.set(
|
|
683
|
+
fnName,
|
|
684
|
+
declareCodec(
|
|
685
|
+
fnName,
|
|
686
|
+
"parsed, target, box, rawData",
|
|
687
|
+
` var u8 = rawData instanceof Uint8Array ? rawData : new Uint8Array(rawData);\n u8.__raw = true;\n if (box) box.value = u8;\n return null;`,
|
|
688
|
+
),
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
return fnName;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (isByteSliceType(baseType)) {
|
|
695
|
+
const fnName = "__json_dec_byte_slice";
|
|
696
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
697
|
+
this._jsonCodecs.set(
|
|
698
|
+
fnName,
|
|
699
|
+
declareCodec(
|
|
700
|
+
fnName,
|
|
701
|
+
"parsed, target, box",
|
|
702
|
+
` if (parsed === null) { if (box) box.value = null; return null; }\n var u8 = __json_bytes_decode(parsed);\n if (u8 && u8._msg) return u8;\n if (box) box.value = u8;\n return null;`,
|
|
703
|
+
),
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
return fnName;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const base = unwrapNamed(baseType);
|
|
710
|
+
if (base?.kind === "struct") {
|
|
711
|
+
const typeName = safeIdent(
|
|
712
|
+
(baseType.name ?? "AnonStruct").split(".").pop(),
|
|
713
|
+
);
|
|
714
|
+
const fnName = `__json_dec_${typeName}`;
|
|
715
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
716
|
+
this._jsonCodecs.set(fnName, "");
|
|
717
|
+
const code = this._buildStructDecoder(baseType, fnName);
|
|
718
|
+
this._jsonCodecs.set(fnName, code);
|
|
719
|
+
}
|
|
720
|
+
return fnName;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const composite = COMPOSITE_DECODERS[base?.kind];
|
|
724
|
+
if (composite) return this[composite](base);
|
|
725
|
+
// Top-level scalar targets (`json.Unmarshal(b, &n)`) are type-checked too.
|
|
726
|
+
if (base?.kind === "basic" && SCALAR_KINDS.has(base.name))
|
|
727
|
+
return this._getScalarDecoder(baseType, base);
|
|
728
|
+
|
|
729
|
+
return "__json_dec_any_target";
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
_getSliceDecoder(base) {
|
|
733
|
+
const elemTypeName = typeCodecName(base.elem, "elem");
|
|
734
|
+
const fnName = `__json_dec_slice_${safeIdent(elemTypeName.split(".").pop())}`;
|
|
735
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
736
|
+
const elemDec = this._genElemDecoder(base.elem);
|
|
737
|
+
const typedCtor = typedArrayConstructorForElem(base.elem);
|
|
738
|
+
const initOut = typedCtor ? `new ${typedCtor}(parsed.length)` : `[]`;
|
|
739
|
+
const setElem = typedCtor ? `out[i] = item;` : `out.push(item);`;
|
|
740
|
+
const display = JSON.stringify(`[]${this._jsonDisplayType(base.elem)}`);
|
|
741
|
+
this._jsonCodecs.set(
|
|
742
|
+
fnName,
|
|
743
|
+
declareCodec(
|
|
744
|
+
fnName,
|
|
745
|
+
"parsed, target, box",
|
|
746
|
+
` if (parsed === null) { if (box) box.value = null; return null; }\n if (!Array.isArray(parsed)) return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${display}));\n var out = ${initOut};\n for (var i = 0; i < parsed.length; i++) {\n var item = ${elemDec("parsed[i]", "__json_raw_child(parsed, i)")};\n if (item && item._msg) return item;\n ${setElem}\n }\n if (box) box.value = out;\n return null;`,
|
|
747
|
+
),
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
return fnName;
|
|
751
|
+
},
|
|
752
|
+
|
|
753
|
+
// Arrays decode into a fresh array of the fixed length: surplus JSON elements
|
|
754
|
+
// are dropped and missing ones are zero. JSON null leaves the array alone.
|
|
755
|
+
_getArrayDecoder(base) {
|
|
756
|
+
const elemTypeName = typeCodecName(base.elem, "elem");
|
|
757
|
+
const fnName = `__json_dec_array${base.size}_${safeIdent(elemTypeName.split(".").pop())}`;
|
|
758
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
759
|
+
this._jsonCodecs.set(
|
|
760
|
+
fnName,
|
|
761
|
+
declareCodec(
|
|
762
|
+
fnName,
|
|
763
|
+
"parsed, target, box",
|
|
764
|
+
this._arrayDecoderBody(base),
|
|
765
|
+
),
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
return fnName;
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
_arrayDecoderBody(base) {
|
|
772
|
+
const elemDec = this._genElemDecoder(base.elem);
|
|
773
|
+
const typedCtor = typedArrayConstructorForElem(base.elem);
|
|
774
|
+
const display = JSON.stringify(this._jsonDisplayType(base));
|
|
775
|
+
const init = `new ${typedCtor ?? "Array"}(${base.size})`;
|
|
776
|
+
// Typed arrays start zeroed; plain arrays need their zero values filled in.
|
|
777
|
+
const zero = this.zeroValueForType(base.elem);
|
|
778
|
+
const fill = typedCtor
|
|
779
|
+
? ""
|
|
780
|
+
: `\n for (var z = parsed.length; z < ${base.size}; z++) out[z] = ${zero};`;
|
|
781
|
+
return ` if (parsed === null) return null;\n if (!Array.isArray(parsed)) return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${display}));\n var out = ${init};\n for (var i = 0; i < parsed.length && i < ${base.size}; i++) {\n var item = ${elemDec("parsed[i]", "__json_raw_child(parsed, i)")};\n if (item && item._msg) return item;\n out[i] = item;\n }${fill}\n if (box) box.value = out;\n return null;`;
|
|
782
|
+
},
|
|
783
|
+
|
|
784
|
+
_getMapDecoder(base) {
|
|
785
|
+
const valType = base.value ?? base.elem;
|
|
786
|
+
const valTypeName = typeCodecName(valType, "val");
|
|
787
|
+
const fnName = `__json_dec_map_${safeIdent(valTypeName.split(".").pop())}`;
|
|
788
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
789
|
+
const valDec = this._genElemDecoder(valType);
|
|
790
|
+
const display = JSON.stringify(
|
|
791
|
+
`map[string]${this._jsonDisplayType(valType)}`,
|
|
792
|
+
);
|
|
793
|
+
this._jsonCodecs.set(
|
|
794
|
+
fnName,
|
|
795
|
+
declareCodec(
|
|
796
|
+
fnName,
|
|
797
|
+
"parsed, target, box",
|
|
798
|
+
` if (parsed === null) { if (box) box.value = null; return null; }\n if (typeof parsed !== "object" || Array.isArray(parsed)) return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${display}));\n var out = __json_newmap();\n for (var k in parsed) {\n if (Object.prototype.hasOwnProperty.call(parsed, k)) {\n var item = ${valDec("parsed[k]", "__json_raw_child(parsed, k)")};\n if (item && item._msg) return item;\n out[k] = item;\n }\n }\n if (box) box.value = out;\n return null;`,
|
|
799
|
+
),
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return fnName;
|
|
803
|
+
},
|
|
804
|
+
|
|
805
|
+
_getScalarDecoder(baseType, base) {
|
|
806
|
+
const display = this._jsonDisplayType(baseType);
|
|
807
|
+
const fnName = `__json_dec_scalar_${safeIdent(display)}`;
|
|
808
|
+
if (!this._jsonCodecs.has(fnName)) {
|
|
809
|
+
this._jsonCodecs.set(
|
|
810
|
+
fnName,
|
|
811
|
+
declareCodec(
|
|
812
|
+
fnName,
|
|
813
|
+
"parsed, target, box, rawData, rawStr",
|
|
814
|
+
` if (parsed === null) return null;\n var dv = ${this._jsonScalarDecodeExpr(base.name, display, "parsed", "rawStr")};\n if (dv && dv._msg) return dv;\n if (box) box.value = dv;\n return null;`,
|
|
815
|
+
),
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
return fnName;
|
|
819
|
+
},
|
|
820
|
+
|
|
821
|
+
// Go-style display of a type in decode error messages (reflect's Type.String()).
|
|
822
|
+
_jsonDisplayType(t) {
|
|
823
|
+
const show = (x) => this._jsonDisplayType(x);
|
|
824
|
+
const byKind = {
|
|
825
|
+
pointer: () => `*${show(t.base)}`,
|
|
826
|
+
named: () => this._jsonTypeName(t),
|
|
827
|
+
basic: () => BASIC_DISPLAY[t.name] ?? t.name,
|
|
828
|
+
slice: () => `[]${show(t.elem)}`,
|
|
829
|
+
array: () => `[${t.size}]${show(t.elem)}`,
|
|
830
|
+
map: () => `map[${show(t.key)}]${show(t.value ?? t.elem)}`,
|
|
831
|
+
};
|
|
832
|
+
return byKind[t?.kind]?.() ?? "interface {}";
|
|
833
|
+
},
|
|
834
|
+
|
|
835
|
+
// Decodes one parsed JSON scalar into `kind`; yields the value or an error (`_msg`).
|
|
836
|
+
_jsonScalarDecodeExpr(kind, display, v, rawExpr) {
|
|
837
|
+
const d = JSON.stringify(display);
|
|
838
|
+
const raw = rawExpr ?? "undefined";
|
|
839
|
+
switch (kind) {
|
|
840
|
+
case "string":
|
|
841
|
+
return `(typeof ${v} === "string" && !${v}.endsWith("__bigint") ? ${v} : __json_err("cannot unmarshal " + __json_type(${v}) + " into " + __json_ctx(${d})))`;
|
|
842
|
+
case "bool":
|
|
843
|
+
return `(typeof ${v} === "boolean" ? ${v} : __json_err("cannot unmarshal " + __json_type(${v}) + " into " + __json_ctx(${d})))`;
|
|
844
|
+
case "float32":
|
|
845
|
+
return `__json_float_decode(${v}, 32, ${d}, ${raw})`;
|
|
846
|
+
case "float64":
|
|
847
|
+
return `__json_float_decode(${v}, 64, ${d}, ${raw})`;
|
|
848
|
+
default:
|
|
849
|
+
return `__json_int_decode(${v}, ${JSON.stringify(kind)}, ${d}, ${raw})`;
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
|
|
853
|
+
_genElemDecoder(elemType) {
|
|
854
|
+
if (!elemType) return (v) => `__json_dec_any(${v})`;
|
|
855
|
+
if (isNumberType(elemType))
|
|
856
|
+
return (v, rawExpr) =>
|
|
857
|
+
`__json_number_decode(${v}, "json.Number", ${rawExpr})`;
|
|
858
|
+
const unmarshaler = this._unmarshalerElemDecoder(elemType);
|
|
859
|
+
if (unmarshaler) return unmarshaler;
|
|
860
|
+
if (isRawMessageType(elemType)) {
|
|
861
|
+
return (_v, rawExpr) => `__json_raw_bytes(${rawExpr})`;
|
|
862
|
+
}
|
|
863
|
+
if (isByteSliceType(elemType)) {
|
|
864
|
+
return (v) => `(${v} === null ? null : __json_bytes_decode(${v}))`;
|
|
865
|
+
}
|
|
866
|
+
const base = unwrapNamed(elemType);
|
|
867
|
+
if (base?.kind === "basic" && SCALAR_KINDS.has(base.name)) {
|
|
868
|
+
const display = this._jsonDisplayType(elemType);
|
|
869
|
+
return (v, rawExpr) =>
|
|
870
|
+
this._jsonScalarDecodeExpr(base.name, display, v, rawExpr);
|
|
871
|
+
}
|
|
872
|
+
const ptrElem = this._pointerElemDecoder(elemType);
|
|
873
|
+
if (ptrElem) return ptrElem;
|
|
874
|
+
if (base?.kind === "struct") {
|
|
875
|
+
const dec = this._getJsonDecoder(elemType);
|
|
876
|
+
const structName = elemType.name?.split(".").pop() ?? "Object";
|
|
877
|
+
return (v) =>
|
|
878
|
+
`(() => { var inst = new ${structName}(); var err = ${dec}(${v}, inst); return err || inst; })()`;
|
|
879
|
+
}
|
|
880
|
+
return this._compositeElemDecoder(elemType, base);
|
|
881
|
+
},
|
|
882
|
+
|
|
883
|
+
// `*Struct` and `*scalar` elements; null stays nil.
|
|
884
|
+
_pointerElemDecoder(elemType) {
|
|
885
|
+
if (elemType?.kind !== "pointer" || !elemType.base) return null;
|
|
886
|
+
const innerBase = unwrapNamed(elemType.base);
|
|
887
|
+
if (innerBase?.kind === "struct") {
|
|
888
|
+
const dec = this._getJsonDecoder(elemType.base);
|
|
889
|
+
const structName = elemType.base.name?.split(".").pop() ?? "Object";
|
|
890
|
+
return (v) =>
|
|
891
|
+
`(${v} === null ? null : (() => { var inst = new ${structName}(); var err = ${dec}(${v}, inst); return err || inst; })())`;
|
|
892
|
+
}
|
|
893
|
+
if (innerBase?.kind !== "basic") return null;
|
|
894
|
+
const basicDec = this._genElemDecoder(elemType.base);
|
|
895
|
+
return (v, rawExpr) =>
|
|
896
|
+
`(${v} === null ? null : (() => { var item = ${basicDec(v, rawExpr)}; return item && item._msg ? item : { value: item, __p: true }; })())`;
|
|
897
|
+
},
|
|
898
|
+
|
|
899
|
+
// Slices, arrays and maps decode through their own codec into a fresh box.
|
|
900
|
+
_compositeElemDecoder(elemType, base) {
|
|
901
|
+
if (!COMPOSITE_KINDS.has(base?.kind)) return (v) => `__json_dec_any(${v})`;
|
|
902
|
+
const dec = this._getJsonDecoder(elemType);
|
|
903
|
+
return (v) =>
|
|
904
|
+
`(() => { var b = { value: null }; var err = ${dec}(${v}, null, b); return err || b.value; })()`;
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
_buildStructDecoder(structType, fnName) {
|
|
908
|
+
const underlying = unwrapNamed(structType);
|
|
909
|
+
const fields = this.checker?.resolveJsonFields(underlying) ?? [];
|
|
910
|
+
const structName = structType.name?.split(".").pop() ?? "T";
|
|
911
|
+
const lines = [];
|
|
912
|
+
lines.push(
|
|
913
|
+
`var ${fnName} = typeof ${fnName} !== "undefined" ? ${fnName} : function ${fnName}(parsed, target, box) {`,
|
|
914
|
+
);
|
|
915
|
+
lines.push(
|
|
916
|
+
` if (parsed === null) { if (box) box.value = null; return null; }`,
|
|
917
|
+
);
|
|
918
|
+
lines.push(` if (typeof parsed !== "object" || Array.isArray(parsed)) {`);
|
|
919
|
+
lines.push(
|
|
920
|
+
` return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${JSON.stringify(this._jsonDisplayType(structType))}));`,
|
|
921
|
+
);
|
|
922
|
+
lines.push(` }`);
|
|
923
|
+
const knownKeys = fields.flatMap((f) => [f.jsonKey, f.goName]);
|
|
924
|
+
lines.push(
|
|
925
|
+
` var disallowErr = __json_check_unknown(parsed, ${JSON.stringify(knownKeys)});`,
|
|
926
|
+
);
|
|
927
|
+
lines.push(` if (disallowErr) return disallowErr;`);
|
|
928
|
+
// Go reports errors as "Go struct field <innermost struct>.<json key path>".
|
|
929
|
+
lines.push(` __json_structs.push(${JSON.stringify(structName)});`);
|
|
930
|
+
lines.push(` try {`);
|
|
931
|
+
|
|
932
|
+
for (const f of fields) {
|
|
933
|
+
const key = JSON.stringify(f.jsonKey);
|
|
934
|
+
const goName = JSON.stringify(f.goName);
|
|
935
|
+
lines.push(
|
|
936
|
+
` { var fieldKey = __json_match_field(parsed, ${key}, ${goName}); if (fieldKey !== null) {`,
|
|
937
|
+
);
|
|
938
|
+
lines.push(` __json_path.push(${key});`);
|
|
939
|
+
lines.push(` try {`);
|
|
940
|
+
lines.push(` var val = parsed[fieldKey];`);
|
|
941
|
+
lines.push(` var raw = __json_raw_child(parsed, fieldKey);`);
|
|
942
|
+
lines.push(
|
|
943
|
+
` __json_current_offset = __json_raw_end(parsed, fieldKey) || 0;`,
|
|
944
|
+
);
|
|
945
|
+
const quotedKind = this._jsonQuotedKind(f);
|
|
946
|
+
if (quotedKind) {
|
|
947
|
+
const valueType = f.type?.kind === "pointer" ? f.type.base : f.type;
|
|
948
|
+
const typeStr = this._jsonDisplayType(valueType);
|
|
949
|
+
lines.push(
|
|
950
|
+
` var q = __json_unquote(val, ${JSON.stringify(quotedKind)}, ${JSON.stringify(typeStr)}); if (q && q._msg) return q; val = q.v; raw = q.lit;`,
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
lines.push(` if (val !== null) {`);
|
|
954
|
+
this._emitFieldDecoder(f, structName, lines, "raw");
|
|
955
|
+
lines.push(` } else {`);
|
|
956
|
+
this._emitFieldNull(f, lines, "raw");
|
|
957
|
+
lines.push(` }`);
|
|
958
|
+
lines.push(` } finally { __json_path.pop(); }`);
|
|
959
|
+
lines.push(` }`);
|
|
960
|
+
lines.push(` }`);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
lines.push(` } finally { __json_structs.pop(); }`);
|
|
964
|
+
lines.push(` return null;`);
|
|
965
|
+
lines.push(`};`);
|
|
966
|
+
return lines.join("\n");
|
|
967
|
+
},
|
|
968
|
+
|
|
969
|
+
_emitUnmarshalerField(f, lines, rawExpr) {
|
|
970
|
+
const isPtr = f.type?.kind === "pointer";
|
|
971
|
+
const valueType = isPtr ? f.type.base : f.type;
|
|
972
|
+
const info = this._jsonUnmarshalerInfo(valueType);
|
|
973
|
+
const bytes = `__json_raw_bytes(${rawExpr})`;
|
|
974
|
+
if (info.isStruct) {
|
|
975
|
+
lines.push(` var sub = target.${f.goName};`);
|
|
976
|
+
lines.push(
|
|
977
|
+
` if (!sub) { sub = new ${info.cls}(); target.${f.goName} = sub; }`,
|
|
978
|
+
);
|
|
979
|
+
lines.push(
|
|
980
|
+
` var uerr = ${this._jsonUnmarshalCall(info, "sub", bytes)};`,
|
|
981
|
+
);
|
|
982
|
+
} else if (isPtr) {
|
|
983
|
+
lines.push(` var sub = target.${f.goName};`);
|
|
984
|
+
lines.push(
|
|
985
|
+
` if (!sub) { sub = { value: ${this.zeroValueForType(valueType)} }; target.${f.goName} = sub; }`,
|
|
986
|
+
);
|
|
987
|
+
lines.push(
|
|
988
|
+
` var uerr = ${this._jsonUnmarshalCall(info, "sub", bytes)};`,
|
|
989
|
+
);
|
|
990
|
+
} else {
|
|
991
|
+
lines.push(
|
|
992
|
+
` var addr = { get value() { return target.${f.goName}; }, set value(__v) { target.${f.goName} = __v; } };`,
|
|
993
|
+
);
|
|
994
|
+
lines.push(
|
|
995
|
+
` var uerr = ${this._jsonUnmarshalCall(info, "addr", bytes)};`,
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
lines.push(` if (uerr) return __json_user_err(uerr);`);
|
|
999
|
+
if (f.isEmbedded && info.isStruct) {
|
|
1000
|
+
lines.push(` Object.assign(target, sub);`);
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
|
|
1004
|
+
_emitNumberFieldDecoder(f, lines, rawExpr) {
|
|
1005
|
+
lines.push(
|
|
1006
|
+
` var nv = __json_number_decode(val, "json.Number", ${rawExpr}); if (nv && nv._msg) return nv;`,
|
|
1007
|
+
);
|
|
1008
|
+
const val = f.type?.kind === "pointer" ? "{ value: nv }" : "nv";
|
|
1009
|
+
this._emitAssign(f, val, lines);
|
|
1010
|
+
},
|
|
1011
|
+
|
|
1012
|
+
_emitFieldDecoder(f, structName, lines, rawExpr) {
|
|
1013
|
+
const valueType = f.type?.kind === "pointer" ? f.type.base : f.type;
|
|
1014
|
+
if (this._jsonUnmarshalerInfo(valueType)) {
|
|
1015
|
+
this._emitUnmarshalerField(f, lines, rawExpr);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (isNumberType(valueType)) {
|
|
1019
|
+
this._emitNumberFieldDecoder(f, lines, rawExpr);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (isRawMessageType(f.type)) {
|
|
1023
|
+
lines.push(
|
|
1024
|
+
` var rawBytes = __json_raw_bytes(${rawExpr}); rawBytes.__raw = true;`,
|
|
1025
|
+
);
|
|
1026
|
+
this._emitAssign(f, "rawBytes", lines);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (isByteSliceType(f.type)) {
|
|
1030
|
+
lines.push(` var u8v = __json_bytes_decode(val);`);
|
|
1031
|
+
lines.push(` if (u8v && u8v._msg) return u8v;`);
|
|
1032
|
+
this._emitAssign(f, "u8v", lines);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const base = unwrapNamed(f.type);
|
|
1037
|
+
if (base?.kind === "basic") {
|
|
1038
|
+
this._emitBasicFieldDecoder(f, base.name, structName, lines);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (f.type?.kind === "pointer" && f.type.base) {
|
|
1043
|
+
const innerBase = unwrapNamed(f.type.base);
|
|
1044
|
+
if (innerBase?.kind === "basic") {
|
|
1045
|
+
this._emitPointerBasicFieldDecoder(
|
|
1046
|
+
f,
|
|
1047
|
+
innerBase.name,
|
|
1048
|
+
structName,
|
|
1049
|
+
lines,
|
|
1050
|
+
);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (innerBase?.kind === "struct") {
|
|
1054
|
+
const innerName = f.type.base.name?.split(".").pop() ?? "Object";
|
|
1055
|
+
const dec = this._getJsonDecoder(f.type.base);
|
|
1056
|
+
lines.push(` var sub = target.${f.goName};`);
|
|
1057
|
+
lines.push(
|
|
1058
|
+
` if (!sub) { sub = new ${innerName}(); target.${f.goName} = sub; }`,
|
|
1059
|
+
);
|
|
1060
|
+
lines.push(` var err = ${dec}(val, sub);`);
|
|
1061
|
+
lines.push(` if (err) return err;`);
|
|
1062
|
+
if (f.isEmbedded) {
|
|
1063
|
+
lines.push(` Object.assign(target, sub);`);
|
|
1064
|
+
}
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (base?.kind === "struct") {
|
|
1070
|
+
const innerName = f.type.name?.split(".").pop() ?? "Object";
|
|
1071
|
+
const dec = this._getJsonDecoder(f.type);
|
|
1072
|
+
lines.push(` var sub = target.${f.goName};`);
|
|
1073
|
+
lines.push(
|
|
1074
|
+
` if (!sub) { sub = new ${innerName}(); target.${f.goName} = sub; }`,
|
|
1075
|
+
);
|
|
1076
|
+
lines.push(` var err = ${dec}(val, sub);`);
|
|
1077
|
+
lines.push(` if (err) return err;`);
|
|
1078
|
+
if (f.isEmbedded) {
|
|
1079
|
+
lines.push(` Object.assign(target, sub);`);
|
|
1080
|
+
}
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (COMPOSITE_KINDS.has(base?.kind)) {
|
|
1085
|
+
const dec = this._getJsonDecoder(f.type);
|
|
1086
|
+
lines.push(` var box = { value: null };`);
|
|
1087
|
+
lines.push(` var err = ${dec}(val, null, box);`);
|
|
1088
|
+
lines.push(` if (err) return err;`);
|
|
1089
|
+
this._emitAssign(f, "box.value", lines);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
this._emitAssign(f, "__json_dec_any(val)", lines);
|
|
1094
|
+
},
|
|
1095
|
+
|
|
1096
|
+
// `*T` scalar fields: Go reports the element type in errors.
|
|
1097
|
+
_emitPointerBasicFieldDecoder(f, basicName, _structName, lines) {
|
|
1098
|
+
if (!SCALAR_KINDS.has(basicName)) {
|
|
1099
|
+
this._emitAssign(f, "__json_dec_any(val)", lines);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const display = this._jsonDisplayType(f.type.base);
|
|
1103
|
+
lines.push(
|
|
1104
|
+
` var dv = ${this._jsonScalarDecodeExpr(basicName, display, "val", "raw")};`,
|
|
1105
|
+
);
|
|
1106
|
+
lines.push(` if (dv && dv._msg) return dv;`);
|
|
1107
|
+
this._emitAssign(f, "{ value: dv, __p: true }", lines);
|
|
1108
|
+
},
|
|
1109
|
+
|
|
1110
|
+
_emitBasicFieldDecoder(f, basicName, _structName, lines) {
|
|
1111
|
+
if (!SCALAR_KINDS.has(basicName)) {
|
|
1112
|
+
this._emitAssign(f, "__json_dec_any(val)", lines);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
const display = this._jsonDisplayType(f.type);
|
|
1116
|
+
lines.push(
|
|
1117
|
+
` var dv = ${this._jsonScalarDecodeExpr(basicName, display, "val", "raw")};`,
|
|
1118
|
+
);
|
|
1119
|
+
lines.push(` if (dv && dv._msg) return dv;`);
|
|
1120
|
+
this._emitAssign(f, "dv", lines);
|
|
1121
|
+
},
|
|
1122
|
+
|
|
1123
|
+
_emitAssign(f, expr, lines) {
|
|
1124
|
+
const path = f.path ?? [{ name: f.goName, isPtr: false }];
|
|
1125
|
+
if (path.length === 1) {
|
|
1126
|
+
lines.push(` target.${f.goName} = ${expr};`);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1131
|
+
const seg = path[i];
|
|
1132
|
+
const curPath = `target.${path
|
|
1133
|
+
.slice(0, i + 1)
|
|
1134
|
+
.map((p) => p.name)
|
|
1135
|
+
.join(".")}`;
|
|
1136
|
+
const ctor = seg.typeName || seg.name;
|
|
1137
|
+
const initVal = ctor && ctor !== "Object" ? `new ${ctor}()` : `{}`;
|
|
1138
|
+
lines.push(` if (${curPath} == null) ${curPath} = ${initVal};`);
|
|
1139
|
+
}
|
|
1140
|
+
lines.push(` target.${path.map((p) => p.name).join(".")} = ${expr};`);
|
|
1141
|
+
lines.push(` target.${f.goName} = ${expr};`);
|
|
1142
|
+
},
|
|
1143
|
+
|
|
1144
|
+
_emitFieldNull(f, lines, rawExpr) {
|
|
1145
|
+
// Go hands JSON null to UnmarshalJSON for non-pointer fields; nil pointers stay nil.
|
|
1146
|
+
if (f.type?.kind !== "pointer" && this._jsonUnmarshalerInfo(f.type)) {
|
|
1147
|
+
this._emitUnmarshalerField(f, lines, `"null"`);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (isRawMessageType(f.type)) {
|
|
1151
|
+
lines.push(
|
|
1152
|
+
` var rawNull = __json_raw_bytes(${rawExpr}); rawNull.__raw = true;`,
|
|
1153
|
+
);
|
|
1154
|
+
this._emitAssign(f, "rawNull", lines);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
if (!isJsonNullAssignable(f.type)) return;
|
|
1158
|
+
const path = f.path ?? [{ name: f.goName, isPtr: false }];
|
|
1159
|
+
if (path.length === 1) {
|
|
1160
|
+
lines.push(` target.${f.goName} = null;`);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
lines.push(
|
|
1164
|
+
` target.${path.map((p) => p.name).join(".")} = null; target.${f.goName} = null;`,
|
|
1165
|
+
);
|
|
1166
|
+
},
|
|
1167
|
+
};
|