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
package/src/lower/captures.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// are mutated and thus require heap-allocated environment cells for WASM closures.
|
|
4
4
|
|
|
5
5
|
import { collectMutatedVars } from "./ownership.js";
|
|
6
|
+
import { getOrCreateIteratorFuncLit, isIteratorRangeFor } from "./range.js";
|
|
6
7
|
import { forEachChild } from "./walk.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -12,6 +13,9 @@ export class CaptureAnalysis {
|
|
|
12
13
|
constructor() {
|
|
13
14
|
// Map<FuncLit, Set<string>>: free variables captured by each closure
|
|
14
15
|
this.capturesByClosure = new Map();
|
|
16
|
+
// Map<FuncLit, Map<string, Type>>: the type of each captured variable,
|
|
17
|
+
// read off the closure's first reference to it.
|
|
18
|
+
this.captureTypes = new Map();
|
|
15
19
|
// Set<string>: variables in the enclosing scope that are captured AND mutated
|
|
16
20
|
this.mutatedCaptures = new Set();
|
|
17
21
|
}
|
|
@@ -40,58 +44,151 @@ export function collectDeclaredNames(node, names = new Set()) {
|
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
// Do not descend into nested functions when collecting local declarations
|
|
43
|
-
if (node.kind === "FuncLit") return names;
|
|
47
|
+
if (node.kind === "FuncLit" || isIteratorRangeFor(node)) return names;
|
|
44
48
|
|
|
45
49
|
forEachChild(node, (child) => collectDeclaredNames(child, names));
|
|
46
50
|
return names;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
/**
|
|
50
|
-
* Finds
|
|
54
|
+
* Finds the identifiers a function literal body reads from outside itself. A
|
|
55
|
+
* name is local only from its declaration onwards and only inside its block,
|
|
56
|
+
* so `m := f(m)` reads the enclosing `m`, and `{ m := 1 }; use(m)` reads it
|
|
57
|
+
* too. `scopes` is the stack of name sets visible inside the literal;
|
|
58
|
+
* `types` receives the type of the first reference to each free name.
|
|
51
59
|
*/
|
|
52
|
-
function collectReferencedNames(node,
|
|
60
|
+
function collectReferencedNames(node, scopes, refs = new Set(), types) {
|
|
53
61
|
if (!node || typeof node !== "object") return refs;
|
|
54
62
|
if (Array.isArray(node)) {
|
|
55
|
-
for (const item of node) collectReferencedNames(item,
|
|
56
|
-
return refs;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// In struct literals, field names in KeyValueExpr are not variable references
|
|
60
|
-
if (node.kind === "KeyValueExpr") {
|
|
61
|
-
collectReferencedNames(node.value, localDecls, refs);
|
|
63
|
+
for (const item of node) collectReferencedNames(item, scopes, refs, types);
|
|
62
64
|
return refs;
|
|
63
65
|
}
|
|
66
|
+
const walk = (child) => collectReferencedNames(child, scopes, refs, types);
|
|
67
|
+
const scoped = (fn) => {
|
|
68
|
+
scopes.push(new Set());
|
|
69
|
+
fn();
|
|
70
|
+
scopes.pop();
|
|
71
|
+
};
|
|
72
|
+
const declare = (name) => {
|
|
73
|
+
if (name && name !== "_") scopes[scopes.length - 1].add(name);
|
|
74
|
+
};
|
|
64
75
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
switch (node.kind) {
|
|
77
|
+
// In struct literals, field names in KeyValueExpr are not variable references
|
|
78
|
+
case "KeyValueExpr":
|
|
79
|
+
walk(node.value);
|
|
80
|
+
return refs;
|
|
81
|
+
// Map keys are expressions that may read captured variables; struct
|
|
82
|
+
// field names are not references.
|
|
83
|
+
case "CompositeLit": {
|
|
84
|
+
const t = node._type?.underlying ?? node._type;
|
|
85
|
+
if (t && t.kind !== "struct") {
|
|
86
|
+
for (const el of node.elems ?? []) {
|
|
87
|
+
if (el?.kind === "KeyValueExpr") walk(el.key);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
72
91
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
case "FuncLit":
|
|
93
|
+
scoped(() => {
|
|
94
|
+
for (const p of node.params ?? []) declare(p.name);
|
|
95
|
+
for (const r of node.returnType?._namedReturns ?? []) declare(r.name);
|
|
96
|
+
forEachChild(node, walk);
|
|
97
|
+
});
|
|
98
|
+
return refs;
|
|
99
|
+
case "DefineStmt":
|
|
100
|
+
walk(node.rhs);
|
|
101
|
+
for (const l of node.lhs ?? []) {
|
|
102
|
+
if (l.kind === "Ident") declare(l.name);
|
|
103
|
+
else walk(l);
|
|
104
|
+
}
|
|
105
|
+
return refs;
|
|
106
|
+
case "VarDecl":
|
|
107
|
+
case "ConstDecl":
|
|
108
|
+
for (const spec of node.decls ?? []) {
|
|
109
|
+
walk(spec.value ?? spec.init);
|
|
110
|
+
for (const name of spec.names ?? []) declare(name);
|
|
111
|
+
}
|
|
112
|
+
return refs;
|
|
113
|
+
case "TypeSwitchStmt":
|
|
114
|
+
scoped(() => {
|
|
115
|
+
walk(node.expr);
|
|
116
|
+
for (const c of node.cases ?? []) {
|
|
117
|
+
scoped(() => {
|
|
118
|
+
declare(node.assign);
|
|
119
|
+
walk(c.stmts);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return refs;
|
|
124
|
+
case "Block":
|
|
125
|
+
case "CaseClause":
|
|
126
|
+
case "IfStmt":
|
|
127
|
+
case "ForStmt":
|
|
128
|
+
case "SwitchStmt":
|
|
129
|
+
scoped(() => forEachChild(node, walk));
|
|
130
|
+
return refs;
|
|
131
|
+
case "Ident":
|
|
132
|
+
if (node.name !== "_" && !scopes.some((sc) => sc.has(node.name))) {
|
|
133
|
+
refs.add(node.name);
|
|
134
|
+
if (node._type && types && !types.has(node.name))
|
|
135
|
+
types.set(node.name, node._type);
|
|
136
|
+
}
|
|
137
|
+
return refs;
|
|
86
138
|
}
|
|
87
139
|
|
|
88
140
|
// For nested closures, free variables are also referenced
|
|
89
|
-
forEachChild(node,
|
|
90
|
-
collectReferencedNames(child, localDecls, refs),
|
|
91
|
-
);
|
|
141
|
+
forEachChild(node, walk);
|
|
92
142
|
return refs;
|
|
93
143
|
}
|
|
94
144
|
|
|
145
|
+
function hasReturnInNode(n) {
|
|
146
|
+
let found = false;
|
|
147
|
+
const check = (x) => {
|
|
148
|
+
if (!x || typeof x !== "object" || found) return;
|
|
149
|
+
if (Array.isArray(x)) {
|
|
150
|
+
for (const elem of x) check(elem);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (x.kind === "ReturnStmt") {
|
|
154
|
+
found = true;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (x.kind === "FuncLit" && !x._isYieldClosure) return;
|
|
158
|
+
for (const k of Object.keys(x)) {
|
|
159
|
+
if (k.startsWith("_")) continue;
|
|
160
|
+
check(x[k]);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
check(n);
|
|
164
|
+
return found;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function hasIteratorWithReturn(body) {
|
|
168
|
+
let found = false;
|
|
169
|
+
const check = (x) => {
|
|
170
|
+
if (!x || typeof x !== "object" || found) return;
|
|
171
|
+
if (Array.isArray(x)) {
|
|
172
|
+
for (const elem of x) check(elem);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (x.kind === "FuncLit") return;
|
|
176
|
+
if (isIteratorRangeFor(x)) {
|
|
177
|
+
const lit = getOrCreateIteratorFuncLit(x);
|
|
178
|
+
if (hasReturnInNode(lit?.body)) {
|
|
179
|
+
found = true;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
for (const k of Object.keys(x)) {
|
|
184
|
+
if (k.startsWith("_")) continue;
|
|
185
|
+
check(x[k]);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
check(body);
|
|
189
|
+
return found;
|
|
190
|
+
}
|
|
191
|
+
|
|
95
192
|
/**
|
|
96
193
|
* Analyzes a function or method declaration for closures and their captured variables.
|
|
97
194
|
* @param {Object} fnDecl AST FuncDecl or FuncLit node
|
|
@@ -101,6 +198,26 @@ export function analyzeCaptures(fnDecl) {
|
|
|
101
198
|
const result = new CaptureAnalysis();
|
|
102
199
|
if (!fnDecl?.body) return result;
|
|
103
200
|
|
|
201
|
+
// Synthesize named returns if not already present, so iterator returns can share boxed slots
|
|
202
|
+
if (
|
|
203
|
+
fnDecl.returnType &&
|
|
204
|
+
!fnDecl.returnType._namedReturns &&
|
|
205
|
+
hasIteratorWithReturn(fnDecl.body)
|
|
206
|
+
) {
|
|
207
|
+
const ret = fnDecl.returnType;
|
|
208
|
+
if (ret.name !== "void") {
|
|
209
|
+
const types =
|
|
210
|
+
ret.kind === "TupleType" || ret.kind === "tuple" ? ret.types : [ret];
|
|
211
|
+
if (types && types.length > 0) {
|
|
212
|
+
fnDecl.returnType._namedReturns = types.map((t, i) => ({
|
|
213
|
+
name: `__retVal$${i}`,
|
|
214
|
+
type: { ...t, _namedReturns: undefined },
|
|
215
|
+
_synthesized: true,
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
104
221
|
// Collect parameters, receiver and named returns of the root function
|
|
105
222
|
const rootScope = new Set();
|
|
106
223
|
if (fnDecl.recvName && fnDecl.recvName !== "_") {
|
|
@@ -117,46 +234,97 @@ export function analyzeCaptures(fnDecl) {
|
|
|
117
234
|
// Fast pre-collected set of all mutated variables in the entire function
|
|
118
235
|
const mutatedVars = collectMutatedVars(fnDecl.body);
|
|
119
236
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return;
|
|
237
|
+
const handleIteratorRange = (node, scopeStack) => {
|
|
238
|
+
const funcLit = getOrCreateIteratorFuncLit(node);
|
|
239
|
+
if (node._iterStateName) {
|
|
240
|
+
rootScope.add(node._iterStateName);
|
|
241
|
+
result.mutatedCaptures.add(node._iterStateName);
|
|
126
242
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (const
|
|
131
|
-
if (
|
|
243
|
+
const rootNamed = fnDecl.returnType?._namedReturns ?? [];
|
|
244
|
+
const hasRet = hasReturnInNode(funcLit.body);
|
|
245
|
+
if (hasRet && rootNamed.length > 0) {
|
|
246
|
+
for (const r of rootNamed) {
|
|
247
|
+
if (r.name && r.name !== "_") {
|
|
248
|
+
rootScope.add(r.name);
|
|
249
|
+
result.mutatedCaptures.add(r.name);
|
|
250
|
+
}
|
|
132
251
|
}
|
|
133
|
-
|
|
134
|
-
|
|
252
|
+
}
|
|
253
|
+
walkClosures(funcLit, scopeStack);
|
|
254
|
+
// A func literal ranged over directly is a closure of its own.
|
|
255
|
+
walkClosures(node.cond?.expr ?? node.init?.rhs?.[0]?.expr, scopeStack);
|
|
256
|
+
const captured = result.capturesByClosure.get(funcLit) ?? new Set();
|
|
257
|
+
if (node._iterStateName) {
|
|
258
|
+
captured.add(node._iterStateName);
|
|
259
|
+
}
|
|
260
|
+
if (hasRet && rootNamed.length > 0) {
|
|
261
|
+
for (const r of rootNamed) {
|
|
262
|
+
if (r.name && r.name !== "_") {
|
|
263
|
+
captured.add(r.name);
|
|
264
|
+
}
|
|
135
265
|
}
|
|
136
|
-
|
|
266
|
+
}
|
|
267
|
+
result.capturesByClosure.set(funcLit, captured);
|
|
268
|
+
};
|
|
137
269
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
270
|
+
const handleFuncLit = (node, scopeStack) => {
|
|
271
|
+
const closureLocals = new Set();
|
|
272
|
+
for (const p of node.params ?? []) {
|
|
273
|
+
if (p.name && p.name !== "_") closureLocals.add(p.name);
|
|
274
|
+
}
|
|
275
|
+
for (const r of node.returnType?._namedReturns ?? []) {
|
|
276
|
+
if (r.name && r.name !== "_") closureLocals.add(r.name);
|
|
277
|
+
}
|
|
278
|
+
const paramScope = new Set(closureLocals);
|
|
279
|
+
collectDeclaredNames(node.body, closureLocals);
|
|
143
280
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
for (const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
281
|
+
const allOuterScope = new Set();
|
|
282
|
+
for (const scope of scopeStack) {
|
|
283
|
+
for (const name of scope) allOuterScope.add(name);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const refTypes = new Map();
|
|
287
|
+
const allRefs = collectReferencedNames(
|
|
288
|
+
node.body,
|
|
289
|
+
[paramScope],
|
|
290
|
+
new Set(),
|
|
291
|
+
refTypes,
|
|
292
|
+
);
|
|
293
|
+
const captured = new Set();
|
|
294
|
+
const capturedTypes = new Map();
|
|
295
|
+
for (const ref of allRefs) {
|
|
296
|
+
if (allOuterScope.has(ref)) {
|
|
297
|
+
captured.add(ref);
|
|
298
|
+
if (refTypes.has(ref)) capturedTypes.set(ref, refTypes.get(ref));
|
|
150
299
|
}
|
|
151
|
-
|
|
300
|
+
}
|
|
301
|
+
result.capturesByClosure.set(node, captured);
|
|
302
|
+
result.captureTypes.set(node, capturedTypes);
|
|
152
303
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
304
|
+
for (const varName of captured) {
|
|
305
|
+
if (mutatedVars.has(varName)) {
|
|
306
|
+
result.mutatedCaptures.add(varName);
|
|
157
307
|
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
walkClosures(node.body, [...scopeStack, closureLocals]);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Traverse closures with lexical scope stack to properly handle arbitrary nesting
|
|
314
|
+
const walkClosures = (node, scopeStack) => {
|
|
315
|
+
if (!node || typeof node !== "object") return;
|
|
316
|
+
if (Array.isArray(node)) {
|
|
317
|
+
for (const item of node) walkClosures(item, scopeStack);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
158
320
|
|
|
159
|
-
|
|
321
|
+
if (isIteratorRangeFor(node)) {
|
|
322
|
+
handleIteratorRange(node, scopeStack);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (node.kind === "FuncLit") {
|
|
327
|
+
handleFuncLit(node, scopeStack);
|
|
160
328
|
return;
|
|
161
329
|
}
|
|
162
330
|
|
|
@@ -166,3 +334,38 @@ export function analyzeCaptures(fnDecl) {
|
|
|
166
334
|
walkClosures(fnDecl.body, [rootScope]);
|
|
167
335
|
return result;
|
|
168
336
|
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Go 1.22 per-iteration loop variables: the names declared by a three-clause
|
|
340
|
+
* `for` init (`for i := …`) that a closure inside the loop (body, cond or
|
|
341
|
+
* post) refers to, or whose address is taken there (`&i`, `&p.X`): a pointer
|
|
342
|
+
* from one iteration must keep pointing at that iteration's variable.
|
|
343
|
+
* Conservative by name. Only these need a fresh copy per iteration; the
|
|
344
|
+
* others stay plain locals.
|
|
345
|
+
*/
|
|
346
|
+
export function loopVarsCapturedInLoop(stmt) {
|
|
347
|
+
const out = new Set();
|
|
348
|
+
if (stmt?.init?.kind !== "DefineStmt") return out;
|
|
349
|
+
const names = new Set(
|
|
350
|
+
(stmt.init.lhs ?? [])
|
|
351
|
+
.filter((l) => l.kind === "Ident" && l.name !== "_")
|
|
352
|
+
.map((l) => l.name),
|
|
353
|
+
);
|
|
354
|
+
if (names.size === 0) return out;
|
|
355
|
+
const walk = (node, inClosure) => {
|
|
356
|
+
if (!node || typeof node !== "object") return;
|
|
357
|
+
if (Array.isArray(node)) {
|
|
358
|
+
for (const n of node) walk(n, inClosure);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (inClosure && node.kind === "Ident" && names.has(node.name))
|
|
362
|
+
out.add(node.name);
|
|
363
|
+
const nowIn = inClosure || node.kind === "FuncLit";
|
|
364
|
+
forEachChild(node, (child) => walk(child, nowIn));
|
|
365
|
+
};
|
|
366
|
+
const loop = [stmt.cond, stmt.post, stmt.body];
|
|
367
|
+
walk(loop, false);
|
|
368
|
+
for (const name of collectMutatedVars(loop, new Set(), true))
|
|
369
|
+
if (names.has(name)) out.add(name);
|
|
370
|
+
return out;
|
|
371
|
+
}
|
package/src/lower/embedding.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
// src/lower/embedding.js
|
|
2
2
|
// Computes promoted methods and delegation stubs for embedded structs.
|
|
3
3
|
|
|
4
|
+
import { embeddedFieldName } from "../typechecker/types.js";
|
|
5
|
+
|
|
6
|
+
// A method promoted from an embedded struct value but declared on its
|
|
7
|
+
// pointer receiver is outside the method set of the outer struct's value.
|
|
8
|
+
function isPtrOnlyPromotion(rawEmbed, embed, mName, mType) {
|
|
9
|
+
if (rawEmbed.kind === "pointer") return false;
|
|
10
|
+
return Boolean((mType ?? embed.methods?.get(mName))?._ptrRecv);
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
export function computeEmbeddedStubs(name, methodDecls, checker) {
|
|
5
14
|
if (!checker) return [];
|
|
6
15
|
const resolvedType = checker.types.get(name)?.underlying;
|
|
@@ -9,18 +18,38 @@ export function computeEmbeddedStubs(name, methodDecls, checker) {
|
|
|
9
18
|
const declared = new Set(methodDecls.map((m) => m.name));
|
|
10
19
|
const stubs = [];
|
|
11
20
|
|
|
12
|
-
|
|
21
|
+
const embedded = (resolvedType._structFields ?? []).filter((f) => f.embedded);
|
|
22
|
+
for (const [i, rawEmbed] of resolvedType._embeds.entries()) {
|
|
23
|
+
const fieldName = embedded[i] ? embeddedFieldName(embedded[i]) : null;
|
|
13
24
|
const embed = rawEmbed.kind === "pointer" ? rawEmbed.base : rawEmbed;
|
|
14
25
|
const embedName = embed.kind === "named" ? embed.name : null;
|
|
15
26
|
if (!embedName) continue;
|
|
16
27
|
const embedBase = embed.kind === "named" ? embed.underlying : embed;
|
|
17
|
-
if (embedBase?.kind !== "struct" || !embedBase.methods)
|
|
28
|
+
if (embedBase?.kind !== "struct" || !embedBase.methods) {
|
|
29
|
+
// A named non-struct type (`time.Duration`) promotes its value
|
|
30
|
+
// methods; the stub passes the embedded value as the receiver.
|
|
31
|
+
if (rawEmbed.kind === "pointer" || embedBase?.kind === "interface")
|
|
32
|
+
continue;
|
|
33
|
+
for (const [mName, mType] of embed.methods?.entries() ?? []) {
|
|
34
|
+
if (declared.has(mName) || mType?._ptrRecv) continue;
|
|
35
|
+
stubs.push({
|
|
36
|
+
methodName: mName,
|
|
37
|
+
embedName,
|
|
38
|
+
fieldName,
|
|
39
|
+
valueEmbed: true,
|
|
40
|
+
});
|
|
41
|
+
declared.add(mName);
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
18
45
|
|
|
19
|
-
for (const [mName] of embedBase.methods.entries()) {
|
|
46
|
+
for (const [mName, mType] of embedBase.methods.entries()) {
|
|
20
47
|
if (!declared.has(mName)) {
|
|
21
48
|
stubs.push({
|
|
22
49
|
methodName: mName,
|
|
23
50
|
embedName,
|
|
51
|
+
fieldName,
|
|
52
|
+
ptrOnly: isPtrOnlyPromotion(rawEmbed, embed, mName, mType),
|
|
24
53
|
});
|
|
25
54
|
declared.add(mName); // Prevent duplicate stubs for diamond embeds
|
|
26
55
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// src/lower/errors-as.js
|
|
2
|
+
// errors.As as generated Go source.
|
|
3
|
+
//
|
|
4
|
+
// `errors.As(err, &x)` stores through a pointer whose type only the checker
|
|
5
|
+
// knows. Each call site gets a function generated for its static target
|
|
6
|
+
// type; it walks the error tree like the real package and honours custom
|
|
7
|
+
// `As(any) bool` methods. The functions are ordinary Go, parsed and checked
|
|
8
|
+
// as part of the package's generated file (src/link/stdlib.js), so both
|
|
9
|
+
// backends compile the same code.
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
importsFor,
|
|
13
|
+
mentionsTypeParam,
|
|
14
|
+
TypeSpeller,
|
|
15
|
+
walkNodes,
|
|
16
|
+
} from "./gen-types.js";
|
|
17
|
+
|
|
18
|
+
const ERRORS_PATH = "errors";
|
|
19
|
+
const NOT_POINTER =
|
|
20
|
+
"errors.As: second argument must be a non-nil pointer to a type that implements error or to any interface type";
|
|
21
|
+
|
|
22
|
+
// The names the file imports `errors` as.
|
|
23
|
+
function errorsAliases(program) {
|
|
24
|
+
const aliases = new Set();
|
|
25
|
+
for (const imp of program.imports ?? [])
|
|
26
|
+
for (const spec of imp.imports)
|
|
27
|
+
if (spec.path === ERRORS_PATH && spec.alias !== "_")
|
|
28
|
+
aliases.add(spec.alias ?? "errors");
|
|
29
|
+
return aliases;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isAsRef = (node, aliases) =>
|
|
33
|
+
node.kind === "SelectorExpr" &&
|
|
34
|
+
node.expr?.kind === "Ident" &&
|
|
35
|
+
node.expr._type?.kind === "namespace" &&
|
|
36
|
+
aliases.has(node.expr.name) &&
|
|
37
|
+
node.field === "As";
|
|
38
|
+
|
|
39
|
+
const isAsCall = (node, aliases) =>
|
|
40
|
+
node.kind === "CallExpr" && isAsRef(node.func, aliases);
|
|
41
|
+
|
|
42
|
+
// Marks `call` to call the generated function `name`. The function takes the
|
|
43
|
+
// typed pointer: the target is not boxed into an interface the way
|
|
44
|
+
// errors.As's `any` parameter asked.
|
|
45
|
+
function markCall(call, name) {
|
|
46
|
+
call.func._genEntry = name;
|
|
47
|
+
const arg = call.args[1];
|
|
48
|
+
arg._ifaceBox = undefined;
|
|
49
|
+
arg._ifaceSrc = undefined;
|
|
50
|
+
arg._ifaceNamedType = undefined;
|
|
51
|
+
arg._ifacePtrFields = undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// One function per distinct target type; `tparams` ("[T any]") makes it the
|
|
55
|
+
// shared function for targets that are a type parameter.
|
|
56
|
+
function asFunction(name, target, tparams = "") {
|
|
57
|
+
return `func ${name}${tparams}(err error, target *${target}) bool {
|
|
58
|
+
if target == nil {
|
|
59
|
+
panic("errors: target must be a non-nil pointer")
|
|
60
|
+
}
|
|
61
|
+
for err != nil {
|
|
62
|
+
if v, ok := err.(${target}); ok {
|
|
63
|
+
*target = v
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
if a, ok := err.(interface{ As(any) bool }); ok && a.As(target) {
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
switch u := err.(type) {
|
|
70
|
+
case interface{ Unwrap() error }:
|
|
71
|
+
err = u.Unwrap()
|
|
72
|
+
case interface{ Unwrap() []error }:
|
|
73
|
+
for _, e := range u.Unwrap() {
|
|
74
|
+
if e != nil && ${name}${tparams ? "[T]" : ""}(e, target) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return false
|
|
79
|
+
default:
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false
|
|
84
|
+
}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The `errors.As` calls of the programs, and a diagnostic for each use of
|
|
88
|
+
// errors.As that is not a call.
|
|
89
|
+
function findSites(programs) {
|
|
90
|
+
const sites = [];
|
|
91
|
+
const diagnostics = [];
|
|
92
|
+
for (const program of programs) {
|
|
93
|
+
const aliases = errorsAliases(program);
|
|
94
|
+
if (aliases.size === 0) continue;
|
|
95
|
+
const callees = new Set();
|
|
96
|
+
const refs = [];
|
|
97
|
+
walkNodes(program.decls, (node) => {
|
|
98
|
+
if (isAsCall(node, aliases)) {
|
|
99
|
+
sites.push({ call: node, program });
|
|
100
|
+
callees.add(node.func);
|
|
101
|
+
} else if (isAsRef(node, aliases)) {
|
|
102
|
+
refs.push(node);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
for (const node of refs)
|
|
106
|
+
if (!callees.has(node))
|
|
107
|
+
diagnostics.push({
|
|
108
|
+
program,
|
|
109
|
+
node: node.expr,
|
|
110
|
+
message: "errors.As cannot be used as a value",
|
|
111
|
+
hint: "call errors.As directly",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return { sites, diagnostics };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// What a call's target type allows: { base } for a target to generate for, or
|
|
118
|
+
// { message, hint } for one the generator rejects.
|
|
119
|
+
function classifyTarget(call) {
|
|
120
|
+
const argType = call.args[1]?._type;
|
|
121
|
+
if (argType?.kind !== "pointer") return { message: NOT_POINTER };
|
|
122
|
+
const base = argType.base;
|
|
123
|
+
if (base?.kind === "interface" && !base.name && base.methods?.size > 0)
|
|
124
|
+
return {
|
|
125
|
+
message:
|
|
126
|
+
"errors.As with a target of an anonymous interface type is not yet available",
|
|
127
|
+
hint: "declare a named interface type for the target",
|
|
128
|
+
};
|
|
129
|
+
if (base?.kind !== "typeParam" && mentionsTypeParam(base))
|
|
130
|
+
return {
|
|
131
|
+
message:
|
|
132
|
+
"errors.As with a target that mentions a type parameter is not yet available",
|
|
133
|
+
hint: "use a target that is a type parameter itself, or a concrete type",
|
|
134
|
+
};
|
|
135
|
+
return { base };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Generates the `errors.As` functions for a package's call sites. Returns
|
|
139
|
+
// null when it has none; otherwise { body, imports, diagnostics, link } and
|
|
140
|
+
// marks each call's SelectorExpr with `_genEntry`.
|
|
141
|
+
export function generateErrorsAs(programs, _checker, pkgName) {
|
|
142
|
+
const { sites, diagnostics } = findSites(programs);
|
|
143
|
+
if (sites.length + diagnostics.length === 0) return null;
|
|
144
|
+
const qualifiers = new Map();
|
|
145
|
+
const speller = new TypeSpeller(pkgName, qualifiers, programs);
|
|
146
|
+
const prefix = `gfe_${String(pkgName).replace(/[^A-Za-z0-9_]/g, "_")}_as_`;
|
|
147
|
+
const names = new Map(); // spelled target -> function name
|
|
148
|
+
const out = [];
|
|
149
|
+
const sharedSites = []; // targets that are a type parameter
|
|
150
|
+
for (const { call, program } of sites) {
|
|
151
|
+
const { base, message, hint } = classifyTarget(call);
|
|
152
|
+
let target;
|
|
153
|
+
try {
|
|
154
|
+
if (base?.kind === "typeParam") sharedSites.push(call);
|
|
155
|
+
else if (base) target = speller.spell(base);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
diagnostics.push({
|
|
158
|
+
program,
|
|
159
|
+
node: call.func.expr,
|
|
160
|
+
message: `errors.As: ${e.message}`,
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!base) {
|
|
165
|
+
diagnostics.push({ program, node: call.func.expr, message, hint });
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (target === undefined) continue;
|
|
169
|
+
if (!names.has(target)) {
|
|
170
|
+
names.set(target, `${prefix}${names.size}`);
|
|
171
|
+
out.push(asFunction(names.get(target), target));
|
|
172
|
+
}
|
|
173
|
+
markCall(call, names.get(target));
|
|
174
|
+
}
|
|
175
|
+
const sharedName = `${prefix}t`;
|
|
176
|
+
if (sharedSites.length > 0) out.push(asFunction(sharedName, "T", "[T any]"));
|
|
177
|
+
for (const call of sharedSites) markCall(call, sharedName);
|
|
178
|
+
// The shared function is generic: once it is checked, its call sites take
|
|
179
|
+
// the type arguments the checker would have inferred.
|
|
180
|
+
const link = (chk) => {
|
|
181
|
+
const generic = chk.globals.symbols.get(sharedName);
|
|
182
|
+
for (const call of sharedSites) {
|
|
183
|
+
const typeArgs = [call.args[1]._type.base];
|
|
184
|
+
for (const n of [call, call.func]) {
|
|
185
|
+
n._typeArgs = typeArgs;
|
|
186
|
+
n._genericFnType = generic;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
body: out.join("\n\n"),
|
|
192
|
+
imports: importsFor(qualifiers),
|
|
193
|
+
diagnostics,
|
|
194
|
+
link,
|
|
195
|
+
};
|
|
196
|
+
}
|