gofront 1.7.0 → 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.
Files changed (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -0,0 +1,272 @@
1
+ // src/backend/js/type-info.js
2
+ // Run-time type information for erased generics. A generic function (or the
3
+ // instance of a generic type) receives one zero-value factory per type
4
+ // parameter; when its body needs to know the instantiated type (type
5
+ // assertions to a type parameter, conversions to it, `==` on it, building an
6
+ // instance of another generic type from it) the factory also carries the
7
+ // type's kind, identity and a type test (see `__ti` in HELPER_GENERICS).
8
+
9
+ import { forEachChild } from "../../lower/index.js";
10
+ import { isAny, typeStr } from "../../typechecker/types.js";
11
+ import { mentionsTypeParam } from "./expressions.js";
12
+
13
+ // True when type node `node` names one of the type parameters `names`.
14
+ function typeNodeMentions(node, names) {
15
+ if (Array.isArray(node)) return node.some((n) => typeNodeMentions(n, names));
16
+ if (!node || typeof node !== "object") return false;
17
+ if (
18
+ (node.kind === "TypeName" || node.kind === "Ident") &&
19
+ names.has(node.name)
20
+ )
21
+ return true;
22
+ let found = false;
23
+ forEachChild(node, (c) => {
24
+ if (!found && typeNodeMentions(c, names)) found = true;
25
+ });
26
+ return found;
27
+ }
28
+
29
+ function typeArgsMention(node) {
30
+ const args = node._typeArgs ?? node._type?.typeArgs;
31
+ return Array.isArray(args) && args.some((t) => mentionsTypeParam(t));
32
+ }
33
+
34
+ export const typeInfoGenMethods = {
35
+ // Whether the body of generic `decl` (a function or method declaration)
36
+ // needs the instantiated types of `names`.
37
+ _declNeedsTypeInfo(decl, names) {
38
+ this._typeInfoMemo ??= new Map();
39
+ this._typeInfoActive ??= new Set();
40
+ if (this._typeInfoMemo.has(decl)) return this._typeInfoMemo.get(decl);
41
+ if (this._typeInfoActive.has(decl)) {
42
+ this._typeInfoCycle = true; // answer depends on an unfinished scan
43
+ return false;
44
+ }
45
+ const outer = this._typeInfoCycle;
46
+ this._typeInfoCycle = false;
47
+ this._typeInfoActive.add(decl);
48
+ const result = this._scanTypeInfoUse(decl.body, new Set(names));
49
+ this._typeInfoActive.delete(decl);
50
+ // A false that leaned on an in-progress scan is provisional: only cache
51
+ // it once no scan is in progress, otherwise a mutually recursive
52
+ // function would keep a stale answer.
53
+ if (result || !this._typeInfoCycle || this._typeInfoActive.size === 0)
54
+ this._typeInfoMemo.set(decl, result);
55
+ this._typeInfoCycle = outer || this._typeInfoCycle;
56
+ return result;
57
+ },
58
+
59
+ _scanTypeInfoUse(node, names) {
60
+ if (Array.isArray(node))
61
+ return node.some((n) => this._scanTypeInfoUse(n, names));
62
+ if (!node || typeof node !== "object") return false;
63
+ if (this._nodeUsesTypeInfo(node, names)) return true;
64
+ let found = false;
65
+ forEachChild(node, (c) => {
66
+ if (!found && this._scanTypeInfoUse(c, names)) found = true;
67
+ });
68
+ return found;
69
+ },
70
+
71
+ _nodeUsesTypeInfo(node, names) {
72
+ if (node._ifaceKeyDyn && names.has(node._ifaceKeyParam)) return true;
73
+ switch (node.kind) {
74
+ case "TypeAssertExpr":
75
+ return typeNodeMentions(node.type, names);
76
+ case "TypeSwitchStmt":
77
+ return node.cases.some((c) => typeNodeMentions(c.types, names));
78
+ case "TypeConversion":
79
+ return typeNodeMentions(node.targetType, names);
80
+ case "BinaryExpr":
81
+ return (
82
+ (node.op === "==" || node.op === "!=") &&
83
+ node.left._type?.kind === "typeParam"
84
+ );
85
+ case "CallExpr":
86
+ return this._callUsesTypeInfo(node);
87
+ case "CompositeLit":
88
+ case "GenericTypeName":
89
+ return typeArgsMention(node);
90
+ default:
91
+ return false;
92
+ }
93
+ },
94
+
95
+ _callUsesTypeInfo(call) {
96
+ if (call._isTypeConversion)
97
+ return call._conversionTargetType?.kind === "typeParam";
98
+ // A fmt operand of a type parameter type is formatted by its instantiated
99
+ // type (a Stringer type argument), see _fmtArgJs.
100
+ if (
101
+ call.func?.kind === "SelectorExpr" &&
102
+ call.func.expr?.name === "fmt" &&
103
+ call.args.some((a) => a?._type?.kind === "typeParam")
104
+ )
105
+ return true;
106
+ const site = call._genericFnType ? call : call.func;
107
+ if (!site?._genericFnType) return false;
108
+ if (!site._typeArgs?.some((t) => mentionsTypeParam(t))) return false;
109
+ return this._genericFnNeedsTypeInfo(site._genericFnType);
110
+ },
111
+
112
+ // Whether callers of generic function `generic` pass type information.
113
+ _genericFnNeedsTypeInfo(generic) {
114
+ const decl = generic?.declNode;
115
+ if (!decl) return false;
116
+ return this._declNeedsTypeInfo(
117
+ decl,
118
+ (decl.typeParams ?? []).map((tp) => tp.name),
119
+ );
120
+ },
121
+
122
+ // Whether instances of generic type `named` carry type information: any
123
+ // of its methods needs it.
124
+ _genericTypeNeedsTypeInfo(named) {
125
+ const generic = named?._generic;
126
+ if (!generic?.methods) return false;
127
+ const names = generic.typeParams.map((tp) => tp.name);
128
+ for (const m of generic.methods.values())
129
+ if (m?._decl && this._declNeedsTypeInfo(m._decl, names)) return true;
130
+ return false;
131
+ },
132
+
133
+ // The factory a generic callee receives for type argument `t`, carrying
134
+ // the type's kind, identity and type test.
135
+ _typeInfoFactory(t) {
136
+ if (t?.kind === "typeParam") {
137
+ const bound = this._typeParamZeroFactory(t.name);
138
+ if (bound) return bound;
139
+ }
140
+ this.useHelper("generics");
141
+ const base = this.zeroFactoryFor(t);
142
+ const u = this._underlyingOf(t);
143
+ let k = "";
144
+ if (isAny(u) || u?.kind === "interface") k = "iface";
145
+ else if (u?.kind === "basic") k = u.name;
146
+ const check = this._typeCheckForType(t, "__v");
147
+ const is = check ? `(__v) => ${check}` : "null";
148
+ const unbox = this._boxesInInterface(t) ? "true" : "false";
149
+ const box = this._ifaceKeyBox(t);
150
+ const fmtBox = this._fmtBoxFor(t);
151
+ const tail = fmtBox
152
+ ? `, ${box ?? "null"}, ${fmtBox}`
153
+ : box
154
+ ? `, ${box}`
155
+ : "";
156
+ return `__ti(${base}, ${JSON.stringify(k)}, ${JSON.stringify(typeStr(t))}, ${is}, ${unbox}${tail})`;
157
+ },
158
+
159
+ // A function boxing a plain value of named basic type `t`, as converting
160
+ // it to an interface does; null when `t` is not boxed.
161
+ _ifaceKeyBox(t) {
162
+ if (!this._boxesInInterface(t) || t?.kind !== "named") return null;
163
+ return `(__v) => ${this._genNamedIfaceBox("__v", t, false)}`;
164
+ },
165
+
166
+ // A function giving a plain value of type `t` the type information fmt
167
+ // needs once it is an `any` operand (see _fmtDesc); null when it needs none.
168
+ _fmtBoxFor(t) {
169
+ if (!t || t.kind === "typeParam" || mentionsTypeParam(t)) return null;
170
+ if (this._ifaceKeyBox(t)) return null; // boxed by the key box already
171
+ const u = this._underlyingOf(t);
172
+ if (t.kind === "pointer" && this._underlyingOf(t.base)?.kind === "struct") {
173
+ this.useHelper("ifaceBox");
174
+ return "(__v) => __ifp(__v)";
175
+ }
176
+ const desc = this._fmtDesc(t);
177
+ if (desc) this.useHelper("ifd");
178
+ const tagged = (v) => (desc ? `__ifd(${v}, ${desc})` : v);
179
+ if (u?.kind === "map") {
180
+ this.useHelper("ifaceBox");
181
+ return `(__v) => __ifm(${tagged("__v")})`;
182
+ }
183
+ if (u?.kind === "slice") return `(__v) => ${tagged("(__v ?? [])")}`;
184
+ return desc ? `(__v) => ${tagged("__v")}` : null;
185
+ },
186
+
187
+ _underlyingOf(t) {
188
+ let u = t;
189
+ for (let i = 0; i < 8 && u?.kind === "named"; i++) u = u.underlying;
190
+ return u;
191
+ },
192
+
193
+ // A named non-struct value (or pointer to one) is boxed in an interface.
194
+ _boxesInInterface(t) {
195
+ const base = t?.kind === "pointer" ? t.base : t;
196
+ return Boolean(this._namedBoxInfo(base));
197
+ },
198
+
199
+ // JS test that `val` holds a value of type `t`, or null when it cannot be
200
+ // told at run time.
201
+ _typeCheckForType(t, val) {
202
+ switch (t?.kind) {
203
+ case "basic":
204
+ return this._typeNameCheckExpr(t.name, val);
205
+ case "named":
206
+ return this._namedTypeCheck(t, val);
207
+ case "pointer":
208
+ return this._pointerTypeCheck(t.base, val);
209
+ case "interface":
210
+ return t.methods?.size
211
+ ? this._ifaceMethodCheck(
212
+ val,
213
+ [...t.methods.keys()],
214
+ this._unwrapIsMulti(t.methods.get("Unwrap")),
215
+ )
216
+ : null;
217
+ case "typeParam": {
218
+ const f = this._typeParamZeroFactory(t.name);
219
+ return f ? `(${f}?.is?.(${val}) ?? true)` : null;
220
+ }
221
+ default:
222
+ return null;
223
+ }
224
+ },
225
+
226
+ _namedTypeCheck(t, val) {
227
+ if (t.typeArgs?.length) {
228
+ const generic = this._genericInstanceCheck(
229
+ { kind: "GenericTypeName", name: t.name, _typeArgs: t.typeArgs },
230
+ val,
231
+ );
232
+ if (generic) return generic;
233
+ }
234
+ const u = this._underlyingOf(t);
235
+ if (u?.kind === "interface" && t.name !== "error")
236
+ return this._ifaceMethodCheck(
237
+ val,
238
+ [...(u.methods?.keys() ?? [])],
239
+ this._unwrapIsMulti(u.methods?.get("Unwrap")),
240
+ );
241
+ return this._typeNameCheckExpr(t.name, val);
242
+ },
243
+
244
+ _pointerTypeCheck(base, val) {
245
+ if (base?.kind !== "named") return null;
246
+ const node = base.typeArgs?.length
247
+ ? { kind: "GenericTypeName", name: base.name, _typeArgs: base.typeArgs }
248
+ : { kind: "TypeName", name: base.name };
249
+ const check = this._typeCheckExpr({ kind: "PointerType", base: node }, val);
250
+ return check === "true" ? null : check;
251
+ },
252
+
253
+ // Runtime instance id of a generic type's instantiation `typeArgs` that
254
+ // mention type parameters: the callee's type arguments joined as
255
+ // `instanceId` prints them; null when it cannot be built.
256
+ _runtimeInstanceId(typeArgs) {
257
+ if (!typeArgs?.length) return null;
258
+ const parts = [];
259
+ for (const t of typeArgs) {
260
+ if (!mentionsTypeParam(t)) {
261
+ parts.push(JSON.stringify(typeStr(t)));
262
+ continue;
263
+ }
264
+ const f =
265
+ t.kind === "typeParam" ? this._typeParamZeroFactory(t.name) : null;
266
+ if (!f) return null;
267
+ parts.push(`${f}?.id`);
268
+ }
269
+ this.useHelper("generics");
270
+ return `__tid(${parts.join(", ")})`;
271
+ },
272
+ };
@@ -0,0 +1,188 @@
1
+ // Static type descriptors for `fmt` (Task 3.1c), shared by the JS and WASM
2
+ // backends. `%T` and `%#v` need the Go type of an operand, which the runtime
3
+ // values do not carry (an `int` and a `float64` holding 3 are both the number
4
+ // 3, a `Color` is a raw number inside a slice). Where a format string is a
5
+ // literal that needs them (`fmtWantsTypes`), codegen passes the operand's
6
+ // static type next to its value as a plain JSON-compatible tree, which
7
+ // `__sprintf` reads (`__ft` on JS, `fmt_arg_type` on WASM).
8
+ //
9
+ // Descriptor shapes (`k`): `b` basic `{ n }`, `named` `{ n, u }` (a named
10
+ // non-struct type, `u` its underlying type), `struct` `{ n, f: [[name, T]] }`
11
+ // (`n` is "" for an anonymous struct), `ptr` `{ e }`, `slice` `{ e }`,
12
+ // `array` `{ e, len }`, `map` `{ key, val }`, `i` an interface `{ n }`,
13
+ // `func` `{ p, r }`, `chan` `{ e }`. A type that is too deep or unknown is
14
+ // `null`; the runtime then derives a best-effort type from the value.
15
+
16
+ const MAX_DEPTH = 4;
17
+ const FLAGS = "#+\\- 0";
18
+ const VERBS = "vTtcdoObxXUeEfFgGsqp";
19
+
20
+ const qualify = (name) => (name.includes(".") ? name : `main.${name}`);
21
+
22
+ /**
23
+ * @param {any} t checker type
24
+ * @param {(t: any) => any} [resolve] resolves a type reference
25
+ * @param {number} [depth]
26
+ * @returns {object | null}
27
+ */
28
+ export function fmtTypeDesc(t, resolve = (x) => x, depth = 0) {
29
+ t = resolve(t) ?? t;
30
+ if (!t) return null;
31
+ const sub = (x) => fmtTypeDesc(x, resolve, depth + 1);
32
+ switch (t.kind) {
33
+ case "basic":
34
+ if (t.name === "any") return { k: "i", n: "interface {}" };
35
+ if (t.name === "nil" || t.name === "void") return null;
36
+ return { k: "b", n: t.name };
37
+ case "untyped":
38
+ return { k: "b", n: t.base };
39
+ case "interface":
40
+ return { k: "i", n: t.name ? qualify(t.name) : "interface {}" };
41
+ case "named":
42
+ return namedDesc(t, resolve, depth, sub);
43
+ case "struct":
44
+ return depth > MAX_DEPTH ? null : structDesc("", t, sub);
45
+ case "pointer":
46
+ return depth > MAX_DEPTH ? null : { k: "ptr", e: sub(t.base) };
47
+ case "slice":
48
+ return depth > MAX_DEPTH ? null : { k: "slice", e: sub(t.elem) };
49
+ case "array":
50
+ return depth > MAX_DEPTH
51
+ ? null
52
+ : { k: "array", e: sub(t.elem), len: t.size ?? 0 };
53
+ case "map":
54
+ return depth > MAX_DEPTH
55
+ ? null
56
+ : { k: "map", key: sub(t.key), val: sub(t.value) };
57
+ case "func":
58
+ return {
59
+ k: "func",
60
+ p: (t.params ?? []).map(sub),
61
+ r: (t.returns ?? []).map(sub),
62
+ v: Boolean(t.variadic),
63
+ };
64
+ case "chan":
65
+ return { k: "chan", e: sub(t.elem) };
66
+ default:
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * The fields a struct declares itself, in declaration order: named fields and
73
+ * one entry per embedded type. Fields promoted from an embedded struct are
74
+ * not listed (`fields` also holds them for selector lookup).
75
+ * @param {any} st checker struct type
76
+ * @returns {[string, any][]}
77
+ */
78
+ export function structOwnFields(st) {
79
+ if (!st._structFields) return [...(st.fields ?? [])];
80
+ const own = [];
81
+ for (const sf of st._structFields) {
82
+ if (!sf.embedded) {
83
+ for (const n of sf.names) own.push([n, st.fields.get(n)]);
84
+ continue;
85
+ }
86
+ const inner = sf.type?.kind === "pointer" ? sf.type.base : sf.type;
87
+ const name = (
88
+ sf.embeddedName ??
89
+ sf.astType?.name ??
90
+ sf.type?.name ??
91
+ sf.type?.base?.name ??
92
+ (inner?.kind === "named" ? inner.name : "")
93
+ )
94
+ .split(".")
95
+ .pop();
96
+ if (name) own.push([name, st.fields.get(name) ?? sf.type]);
97
+ }
98
+ return own;
99
+ }
100
+
101
+ function structDesc(n, st, sub) {
102
+ const f = [];
103
+ for (const [name, ft] of structOwnFields(st)) f.push([name, sub(ft)]);
104
+ return { k: "struct", n, f };
105
+ }
106
+
107
+ function namedDesc(t, resolve, depth, sub) {
108
+ const n = qualify(t.name ?? "");
109
+ const u = resolve(t.underlying) ?? t.underlying;
110
+ if (!u) return { k: "named", n, u: null };
111
+ if (u.kind === "struct")
112
+ return depth > MAX_DEPTH
113
+ ? { k: "struct", n, f: null }
114
+ : structDesc(n, u, sub);
115
+ if (u.kind === "interface") return { k: "i", n };
116
+ return { k: "named", n, u: depth > MAX_DEPTH ? null : sub(u) };
117
+ }
118
+
119
+ /**
120
+ * Whether a type descriptor mentions a float32 or float64. Such operands
121
+ * travel with their static type: a number alone cannot tell `float64(1e6)`
122
+ * (Go prints 1e+06) from an `int`, or a float32 from the float64 holding the
123
+ * same bits.
124
+ * @param {any} desc result of fmtTypeDesc
125
+ * @returns {boolean}
126
+ */
127
+ export function fmtDescHasFloat(desc) {
128
+ if (!desc || typeof desc !== "object") return false;
129
+ switch (desc.k) {
130
+ case "b":
131
+ return desc.n === "float32" || desc.n === "float64";
132
+ case "named":
133
+ return fmtDescHasFloat(desc.u);
134
+ case "struct":
135
+ return (desc.f ?? []).some(([, ft]) => fmtDescHasFloat(ft));
136
+ case "ptr":
137
+ case "slice":
138
+ case "array":
139
+ return fmtDescHasFloat(desc.e);
140
+ case "map":
141
+ return fmtDescHasFloat(desc.key) || fmtDescHasFloat(desc.val);
142
+ default:
143
+ return false;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Whether a type descriptor is a slice or array (possibly named). `%s`, `%q`
149
+ * and `%x` read a byte slice as text, which a plain list of numbers cannot
150
+ * tell from any other slice of small numbers.
151
+ * @param {any} desc result of fmtTypeDesc
152
+ * @returns {boolean}
153
+ */
154
+ export function fmtDescIsSeq(desc) {
155
+ let d = desc;
156
+ while (d?.k === "named") d = d.u;
157
+ return d?.k === "slice" || d?.k === "array";
158
+ }
159
+
160
+ /**
161
+ * Whether a call with this literal format string needs operand types: `%T`,
162
+ * `%#v`, an unknown verb (`%!z(int=5)`), or more operands than verbs
163
+ * (`%!(EXTRA int=1)`).
164
+ * @param {unknown} format
165
+ * @param {number} nargs operand count, -1 when not known statically
166
+ * @param {boolean} [seqArgs] some operand is a slice or array, whose element
167
+ * type decides how `%s`, `%q`, `%x` and `%X` read it
168
+ */
169
+ export function fmtWantsTypes(format, nargs, seqArgs = false) {
170
+ if (typeof format !== "string") return false;
171
+ const re = new RegExp(
172
+ `%([${FLAGS}]*)((?:\\[\\d+\\])?(?:\\*|\\d+)?(?:\\.(?:\\[\\d+\\])?(?:\\*|\\d*))?(?:\\[\\d+\\])?)([^]?)`,
173
+ "g",
174
+ );
175
+ let count = 0;
176
+ let indexed = false;
177
+ for (const m of format.matchAll(re)) {
178
+ const verb = m[3];
179
+ if (verb === "%") continue;
180
+ if (verb === "T" || verb === "p") return true;
181
+ if (verb === "v" && m[1].includes("#")) return true;
182
+ if (seqArgs && "sqxX".includes(verb)) return true;
183
+ if (verb !== "" && !VERBS.includes(verb) && verb !== "w") return true;
184
+ if (/[[*]/.test(m[2])) indexed = true;
185
+ count++;
186
+ }
187
+ return nargs >= 0 && !indexed && nargs > count;
188
+ }