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,426 @@
|
|
|
1
|
+
// Package initialisation order (Go spec, "Package initialization").
|
|
2
|
+
//
|
|
3
|
+
// A package-level variable is initialised once every variable its initialiser
|
|
4
|
+
// refers to has been, where "refers to" follows references to functions and
|
|
5
|
+
// methods transitively. Each step picks the earliest variable in declaration
|
|
6
|
+
// order that is ready; a step with none left over is an initialisation cycle.
|
|
7
|
+
// Constants are folded by value, so they only need ordering among themselves
|
|
8
|
+
// (a constant may name one declared later).
|
|
9
|
+
//
|
|
10
|
+
// The checker reports cycles; both backends emit initialisers in this order.
|
|
11
|
+
// Shadowing follows Go's block scoping: a name declared by a parameter, result,
|
|
12
|
+
// receiver or statement hides the package-level one from the next statement to
|
|
13
|
+
// the end of its block, and not outside it.
|
|
14
|
+
|
|
15
|
+
function forEachChildNode(node, fn) {
|
|
16
|
+
for (const key of Object.keys(node)) {
|
|
17
|
+
if (key.charCodeAt(0) === 95) continue; // "_" side tables
|
|
18
|
+
const v = node[key];
|
|
19
|
+
if (v && typeof v === "object") fn(v);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A lexical scope of locally declared names.
|
|
24
|
+
class Shadow {
|
|
25
|
+
constructor(parent = null) {
|
|
26
|
+
this.parent = parent;
|
|
27
|
+
this.names = new Set();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
has(name) {
|
|
31
|
+
for (let s = this; s; s = s.parent) if (s.names.has(name)) return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
add(...names) {
|
|
36
|
+
for (const n of names) if (n) this.names.add(n);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Names a function-like node declares for its body: receiver, parameters and
|
|
41
|
+
// named results.
|
|
42
|
+
function signatureNames(node) {
|
|
43
|
+
const out = [];
|
|
44
|
+
if (node.recvName) out.push(node.recvName);
|
|
45
|
+
for (const p of node.params ?? []) if (p?.name) out.push(p.name);
|
|
46
|
+
for (const r of node.returnType?._namedReturns ?? []) out.push(r.name);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const lhsName = (l) => (typeof l === "string" ? l : l?.name);
|
|
51
|
+
|
|
52
|
+
// The `Type.method` key of the declaration `name` selects on a value of type
|
|
53
|
+
// `t`, found the way the checker finds it: on the type itself, else on an
|
|
54
|
+
// embedded type (through pointers too). Interface methods have no body to
|
|
55
|
+
// follow, so a call through an interface adds no dependency (Go does the same).
|
|
56
|
+
function methodKeyOf(t, name, funcs, depth = 0) {
|
|
57
|
+
let x = t;
|
|
58
|
+
if (x?.kind === "pointer") x = x.base;
|
|
59
|
+
if (x?.kind !== "named" || depth > 8) return null;
|
|
60
|
+
for (const typeName of [x.name, x._generic?.name]) {
|
|
61
|
+
const key = `${typeName}.${name}`;
|
|
62
|
+
if (typeName && funcs.has(key)) return key;
|
|
63
|
+
}
|
|
64
|
+
for (const e of x.underlying?._embeds ?? []) {
|
|
65
|
+
const key = methodKeyOf(e, name, funcs, depth + 1);
|
|
66
|
+
if (key) return key;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Whether the keys of a composite literal are expressions (map, slice and
|
|
72
|
+
// array literals) rather than field names (struct literals). Before the
|
|
73
|
+
// literal is checked its written type decides; an unnamed one stays ambiguous
|
|
74
|
+
// and its keys are skipped.
|
|
75
|
+
function litHasExprKeys(lit) {
|
|
76
|
+
let t = lit._type;
|
|
77
|
+
for (let i = 0; t && i < 8; i++) {
|
|
78
|
+
if (t.kind === "named") t = t.underlying;
|
|
79
|
+
else if (t.kind === "pointer") t = t.base;
|
|
80
|
+
else break;
|
|
81
|
+
}
|
|
82
|
+
if (t?.kind) return ["map", "slice", "array"].includes(t.kind);
|
|
83
|
+
return ["MapType", "SliceType", "ArrayType"].includes(lit.typeExpr?.kind);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Statements that open a scope and declare names as they run. Each handler
|
|
87
|
+
// walks the node's children in order, adding declarations to `shadow` after
|
|
88
|
+
// the expressions that cannot see them.
|
|
89
|
+
const SCOPED = {
|
|
90
|
+
FuncDecl: walkFunc,
|
|
91
|
+
MethodDecl: walkFunc,
|
|
92
|
+
FuncLit: walkFunc,
|
|
93
|
+
Block: (node, shadow, ctx, out) =>
|
|
94
|
+
walkRefs(node.stmts, new Shadow(shadow), ctx, out),
|
|
95
|
+
DefineStmt: (node, shadow, ctx, out) => {
|
|
96
|
+
walkRefs(node.rhs, shadow, ctx, out);
|
|
97
|
+
shadow.add(...[node.lhs].flat().map(lhsName));
|
|
98
|
+
},
|
|
99
|
+
VarDecl: walkDeclSpecs,
|
|
100
|
+
ConstDecl: walkDeclSpecs,
|
|
101
|
+
IfStmt: (node, shadow, ctx, out) => {
|
|
102
|
+
const inner = new Shadow(shadow);
|
|
103
|
+
for (const c of [node.init, node.cond, node.body, node.elseBody])
|
|
104
|
+
walkRefs(c, inner, ctx, out);
|
|
105
|
+
},
|
|
106
|
+
ForStmt: (node, shadow, ctx, out) => {
|
|
107
|
+
const inner = new Shadow(shadow);
|
|
108
|
+
for (const c of [node.init, node.cond, node.post, node.body])
|
|
109
|
+
walkRefs(c, inner, ctx, out);
|
|
110
|
+
},
|
|
111
|
+
SwitchStmt: walkSwitch,
|
|
112
|
+
TypeSwitchStmt: walkSwitch,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function walkFunc(node, shadow, ctx, out) {
|
|
116
|
+
const inner = new Shadow(shadow);
|
|
117
|
+
inner.add(...signatureNames(node));
|
|
118
|
+
walkRefs(node.body, inner, ctx, out);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function walkDeclSpecs(node, shadow, ctx, out) {
|
|
122
|
+
for (const spec of node.decls ?? []) {
|
|
123
|
+
walkRefs(spec.value, shadow, ctx, out);
|
|
124
|
+
shadow.add(...(spec.names ?? []));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function walkSwitch(node, shadow, ctx, out) {
|
|
129
|
+
const inner = new Shadow(shadow);
|
|
130
|
+
walkRefs(node.init, inner, ctx, out);
|
|
131
|
+
walkRefs(node.tag ?? node.expr, inner, ctx, out);
|
|
132
|
+
for (const c of node.cases ?? []) {
|
|
133
|
+
const clause = new Shadow(inner);
|
|
134
|
+
if (node.assign) clause.add(node.assign);
|
|
135
|
+
walkRefs(c.list, clause, ctx, out);
|
|
136
|
+
walkRefs(c.stmts, clause, ctx, out);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function walkRefs(node, shadow, ctx, out) {
|
|
141
|
+
if (!node || typeof node !== "object") return;
|
|
142
|
+
if (Array.isArray(node)) {
|
|
143
|
+
for (const n of node) walkRefs(n, shadow, ctx, out);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (node.kind === "Ident") {
|
|
147
|
+
// The blank identifier names nothing, whatever `var _` the package has.
|
|
148
|
+
if (
|
|
149
|
+
node.name !== "_" &&
|
|
150
|
+
ctx.pkgNames.has(node.name) &&
|
|
151
|
+
!shadow.has(node.name)
|
|
152
|
+
)
|
|
153
|
+
out.names.add(node.name);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const scoped = SCOPED[node.kind];
|
|
157
|
+
if (scoped) return scoped(node, shadow, ctx, out);
|
|
158
|
+
if (node.kind === "SelectorExpr") {
|
|
159
|
+
const key = methodKeyOf(node.expr?._type, node.field, ctx.funcs);
|
|
160
|
+
if (key) out.methods.add(key);
|
|
161
|
+
}
|
|
162
|
+
if (node.kind === "CompositeLit") {
|
|
163
|
+
const exprKeys = litHasExprKeys(node);
|
|
164
|
+
for (const el of node.elems ?? []) {
|
|
165
|
+
if (el?.kind !== "KeyValueExpr") walkRefs(el, shadow, ctx, out);
|
|
166
|
+
else {
|
|
167
|
+
// A struct literal's key is a field name, not a reference.
|
|
168
|
+
if (exprKeys || el.key?.kind !== "Ident")
|
|
169
|
+
walkRefs(el.key, shadow, ctx, out);
|
|
170
|
+
walkRefs(el.value, shadow, ctx, out);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
walkRefs(node.typeExpr, shadow, ctx, out);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
forEachChildNode(node, (c) => walkRefs(c, shadow, ctx, out));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Collects what `root` (an initialiser expression or a function declaration)
|
|
181
|
+
* refers to: package-level names (`names`) and declarations selected as methods
|
|
182
|
+
* (`methods`, keyed `Type.method`), honouring block-scoped shadowing.
|
|
183
|
+
*/
|
|
184
|
+
function collectRefs(root, ctx, out) {
|
|
185
|
+
walkRefs(root, new Shadow(), ctx, out);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const emptyRefs = () => ({ names: new Set(), methods: new Set() });
|
|
189
|
+
|
|
190
|
+
// Package-level specs and callable declarations of all files.
|
|
191
|
+
function collectDecls(programs) {
|
|
192
|
+
const varEntries = [];
|
|
193
|
+
const constEntries = [];
|
|
194
|
+
const funcs = new Map(); // name | "Type.method" -> declaration
|
|
195
|
+
for (const program of programs) {
|
|
196
|
+
for (const d of program.decls ?? []) {
|
|
197
|
+
if (d.kind === "VarDecl" || d.kind === "ConstDecl") {
|
|
198
|
+
const into = d.kind === "VarDecl" ? varEntries : constEntries;
|
|
199
|
+
for (const spec of d.decls ?? [])
|
|
200
|
+
into.push({ decl: d, spec, names: spec.names ?? [], program });
|
|
201
|
+
} else if (d.kind === "FuncDecl") {
|
|
202
|
+
funcs.set(d.name, d);
|
|
203
|
+
} else if (d.kind === "MethodDecl") {
|
|
204
|
+
const t = d.recvType?.name ?? d.recvType?.base?.name;
|
|
205
|
+
if (t) funcs.set(`${t}.${d.name}`, d);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return { varEntries, constEntries, funcs };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// For each var spec, the other var specs its initialiser depends on (through
|
|
213
|
+
// functions and methods too), with the name chain leading to each.
|
|
214
|
+
function buildVarDeps({ varEntries, constEntries, funcs }) {
|
|
215
|
+
const varOwner = new Map(); // var name -> entry
|
|
216
|
+
for (const e of varEntries) for (const n of e.names) varOwner.set(n, e);
|
|
217
|
+
const pkgNames = new Set([
|
|
218
|
+
...varOwner.keys(),
|
|
219
|
+
...funcs.keys(),
|
|
220
|
+
...constEntries.flatMap((e) => e.names),
|
|
221
|
+
]);
|
|
222
|
+
const ctx = { pkgNames, funcs };
|
|
223
|
+
const funcRefs = new Map();
|
|
224
|
+
const refsOfFunc = (key) => {
|
|
225
|
+
if (funcRefs.has(key)) return funcRefs.get(key);
|
|
226
|
+
const decl = funcs.get(key);
|
|
227
|
+
const out = emptyRefs();
|
|
228
|
+
funcRefs.set(key, out);
|
|
229
|
+
if (decl) collectRefs(decl, ctx, out);
|
|
230
|
+
return out;
|
|
231
|
+
};
|
|
232
|
+
const depsOf = (entry) => {
|
|
233
|
+
const deps = new Map(); // owner entry -> path of names
|
|
234
|
+
const seen = new Set();
|
|
235
|
+
const follow = (key, path) => {
|
|
236
|
+
if (!funcs.has(key) || seen.has(key)) return;
|
|
237
|
+
seen.add(key);
|
|
238
|
+
visit(refsOfFunc(key), [...path, key]);
|
|
239
|
+
};
|
|
240
|
+
const visit = (refs, path) => {
|
|
241
|
+
for (const name of refs.names) {
|
|
242
|
+
const owner = varOwner.get(name);
|
|
243
|
+
if (owner) {
|
|
244
|
+
if (!deps.has(owner)) deps.set(owner, [...path, name]);
|
|
245
|
+
} else follow(name, path);
|
|
246
|
+
}
|
|
247
|
+
for (const m of refs.methods) follow(m, path);
|
|
248
|
+
};
|
|
249
|
+
const refs = emptyRefs();
|
|
250
|
+
for (const v of entry.spec.value ?? []) collectRefs(v, ctx, refs);
|
|
251
|
+
visit(refs, []);
|
|
252
|
+
return deps;
|
|
253
|
+
};
|
|
254
|
+
return new Map(varEntries.map((e) => [e, depsOf(e)]));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// A chain of names leading from `start` back to itself through the specs in
|
|
258
|
+
// `pending`, e.g. ["a", "f", "b", "a"]; null when `start` is on no cycle.
|
|
259
|
+
function findCycle(start, depsOf, pending) {
|
|
260
|
+
const visited = new Set();
|
|
261
|
+
const walk = (entry, chain) => {
|
|
262
|
+
for (const [dep, path] of depsOf.get(entry)) {
|
|
263
|
+
const next = [...chain, ...path];
|
|
264
|
+
if (dep === start) return next;
|
|
265
|
+
if (!pending.has(dep) || visited.has(dep)) continue;
|
|
266
|
+
visited.add(dep);
|
|
267
|
+
const found = walk(dep, next);
|
|
268
|
+
if (found) return found;
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
};
|
|
272
|
+
const chain = walk(start, [start.names[0]]);
|
|
273
|
+
return chain && chain.length === 2 ? [chain[0]] : chain;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function cycleMessage(entry, chain) {
|
|
277
|
+
const name = entry.names[0];
|
|
278
|
+
if (!chain) return `initialization cycle for ${name}`;
|
|
279
|
+
if (chain.length === 1)
|
|
280
|
+
return `initialization cycle: ${name} refers to itself`;
|
|
281
|
+
return `initialization cycle for ${name}: ${chain.join(" refers to ")}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Go's rule: repeatedly take the earliest spec whose dependencies are done.
|
|
285
|
+
function orderVars(varEntries, depsOf) {
|
|
286
|
+
const done = new Set();
|
|
287
|
+
const vars = [];
|
|
288
|
+
const ready = (e) => {
|
|
289
|
+
for (const dep of depsOf.get(e).keys()) {
|
|
290
|
+
if (dep === e && e.names.length > 1) continue;
|
|
291
|
+
if (!done.has(dep)) return false;
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
};
|
|
295
|
+
const remaining = varEntries.slice();
|
|
296
|
+
while (remaining.length > 0) {
|
|
297
|
+
const idx = remaining.findIndex(ready);
|
|
298
|
+
if (idx === -1) {
|
|
299
|
+
// Everything left is on or behind a cycle: report the first spec
|
|
300
|
+
// that is on one.
|
|
301
|
+
const pending = new Set(remaining);
|
|
302
|
+
let culprit = remaining[0];
|
|
303
|
+
let chain = null;
|
|
304
|
+
for (const e of remaining) {
|
|
305
|
+
chain = findCycle(e, depsOf, pending);
|
|
306
|
+
if (chain) {
|
|
307
|
+
culprit = e;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
vars.push(...remaining);
|
|
312
|
+
return {
|
|
313
|
+
vars,
|
|
314
|
+
cycles: [{ spec: culprit.spec, message: cycleMessage(culprit, chain) }],
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const [e] = remaining.splice(idx, 1);
|
|
318
|
+
done.add(e);
|
|
319
|
+
vars.push(e);
|
|
320
|
+
}
|
|
321
|
+
return { vars, cycles: [] };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Constants depend on other constants only; a constant that reaches itself is
|
|
325
|
+
// a cycle (reported once, at the constant where the walk closed it).
|
|
326
|
+
function orderConsts(constEntries) {
|
|
327
|
+
const constNames = new Set(constEntries.flatMap((e) => e.names));
|
|
328
|
+
const ctx = { pkgNames: constNames, funcs: new Map() };
|
|
329
|
+
const owner = new Map();
|
|
330
|
+
for (const e of constEntries) for (const n of e.names) owner.set(n, e);
|
|
331
|
+
const consts = [];
|
|
332
|
+
const cycles = [];
|
|
333
|
+
const state = new Map(); // entry -> 1 while on the walk's path, 2 when done
|
|
334
|
+
const path = [];
|
|
335
|
+
const visit = (e) => {
|
|
336
|
+
state.set(e, 1);
|
|
337
|
+
path.push(e);
|
|
338
|
+
const refs = emptyRefs();
|
|
339
|
+
for (const v of e.spec.value ?? []) collectRefs(v, ctx, refs);
|
|
340
|
+
for (const n of refs.names) {
|
|
341
|
+
const dep = owner.get(n);
|
|
342
|
+
if (!dep) continue;
|
|
343
|
+
if (state.get(dep) === 1) {
|
|
344
|
+
const names = path.slice(path.indexOf(dep)).map((p) => p.names[0]);
|
|
345
|
+
const chain = names.length === 1 ? names : [...names, names[0]];
|
|
346
|
+
cycles.push({ spec: dep.spec, message: cycleMessage(dep, chain) });
|
|
347
|
+
} else if (!state.has(dep)) visit(dep);
|
|
348
|
+
}
|
|
349
|
+
path.pop();
|
|
350
|
+
state.set(e, 2);
|
|
351
|
+
consts.push(e);
|
|
352
|
+
};
|
|
353
|
+
for (const e of constEntries) if (!state.has(e)) visit(e);
|
|
354
|
+
return { consts, cycles };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// A literal initialiser (or none: the zero value) reads nothing and has no
|
|
358
|
+
// effect, and gc initialises it statically, so a function that runs during
|
|
359
|
+
// another variable's initialiser sees it set even when it is declared later
|
|
360
|
+
// (an interface method call adds no dependency in the spec).
|
|
361
|
+
const isStaticValue = (v) =>
|
|
362
|
+
v?.kind === "BasicLit" ||
|
|
363
|
+
(v?.kind === "UnaryExpr" &&
|
|
364
|
+
["-", "+"].includes(v.op) &&
|
|
365
|
+
isStaticValue(v.operand));
|
|
366
|
+
|
|
367
|
+
function staticInitsFirst(vars) {
|
|
368
|
+
const isStatic = (e) => (e.spec.value ?? []).every(isStaticValue);
|
|
369
|
+
return [...vars.filter(isStatic), ...vars.filter((e) => !isStatic(e))];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* @param {object[]} programs parsed (and, for method resolution, checked) files
|
|
374
|
+
* @returns {{ consts: object[], vars: object[], cycles: object[] }}
|
|
375
|
+
* `consts` / `vars`: entries `{ decl, spec, names, program }` in initialisation
|
|
376
|
+
* order; `cycles`: `{ spec, message }` for each initialisation cycle.
|
|
377
|
+
*/
|
|
378
|
+
export function orderPackageInits(programs) {
|
|
379
|
+
const decls = collectDecls(programs);
|
|
380
|
+
const depsOf = buildVarDeps(decls);
|
|
381
|
+
const ordered = orderVars(decls.varEntries, depsOf);
|
|
382
|
+
const { cycles } = ordered;
|
|
383
|
+
const vars =
|
|
384
|
+
cycles.length === 0 ? staticInitsFirst(ordered.vars) : ordered.vars;
|
|
385
|
+
const constOrder = orderConsts(decls.constEntries);
|
|
386
|
+
return {
|
|
387
|
+
consts: constOrder.consts,
|
|
388
|
+
vars,
|
|
389
|
+
cycles: [...constOrder.cycles, ...cycles],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Package-level `var` specs in the order their initialisers must be *typed*:
|
|
395
|
+
* a spec that names another package variable (directly, even inside a func
|
|
396
|
+
* literal) comes after it, since `var a = b + 1; var b = f()` infers `a` from
|
|
397
|
+
* `b`. Declaration order is kept otherwise.
|
|
398
|
+
*/
|
|
399
|
+
export function orderVarSpecsForTyping(programs) {
|
|
400
|
+
const entries = [];
|
|
401
|
+
for (const program of programs)
|
|
402
|
+
for (const d of program.decls ?? [])
|
|
403
|
+
if (d.kind === "VarDecl")
|
|
404
|
+
for (const spec of d.decls ?? [])
|
|
405
|
+
entries.push({ program, decl: d, spec });
|
|
406
|
+
const owner = new Map();
|
|
407
|
+
for (const e of entries) for (const n of e.spec.names) owner.set(n, e);
|
|
408
|
+
const ctx = { pkgNames: new Set(owner.keys()), funcs: new Map() };
|
|
409
|
+
const out = [];
|
|
410
|
+
const state = new Set();
|
|
411
|
+
const visit = (e) => {
|
|
412
|
+
if (state.has(e)) return;
|
|
413
|
+
state.add(e);
|
|
414
|
+
if (!e.spec.type) {
|
|
415
|
+
const refs = emptyRefs();
|
|
416
|
+
for (const v of e.spec.value ?? []) collectRefs(v, ctx, refs);
|
|
417
|
+
for (const n of refs.names) {
|
|
418
|
+
const o = owner.get(n);
|
|
419
|
+
if (o && o !== e) visit(o);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
out.push(e);
|
|
423
|
+
};
|
|
424
|
+
for (const e of entries) visit(e);
|
|
425
|
+
return out;
|
|
426
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Source-like text of an expression for diagnostics ("cannot use y (variable
|
|
2
|
+
// of type float64) as int value"). Only the common shapes are printed in full;
|
|
3
|
+
// anything else falls back to "expression".
|
|
4
|
+
|
|
5
|
+
function litText(node) {
|
|
6
|
+
switch (node.litKind) {
|
|
7
|
+
case "STRING":
|
|
8
|
+
return JSON.stringify(node.value);
|
|
9
|
+
case "NIL":
|
|
10
|
+
return "nil";
|
|
11
|
+
default:
|
|
12
|
+
return String(node.value);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const PRINTERS = {
|
|
17
|
+
Ident: (n) => n.name,
|
|
18
|
+
BasicLit: litText,
|
|
19
|
+
ParenExpr: (n) => `(${exprText(n.expr)})`,
|
|
20
|
+
SelectorExpr: (n) => `${exprText(n.expr)}.${n.field}`,
|
|
21
|
+
UnaryExpr: (n) => `${n.op}${exprText(n.operand)}`,
|
|
22
|
+
BinaryExpr: (n) => `${exprText(n.left)} ${n.op} ${exprText(n.right)}`,
|
|
23
|
+
CallExpr: (n) =>
|
|
24
|
+
`${exprText(n.func)}(${(n.args ?? []).map(exprText).join(", ")})`,
|
|
25
|
+
IndexExpr: (n) => `${exprText(n.expr)}[${exprText(n.index)}]`,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function exprText(node) {
|
|
29
|
+
const print = node && PRINTERS[node.kind];
|
|
30
|
+
return print ? print(node) : "expression";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// "variable of type float64", "untyped int constant", "value of type float64".
|
|
34
|
+
export function operandKind(node, typeName, isUntypedConst) {
|
|
35
|
+
if (isUntypedConst)
|
|
36
|
+
return `untyped ${typeName === "float64" ? "float" : typeName} constant`;
|
|
37
|
+
return node?.kind === "Ident"
|
|
38
|
+
? `variable of type ${typeName}`
|
|
39
|
+
: `value of type ${typeName}`;
|
|
40
|
+
}
|