gofront 1.7.0 → 1.9.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 +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -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 +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +500 -0
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +526 -0
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /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
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
// src/lower/fmt-formatters.js
|
|
2
|
+
// The compile-time half of package fmt, as generated Go source.
|
|
3
|
+
//
|
|
4
|
+
// Go's fmt asks reflection for the type of every operand. GoFront knows it
|
|
5
|
+
// statically, so for each call of the Print family this generator writes the
|
|
6
|
+
// description package fmt reads (`fmtrt.Arg`, see stdlib/internal/fmtrt):
|
|
7
|
+
//
|
|
8
|
+
// - a call with an operand whose runtime representation does not tell its
|
|
9
|
+
// type (a float64 that holds 3, an int8, a named type, a struct) is
|
|
10
|
+
// redirected to a wrapper `gff_<pkg>_<Fn>_<n>` taking the operands with
|
|
11
|
+
// their static types and passing each as a description;
|
|
12
|
+
// - a package that stores values in interfaces that fmt prints registers a
|
|
13
|
+
// lookup, `gff_<pkg>_value`, a type switch over the types it can build,
|
|
14
|
+
// with `fmtrt.Register`.
|
|
15
|
+
//
|
|
16
|
+
// The functions are ordinary Go, parsed and checked as part of the package's
|
|
17
|
+
// generated file (src/link/stdlib.js), so both backends compile the same
|
|
18
|
+
// code.
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
ANY,
|
|
22
|
+
defaultType,
|
|
23
|
+
embeddedFieldName,
|
|
24
|
+
FMT_PRINT_FUNCS,
|
|
25
|
+
LOCAL_TYPE_SUFFIX,
|
|
26
|
+
} from "../typechecker/types.js";
|
|
27
|
+
import {
|
|
28
|
+
collectProgramTypes,
|
|
29
|
+
importsFor,
|
|
30
|
+
isEmptyIface,
|
|
31
|
+
mentionsTypeParam,
|
|
32
|
+
results,
|
|
33
|
+
TypeSpeller,
|
|
34
|
+
under,
|
|
35
|
+
walkNodes,
|
|
36
|
+
} from "./gen-types.js";
|
|
37
|
+
import { holdsFunc, isCompositeType } from "./json-codecs.js";
|
|
38
|
+
|
|
39
|
+
const FMT_PATH = "fmt";
|
|
40
|
+
const RT_PATH = "internal/fmtrt";
|
|
41
|
+
const JSON_PATH = "encoding/json";
|
|
42
|
+
|
|
43
|
+
// The Print family: fn -> what precedes the operands.
|
|
44
|
+
const PRINT_FUNCS = new Map([
|
|
45
|
+
["Sprint", null],
|
|
46
|
+
["Sprintln", null],
|
|
47
|
+
["Print", null],
|
|
48
|
+
["Println", null],
|
|
49
|
+
["Fprint", "io.Writer"],
|
|
50
|
+
["Fprintln", "io.Writer"],
|
|
51
|
+
["Append", "[]byte"],
|
|
52
|
+
["Appendln", "[]byte"],
|
|
53
|
+
["Sprintf", null],
|
|
54
|
+
["Printf", null],
|
|
55
|
+
["Errorf", null],
|
|
56
|
+
["Fprintf", "io.Writer"],
|
|
57
|
+
["Appendf", "[]byte"],
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
// The functions that take a format string after the prefix.
|
|
61
|
+
const isFormatted = (fn) => fn.endsWith("f");
|
|
62
|
+
|
|
63
|
+
const RESULT = {
|
|
64
|
+
Sprint: "string",
|
|
65
|
+
Sprintln: "string",
|
|
66
|
+
Print: "(int, error)",
|
|
67
|
+
Println: "(int, error)",
|
|
68
|
+
Fprint: "(int, error)",
|
|
69
|
+
Fprintln: "(int, error)",
|
|
70
|
+
Append: "[]byte",
|
|
71
|
+
Appendln: "[]byte",
|
|
72
|
+
Sprintf: "string",
|
|
73
|
+
Printf: "(int, error)",
|
|
74
|
+
Errorf: "error",
|
|
75
|
+
Fprintf: "(int, error)",
|
|
76
|
+
Appendf: "[]byte",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Where a wrapper registers the package's lookup (when it has one) before it
|
|
80
|
+
// formats: a package-level initialiser that prints runs before the file's own.
|
|
81
|
+
const ENSURE = "{{ensure}}";
|
|
82
|
+
// The key the lookup registers under: a hash of the generated source.
|
|
83
|
+
const KEY = "{{key}}";
|
|
84
|
+
|
|
85
|
+
const ALIAS = { byte: "uint8", rune: "int32" };
|
|
86
|
+
const unalias = (n) => ALIAS[n] ?? n;
|
|
87
|
+
const q = (s) => JSON.stringify(s);
|
|
88
|
+
|
|
89
|
+
const INT_KINDS = new Set(["int", "int8", "int16", "int32", "int64"]);
|
|
90
|
+
const UINT_KINDS = new Set([
|
|
91
|
+
"uint",
|
|
92
|
+
"uint8",
|
|
93
|
+
"uint16",
|
|
94
|
+
"uint32",
|
|
95
|
+
"uint64",
|
|
96
|
+
"uintptr",
|
|
97
|
+
]);
|
|
98
|
+
const FORMAT_METHODS = { Err: "Error", Str: "String", GoStr: "GoString" };
|
|
99
|
+
|
|
100
|
+
// A type of another package that has no exported name: the generated file
|
|
101
|
+
// cannot spell it.
|
|
102
|
+
function isUnexportedForeign(name) {
|
|
103
|
+
const at = name.indexOf("$$");
|
|
104
|
+
return at > 0 && !/^\p{Lu}/u.test(name);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The types of package testing: the lookup of a package that logs through a
|
|
108
|
+
// *testing.T never describes the test state itself.
|
|
109
|
+
const HOST_TYPE = /^testing\.|\$\$testing$/;
|
|
110
|
+
|
|
111
|
+
const isInterface = (t) =>
|
|
112
|
+
t?.kind === "nil" ||
|
|
113
|
+
(t?.kind === "basic" && (t.name === "nil" || t.name === "any")) ||
|
|
114
|
+
(t?.kind !== "basic" && under(t)?.kind === "interface");
|
|
115
|
+
|
|
116
|
+
// An interface whose dynamic values fmt formats through their own methods
|
|
117
|
+
// (error, Stringer), so no description of their type is needed.
|
|
118
|
+
function isMethodOnly(t) {
|
|
119
|
+
const u = under(t);
|
|
120
|
+
if (u?.kind !== "interface" || !u.methods || u.methods.size === 0)
|
|
121
|
+
return false;
|
|
122
|
+
return [...u.methods.keys()].every((m) => m in { Error: 1, String: 1 });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function methodOf(t, name) {
|
|
126
|
+
if (t?.kind !== "named") return null;
|
|
127
|
+
return t.methods?.get(name) ?? t.underlying?.methods?.get(name) ?? null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const returnsString = (m) => {
|
|
131
|
+
const r = results(m);
|
|
132
|
+
return (
|
|
133
|
+
m.params?.length === 0 &&
|
|
134
|
+
r.length === 1 &&
|
|
135
|
+
under(r[0])?.kind === "basic" &&
|
|
136
|
+
under(r[0]).name === "string"
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
class FmtGen extends TypeSpeller {
|
|
141
|
+
constructor(checker, pkgName, qualifiers, programs, formatted = false) {
|
|
142
|
+
super(pkgName, qualifiers, programs);
|
|
143
|
+
this.checker = checker;
|
|
144
|
+
// A package with a format call: %d and %+v call no methods, so an
|
|
145
|
+
// error or Stringer in an interface is described by its type too.
|
|
146
|
+
this.formatted = formatted;
|
|
147
|
+
this.prefix = `gff_${String(pkgName).replace(/[^A-Za-z0-9_]/g, "_")}_`;
|
|
148
|
+
this.builders = new Map(); // key -> function name
|
|
149
|
+
this.wrappers = new Map(); // signature -> function name
|
|
150
|
+
this.queue = [];
|
|
151
|
+
this.out = [];
|
|
152
|
+
this.counter = 0;
|
|
153
|
+
this.needsLookup = false;
|
|
154
|
+
this.uses = new Set(); // imports the generated code spells itself
|
|
155
|
+
this.topLevel = new Set();
|
|
156
|
+
for (const p of programs)
|
|
157
|
+
for (const d of p.decls ?? [])
|
|
158
|
+
if (d.kind === "TypeDecl") this.topLevel.add(d.name);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Type naming ─────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
key(t) {
|
|
164
|
+
if (!t) return "?";
|
|
165
|
+
const k = (x) => this.key(x);
|
|
166
|
+
const byKind = {
|
|
167
|
+
basic: () => unalias(t.name),
|
|
168
|
+
named: () =>
|
|
169
|
+
t.typeArgs?.length
|
|
170
|
+
? `${t.name}[${t.typeArgs.map(k).join(",")}]`
|
|
171
|
+
: t.name,
|
|
172
|
+
pointer: () => `*${k(t.base)}`,
|
|
173
|
+
slice: () => `[]${k(t.elem)}`,
|
|
174
|
+
array: () => `[${t.size}]${k(t.elem)}`,
|
|
175
|
+
map: () => `map[${k(t.key)}]${k(t.value)}`,
|
|
176
|
+
struct: () => this.structKey(t),
|
|
177
|
+
interface: () => (isEmptyIface(t) ? "any" : `iface:${t.name ?? ""}`),
|
|
178
|
+
func: () => this.funcString(t, k),
|
|
179
|
+
};
|
|
180
|
+
return (byKind[t.kind] ?? (() => `?${t.kind}`))();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
structKey(t) {
|
|
184
|
+
const fields = (t._structFields ?? []).map(
|
|
185
|
+
(f) =>
|
|
186
|
+
`${(f.names ?? []).join(",")}:${this.key(f.type)}:${f.embedded ? 1 : 0}`,
|
|
187
|
+
);
|
|
188
|
+
return `struct{${fields.join(";")}}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// How %T prints the type.
|
|
192
|
+
typeName(t) {
|
|
193
|
+
const n = (x) => this.typeName(x);
|
|
194
|
+
switch (t?.kind) {
|
|
195
|
+
case "basic":
|
|
196
|
+
return t.name === "any" ? "interface {}" : unalias(t.name);
|
|
197
|
+
case "named": {
|
|
198
|
+
let name = t.name.replace(LOCAL_TYPE_SUFFIX, "");
|
|
199
|
+
const at = name.indexOf("$$");
|
|
200
|
+
if (at >= 0)
|
|
201
|
+
name = `${name
|
|
202
|
+
.slice(at + 2)
|
|
203
|
+
.split("$")
|
|
204
|
+
.at(-1)}.${name.slice(0, at)}`;
|
|
205
|
+
else if (!name.includes("."))
|
|
206
|
+
name = `${this.declaringPackage(t)}.${name}`;
|
|
207
|
+
return t.typeArgs?.length
|
|
208
|
+
? `${name}[${t.typeArgs.map(n).join(",")}]`
|
|
209
|
+
: name;
|
|
210
|
+
}
|
|
211
|
+
case "pointer":
|
|
212
|
+
return `*${n(t.base)}`;
|
|
213
|
+
case "slice":
|
|
214
|
+
return `[]${n(t.elem)}`;
|
|
215
|
+
case "array":
|
|
216
|
+
return `[${t.size}]${n(t.elem)}`;
|
|
217
|
+
case "map":
|
|
218
|
+
return `map[${n(t.key)}]${n(t.value)}`;
|
|
219
|
+
case "interface":
|
|
220
|
+
return t.name === "error" || t.name ? t.name : "interface {}";
|
|
221
|
+
case "func":
|
|
222
|
+
return this.funcString(t, n).replace(/^func\(/, "func(");
|
|
223
|
+
case "struct": {
|
|
224
|
+
const fs = (t._structFields ?? []).map((f) =>
|
|
225
|
+
f.embedded ? n(f.type) : `${f.names.join(", ")} ${n(f.type)}`,
|
|
226
|
+
);
|
|
227
|
+
return fs.length ? `struct { ${fs.join("; ")} }` : "struct {}";
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
return "?";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// A type the generated file can name: declared at package level or by
|
|
235
|
+
// another package, or a function-local type, which the checker keeps in
|
|
236
|
+
// its type table under its name.
|
|
237
|
+
isNamedInScope(t) {
|
|
238
|
+
return (
|
|
239
|
+
t.name.includes("$$") ||
|
|
240
|
+
t.name.includes(".") ||
|
|
241
|
+
this.topLevel.has(t.name) ||
|
|
242
|
+
this.checker.types.get(t.name) === t
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
spellableNamed(t, ok, seen) {
|
|
247
|
+
if (
|
|
248
|
+
t.name.includes("[") ||
|
|
249
|
+
!(t.typeArgs ?? []).every(ok) ||
|
|
250
|
+
!this.isNamedInScope(t) ||
|
|
251
|
+
isUnexportedForeign(t.name) ||
|
|
252
|
+
HOST_TYPE.test(t.name)
|
|
253
|
+
)
|
|
254
|
+
return false;
|
|
255
|
+
// A named struct is as spellable as its fields (a type may hold itself).
|
|
256
|
+
const u = under(t);
|
|
257
|
+
if (u?.kind === "namespace") return false;
|
|
258
|
+
if (u?.kind !== "struct" || seen.has(t)) return true;
|
|
259
|
+
seen.add(t);
|
|
260
|
+
return this.spellableStruct(u, ok);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
spellableInterface(t) {
|
|
264
|
+
return t.name === "error" || isEmptyIface(t);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// A field of an interface type is described by its value.
|
|
268
|
+
spellableStruct(t, ok) {
|
|
269
|
+
return (t._structFields ?? []).every(
|
|
270
|
+
(f) => isInterface(f.type) || ok(f.type),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── Descriptions ────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
// Whether a method declared on a generic type has a pointer receiver:
|
|
277
|
+
// instances hold substituted copies that lose the flag.
|
|
278
|
+
ptrRecv(base, m, name) {
|
|
279
|
+
if (Object.hasOwn(m, "_ptrRecv")) return Boolean(m._ptrRecv);
|
|
280
|
+
return Boolean(
|
|
281
|
+
this.checker.types.get(base.name)?._generic?.methods?.get(name)?._ptrRecv,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// How the method set of `t` (a type or a pointer to one) holds the
|
|
286
|
+
// string-valued method `name`: null when it does not, otherwise the chain
|
|
287
|
+
// of embedded fields to the interface the method is promoted from (the
|
|
288
|
+
// checker does not treat such a type as implementing the interface), or
|
|
289
|
+
// null in `path` when the type declares or promotes it from a struct.
|
|
290
|
+
hasFormatMethod(t, name) {
|
|
291
|
+
const isPtr = t?.kind === "pointer";
|
|
292
|
+
return this.methodSetHas(isPtr ? t.base : t, name, isPtr, 0);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
methodSetHas(base, name, isPtr, depth) {
|
|
296
|
+
if (depth > 4 || !base) return null;
|
|
297
|
+
const u = under(base);
|
|
298
|
+
if (u?.kind === "interface") {
|
|
299
|
+
const im = u.methods?.get(name);
|
|
300
|
+
return im && returnsString(im) ? { path: [] } : null;
|
|
301
|
+
}
|
|
302
|
+
const m = methodOf(base, name);
|
|
303
|
+
if (m) {
|
|
304
|
+
if (!isPtr && this.ptrRecv(base, m, name)) return null;
|
|
305
|
+
return returnsString(m) ? { path: null } : null;
|
|
306
|
+
}
|
|
307
|
+
if (u?.kind !== "struct") return null;
|
|
308
|
+
for (const f of u._structFields ?? []) {
|
|
309
|
+
if (!f.embedded) continue;
|
|
310
|
+
const viaPtr = f.type?.kind === "pointer";
|
|
311
|
+
const inner = viaPtr ? f.type.base : f.type;
|
|
312
|
+
const found = this.methodSetHas(inner, name, isPtr || viaPtr, depth + 1);
|
|
313
|
+
if (!found) continue;
|
|
314
|
+
// Through an interface: the field names lead to it.
|
|
315
|
+
return found.path
|
|
316
|
+
? { path: [embeddedFieldName(f), ...found.path] }
|
|
317
|
+
: found;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// The `Err`, `Str` and `GoStr` fields for a value of type `t`.
|
|
323
|
+
methodFields(t, x) {
|
|
324
|
+
let out = "";
|
|
325
|
+
for (const [field, method] of Object.entries(FORMAT_METHODS)) {
|
|
326
|
+
const found = this.hasFormatMethod(t, method);
|
|
327
|
+
if (!found) continue;
|
|
328
|
+
out += `, ${field}: ${found.path ? [x, ...found.path].join(".") : x}`;
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// A Go expression of type fmtrt.Arg describing the value `x` of type `t`.
|
|
334
|
+
describe(t, x) {
|
|
335
|
+
t = defaultType(t);
|
|
336
|
+
const T = q(this.typeName(t));
|
|
337
|
+
if (isInterface(t) || t?.kind === "typeParam") {
|
|
338
|
+
if (this.formatted || !isMethodOnly(t)) this.needsLookup = true;
|
|
339
|
+
return `fmtrt.Arg{K: fmtrt.KInterface, V: ${x}}`;
|
|
340
|
+
}
|
|
341
|
+
const m = this.methodFields(t, x);
|
|
342
|
+
const u = under(t);
|
|
343
|
+
if (u?.kind === "basic") return this.describeBasic(u.name, T, x, m);
|
|
344
|
+
return `${this.builder(t)}(${x})`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
describeBasic(name, T, x, m) {
|
|
348
|
+
name = unalias(name);
|
|
349
|
+
const head = (k, rest) => `fmtrt.Arg{K: fmtrt.${k}, T: ${T}, ${rest}${m}}`;
|
|
350
|
+
if (name === "bool") return head("KBool", `B: bool(${x})`);
|
|
351
|
+
if (name === "string") return head("KString", `S: string(${x})`);
|
|
352
|
+
if (INT_KINDS.has(name)) return head("KInt", `I: int64(${x})`);
|
|
353
|
+
if (UINT_KINDS.has(name)) return head("KUint", `U: uint64(${x})`);
|
|
354
|
+
if (name === "float32" || name === "float64")
|
|
355
|
+
return head(
|
|
356
|
+
"KFloat",
|
|
357
|
+
`Size: ${name === "float32" ? 32 : 64}, R: float64(${x})`,
|
|
358
|
+
);
|
|
359
|
+
if (name === "complex64" || name === "complex128")
|
|
360
|
+
return head(
|
|
361
|
+
"KComplex",
|
|
362
|
+
`Size: ${name === "complex64" ? 64 : 128}, R: float64(real(${x})), Im: float64(imag(${x}))`,
|
|
363
|
+
);
|
|
364
|
+
return `fmtrt.Arg{K: fmtrt.KInterface, V: ${x}}`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// The function describing values of the composite type `t`.
|
|
368
|
+
builder(t) {
|
|
369
|
+
const key = this.key(t);
|
|
370
|
+
let name = this.builders.get(key);
|
|
371
|
+
if (!name) {
|
|
372
|
+
name = `${this.prefix}a${this.counter++}`;
|
|
373
|
+
this.builders.set(key, name);
|
|
374
|
+
this.queue.push(() => this.genBuilder(name, t));
|
|
375
|
+
}
|
|
376
|
+
return name;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
genBuilder(name, t) {
|
|
380
|
+
const T = q(this.typeName(t));
|
|
381
|
+
const m = this.methodFields(t, "x");
|
|
382
|
+
const u = under(t);
|
|
383
|
+
const body = {
|
|
384
|
+
struct: () => this.structArg(u, T, m),
|
|
385
|
+
array: () => this.elemsArg("KArray", u, T, m, `${u.size}`),
|
|
386
|
+
slice: () => this.elemsArg("KSlice", u, T, m, "len(x)"),
|
|
387
|
+
simd: () => this.simdArg(u, T, m),
|
|
388
|
+
map: () => this.mapArg(u, T, m),
|
|
389
|
+
pointer: () => this.pointerArg(u, T, m),
|
|
390
|
+
func: () =>
|
|
391
|
+
`fmtrt.Arg{K: fmtrt.KFunc, T: ${T}, Nil: x == nil, Ptr: x${m}}`,
|
|
392
|
+
}[u.kind];
|
|
393
|
+
const expr = body ? body() : `fmtrt.Arg{K: fmtrt.KInterface, V: x}`;
|
|
394
|
+
this.out.push(
|
|
395
|
+
`func ${name}(x ${this.spell(t)}) fmtrt.Arg {\n\treturn ${expr}\n}`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
structArg(u, T, m) {
|
|
400
|
+
const cases = [];
|
|
401
|
+
let n = 0;
|
|
402
|
+
for (const f of u._structFields ?? []) {
|
|
403
|
+
const names = f.embedded ? [embeddedFieldName(f)] : f.names;
|
|
404
|
+
for (const name of names) {
|
|
405
|
+
const sel = `x.${name}`;
|
|
406
|
+
const desc = this.describe(f.type, sel);
|
|
407
|
+
cases.push(`\t\tcase ${n}:\n\t\t\treturn ${q(name)}, ${desc}`);
|
|
408
|
+
n++;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const field =
|
|
412
|
+
n === 0
|
|
413
|
+
? "nil"
|
|
414
|
+
: `func(i int) (string, fmtrt.Arg) {\n\t\tswitch i {\n${cases.join("\n")}\n\t\t}\n\t\treturn "", fmtrt.Arg{}\n\t}`;
|
|
415
|
+
return `fmtrt.Arg{K: fmtrt.KStruct, T: ${T}, N: ${n}, Field: ${field}${m}}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// A vector prints as its lanes; Lane takes a constant index.
|
|
419
|
+
simdArg(u, T, m) {
|
|
420
|
+
const cases = [];
|
|
421
|
+
for (let i = 0; i < u.lanes; i++)
|
|
422
|
+
cases.push(
|
|
423
|
+
`\t\tcase ${i}:\n\t\t\treturn ${this.describe(u.elem, `x.Lane(${i})`)}`,
|
|
424
|
+
);
|
|
425
|
+
return `fmtrt.Arg{K: fmtrt.KArray, T: ${T}, N: ${u.lanes}, Elem: func(i int) fmtrt.Arg {\n\t\tswitch i {\n${cases.join("\n")}\n\t\t}\n\t\treturn fmtrt.Arg{}\n\t}${m}}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
elemsArg(kind, u, T, m, len) {
|
|
429
|
+
const isBytes = u.elem.kind === "basic" && unalias(u.elem.name) === "uint8";
|
|
430
|
+
const bytes = isBytes
|
|
431
|
+
? kind === "KSlice"
|
|
432
|
+
? ", IsBytes: true, Bytes: []byte(x)"
|
|
433
|
+
: ", IsBytes: true, Bytes: x[:]"
|
|
434
|
+
: "";
|
|
435
|
+
const nil = kind === "KSlice" ? ", Nil: x == nil, Ptr: x" : "";
|
|
436
|
+
return `fmtrt.Arg{K: fmtrt.${kind}, T: ${T}, N: ${len}${nil}${bytes}, Elem: func(i int) fmtrt.Arg {\n\t\treturn ${this.describe(u.elem, "x[i]")}\n\t}${m}}`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
mapArg(u, T, m) {
|
|
440
|
+
const key = this.describe(u.key, "k");
|
|
441
|
+
const val = this.describe(u.value, "v");
|
|
442
|
+
return `fmtrt.Arg{K: fmtrt.KMap, T: ${T}, Nil: x == nil, Ptr: x, N: len(x), Entries: func() []fmtrt.Entry {\n\t\tif x == nil {\n\t\t\treturn nil\n\t\t}\n\t\tes := make([]fmtrt.Entry, 0, len(x))\n\t\tfor k, v := range x {\n\t\t\tes = append(es, fmtrt.Entry{Key: ${key}, Val: ${val}})\n\t\t}\n\t\treturn es\n\t}${m}}`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
pointerArg(u, T, m) {
|
|
446
|
+
const k = under(u.base)?.kind;
|
|
447
|
+
const inner =
|
|
448
|
+
k === "struct" || k === "array" || k === "slice" || k === "map"
|
|
449
|
+
? `, Inner: func() fmtrt.Arg {\n\t\treturn ${this.describe(u.base, "*x")}\n\t}`
|
|
450
|
+
: "";
|
|
451
|
+
const boxed = k !== "struct" && k !== "array" ? ", Boxed: true" : "";
|
|
452
|
+
return `fmtrt.Arg{K: fmtrt.KPtr, T: ${T}, Nil: x == nil, Ptr: x${boxed}${inner}${m}}`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ── Call sites ──────────────────────────────────────────────
|
|
456
|
+
|
|
457
|
+
// Whether an operand of static type `t` needs a description, or reaches
|
|
458
|
+
// fmt in a form it understands: { wrap } or { bare, lookup }.
|
|
459
|
+
classify(t, formatted = false) {
|
|
460
|
+
t = defaultType(t);
|
|
461
|
+
if (!t) return { bare: true, lookup: true };
|
|
462
|
+
if (t.kind === "nil" || (t.kind === "basic" && t.name === "nil"))
|
|
463
|
+
return { bare: true };
|
|
464
|
+
if (mentionsTypeParam(t) || t.kind === "typeParam")
|
|
465
|
+
return { bare: true, lookup: true };
|
|
466
|
+
if (isInterface(t))
|
|
467
|
+
return { bare: true, lookup: formatted || !isMethodOnly(t) };
|
|
468
|
+
if (t.kind === "basic" && !t.alias) {
|
|
469
|
+
if (t.name === "string" || t.name === "bool") return { bare: true };
|
|
470
|
+
// A JS number holds an int exactly only up to 2^53: past it the
|
|
471
|
+
// number reads as a float, so ints are described unless the
|
|
472
|
+
// package is WebAssembly only.
|
|
473
|
+
if (t.name === "int" && this.checker.target === "wasm")
|
|
474
|
+
return { bare: true };
|
|
475
|
+
}
|
|
476
|
+
if (!this.spellable(t)) return { bare: true, lookup: true };
|
|
477
|
+
// A named type whose value has String or Error answers to it as it is:
|
|
478
|
+
// fmt asks the value.
|
|
479
|
+
// (A string type is described: Print spaces only between operands that
|
|
480
|
+
// are not strings, and a bare value does not say its kind. So are
|
|
481
|
+
// slices and maps, which WebAssembly does not box with their methods.)
|
|
482
|
+
const declared = (m) => this.hasFormatMethod(t, m)?.path === null;
|
|
483
|
+
// A format call may print it with %d or %+v, which call no methods, so
|
|
484
|
+
// there it is described.
|
|
485
|
+
if (
|
|
486
|
+
!formatted &&
|
|
487
|
+
t.kind === "named" &&
|
|
488
|
+
(under(t)?.kind === "struct" ||
|
|
489
|
+
(under(t)?.kind === "basic" && under(t).name !== "string")) &&
|
|
490
|
+
(declared("Error") || declared("String"))
|
|
491
|
+
)
|
|
492
|
+
return { bare: true };
|
|
493
|
+
return { wrap: true };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// The wrapper for a call with these operands. A call that forwards the
|
|
497
|
+
// results of another (`fmt.Println(f())`) is wrapped with every operand
|
|
498
|
+
// typed exactly; null when one cannot be spelled.
|
|
499
|
+
wrapper(fn, prefixType, operandTypes, kinds, exact) {
|
|
500
|
+
const spelled = (t) => this.spell(defaultType(t));
|
|
501
|
+
if (exact && !operandTypes.every((t) => this.spellable(defaultType(t))))
|
|
502
|
+
return null;
|
|
503
|
+
const sig = `${fn}|${prefixType ?? ""}|${exact ? "!" : ""}${operandTypes.map((t, i) => (exact || kinds[i].wrap ? this.key(defaultType(t)) : "any")).join(",")}`;
|
|
504
|
+
let name = this.wrappers.get(sig);
|
|
505
|
+
if (name) return name;
|
|
506
|
+
name = `${this.prefix}${fn}_${this.wrappers.size}`;
|
|
507
|
+
this.wrappers.set(sig, name);
|
|
508
|
+
const params = [];
|
|
509
|
+
if (prefixType) params.push(`p ${prefixType}`);
|
|
510
|
+
if (isFormatted(fn)) params.push("format string");
|
|
511
|
+
const args = [];
|
|
512
|
+
operandTypes.forEach((t, i) => {
|
|
513
|
+
const a = `a${i}`;
|
|
514
|
+
if (kinds[i].wrap) {
|
|
515
|
+
params.push(`${a} ${spelled(t)}`);
|
|
516
|
+
args.push(this.describe(t, a));
|
|
517
|
+
} else {
|
|
518
|
+
params.push(`${a} ${exact ? spelled(t) : "any"}`);
|
|
519
|
+
args.push(a);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
const callArgs = [
|
|
523
|
+
...(prefixType ? ["p"] : []),
|
|
524
|
+
...(isFormatted(fn) ? ["format"] : []),
|
|
525
|
+
...args,
|
|
526
|
+
].join(", ");
|
|
527
|
+
this.out.push(
|
|
528
|
+
`func ${name}(${params.join(", ")}) ${RESULT[fn]} {${ENSURE}\n\treturn fmt.${fn}(${callArgs})\n}`,
|
|
529
|
+
);
|
|
530
|
+
if (prefixType === "io.Writer") this.uses.add("io");
|
|
531
|
+
return name;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
drain() {
|
|
535
|
+
while (this.queue.length > 0) this.queue.shift()();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ── The lookup ──────────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
// Types an interface may hold that fmt prints: the types of the
|
|
541
|
+
// package's expressions, its named types, and a pointer to each.
|
|
542
|
+
candidateTypes() {
|
|
543
|
+
const found = new Map();
|
|
544
|
+
const add = (t) => {
|
|
545
|
+
if (!t || !this.usable(t)) return;
|
|
546
|
+
found.set(this.key(t), t);
|
|
547
|
+
if (t.kind === "named" && under(t)?.kind !== "func")
|
|
548
|
+
found.set(this.key({ kind: "pointer", base: t }), {
|
|
549
|
+
kind: "pointer",
|
|
550
|
+
base: t,
|
|
551
|
+
});
|
|
552
|
+
};
|
|
553
|
+
for (const { type, node, via } of collectProgramTypes(
|
|
554
|
+
this.programs,
|
|
555
|
+
this.checker,
|
|
556
|
+
)) {
|
|
557
|
+
// A member of a package (fmt.Println) is no value of its own.
|
|
558
|
+
if (via === "expr" && node.expr?._type?.kind === "namespace") continue;
|
|
559
|
+
if (via === "declared" && !this.topLevel.has(type.name)) continue;
|
|
560
|
+
add(type);
|
|
561
|
+
}
|
|
562
|
+
// Only instances of generic types (and pointers to them): they are the
|
|
563
|
+
// types a package never spells, and any other type here would take the
|
|
564
|
+
// place of the dispatch a type parameter value already has.
|
|
565
|
+
for (const type of this.genericBodyTypes()) {
|
|
566
|
+
const base = type.kind === "pointer" ? type.base : type;
|
|
567
|
+
if (base.kind === "named" && base.typeArgs?.length > 0) add(type);
|
|
568
|
+
}
|
|
569
|
+
return [...found.values()];
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// The types the bodies of generic functions build for each instantiation
|
|
573
|
+
// the package makes: `Box[T]{v}` inside `wrap[T]` is a `Box[int]` once
|
|
574
|
+
// `wrap(2)` is called, a type no expression of the package spells.
|
|
575
|
+
genericBodyTypes() {
|
|
576
|
+
const decls = new Map();
|
|
577
|
+
for (const program of this.programs)
|
|
578
|
+
for (const d of program.decls ?? [])
|
|
579
|
+
if (d.kind === "FuncDecl" && !d.recv && d.typeParams?.length)
|
|
580
|
+
decls.set(d.name, d);
|
|
581
|
+
if (decls.size === 0) return [];
|
|
582
|
+
const out = [];
|
|
583
|
+
const seen = new Set();
|
|
584
|
+
const queue = [];
|
|
585
|
+
const request = (call, map) => {
|
|
586
|
+
const inst = this.genericInstance(call, map, decls);
|
|
587
|
+
if (!inst || seen.has(inst.id) || seen.size >= 512) return;
|
|
588
|
+
seen.add(inst.id);
|
|
589
|
+
queue.push(inst);
|
|
590
|
+
};
|
|
591
|
+
for (const program of this.programs)
|
|
592
|
+
walkNodes(program.decls, (node) => request(node, new Map()));
|
|
593
|
+
while (queue.length > 0) {
|
|
594
|
+
const { decl, map } = queue.shift();
|
|
595
|
+
walkNodes(decl.body, (node) => {
|
|
596
|
+
// A type without a type parameter is found by the walk of the
|
|
597
|
+
// package already.
|
|
598
|
+
if (node._type && mentionsTypeParam(node._type))
|
|
599
|
+
out.push(defaultType(this.checker.substituteType(node._type, map)));
|
|
600
|
+
request(node, map);
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
return out;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// The instantiation of a generic function that `call` makes, with its
|
|
607
|
+
// type arguments substituted through `map` (the instantiation of the
|
|
608
|
+
// function that contains the call), or null for any other node or one
|
|
609
|
+
// whose type arguments are still open.
|
|
610
|
+
genericInstance(call, map, decls) {
|
|
611
|
+
const generic = call._genericFnType;
|
|
612
|
+
const decl = generic && decls.get(generic.name);
|
|
613
|
+
const args = call._typeArgs ?? call.func?._typeArgs;
|
|
614
|
+
if (!decl || args?.length !== generic.typeParams?.length) return null;
|
|
615
|
+
const sub = args.map((a) => this.checker.substituteType(a, map));
|
|
616
|
+
if (sub.some((a) => mentionsTypeParam(a))) return null;
|
|
617
|
+
return {
|
|
618
|
+
decl,
|
|
619
|
+
id: `${decl.name}|${sub.map((a) => this.key(a)).join(",")}`,
|
|
620
|
+
map: new Map(generic.typeParams.map((tp, i) => [tp.name, sub[i]])),
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
usable(t) {
|
|
625
|
+
if (!this.spellable(t) || mentionsTypeParam(t)) return false;
|
|
626
|
+
if (isInterface(t) || under(t)?.kind === "interface") return false;
|
|
627
|
+
if (t.kind === "basic") return false;
|
|
628
|
+
if (t.kind === "func") return true;
|
|
629
|
+
if (holdsFunc(t)) return false;
|
|
630
|
+
// An unnamed array or map of small integers would shadow its look-alikes
|
|
631
|
+
// in the type switch (see SHARED_REPR in json-codecs).
|
|
632
|
+
if (t.kind === "array" || t.kind === "map" || t.kind === "slice")
|
|
633
|
+
return isCompositeType(t);
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
genLookup() {
|
|
638
|
+
const cases = [];
|
|
639
|
+
const seen = new Set();
|
|
640
|
+
for (const t of this.candidateTypes()) {
|
|
641
|
+
const spelled = this.spell(t);
|
|
642
|
+
if (seen.has(spelled)) continue;
|
|
643
|
+
seen.add(spelled);
|
|
644
|
+
cases.push(
|
|
645
|
+
`\tcase ${spelled}:\n\t\treturn ${this.describe(t, "x")}, true`,
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
this.drain();
|
|
649
|
+
const name = `${this.prefix}value`;
|
|
650
|
+
this.out.push(
|
|
651
|
+
[
|
|
652
|
+
`func ${name}(v any) (fmtrt.Arg, bool) {`,
|
|
653
|
+
"\tswitch x := v.(type) {",
|
|
654
|
+
...cases,
|
|
655
|
+
"\t}",
|
|
656
|
+
"\treturn fmtrt.Arg{}, false",
|
|
657
|
+
"}",
|
|
658
|
+
"",
|
|
659
|
+
`func ${this.prefix}ensure() {`,
|
|
660
|
+
`\tfmtrt.Register(${KEY}, ${name})`,
|
|
661
|
+
"}",
|
|
662
|
+
"",
|
|
663
|
+
`var _ = fmtrt.Register(${KEY}, ${name})`,
|
|
664
|
+
].join("\n"),
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// FNV-1a of a string, in hex.
|
|
670
|
+
function hash(text) {
|
|
671
|
+
let h = 0x811c9dc5;
|
|
672
|
+
for (let i = 0; i < text.length; i++)
|
|
673
|
+
h = Math.imul(h ^ text.charCodeAt(i), 0x01000193) >>> 0;
|
|
674
|
+
return h.toString(16);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// The names the file imports `fmt` as.
|
|
678
|
+
function fmtAliases(program) {
|
|
679
|
+
const aliases = new Set();
|
|
680
|
+
for (const imp of program.imports ?? [])
|
|
681
|
+
for (const spec of imp.imports)
|
|
682
|
+
if (spec.path === FMT_PATH && spec.alias !== "_")
|
|
683
|
+
aliases.add(spec.alias ?? "fmt");
|
|
684
|
+
return aliases;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const isFmtRef = (node, aliases) =>
|
|
688
|
+
node.kind === "SelectorExpr" &&
|
|
689
|
+
node.expr?.kind === "Ident" &&
|
|
690
|
+
node.expr._type?.kind === "namespace" &&
|
|
691
|
+
aliases.has(node.expr.name) &&
|
|
692
|
+
FMT_PRINT_FUNCS.has(node.field);
|
|
693
|
+
|
|
694
|
+
// The methods of *testing.T, *testing.B and testing.TB that format their
|
|
695
|
+
// operands like Print (Log, Error, Fatal, Skip) or like Printf (the f forms).
|
|
696
|
+
const TESTING_FORMATS = new Set(
|
|
697
|
+
["Log", "Error", "Fatal", "Skip"].flatMap((m) => [m, `${m}f`]),
|
|
698
|
+
);
|
|
699
|
+
const TESTING_RECEIVER = /^(T|B|TB)\$\$testing$|^testing\.(T|B|TB)$/;
|
|
700
|
+
|
|
701
|
+
// Whether the package logs operands through testing's methods, and whether
|
|
702
|
+
// any of those calls is formatted. The operands reach fmt as `any` values, so
|
|
703
|
+
// the package needs the lookup (and, for a format call, the description of
|
|
704
|
+
// Stringers and errors) as for a forwarded Print call.
|
|
705
|
+
function findTestingCalls(programs) {
|
|
706
|
+
const found = { calls: false, formatted: false };
|
|
707
|
+
for (const program of programs)
|
|
708
|
+
walkNodes(program.decls, (node) => {
|
|
709
|
+
if (node.kind !== "CallExpr" || node.func?.kind !== "SelectorExpr")
|
|
710
|
+
return;
|
|
711
|
+
if (!TESTING_FORMATS.has(node.func.field)) return;
|
|
712
|
+
let recv = node.func.expr?._type;
|
|
713
|
+
if (recv?.kind === "pointer") recv = recv.base;
|
|
714
|
+
if (!TESTING_RECEIVER.test(recv?.name ?? "")) return;
|
|
715
|
+
found.calls = true;
|
|
716
|
+
if (isFormatted(node.func.field)) found.formatted = true;
|
|
717
|
+
});
|
|
718
|
+
return found;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function findSites(programs) {
|
|
722
|
+
const sites = [];
|
|
723
|
+
const values = []; // uses that are not calls: the operands are unknown
|
|
724
|
+
for (const program of programs) {
|
|
725
|
+
const aliases = fmtAliases(program);
|
|
726
|
+
if (aliases.size === 0) continue;
|
|
727
|
+
const callees = new Set();
|
|
728
|
+
const refs = [];
|
|
729
|
+
walkNodes(program.decls, (node) => {
|
|
730
|
+
if (node.kind === "CallExpr" && isFmtRef(node.func, aliases)) {
|
|
731
|
+
sites.push({ call: node, program });
|
|
732
|
+
callees.add(node.func);
|
|
733
|
+
} else if (isFmtRef(node, aliases)) {
|
|
734
|
+
refs.push(node);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
for (const node of refs) if (!callees.has(node)) values.push(node);
|
|
738
|
+
}
|
|
739
|
+
return { sites, values };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// `fmt.Println(any(x))` formats x: the conversion hides the static type the
|
|
743
|
+
// description is made from, and drops nothing else.
|
|
744
|
+
function unwrapInterfaceConversions(call, skip) {
|
|
745
|
+
const args = call.args ?? [];
|
|
746
|
+
for (let i = skip; i < args.length; i++) {
|
|
747
|
+
const a = args[i];
|
|
748
|
+
if (a?.kind !== "TypeConversion" || !isInterface(a._type)) continue;
|
|
749
|
+
const inner = a.expr;
|
|
750
|
+
const t = defaultType(inner?._type);
|
|
751
|
+
if (t && t.kind !== "tuple" && !isInterface(t) && !inner._spread)
|
|
752
|
+
args[i] = inner;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Whether the package passes json.Unmarshal or a Decode call a target that is
|
|
757
|
+
// not statically a pointer (an `any` that may hold a value): the decoder then
|
|
758
|
+
// reports the target's type, which only a registered lookup can name.
|
|
759
|
+
function hasJsonTargetNotPointer(programs) {
|
|
760
|
+
let found = false;
|
|
761
|
+
for (const program of programs) {
|
|
762
|
+
if (
|
|
763
|
+
!(program.imports ?? []).some((imp) =>
|
|
764
|
+
imp.imports.some((spec) => spec.path === JSON_PATH),
|
|
765
|
+
)
|
|
766
|
+
)
|
|
767
|
+
continue;
|
|
768
|
+
walkNodes(program.decls, (node) => {
|
|
769
|
+
if (found || node.kind !== "CallExpr") return;
|
|
770
|
+
const f = node.func;
|
|
771
|
+
if (f?.kind !== "SelectorExpr") return;
|
|
772
|
+
const at = f.field === "Unmarshal" ? 1 : f.field === "Decode" ? 0 : -1;
|
|
773
|
+
const arg = node.args?.[at];
|
|
774
|
+
if (at < 0 || !arg?._type) return;
|
|
775
|
+
if (under(defaultType(arg._type))?.kind !== "pointer") found = true;
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
return found;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// The operand types of a call, or null when it cannot be wrapped.
|
|
782
|
+
function operandTypes(call, skip) {
|
|
783
|
+
const args = call.args ?? [];
|
|
784
|
+
if (args.some((a) => a?._spread)) return null;
|
|
785
|
+
if (call._multiForward) {
|
|
786
|
+
const types = args[0]?._type?.types;
|
|
787
|
+
return types ? types.slice(skip) : null;
|
|
788
|
+
}
|
|
789
|
+
return args.slice(skip).map((a) => a?._type);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Marks `call` to call the generated wrapper `name`, which takes the
|
|
793
|
+
// operands with their static types (every operand, for a forwarded call) and
|
|
794
|
+
// is not variadic.
|
|
795
|
+
function markCall(call, { name, kinds, skip, types, exact }) {
|
|
796
|
+
const orig = call.func._type;
|
|
797
|
+
const operand = (t, i) => (exact || kinds[i].wrap ? defaultType(t) : ANY);
|
|
798
|
+
call.func._genEntry = name;
|
|
799
|
+
call.func._type = {
|
|
800
|
+
kind: "func",
|
|
801
|
+
params: [...orig.params.slice(0, skip), ...types.map(operand)],
|
|
802
|
+
returns: orig.returns,
|
|
803
|
+
variadic: false,
|
|
804
|
+
};
|
|
805
|
+
if (exact) return;
|
|
806
|
+
kinds.forEach((k, i) => {
|
|
807
|
+
if (!k.wrap) return;
|
|
808
|
+
const arg = call.args[skip + i];
|
|
809
|
+
arg._ifaceBox = undefined;
|
|
810
|
+
arg._ifaceSrc = undefined;
|
|
811
|
+
arg._ifaceNamedType = undefined;
|
|
812
|
+
arg._ifacePtrFields = undefined;
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Generates the formatters for a package's Print-family calls. Returns null
|
|
817
|
+
// when it needs none; otherwise { body, imports, diagnostics } and redirects
|
|
818
|
+
// each call that needs a wrapper with `_genEntry`.
|
|
819
|
+
export function generateFmtFormatters(programs, checker, pkgName) {
|
|
820
|
+
const { sites, values } = findSites(programs);
|
|
821
|
+
// encoding/json names the type of a target that is not a pointer with %T,
|
|
822
|
+
// an operand that arrives through an interface.
|
|
823
|
+
const usesJson = hasJsonTargetNotPointer(programs);
|
|
824
|
+
const testing = findTestingCalls(programs);
|
|
825
|
+
if (sites.length + values.length === 0 && !usesJson && !testing.calls)
|
|
826
|
+
return null;
|
|
827
|
+
const qualifiers = new Map();
|
|
828
|
+
const gen = new FmtGen(
|
|
829
|
+
checker,
|
|
830
|
+
pkgName,
|
|
831
|
+
qualifiers,
|
|
832
|
+
programs,
|
|
833
|
+
testing.formatted || sites.some((s) => isFormatted(s.call.func.field)),
|
|
834
|
+
);
|
|
835
|
+
const diagnostics = [];
|
|
836
|
+
const marks = [];
|
|
837
|
+
let lookup = values.length > 0 || usesJson || testing.calls;
|
|
838
|
+
for (const { call, program } of sites) {
|
|
839
|
+
// Parsed trees outlive a build: forget how an earlier build wrapped it.
|
|
840
|
+
delete call.func._genEntry;
|
|
841
|
+
for (const a of call.args ?? []) if (a) delete a._boxTypeParam;
|
|
842
|
+
const fn = call.func.field;
|
|
843
|
+
const prefix = PRINT_FUNCS.get(fn);
|
|
844
|
+
const formatted = isFormatted(fn);
|
|
845
|
+
const skip = (prefix ? 1 : 0) + (formatted ? 1 : 0);
|
|
846
|
+
if (!call._multiForward) unwrapInterfaceConversions(call, skip);
|
|
847
|
+
const types = operandTypes(call, skip);
|
|
848
|
+
if (!types) {
|
|
849
|
+
lookup = true;
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const kinds = types.map((t) => gen.classify(t, formatted));
|
|
853
|
+
if (kinds.some((k) => k.lookup)) lookup = true;
|
|
854
|
+
// A type parameter value is boxed by its type argument (JS backend).
|
|
855
|
+
if (!call._multiForward)
|
|
856
|
+
types.forEach((t, i) => {
|
|
857
|
+
if (t?.kind === "typeParam") call.args[skip + i]._boxTypeParam = true;
|
|
858
|
+
});
|
|
859
|
+
if (!kinds.some((k) => k.wrap || k.lookup) && !call._multiForward) continue;
|
|
860
|
+
const exact = Boolean(call._multiForward);
|
|
861
|
+
let prefixType = prefix;
|
|
862
|
+
if (exact && prefix) {
|
|
863
|
+
const t = defaultType(call.args[0]._type.types[0]);
|
|
864
|
+
prefixType = gen.spellable(t) ? gen.spell(t) : null;
|
|
865
|
+
}
|
|
866
|
+
const name =
|
|
867
|
+
exact && prefix && !prefixType
|
|
868
|
+
? null
|
|
869
|
+
: gen.wrapper(fn, prefixType, types, kinds, exact);
|
|
870
|
+
if (!name) {
|
|
871
|
+
diagnostics.push({
|
|
872
|
+
program,
|
|
873
|
+
node: call.func.expr,
|
|
874
|
+
message:
|
|
875
|
+
"passing multiple return values to a variadic function is not supported in wasm packages; assign them to variables first",
|
|
876
|
+
});
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
marks.push({ call, name, kinds, skip, types, exact });
|
|
880
|
+
}
|
|
881
|
+
if (lookup) gen.needsLookup = true;
|
|
882
|
+
gen.drain();
|
|
883
|
+
if (gen.needsLookup) gen.genLookup();
|
|
884
|
+
gen.drain();
|
|
885
|
+
if (gen.out.length === 0 && diagnostics.length === 0) return null;
|
|
886
|
+
for (const mark of marks) markCall(mark.call, mark);
|
|
887
|
+
const ensure = gen.needsLookup ? `\n\t${gen.prefix}ensure()` : "";
|
|
888
|
+
const source = gen.out.join("\n\n").replaceAll(ENSURE, ensure);
|
|
889
|
+
const body = source.replaceAll(
|
|
890
|
+
KEY,
|
|
891
|
+
JSON.stringify(`${pkgName}:${hash(source)}`),
|
|
892
|
+
);
|
|
893
|
+
const imports = new Set(gen.uses);
|
|
894
|
+
if (
|
|
895
|
+
body.includes("fmt.Sprint") ||
|
|
896
|
+
body.includes("fmt.Print") ||
|
|
897
|
+
body.includes("fmt.Fprint") ||
|
|
898
|
+
body.includes("fmt.Append") ||
|
|
899
|
+
body.includes("fmt.Errorf")
|
|
900
|
+
)
|
|
901
|
+
imports.add(FMT_PATH);
|
|
902
|
+
if (body.includes("fmtrt.")) imports.add(RT_PATH);
|
|
903
|
+
for (const p of importsFor(qualifiers)) imports.add(p);
|
|
904
|
+
return { body, imports: [...imports], diagnostics };
|
|
905
|
+
}
|