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
package/src/backend/js/templ.js
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
// Mounts an HTML/expression tree onto a parent DOM node by emitting plain
|
|
3
3
|
// document.createElement / appendChild / setAttribute calls.
|
|
4
4
|
|
|
5
|
-
import { Parser } from "../../parser/index.js";
|
|
6
|
-
import { T, Token } from "../../tokens.js";
|
|
7
|
-
|
|
8
5
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
9
6
|
|
|
10
7
|
/** @type {ThisType<CodeGen>} */
|
|
@@ -17,6 +14,12 @@ export const templGenMethods = {
|
|
|
17
14
|
.join(", ");
|
|
18
15
|
this.line(`function ${decl.name}(${params}) {`);
|
|
19
16
|
this.indented(() => {
|
|
17
|
+
// `@W(xs...)` passes `{ __sp: xs }` like any Go variadic call.
|
|
18
|
+
const last = decl.params[decl.params.length - 1];
|
|
19
|
+
if (last?.variadic && last.name && last.name !== "_") {
|
|
20
|
+
this.useHelper("va");
|
|
21
|
+
this.line(`${last.name} = __va(${last.name});`);
|
|
22
|
+
}
|
|
20
23
|
this.line(`return {Mount(___p, ___refs) {`);
|
|
21
24
|
this.indented(() => {
|
|
22
25
|
this._genTemplNodes(decl.body, "___p");
|
|
@@ -79,7 +82,7 @@ export const templGenMethods = {
|
|
|
79
82
|
);
|
|
80
83
|
case "TemplExpr":
|
|
81
84
|
return this.line(
|
|
82
|
-
`${p}.appendChild(document.createTextNode(String(${this.
|
|
85
|
+
`${p}.appendChild(document.createTextNode(String(${this._genTemplExpr(node, '""')})));`,
|
|
83
86
|
);
|
|
84
87
|
case "TemplComponent":
|
|
85
88
|
return this._genTemplComponentMount(node, p);
|
|
@@ -101,20 +104,12 @@ export const templGenMethods = {
|
|
|
101
104
|
|
|
102
105
|
_genTemplComponentMount(node, p) {
|
|
103
106
|
// Special case: @templ.Raw(expr) → insertAdjacentHTML
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
toks.length >= 4 &&
|
|
107
|
-
toks[0]?.value === "templ" &&
|
|
108
|
-
toks[1]?.type === T.DOT &&
|
|
109
|
-
toks[2]?.value === "Raw" &&
|
|
110
|
-
toks[3]?.type === T.LPAREN
|
|
111
|
-
) {
|
|
112
|
-
const argTokens = toks.slice(4, toks.length - 1);
|
|
113
|
-
const argJs = this._genTemplTokenExpr(argTokens);
|
|
107
|
+
if (node.raw) {
|
|
108
|
+
const argJs = this._genTemplExpr(node, '""');
|
|
114
109
|
return this.line(`${p}.insertAdjacentHTML("beforeend", ${argJs});`);
|
|
115
110
|
}
|
|
116
111
|
return this.line(
|
|
117
|
-
`(${this.
|
|
112
|
+
`(${this._genTemplExpr(node, "null")}).Mount(${p}, ___refs);`,
|
|
118
113
|
);
|
|
119
114
|
},
|
|
120
115
|
|
|
@@ -160,7 +155,7 @@ export const templGenMethods = {
|
|
|
160
155
|
);
|
|
161
156
|
}
|
|
162
157
|
} else if (attr.kind === "expr") {
|
|
163
|
-
const exprJs = this.
|
|
158
|
+
const exprJs = this._genTemplExpr(attr, '""');
|
|
164
159
|
if (name === "class") {
|
|
165
160
|
this.line(setClass(exprJs));
|
|
166
161
|
} else {
|
|
@@ -171,7 +166,7 @@ export const templGenMethods = {
|
|
|
171
166
|
} else if (attr.kind === "bool") {
|
|
172
167
|
this.line(`${el}.setAttribute(${JSON.stringify(name)}, "");`);
|
|
173
168
|
} else if (attr.kind === "cond-bool") {
|
|
174
|
-
const exprJs = this.
|
|
169
|
+
const exprJs = this._genTemplExpr(attr, '""');
|
|
175
170
|
this.line(
|
|
176
171
|
`if(${exprJs})${el}.setAttribute(${JSON.stringify(name)}, "");`,
|
|
177
172
|
);
|
|
@@ -179,7 +174,7 @@ export const templGenMethods = {
|
|
|
179
174
|
},
|
|
180
175
|
|
|
181
176
|
_genTemplIf(node, p) {
|
|
182
|
-
const condJs = this.
|
|
177
|
+
const condJs = this._genTemplExpr(node, "false");
|
|
183
178
|
this.line(`if (${condJs}) {`);
|
|
184
179
|
this.indented(() => this._genTemplNodes(node.then, p));
|
|
185
180
|
this._genTemplIfElse(node.else_, p);
|
|
@@ -192,7 +187,7 @@ export const templGenMethods = {
|
|
|
192
187
|
}
|
|
193
188
|
if (else_.length === 1 && else_[0].kind === "TemplIf") {
|
|
194
189
|
const elseIf = else_[0];
|
|
195
|
-
this.line(`} else if (${this.
|
|
190
|
+
this.line(`} else if (${this._genTemplExpr(elseIf, "false")}) {`);
|
|
196
191
|
this.indented(() => this._genTemplNodes(elseIf.then, p));
|
|
197
192
|
this._genTemplIfElse(elseIf.else_, p);
|
|
198
193
|
} else {
|
|
@@ -203,16 +198,16 @@ export const templGenMethods = {
|
|
|
203
198
|
},
|
|
204
199
|
|
|
205
200
|
_genTemplSwitch(node, p) {
|
|
206
|
-
const {
|
|
201
|
+
const { cases } = node;
|
|
207
202
|
if (cases.length === 0) return;
|
|
208
|
-
const exprJs = this.
|
|
203
|
+
const exprJs = this._genTemplExpr(node, '""');
|
|
209
204
|
this.line(`switch (${exprJs}) {`);
|
|
210
205
|
this.indented(() => {
|
|
211
206
|
for (const c of cases) {
|
|
212
207
|
const label =
|
|
213
208
|
c.caseTokens === null
|
|
214
209
|
? "default"
|
|
215
|
-
: `case ${this.
|
|
210
|
+
: `case ${this._genTemplExpr(c, '""')}`;
|
|
216
211
|
this.line(`${label}: {`);
|
|
217
212
|
this.indented(() => this._genTemplNodes(c.body, p));
|
|
218
213
|
this.line(`break; }`);
|
|
@@ -222,35 +217,10 @@ export const templGenMethods = {
|
|
|
222
217
|
},
|
|
223
218
|
|
|
224
219
|
_genTemplFor(node, p) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
while (tokens.length > 0 && tokens[tokens.length - 1].type === T.SEMICOLON)
|
|
230
|
-
tokens = tokens.slice(0, -1);
|
|
231
|
-
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
232
|
-
const lbrace = new Token(T.LBRACE, "{", 1, 1);
|
|
233
|
-
const rbrace = new Token(T.RBRACE, "}", 1, 1);
|
|
234
|
-
const semi = new Token(T.SEMICOLON, ";", 1, 1);
|
|
235
|
-
const withFor =
|
|
236
|
-
tokens[0]?.type === T.FOR
|
|
237
|
-
? [...tokens, lbrace, rbrace, semi, eofTok]
|
|
238
|
-
: [
|
|
239
|
-
new Token(T.FOR, "for", 1, 1),
|
|
240
|
-
...tokens,
|
|
241
|
-
lbrace,
|
|
242
|
-
rbrace,
|
|
243
|
-
semi,
|
|
244
|
-
eofTok,
|
|
245
|
-
];
|
|
246
|
-
try {
|
|
247
|
-
const p2 = new Parser(withFor);
|
|
248
|
-
const forStmt = p2.parseFor();
|
|
249
|
-
this._genTemplRangeFor(forStmt, node.body, p);
|
|
250
|
-
} catch {
|
|
251
|
-
// Fallback for non-range loops: emit a comment + skip
|
|
252
|
-
this.line(`/* templ for: ${this._tokensToSource(tokens)} */`);
|
|
253
|
-
}
|
|
220
|
+
if (node.forError) throw node.forError;
|
|
221
|
+
if (!node.forStmt)
|
|
222
|
+
throw new Error("templ for header was not parsed by TemplParser");
|
|
223
|
+
this._genTemplRangeFor(node.forStmt, node.body, p);
|
|
254
224
|
},
|
|
255
225
|
|
|
256
226
|
_genTemplRangeFor(forStmt, templBody, mountParent) {
|
|
@@ -290,37 +260,29 @@ export const templGenMethods = {
|
|
|
290
260
|
return `for (const [${keyName}, ${valName}] of __s(${iterJs}).entries()) {`;
|
|
291
261
|
},
|
|
292
262
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
263
|
+
// Emit JS for the Go expression TemplParser parsed onto `node`. `emptyJs` is
|
|
264
|
+
// the value when the node holds no tokens. A parse error is raised here,
|
|
265
|
+
// not in the parser, so it surfaces at the same point as it always did.
|
|
266
|
+
_genTemplExpr(node, emptyJs) {
|
|
267
|
+
if (node.exprError) throw node.exprError;
|
|
268
|
+
if (node.expr === undefined)
|
|
269
|
+
throw new Error("templ expression was not parsed by TemplParser");
|
|
270
|
+
return node.expr ? this.genExpr(node.expr) : emptyJs;
|
|
300
271
|
},
|
|
301
272
|
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const ast = p.parseExpr();
|
|
308
|
-
return this.genExpr(ast);
|
|
273
|
+
// Rename handlers for `_renameReservedIdents`: templ bodies keep their Go
|
|
274
|
+
// expressions in non-enumerable props (see parser/templ.js), which its
|
|
275
|
+
// generic walk does not visit, so `class` in `{ class }` would stay unsafe.
|
|
276
|
+
_renameTemplExprNode(node) {
|
|
277
|
+
this._renameReservedIdents(node.expr);
|
|
309
278
|
},
|
|
310
279
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (!tokens || tokens.length === 0) return "false";
|
|
314
|
-
let toks = tokens;
|
|
315
|
-
if (toks[0]?.type === T.IF) toks = toks.slice(1); // skip `if` keyword
|
|
316
|
-
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
317
|
-
const p = new Parser([...toks, eofTok]);
|
|
318
|
-
const ast = p.parseExpr();
|
|
319
|
-
return this.genExpr(ast);
|
|
280
|
+
_renameTemplForNode(node) {
|
|
281
|
+
this._renameReservedIdents(node.forStmt);
|
|
320
282
|
},
|
|
321
283
|
|
|
322
|
-
|
|
323
|
-
|
|
284
|
+
_renameTemplElementNode(node) {
|
|
285
|
+
for (const attr of node.attrs ?? []) this._renameReservedIdents(attr.expr);
|
|
324
286
|
},
|
|
325
287
|
|
|
326
288
|
// Fresh variable name counter
|
|
@@ -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
|
+
}
|