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
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
// Type resolution methods — resolveTypeNode, resolveType, fieldType, generics.
|
|
2
2
|
// Installed as a mixin on TypeChecker.prototype.
|
|
3
3
|
|
|
4
|
+
import { parseJsonTag } from "../parser/struct-tag.js";
|
|
5
|
+
import { foldConstExpr } from "./const-fold.js";
|
|
6
|
+
import { EXCLUDED_STDLIB_MEMBERS } from "./stdlib/excluded.js";
|
|
4
7
|
import {
|
|
5
8
|
ANY,
|
|
6
9
|
BASIC_TYPES,
|
|
7
10
|
COMPARABLE,
|
|
8
11
|
defaultType,
|
|
12
|
+
displayTypeName,
|
|
9
13
|
isAny,
|
|
10
14
|
isTypedArraySlice,
|
|
11
15
|
Scope,
|
|
@@ -19,6 +23,73 @@ import {
|
|
|
19
23
|
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
20
24
|
|
|
21
25
|
// Dispatch table for substituteType — keyed by type.kind.
|
|
26
|
+
// Nesting depth of in-progress instances of one generic type before the
|
|
27
|
+
// instantiation is reported as a cycle.
|
|
28
|
+
const MAX_INSTANTIATION_DEPTH = 32;
|
|
29
|
+
|
|
30
|
+
// Two imported packages may both declare `Pt`, so the cache key of an
|
|
31
|
+
// instance tells declarations apart by identity instead of by display name.
|
|
32
|
+
const declIds = new WeakMap();
|
|
33
|
+
let nextDeclId = 0;
|
|
34
|
+
function declId(t) {
|
|
35
|
+
if (!declIds.has(t)) declIds.set(t, ++nextDeclId);
|
|
36
|
+
return declIds.get(t);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Go infers T from the "largest" untyped constant kind (int < rune < float <
|
|
40
|
+
// complex), and a typed argument beats any untyped one.
|
|
41
|
+
const UNTYPED_RANK = { int: 0, int32: 1, float64: 2, complex128: 3 };
|
|
42
|
+
function untypedWins(prev, next) {
|
|
43
|
+
if (prev.kind !== "untyped") return false;
|
|
44
|
+
if (next.kind !== "untyped") return true;
|
|
45
|
+
return (UNTYPED_RANK[next.base] ?? 0) > (UNTYPED_RANK[prev.base] ?? 0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Node count of a type, so a request that keeps growing can be told from one
|
|
49
|
+
// that merely repeats.
|
|
50
|
+
function typeSize(t) {
|
|
51
|
+
switch (t?.kind) {
|
|
52
|
+
case "named":
|
|
53
|
+
return 1 + (t.typeArgs ?? []).reduce((n, a) => n + typeSize(a), 0);
|
|
54
|
+
case "pointer":
|
|
55
|
+
return 1 + typeSize(t.base);
|
|
56
|
+
case "slice":
|
|
57
|
+
case "array":
|
|
58
|
+
return 1 + typeSize(t.elem);
|
|
59
|
+
case "map":
|
|
60
|
+
return 1 + typeSize(t.key) + typeSize(t.value);
|
|
61
|
+
case "func":
|
|
62
|
+
return (
|
|
63
|
+
1 +
|
|
64
|
+
[...(t.params ?? []), ...(t.returns ?? [])].reduce(
|
|
65
|
+
(n, a) => n + typeSize(a),
|
|
66
|
+
0,
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
default:
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function instanceKey(t) {
|
|
75
|
+
switch (t?.kind) {
|
|
76
|
+
case "named":
|
|
77
|
+
return t.typeArgs?.length
|
|
78
|
+
? `${typeStr(t)}`
|
|
79
|
+
: `${typeStr(t)}#${declId(t)}`;
|
|
80
|
+
case "pointer":
|
|
81
|
+
return `*${instanceKey(t.base)}`;
|
|
82
|
+
case "slice":
|
|
83
|
+
return `[]${instanceKey(t.elem)}`;
|
|
84
|
+
case "array":
|
|
85
|
+
return `[${t.size ?? "..."}]${instanceKey(t.elem)}`;
|
|
86
|
+
case "map":
|
|
87
|
+
return `map[${instanceKey(t.key)}]${instanceKey(t.value)}`;
|
|
88
|
+
default:
|
|
89
|
+
return typeStr(t);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
22
93
|
const SUBSTITUTE_DISPATCH = {
|
|
23
94
|
basic: (_s, type) => type,
|
|
24
95
|
untyped: (_s, type) => type,
|
|
@@ -62,6 +133,7 @@ const SUBSTITUTE_DISPATCH = {
|
|
|
62
133
|
fields,
|
|
63
134
|
methods,
|
|
64
135
|
_embeds: type._embeds,
|
|
136
|
+
_structFields: type._structFields,
|
|
65
137
|
};
|
|
66
138
|
},
|
|
67
139
|
interface: (s, type, map) => {
|
|
@@ -69,11 +141,24 @@ const SUBSTITUTE_DISPATCH = {
|
|
|
69
141
|
for (const [k, v] of type.methods) methods.set(k, s.substituteType(v, map));
|
|
70
142
|
return { kind: "interface", name: type.name, methods };
|
|
71
143
|
},
|
|
72
|
-
named: (s, type, map) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
underlying
|
|
76
|
-
|
|
144
|
+
named: (s, type, map) => {
|
|
145
|
+
// An instance of a generic type: re-instantiate with the substituted
|
|
146
|
+
// arguments (through the instance cache) rather than copying its
|
|
147
|
+
// possibly cyclic underlying type.
|
|
148
|
+
if (type._genericOf)
|
|
149
|
+
return s.instantiateGenericWithArgs(
|
|
150
|
+
type._genericOf,
|
|
151
|
+
type.typeArgs.map((t) => s.substituteType(t, map)),
|
|
152
|
+
);
|
|
153
|
+
return {
|
|
154
|
+
kind: "named",
|
|
155
|
+
name: type.name,
|
|
156
|
+
...(type.typeArgs && {
|
|
157
|
+
typeArgs: type.typeArgs.map((t) => s.substituteType(t, map)),
|
|
158
|
+
}),
|
|
159
|
+
underlying: s.substituteType(type.underlying, map),
|
|
160
|
+
};
|
|
161
|
+
},
|
|
77
162
|
};
|
|
78
163
|
|
|
79
164
|
/** @type {ThisType<TypeChecker>} */
|
|
@@ -87,40 +172,27 @@ export const resolveMethods = {
|
|
|
87
172
|
if (scope) {
|
|
88
173
|
const fromScope = scope.lookup(node.name);
|
|
89
174
|
if (fromScope?.kind === "typeParam") return fromScope;
|
|
175
|
+
if (fromScope?.kind === "aliasArg") return fromScope.type;
|
|
90
176
|
}
|
|
91
177
|
const named = this.types.get(node.name);
|
|
92
178
|
if (named) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
179
|
+
const wasmErr = this._checkWasmStdlibTypeMember(node);
|
|
180
|
+
if (wasmErr) return wasmErr;
|
|
181
|
+
this._useQualifiedTypeName(node, named);
|
|
96
182
|
return named;
|
|
97
183
|
}
|
|
184
|
+
if (EXCLUDED_STDLIB_MEMBERS.has(node.name)) {
|
|
185
|
+
return this.err(
|
|
186
|
+
`${node.name} is not available: ${EXCLUDED_STDLIB_MEMBERS.get(node.name)}`,
|
|
187
|
+
node,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
98
190
|
return this.err(`Unknown type '${node.name}'`, node);
|
|
99
191
|
}
|
|
100
192
|
case "GenericTypeName":
|
|
101
193
|
case "InstantiationExpr":
|
|
102
|
-
case "IndexExpr":
|
|
103
|
-
|
|
104
|
-
node.name ?? (node.expr?.kind === "Ident" ? node.expr.name : null);
|
|
105
|
-
const typeArgs = node.typeArgs ?? (node.index ? [node.index] : []);
|
|
106
|
-
const base = name
|
|
107
|
-
? this.types.get(name)
|
|
108
|
-
: node.expr
|
|
109
|
-
? this.resolveTypeNode(node.expr, scope)
|
|
110
|
-
: null;
|
|
111
|
-
if (!base) return this.err(`Unknown type '${name ?? "?"}'`, node);
|
|
112
|
-
if (base.kind === "named" && base._generic) {
|
|
113
|
-
const res = this.instantiateGenericType(
|
|
114
|
-
base._generic,
|
|
115
|
-
typeArgs,
|
|
116
|
-
scope,
|
|
117
|
-
);
|
|
118
|
-
node._typeArgs = res.typeArgs;
|
|
119
|
-
node._genericType = base;
|
|
120
|
-
return res;
|
|
121
|
-
}
|
|
122
|
-
return this.err(`Type '${name ?? "?"}' is not generic`, node);
|
|
123
|
-
}
|
|
194
|
+
case "IndexExpr":
|
|
195
|
+
return this._resolveGenericTypeName(node, scope);
|
|
124
196
|
case "TypeParam": {
|
|
125
197
|
const constraint = node.constraint
|
|
126
198
|
? this.resolveTypeNode(node.constraint, scope)
|
|
@@ -156,11 +228,14 @@ export const resolveMethods = {
|
|
|
156
228
|
// SelectorExpr; resolve it like a qualified TypeName.
|
|
157
229
|
if (node.expr?.kind === "Ident" && typeof node.field === "string") {
|
|
158
230
|
const qualified = `${node.expr.name}.${node.field}`;
|
|
159
|
-
if (this.types.has(qualified))
|
|
160
|
-
|
|
231
|
+
if (this.types.has(qualified)) {
|
|
232
|
+
const resolved = this.resolveTypeNode(
|
|
161
233
|
{ kind: "TypeName", name: qualified },
|
|
162
234
|
scope,
|
|
163
235
|
);
|
|
236
|
+
this._useQualifiedTypeName(node, resolved);
|
|
237
|
+
return resolved;
|
|
238
|
+
}
|
|
164
239
|
}
|
|
165
240
|
return ANY;
|
|
166
241
|
case "ArrayType":
|
|
@@ -177,24 +252,116 @@ export const resolveMethods = {
|
|
|
177
252
|
}
|
|
178
253
|
},
|
|
179
254
|
|
|
255
|
+
_checkWasmStdlibTypeMember(node) {
|
|
256
|
+
if (this.target !== "wasm" && this.target !== "both") return null;
|
|
257
|
+
const dot = node.name.indexOf(".");
|
|
258
|
+
if (dot === -1) return null;
|
|
259
|
+
const pkg = node.name.slice(0, dot);
|
|
260
|
+
if (pkg !== "time") return null;
|
|
261
|
+
const field = node.name.slice(dot + 1);
|
|
262
|
+
const allowed = WASM_STDLIB_MEMBERS.get(pkg);
|
|
263
|
+
if (allowed && !allowed.has(field)) {
|
|
264
|
+
this.recordBlocker("unsupported stdlib member", node.name);
|
|
265
|
+
return this.err(
|
|
266
|
+
`'${node.name}' is not yet available in wasm packages`,
|
|
267
|
+
node,
|
|
268
|
+
"move this call to a JS package, or use a supported alternative",
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
// `pkg.T` in a type position counts as using the import; a Go-source
|
|
275
|
+
// stdlib type is emitted under its mangled name.
|
|
276
|
+
_useQualifiedTypeName(node, named) {
|
|
277
|
+
const name =
|
|
278
|
+
node.name ?? (node.expr ? `${node.expr.name}.${node.field}` : "");
|
|
279
|
+
const dot = name.indexOf(".");
|
|
280
|
+
if (dot <= 0) return;
|
|
281
|
+
this.globals.lookup(name.slice(0, dot));
|
|
282
|
+
if (named.name?.includes("$$")) node._stdName = named.name;
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
// `[N]T` with a named constant (or constant expression) length folds to a number.
|
|
286
|
+
_foldArrayLen(size, scope) {
|
|
287
|
+
const resolve = (name, depth = 0) => {
|
|
288
|
+
const e = depth < 16 ? scope.lookupConstExpr(name) : null;
|
|
289
|
+
return e ? foldConstExpr(e, (n) => resolve(n, depth + 1)) : undefined;
|
|
290
|
+
};
|
|
291
|
+
try {
|
|
292
|
+
const lit = foldConstExpr(size, resolve);
|
|
293
|
+
const n = lit ? Number(lit.value) : Number.NaN;
|
|
294
|
+
return Number.isInteger(n) && n >= 0 ? n : size;
|
|
295
|
+
} catch {
|
|
296
|
+
return size;
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
|
|
180
300
|
_resolveArrayTypeNode(node, scope) {
|
|
301
|
+
// Fold in place so backends that read the type node see a literal length.
|
|
302
|
+
if (!node.inferLen && node.size?.value === undefined) {
|
|
303
|
+
const n = this._foldArrayLen(node.size, scope);
|
|
304
|
+
if (typeof n === "number")
|
|
305
|
+
node.size = {
|
|
306
|
+
kind: "BasicLit",
|
|
307
|
+
litKind: "INT",
|
|
308
|
+
value: String(n),
|
|
309
|
+
_line: node.size._line,
|
|
310
|
+
_col: node.size._col,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
181
313
|
return {
|
|
182
314
|
kind: "array",
|
|
183
315
|
size: node.inferLen
|
|
184
316
|
? null
|
|
185
317
|
: node.size?.value !== undefined
|
|
186
318
|
? Number(node.size.value)
|
|
187
|
-
: node.size,
|
|
319
|
+
: this._foldArrayLen(node.size, scope),
|
|
188
320
|
elem: this.resolveTypeNode(node.elem, scope),
|
|
189
321
|
};
|
|
190
322
|
},
|
|
191
323
|
|
|
324
|
+
// `G[X]`: instantiate a generic named type, or expand a generic alias.
|
|
325
|
+
_resolveGenericTypeName(node, scope) {
|
|
326
|
+
const sel = node.expr?.kind === "SelectorExpr" ? node.expr : null;
|
|
327
|
+
const qualified =
|
|
328
|
+
sel?.expr?.kind === "Ident" ? `${sel.expr.name}.${sel.field}` : null;
|
|
329
|
+
const name =
|
|
330
|
+
node.name ??
|
|
331
|
+
(node.expr?.kind === "Ident" ? node.expr.name : null) ??
|
|
332
|
+
qualified;
|
|
333
|
+
const typeArgs = node.typeArgs ?? (node.index ? [node.index] : []);
|
|
334
|
+
const base = name
|
|
335
|
+
? this.types.get(name)
|
|
336
|
+
: node.expr
|
|
337
|
+
? this.resolveTypeNode(node.expr, scope)
|
|
338
|
+
: null;
|
|
339
|
+
if (!base) return this.err(`Unknown type '${name ?? "?"}'`, node);
|
|
340
|
+
if (qualified) this.globals.lookup(sel.expr.name);
|
|
341
|
+
else if (name)
|
|
342
|
+
this._useQualifiedTypeName(node.name ? node : node.expr, base);
|
|
343
|
+
if (base.kind === "named" && base._generic?.isAlias) {
|
|
344
|
+
const aliasArgs = typeArgs.map((n) => this.resolveTypeNode(n, scope));
|
|
345
|
+
node._typeArgs = aliasArgs;
|
|
346
|
+
return this._instantiateGenericAlias(base._generic, aliasArgs);
|
|
347
|
+
}
|
|
348
|
+
if (base.kind === "named" && base._generic) {
|
|
349
|
+
const res = this.instantiateGenericType(base._generic, typeArgs, scope);
|
|
350
|
+
node._typeArgs = res.typeArgs;
|
|
351
|
+
node._genericType = base;
|
|
352
|
+
return res;
|
|
353
|
+
}
|
|
354
|
+
return this.err(`Type '${name ?? "?"}' is not generic`, node);
|
|
355
|
+
},
|
|
356
|
+
|
|
192
357
|
_resolveFuncTypeNode(node, scope) {
|
|
193
358
|
const params = node.params.map((p) => this.resolveTypeNode(p.type, scope));
|
|
194
359
|
const returns = node.returnType
|
|
195
360
|
? [this.resolveTypeNode(node.returnType, scope)]
|
|
196
361
|
: [VOID];
|
|
197
|
-
|
|
362
|
+
const fn = { kind: "func", params, returns };
|
|
363
|
+
if (node.params.at(-1)?.variadic) fn.variadic = true;
|
|
364
|
+
return fn;
|
|
198
365
|
},
|
|
199
366
|
|
|
200
367
|
resolveTypeNodeName(node, scope) {
|
|
@@ -209,10 +376,29 @@ export const resolveMethods = {
|
|
|
209
376
|
_resolveStructType(node, scope) {
|
|
210
377
|
const fields = new Map();
|
|
211
378
|
const embeds = [];
|
|
379
|
+
const structFields = [];
|
|
212
380
|
for (const f of node.fields) {
|
|
213
381
|
const ft = this.resolveTypeNode(f.type, scope);
|
|
382
|
+
structFields.push({
|
|
383
|
+
names: f.names,
|
|
384
|
+
type: ft,
|
|
385
|
+
tag: f.tag ?? null,
|
|
386
|
+
embedded: Boolean(f.embedded),
|
|
387
|
+
astType: f.type,
|
|
388
|
+
});
|
|
214
389
|
if (f.embedded) {
|
|
215
|
-
const
|
|
390
|
+
const inner = ft.kind === "pointer" ? ft.base : ft;
|
|
391
|
+
const embedName = (
|
|
392
|
+
f.embeddedName ??
|
|
393
|
+
f.astType?.name ??
|
|
394
|
+
f.type?.name ??
|
|
395
|
+
f.type?.base?.name ??
|
|
396
|
+
(inner.kind === "named" ? inner.name : "")
|
|
397
|
+
)
|
|
398
|
+
.split(".")
|
|
399
|
+
.pop();
|
|
400
|
+
if (embedName) fields.set(embedName, ft);
|
|
401
|
+
const base = inner.kind === "named" ? inner.underlying : inner;
|
|
216
402
|
if (base?.kind === "struct") {
|
|
217
403
|
for (const [k, v] of base.fields.entries()) fields.set(k, v);
|
|
218
404
|
}
|
|
@@ -221,7 +407,13 @@ export const resolveMethods = {
|
|
|
221
407
|
for (const n of f.names) fields.set(n, ft);
|
|
222
408
|
}
|
|
223
409
|
}
|
|
224
|
-
return {
|
|
410
|
+
return {
|
|
411
|
+
kind: "struct",
|
|
412
|
+
fields,
|
|
413
|
+
methods: new Map(),
|
|
414
|
+
_embeds: embeds,
|
|
415
|
+
_structFields: structFields,
|
|
416
|
+
};
|
|
225
417
|
},
|
|
226
418
|
|
|
227
419
|
_resolveInterfaceType(node, scope) {
|
|
@@ -237,6 +429,9 @@ export const resolveMethods = {
|
|
|
237
429
|
if (isVariadic) mType.variadic = true;
|
|
238
430
|
methods.set(m.name, mType);
|
|
239
431
|
}
|
|
432
|
+
// Embedded interfaces that carry a type set (`interface{ Integer }`):
|
|
433
|
+
// kept so type-parameter operator checks can follow them.
|
|
434
|
+
const typeSetEmbeds = [];
|
|
240
435
|
if (node.embeds) {
|
|
241
436
|
for (const embed of node.embeds) {
|
|
242
437
|
const resolved = this.resolveTypeNode(embed, scope);
|
|
@@ -246,6 +441,8 @@ export const resolveMethods = {
|
|
|
246
441
|
for (const [mName, mType] of base.methods) {
|
|
247
442
|
if (!methods.has(mName)) methods.set(mName, mType);
|
|
248
443
|
}
|
|
444
|
+
if (base.unionConstraint || base._typeSetEmbeds)
|
|
445
|
+
typeSetEmbeds.push(base);
|
|
249
446
|
} else if (!isAny(resolved)) {
|
|
250
447
|
this.err(
|
|
251
448
|
`cannot embed non-interface type ${typeStr(resolved)}`,
|
|
@@ -256,6 +453,7 @@ export const resolveMethods = {
|
|
|
256
453
|
}
|
|
257
454
|
const result = { kind: "interface", methods };
|
|
258
455
|
if (node.unionConstraint) result.unionConstraint = node.unionConstraint;
|
|
456
|
+
if (typeSetEmbeds.length > 0) result._typeSetEmbeds = typeSetEmbeds;
|
|
259
457
|
return result;
|
|
260
458
|
},
|
|
261
459
|
|
|
@@ -276,36 +474,127 @@ export const resolveMethods = {
|
|
|
276
474
|
},
|
|
277
475
|
|
|
278
476
|
instantiateGenericType(generic, typeArgNodes, scope) {
|
|
279
|
-
const map = new Map();
|
|
280
477
|
const typeArgs = typeArgNodes.map((n) => this.resolveTypeNode(n, scope));
|
|
478
|
+
return this.instantiateGenericWithArgs(generic, typeArgs);
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
// One instance per type-argument list. The instance is cached before its
|
|
482
|
+
// fields are resolved, so a field that names the instance again (`Next
|
|
483
|
+
// *List[T]`, `Children []Tree[T]`, or a cycle through another generic type)
|
|
484
|
+
// reaches the same object instead of recursing forever.
|
|
485
|
+
instantiateGenericWithArgs(generic, typeArgs) {
|
|
486
|
+
const key = typeArgs.map(instanceKey).join(",");
|
|
487
|
+
generic._instances ??= new Map();
|
|
488
|
+
const cached = generic._instances.get(key);
|
|
489
|
+
if (cached) {
|
|
490
|
+
this._syncInstanceMethods(generic, cached);
|
|
491
|
+
return cached;
|
|
492
|
+
}
|
|
493
|
+
// `type T[X any] struct{ n *T[[]X] }` asks for T[[]X], then T[[][]X], and
|
|
494
|
+
// so on: an instance still being built that keeps asking for a larger
|
|
495
|
+
// one never settles.
|
|
496
|
+
generic._building ??= [];
|
|
497
|
+
generic._buildSizes ??= [];
|
|
498
|
+
const building = generic._building;
|
|
499
|
+
const size = typeArgs.reduce((n, a) => n + typeSize(a), 0);
|
|
500
|
+
// A request for a larger instance while one is being built never
|
|
501
|
+
// settles (`T[X]` containing `T[[]X]`), and doubling forms such as
|
|
502
|
+
// `T[Pair[X, X]]` blow up long before a depth limit is reached.
|
|
503
|
+
const top = generic._buildSizes[generic._buildSizes.length - 1];
|
|
504
|
+
if (
|
|
505
|
+
building.length >= MAX_INSTANTIATION_DEPTH ||
|
|
506
|
+
(top !== undefined && size > top)
|
|
507
|
+
) {
|
|
508
|
+
if (!generic._cycleReported) {
|
|
509
|
+
generic._cycleReported = true;
|
|
510
|
+
this.err(
|
|
511
|
+
`instantiation cycle: ${generic.declNode.name} instantiates itself with ever larger type arguments`,
|
|
512
|
+
generic.declNode,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
return ANY;
|
|
516
|
+
}
|
|
517
|
+
const map = new Map();
|
|
281
518
|
for (let i = 0; i < generic.typeParams.length; i++)
|
|
282
519
|
map.set(generic.typeParams[i].name, typeArgs[i] ?? ANY);
|
|
520
|
+
const named = {
|
|
521
|
+
kind: "named",
|
|
522
|
+
name: generic.declNode.name,
|
|
523
|
+
typeArgs,
|
|
524
|
+
underlying: null,
|
|
525
|
+
_genericOf: generic,
|
|
526
|
+
};
|
|
527
|
+
generic._instances.set(key, named);
|
|
283
528
|
// Resolve the declaration with its type params in scope (as typeParams,
|
|
284
529
|
// never as value symbols), then substitute the concrete arguments.
|
|
285
530
|
const typeScope = new Scope(this.globals);
|
|
286
531
|
for (const tp of generic.typeParams) typeScope.define(tp.name, tp);
|
|
287
|
-
|
|
288
|
-
|
|
532
|
+
building.push(key);
|
|
533
|
+
generic._buildSizes.push(size);
|
|
534
|
+
let instantiated;
|
|
535
|
+
try {
|
|
536
|
+
const underlying = this.resolveTypeNode(generic.declNode.type, typeScope);
|
|
537
|
+
instantiated = this.substituteType(underlying, map);
|
|
538
|
+
} finally {
|
|
539
|
+
building.pop();
|
|
540
|
+
generic._buildSizes.pop();
|
|
541
|
+
}
|
|
289
542
|
if (instantiated.kind === "struct") {
|
|
290
543
|
instantiated.name = generic.declNode.name;
|
|
291
544
|
instantiated.typeArgs = typeArgs;
|
|
292
545
|
if (!instantiated.methods) instantiated.methods = new Map();
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
546
|
+
}
|
|
547
|
+
named.underlying = instantiated;
|
|
548
|
+
named.methods = instantiated.methods;
|
|
549
|
+
named._instMap = map;
|
|
550
|
+
this._syncInstanceMethods(generic, named);
|
|
551
|
+
return named;
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
// Methods are collected after types, so an instance created earlier picks
|
|
555
|
+
// up the methods declared since.
|
|
556
|
+
_syncInstanceMethods(generic, named) {
|
|
557
|
+
const methods = named.methods;
|
|
558
|
+
if (!methods || !named._instMap || named._syncing) return;
|
|
559
|
+
// A method signature that names the instance reaches this again.
|
|
560
|
+
named._syncing = true;
|
|
561
|
+
for (const [mName, mType] of generic.methods ?? [])
|
|
562
|
+
if (!methods.has(mName))
|
|
563
|
+
methods.set(mName, this.substituteType(mType, named._instMap));
|
|
564
|
+
// Methods promoted from embedded structs (`Outer[T]` embedding `Base`).
|
|
565
|
+
this._promoteEmbeddedInto(named.underlying);
|
|
566
|
+
named._syncing = false;
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
// A[X] for `type A[P any] = T`: resolve T with each P bound to the
|
|
570
|
+
// concrete X, so A[int] is exactly T[int] (generic named types inside T
|
|
571
|
+
// are instantiated normally and keep their identity).
|
|
572
|
+
_instantiateGenericAlias(generic, typeArgs) {
|
|
573
|
+
const { typeParams, declNode } = generic;
|
|
574
|
+
if (typeArgs.length !== typeParams.length) {
|
|
575
|
+
return this.err(
|
|
576
|
+
`got ${typeArgs.length} type arguments but ${declNode.name} has ${typeParams.length} type parameters`,
|
|
577
|
+
declNode,
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
const aliasScope = new Scope(this.globals);
|
|
581
|
+
for (let i = 0; i < typeParams.length; i++) {
|
|
582
|
+
const tp = typeParams[i];
|
|
583
|
+
if (tp.constraint) {
|
|
584
|
+
const constraint = this.resolveTypeNode(tp.constraint, this.globals);
|
|
585
|
+
this.checkConstraint(typeArgs[i], constraint, declNode);
|
|
296
586
|
}
|
|
587
|
+
aliasScope.define(tp.name, { kind: "aliasArg", type: typeArgs[i] });
|
|
297
588
|
}
|
|
298
|
-
return
|
|
299
|
-
kind: "named",
|
|
300
|
-
name: generic.declNode.name,
|
|
301
|
-
typeArgs,
|
|
302
|
-
underlying: instantiated,
|
|
303
|
-
methods: instantiated.methods,
|
|
304
|
-
};
|
|
589
|
+
return this.resolveTypeNode(declNode.type, aliasScope);
|
|
305
590
|
},
|
|
306
591
|
|
|
307
|
-
|
|
592
|
+
// `explicit` seeds the leading type arguments of a partial instantiation
|
|
593
|
+
// (`F[int](x)`); the rest are inferred from the call arguments.
|
|
594
|
+
inferTypeArgs(genericType, argTypes, explicit = []) {
|
|
308
595
|
const map = new Map();
|
|
596
|
+
for (let i = 0; i < explicit.length; i++)
|
|
597
|
+
map.set(genericType.typeParams[i].name, explicit[i]);
|
|
309
598
|
const params = genericType.underlying.params;
|
|
310
599
|
for (let i = 0; i < params.length && i < argTypes.length; i++)
|
|
311
600
|
this._inferFromPair(params[i], argTypes[i], map);
|
|
@@ -339,14 +628,36 @@ export const resolveMethods = {
|
|
|
339
628
|
}
|
|
340
629
|
if (paramType.kind === "func" && argType.kind === "func")
|
|
341
630
|
this._inferFromFuncPair(paramType, argType, map);
|
|
631
|
+
if (paramType.kind === "tuple" && argType.kind === "tuple") {
|
|
632
|
+
for (
|
|
633
|
+
let i = 0;
|
|
634
|
+
i < paramType.types.length && i < argType.types.length;
|
|
635
|
+
i++
|
|
636
|
+
)
|
|
637
|
+
this._inferFromPair(paramType.types[i], argType.types[i], map);
|
|
638
|
+
}
|
|
342
639
|
},
|
|
343
640
|
|
|
344
641
|
_inferFromPair(paramType, argType, map) {
|
|
345
642
|
if (!paramType || !argType) return;
|
|
346
643
|
if (paramType.kind === "typeParam") {
|
|
347
|
-
|
|
644
|
+
const prev = map.get(paramType.name);
|
|
645
|
+
if (!prev || untypedWins(prev, argType)) map.set(paramType.name, argType);
|
|
348
646
|
return;
|
|
349
647
|
}
|
|
648
|
+
if (paramType.kind === "named" && paramType.typeArgs) {
|
|
649
|
+
// `iter.Seq[E]` against `iter.Seq[int]`: unify the type arguments;
|
|
650
|
+
// against any other type, its underlying type.
|
|
651
|
+
if (argType.kind === "named" && argType.name === paramType.name) {
|
|
652
|
+
const args = argType.typeArgs ?? [];
|
|
653
|
+
for (let i = 0; i < paramType.typeArgs.length; i++)
|
|
654
|
+
this._inferFromPair(paramType.typeArgs[i], args[i], map);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
paramType = paramType.underlying;
|
|
658
|
+
}
|
|
659
|
+
if (argType.kind === "named" && argType.underlying)
|
|
660
|
+
argType = argType.underlying;
|
|
350
661
|
this._inferFromStructuredPair(paramType, argType, map);
|
|
351
662
|
},
|
|
352
663
|
|
|
@@ -397,11 +708,37 @@ export const resolveMethods = {
|
|
|
397
708
|
(!t.underlying || !t.methods)
|
|
398
709
|
) {
|
|
399
710
|
const found = this.types.get(t.name);
|
|
400
|
-
|
|
711
|
+
// Two imported packages can both declare `Pt`; the bare name only
|
|
712
|
+
// reaches the last one registered, so a declaration that came from
|
|
713
|
+
// an import's own type table stays itself.
|
|
714
|
+
if (found && !(t.underlying && this._ambiguousImported?.has(t)))
|
|
715
|
+
return found;
|
|
401
716
|
}
|
|
717
|
+
if (t.kind === "named" && t.typeArgs?.length)
|
|
718
|
+
return this._withGenericMethods(t);
|
|
402
719
|
return t;
|
|
403
720
|
},
|
|
404
721
|
|
|
722
|
+
// A substituted generic instance (e.g. Stack[int] from an inferred call)
|
|
723
|
+
// drops the methods map; re-derive it from the generic declaration.
|
|
724
|
+
_withGenericMethods(t) {
|
|
725
|
+
const decl = this.types.get(t.name);
|
|
726
|
+
const generic = decl?._generic;
|
|
727
|
+
// Struct generics only: codegen has no receiver support for generic
|
|
728
|
+
// slice/map/func named types, so those keep the baseline diagnostics.
|
|
729
|
+
if (this.resolveType(decl?.underlying)?.kind !== "struct") return t;
|
|
730
|
+
if (!generic?.methods || generic.methods.size === 0) return t;
|
|
731
|
+
if (t.methods && generic.methods.size <= t.methods.size) return t;
|
|
732
|
+
const map = new Map();
|
|
733
|
+
for (let i = 0; i < generic.typeParams.length; i++)
|
|
734
|
+
map.set(generic.typeParams[i].name, t.typeArgs[i] ?? ANY);
|
|
735
|
+
const methods = new Map(t.methods);
|
|
736
|
+
for (const [name, mType] of generic.methods)
|
|
737
|
+
if (!methods.has(name))
|
|
738
|
+
methods.set(name, this.substituteType(mType, map));
|
|
739
|
+
return { ...t, methods };
|
|
740
|
+
},
|
|
741
|
+
|
|
405
742
|
// ── Field type lookup ─────────────────────────────────────────────
|
|
406
743
|
|
|
407
744
|
fieldType(baseType, field, node) {
|
|
@@ -434,6 +771,7 @@ export const resolveMethods = {
|
|
|
434
771
|
case "namespace":
|
|
435
772
|
return this._fieldTypeNamespace(base, field, node);
|
|
436
773
|
case "shared":
|
|
774
|
+
case "simd":
|
|
437
775
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
438
776
|
return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
|
|
439
777
|
case "slice":
|
|
@@ -464,12 +802,54 @@ export const resolveMethods = {
|
|
|
464
802
|
return base.fields.get(field);
|
|
465
803
|
}
|
|
466
804
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
805
|
+
const typeName = baseType?.name || base.name;
|
|
806
|
+
if (typeName) {
|
|
807
|
+
const dName = displayTypeName(typeName);
|
|
808
|
+
const shortName = typeName.includes("$$")
|
|
809
|
+
? typeName.split("$$")[0]
|
|
810
|
+
: typeName.includes(".")
|
|
811
|
+
? typeName.split(".").pop()
|
|
812
|
+
: typeName;
|
|
813
|
+
for (const key of [
|
|
814
|
+
`${dName}.${field}`,
|
|
815
|
+
`${typeName}.${field}`,
|
|
816
|
+
`${shortName}.${field}`,
|
|
817
|
+
]) {
|
|
818
|
+
if (EXCLUDED_STDLIB_MEMBERS.has(key)) {
|
|
819
|
+
return this.err(
|
|
820
|
+
`${dName}.${field} is not available: ${EXCLUDED_STDLIB_MEMBERS.get(key)}`,
|
|
821
|
+
node,
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
467
826
|
return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
|
|
468
827
|
},
|
|
469
828
|
|
|
470
829
|
_fieldTypeInterface(base, baseType, field, node) {
|
|
471
830
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
472
831
|
if (base.fields?.has(field)) return base.fields.get(field);
|
|
832
|
+
const typeName = baseType?.name || base.name;
|
|
833
|
+
if (typeName) {
|
|
834
|
+
const dName = displayTypeName(typeName);
|
|
835
|
+
const shortName = typeName.includes("$$")
|
|
836
|
+
? typeName.split("$$")[0]
|
|
837
|
+
: typeName.includes(".")
|
|
838
|
+
? typeName.split(".").pop()
|
|
839
|
+
: typeName;
|
|
840
|
+
for (const key of [
|
|
841
|
+
`${dName}.${field}`,
|
|
842
|
+
`${typeName}.${field}`,
|
|
843
|
+
`${shortName}.${field}`,
|
|
844
|
+
]) {
|
|
845
|
+
if (EXCLUDED_STDLIB_MEMBERS.has(key)) {
|
|
846
|
+
return this.err(
|
|
847
|
+
`${dName}.${field} is not available: ${EXCLUDED_STDLIB_MEMBERS.get(key)}`,
|
|
848
|
+
node,
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
473
853
|
if (base._excluded?.has(field))
|
|
474
854
|
return this.err(`No member '${field}' on ${typeStr(baseType)}`, node);
|
|
475
855
|
if (base._isOpen) return ANY;
|
|
@@ -517,6 +897,12 @@ export const resolveMethods = {
|
|
|
517
897
|
`cannot refer to unexported name ${base.name}.${field}`,
|
|
518
898
|
node,
|
|
519
899
|
);
|
|
900
|
+
// Go-source stdlib member: backends emit the mangled top-level name.
|
|
901
|
+
const mangled = base._mangle?.get(field);
|
|
902
|
+
if (mangled) {
|
|
903
|
+
node._stdName = mangled;
|
|
904
|
+
return base.members[field];
|
|
905
|
+
}
|
|
520
906
|
if (field in base.members) {
|
|
521
907
|
if (
|
|
522
908
|
(this.target === "wasm" || this.target === "both") &&
|
|
@@ -535,6 +921,164 @@ export const resolveMethods = {
|
|
|
535
921
|
}
|
|
536
922
|
return base.members[field];
|
|
537
923
|
}
|
|
924
|
+
const excluded = EXCLUDED_STDLIB_MEMBERS.get(`${base.name}.${field}`);
|
|
925
|
+
if (excluded)
|
|
926
|
+
return this.err(
|
|
927
|
+
`${base.name}.${field} is not available: ${excluded}`,
|
|
928
|
+
node,
|
|
929
|
+
);
|
|
538
930
|
return this.err(`No member '${field}' in namespace ${base.name}`, node);
|
|
539
931
|
},
|
|
932
|
+
|
|
933
|
+
resolveJsonFields(structType) {
|
|
934
|
+
return resolveJsonFields(structType);
|
|
935
|
+
},
|
|
540
936
|
};
|
|
937
|
+
|
|
938
|
+
function isExported(name) {
|
|
939
|
+
if (!name || typeof name !== "string") return false;
|
|
940
|
+
const code = name.charCodeAt(0);
|
|
941
|
+
return code >= 65 && code <= 90;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function _processJsonField(
|
|
945
|
+
f,
|
|
946
|
+
depth,
|
|
947
|
+
path,
|
|
948
|
+
visited,
|
|
949
|
+
queue,
|
|
950
|
+
candidates,
|
|
951
|
+
index,
|
|
952
|
+
pos,
|
|
953
|
+
) {
|
|
954
|
+
const tagInfo = parseJsonTag(f.tag);
|
|
955
|
+
if (tagInfo.ignored) return;
|
|
956
|
+
|
|
957
|
+
if (f.embedded) {
|
|
958
|
+
const isPtr = f.type.kind === "pointer" || f.type.kind === "PointerType";
|
|
959
|
+
const inner = isPtr ? f.type.base : f.type;
|
|
960
|
+
const embedStruct = inner.kind === "named" ? inner.underlying : inner;
|
|
961
|
+
const embedName = (
|
|
962
|
+
f.astType?.name ??
|
|
963
|
+
f.astType?.base?.name ??
|
|
964
|
+
inner.name ??
|
|
965
|
+
""
|
|
966
|
+
)
|
|
967
|
+
.split(".")
|
|
968
|
+
.pop();
|
|
969
|
+
|
|
970
|
+
if (tagInfo.name !== null) {
|
|
971
|
+
if (isExported(embedName)) {
|
|
972
|
+
candidates.push({
|
|
973
|
+
jsonKey: tagInfo.name,
|
|
974
|
+
goName: embedName,
|
|
975
|
+
type: f.type,
|
|
976
|
+
omitempty: tagInfo.omitempty,
|
|
977
|
+
depth,
|
|
978
|
+
tagged: true,
|
|
979
|
+
isEmbedded: true,
|
|
980
|
+
index: [...index, pos],
|
|
981
|
+
path: [...path, { name: embedName, isPtr, typeName: embedName }],
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
} else if (embedStruct?.kind === "struct") {
|
|
985
|
+
if (!visited.has(embedStruct)) {
|
|
986
|
+
const nextVisited = new Set(visited);
|
|
987
|
+
nextVisited.add(embedStruct);
|
|
988
|
+
queue.push({
|
|
989
|
+
struct: embedStruct,
|
|
990
|
+
depth: depth + 1,
|
|
991
|
+
index: [...index, pos],
|
|
992
|
+
path: [...path, { name: embedName, isPtr, typeName: embedName }],
|
|
993
|
+
visited: nextVisited,
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
for (const [i, name] of f.names.entries()) {
|
|
1001
|
+
if (!isExported(name)) continue;
|
|
1002
|
+
candidates.push({
|
|
1003
|
+
index: [...index, pos + i],
|
|
1004
|
+
jsonKey: tagInfo.name !== null ? tagInfo.name : name,
|
|
1005
|
+
goName: name,
|
|
1006
|
+
type: f.type,
|
|
1007
|
+
omitempty: tagInfo.omitempty,
|
|
1008
|
+
quoted: tagInfo.options.includes("string"),
|
|
1009
|
+
depth,
|
|
1010
|
+
tagged: tagInfo.name !== null,
|
|
1011
|
+
path: [...path, { name, isPtr: false }],
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function _resolveCandidateCollisions(candidates) {
|
|
1017
|
+
const byKey = new Map();
|
|
1018
|
+
for (const cand of candidates) {
|
|
1019
|
+
if (!byKey.has(cand.jsonKey)) byKey.set(cand.jsonKey, []);
|
|
1020
|
+
byKey.get(cand.jsonKey).push(cand);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const winningFields = [];
|
|
1024
|
+
for (const [, group] of byKey.entries()) {
|
|
1025
|
+
let minDepth = Infinity;
|
|
1026
|
+
for (const c of group) {
|
|
1027
|
+
if (c.depth < minDepth) minDepth = c.depth;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const atMin = group.filter((c) => c.depth === minDepth);
|
|
1031
|
+
if (atMin.length === 1) {
|
|
1032
|
+
winningFields.push(atMin[0]);
|
|
1033
|
+
} else {
|
|
1034
|
+
const tagged = atMin.filter((c) => c.tagged);
|
|
1035
|
+
if (tagged.length === 1) winningFields.push(tagged[0]);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Go orders the fields of a struct by their index sequence, so a promoted
|
|
1040
|
+
// field sits where its embedded struct is declared.
|
|
1041
|
+
winningFields.sort((a, b) => compareIndex(a.index, b.index));
|
|
1042
|
+
return winningFields;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function compareIndex(a, b) {
|
|
1046
|
+
for (let i = 0; i < a.length && i < b.length; i++)
|
|
1047
|
+
if (a[i] !== b[i]) return a[i] - b[i];
|
|
1048
|
+
return a.length - b.length;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
export function resolveJsonFields(structType) {
|
|
1052
|
+
if (!structType) return [];
|
|
1053
|
+
const underlying =
|
|
1054
|
+
structType.kind === "named" ? structType.underlying : structType;
|
|
1055
|
+
if (underlying?.kind !== "struct") return [];
|
|
1056
|
+
if (underlying._jsonFields) return underlying._jsonFields;
|
|
1057
|
+
|
|
1058
|
+
const queue = [
|
|
1059
|
+
{
|
|
1060
|
+
struct: underlying,
|
|
1061
|
+
depth: 0,
|
|
1062
|
+
path: [],
|
|
1063
|
+
index: [],
|
|
1064
|
+
visited: new Set([underlying]),
|
|
1065
|
+
},
|
|
1066
|
+
];
|
|
1067
|
+
const candidates = [];
|
|
1068
|
+
|
|
1069
|
+
while (queue.length > 0) {
|
|
1070
|
+
const { struct: curr, depth, path, index, visited } = queue.shift();
|
|
1071
|
+
if (curr?.kind !== "struct") continue;
|
|
1072
|
+
|
|
1073
|
+
// Each name of a `A, B int` field is its own field, as in Go.
|
|
1074
|
+
let pos = 0;
|
|
1075
|
+
for (const f of curr._structFields || []) {
|
|
1076
|
+
_processJsonField(f, depth, path, visited, queue, candidates, index, pos);
|
|
1077
|
+
pos += f.embedded ? 1 : (f.names?.length ?? 1);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const winning = _resolveCandidateCollisions(candidates);
|
|
1082
|
+
underlying._jsonFields = winning;
|
|
1083
|
+
return winning;
|
|
1084
|
+
}
|