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/ownership.js
CHANGED
|
@@ -13,6 +13,31 @@ export function rootIdentName(e) {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// True when `e` names an element of a slice or array, possibly through
|
|
17
|
+
// fields (`xs[i]`, `xs[i].f.g`).
|
|
18
|
+
function isElementPath(e) {
|
|
19
|
+
while (e) {
|
|
20
|
+
if (e.kind === "IndexExpr") return !e._mapValueType;
|
|
21
|
+
if (e.kind !== "SelectorExpr" && e.kind !== "ParenExpr") return false;
|
|
22
|
+
e = e.expr;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// True when `node` contains `&xs[i]` (or `&xs[i].f`): some code may then hold a
|
|
28
|
+
// pointer to an element, so a whole-element store must write into it.
|
|
29
|
+
export function takesElementAddress(node) {
|
|
30
|
+
if (!node || typeof node !== "object") return false;
|
|
31
|
+
if (Array.isArray(node)) return node.some(takesElementAddress);
|
|
32
|
+
if (
|
|
33
|
+
node.kind === "UnaryExpr" &&
|
|
34
|
+
node.op === "&" &&
|
|
35
|
+
isElementPath(node.operand)
|
|
36
|
+
)
|
|
37
|
+
return true;
|
|
38
|
+
return someChild(node, takesElementAddress);
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
// True when `node` itself (not its children) writes to variable `name`.
|
|
17
42
|
export function nodeWritesVar(node, name) {
|
|
18
43
|
const root = (e) => rootIdentName(e) === name;
|
|
@@ -32,6 +57,22 @@ export function nodeWritesVar(node, name) {
|
|
|
32
57
|
}
|
|
33
58
|
}
|
|
34
59
|
|
|
60
|
+
// The variable whose address `node` takes without an explicit `&`, or null:
|
|
61
|
+
// a pointer-receiver method call or method value (`v.M()`, `v.M` with
|
|
62
|
+
// `func (*T) M`) takes `&v`, and slicing an array (`a[:]`) aliases it.
|
|
63
|
+
export function implicitAddrRoot(node) {
|
|
64
|
+
if (node.kind === "SelectorExpr" && node._type?._ptrRecv === true)
|
|
65
|
+
return rootIdentName(node.expr);
|
|
66
|
+
if (node.kind === "SliceExpr" && isArrayTyped(node.expr?._type))
|
|
67
|
+
return rootIdentName(node.expr);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isArrayTyped(t) {
|
|
72
|
+
while (t?.kind === "named") t = t.underlying;
|
|
73
|
+
return t?.kind === "array";
|
|
74
|
+
}
|
|
75
|
+
|
|
35
76
|
// True when `node` may modify (or take the address of) the value held by variable `name`.
|
|
36
77
|
export function nodeMutatesVar(node, name, addrOnly = false) {
|
|
37
78
|
if (!node || typeof node !== "object") return false;
|
|
@@ -43,11 +84,13 @@ export function nodeMutatesVar(node, name, addrOnly = false) {
|
|
|
43
84
|
rootIdentName(node.operand) === name
|
|
44
85
|
)
|
|
45
86
|
return true;
|
|
87
|
+
if (addrOnly && implicitAddrRoot(node) === name) return true;
|
|
46
88
|
if (!addrOnly && nodeWritesVar(node, name)) return true;
|
|
47
89
|
return someChild(node, (child) => nodeMutatesVar(child, name, addrOnly));
|
|
48
90
|
}
|
|
49
91
|
|
|
50
92
|
// Single-pass collector of all mutated or address-taken variables in an AST node.
|
|
93
|
+
// With `addrOnly`, only address-taken ones, implicit address-taking included.
|
|
51
94
|
export function collectMutatedVars(
|
|
52
95
|
node,
|
|
53
96
|
mutated = new Set(),
|
|
@@ -62,7 +105,10 @@ export function collectMutatedVars(
|
|
|
62
105
|
if (node.kind === "UnaryExpr" && node.op === "&") {
|
|
63
106
|
const root = rootIdentName(node.operand);
|
|
64
107
|
if (root) mutated.add(root);
|
|
65
|
-
} else if (
|
|
108
|
+
} else if (addrOnly) {
|
|
109
|
+
const root = implicitAddrRoot(node);
|
|
110
|
+
if (root) mutated.add(root);
|
|
111
|
+
} else {
|
|
66
112
|
if (node.kind === "AssignStmt") {
|
|
67
113
|
for (const e of node.lhs) {
|
|
68
114
|
const root = rootIdentName(e);
|
|
@@ -139,13 +185,6 @@ export class OwnershipContext {
|
|
|
139
185
|
constructor(body) {
|
|
140
186
|
this.body = body;
|
|
141
187
|
this.mut = new Map();
|
|
142
|
-
this.owned = new Set();
|
|
143
|
-
this.borrowed = new Set();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
markOwnership(name, owned) {
|
|
147
|
-
if (!name || name === "_") return;
|
|
148
|
-
(owned ? this.owned : this.borrowed).add(name);
|
|
149
188
|
}
|
|
150
189
|
|
|
151
190
|
fnMutates(name, addrOnly = false) {
|
package/src/lower/range.js
CHANGED
|
@@ -1,9 +1,94 @@
|
|
|
1
1
|
// src/lower/range.js
|
|
2
2
|
// Classifies for-range loop shapes and variable scoping/redelcarations.
|
|
3
3
|
|
|
4
|
-
import { isNumeric } from "../typechecker/types.js";
|
|
4
|
+
import { isNumeric, iteratorYieldParams } from "../typechecker/types.js";
|
|
5
5
|
import { someChild } from "./walk.js";
|
|
6
6
|
|
|
7
|
+
let iterStateCounter = 0;
|
|
8
|
+
|
|
9
|
+
export function isIteratorRangeFor(stmt) {
|
|
10
|
+
if (stmt?.kind !== "ForStmt") return false;
|
|
11
|
+
const rangeExpr =
|
|
12
|
+
stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init?.rhs?.[0];
|
|
13
|
+
if (rangeExpr?.kind !== "RangeExpr") return false;
|
|
14
|
+
if (rangeExpr._isIterator) return true;
|
|
15
|
+
const iterType = rangeExpr.expr?._type;
|
|
16
|
+
return Boolean(iteratorYieldParams(iterType));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function buildYieldParamsAndAssignments(lhs, yieldTypes, isAssign) {
|
|
20
|
+
const params = [];
|
|
21
|
+
const assignStmts = [];
|
|
22
|
+
for (let i = 0; i < yieldTypes.length; i++) {
|
|
23
|
+
const yType = yieldTypes[i] ?? { kind: "basic", name: "any" };
|
|
24
|
+
const lhsElem = lhs[i];
|
|
25
|
+
if (!isAssign && lhsElem?.name && lhsElem.name !== "_") {
|
|
26
|
+
params.push({ name: lhsElem.name, type: yType });
|
|
27
|
+
} else {
|
|
28
|
+
const paramName = `__yield_p${i}`;
|
|
29
|
+
params.push({ name: paramName, type: yType });
|
|
30
|
+
if (isAssign && lhsElem && lhsElem.name !== "_") {
|
|
31
|
+
assignStmts.push({
|
|
32
|
+
kind: "AssignStmt",
|
|
33
|
+
lhs: [lhsElem],
|
|
34
|
+
rhs: [{ kind: "Ident", name: paramName, _type: yType }],
|
|
35
|
+
op: "=",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { params, assignStmts };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getOrCreateIteratorFuncLit(stmt) {
|
|
44
|
+
if (stmt._yieldFuncLit) return stmt._yieldFuncLit;
|
|
45
|
+
|
|
46
|
+
const rangeExpr =
|
|
47
|
+
stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init?.rhs?.[0];
|
|
48
|
+
const iterType = rangeExpr?.expr?._type;
|
|
49
|
+
const info = iteratorYieldParams(iterType);
|
|
50
|
+
const yieldTypes = rangeExpr?._yieldParams ?? info?.yieldParams ?? [];
|
|
51
|
+
|
|
52
|
+
if (!stmt._iterStateName) {
|
|
53
|
+
stmt._iterStateName = `__iterState$${++iterStateCounter}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const isAssign = stmt.init?.kind === "AssignStmt";
|
|
57
|
+
const lhs = stmt.init?.lhs ?? [];
|
|
58
|
+
const { params, assignStmts } = buildYieldParamsAndAssignments(
|
|
59
|
+
lhs,
|
|
60
|
+
yieldTypes,
|
|
61
|
+
isAssign,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const rawStmts = stmt.body?.stmts ?? stmt.body?.list ?? [];
|
|
65
|
+
const bodyStmts =
|
|
66
|
+
assignStmts.length > 0 ? [...assignStmts, ...rawStmts] : rawStmts;
|
|
67
|
+
|
|
68
|
+
const funcLit = {
|
|
69
|
+
kind: "FuncLit",
|
|
70
|
+
params,
|
|
71
|
+
returnType: { kind: "basic", name: "bool" },
|
|
72
|
+
body: {
|
|
73
|
+
kind: "Block",
|
|
74
|
+
stmts: bodyStmts,
|
|
75
|
+
list: bodyStmts,
|
|
76
|
+
},
|
|
77
|
+
_type: {
|
|
78
|
+
kind: "func",
|
|
79
|
+
params: params.map((p) => p.type),
|
|
80
|
+
returns: [{ kind: "basic", name: "bool" }],
|
|
81
|
+
},
|
|
82
|
+
_isYieldClosure: true,
|
|
83
|
+
_iterStateName: stmt._iterStateName,
|
|
84
|
+
_forStmt: stmt,
|
|
85
|
+
_line: stmt._line,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
stmt._yieldFuncLit = funcLit;
|
|
89
|
+
return funcLit;
|
|
90
|
+
}
|
|
91
|
+
|
|
7
92
|
export function isRangeFor(stmt) {
|
|
8
93
|
if (!stmt?.init) return false;
|
|
9
94
|
const init = stmt.init;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// src/lower/reachability.js
|
|
2
|
+
// Reachability for Go-source stdlib packages: only declarations reachable
|
|
3
|
+
// from user code are emitted, so an imported but unused package costs zero
|
|
4
|
+
// bytes (JS output and dev-mode WASM, where Binaryen does not run).
|
|
5
|
+
//
|
|
6
|
+
// Stdlib top-level names are mangled (`Compare$$cmp`) and user references to
|
|
7
|
+
// them are plain identifiers after type-checking, so reachability is a
|
|
8
|
+
// closure over names. A method is reachable when its receiver type is and
|
|
9
|
+
// its name is selected somewhere reachable, or is called implicitly.
|
|
10
|
+
|
|
11
|
+
import { forEachChild } from "./walk.js";
|
|
12
|
+
|
|
13
|
+
// Methods the runtime or emitter-backed stdlib call without a selector in
|
|
14
|
+
// GoFront code (fmt verbs and Fprint* writers, errors.Is/As/Unwrap,
|
|
15
|
+
// encoding/json).
|
|
16
|
+
const IMPLICIT_METHODS = new Set([
|
|
17
|
+
"Error",
|
|
18
|
+
"String",
|
|
19
|
+
"Write",
|
|
20
|
+
"GoString",
|
|
21
|
+
"Format",
|
|
22
|
+
"Unwrap",
|
|
23
|
+
"Is",
|
|
24
|
+
"As",
|
|
25
|
+
"MarshalJSON",
|
|
26
|
+
"UnmarshalJSON",
|
|
27
|
+
"MarshalText",
|
|
28
|
+
"UnmarshalText",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
function declNames(d) {
|
|
32
|
+
if (d.kind === "FuncDecl" || d.kind === "TypeDecl") return [d.name];
|
|
33
|
+
if (d.kind === "VarDecl" || d.kind === "ConstDecl")
|
|
34
|
+
return (d.decls ?? []).flatMap((spec) => spec.names ?? []);
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Names (identifiers and type names) and selected fields used under `node`.
|
|
39
|
+
function collectRefs(node, names, fields) {
|
|
40
|
+
if (!node || typeof node !== "object") return;
|
|
41
|
+
if (Array.isArray(node)) {
|
|
42
|
+
for (const n of node) collectRefs(n, names, fields);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (node._stdName) names.add(node._stdName); // `cmp.Compare` in user code
|
|
46
|
+
if (
|
|
47
|
+
node.kind === "Ident" ||
|
|
48
|
+
node.kind === "TypeName" ||
|
|
49
|
+
node.kind === "GenericTypeName"
|
|
50
|
+
)
|
|
51
|
+
names.add(node.name);
|
|
52
|
+
else if (node.kind === "SelectorExpr") fields.add(node.field);
|
|
53
|
+
// A reached interface needs its methods on the types that satisfy it.
|
|
54
|
+
else if (node.kind === "InterfaceType")
|
|
55
|
+
for (const m of node.methods ?? []) fields.add(m.name);
|
|
56
|
+
forEachChild(node, (child) => collectRefs(child, names, fields));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function indexStdlib(stdlibPrograms) {
|
|
60
|
+
const byName = new Map(); // name -> decls
|
|
61
|
+
const methods = []; // { decl, recv }
|
|
62
|
+
const inits = new Map(); // program -> init decls
|
|
63
|
+
for (const p of stdlibPrograms) {
|
|
64
|
+
for (const d of p.decls ?? []) {
|
|
65
|
+
if (d.kind === "MethodDecl")
|
|
66
|
+
methods.push({ decl: d, recv: d.recvType?.name });
|
|
67
|
+
else if (d.kind === "FuncDecl" && d.name === "init")
|
|
68
|
+
inits.set(p, [...(inits.get(p) ?? []), d]);
|
|
69
|
+
for (const n of declNames(d)) {
|
|
70
|
+
if (!byName.has(n)) byName.set(n, []);
|
|
71
|
+
byName.get(n).push(d);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { byName, methods, inits };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Incremental closure over one or more Go-source stdlib packages. A
|
|
79
|
+
// declaration's body may still be unchecked (lazy checking): `decl._ensure()`,
|
|
80
|
+
// when present, checks and mangles it first and returns an error text on
|
|
81
|
+
// failure, so the references collected from it are the mangled ones. Failed
|
|
82
|
+
// declarations are listed in `failures`.
|
|
83
|
+
export class StdlibReach {
|
|
84
|
+
constructor(stdlibPrograms) {
|
|
85
|
+
this.programs = stdlibPrograms;
|
|
86
|
+
const { byName, methods, inits } = indexStdlib(stdlibPrograms);
|
|
87
|
+
this.byName = byName;
|
|
88
|
+
this.methods = methods;
|
|
89
|
+
this.inits = inits;
|
|
90
|
+
this.reached = new Set();
|
|
91
|
+
this.names = new Set();
|
|
92
|
+
this.fields = new Set(IMPLICIT_METHODS);
|
|
93
|
+
this.pending = [];
|
|
94
|
+
this.failures = [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Adds `rootNodes` (AST that is emitted) and closes over what they use.
|
|
98
|
+
add(rootNodes) {
|
|
99
|
+
const roots = new Set();
|
|
100
|
+
collectRefs(rootNodes, roots, this.fields);
|
|
101
|
+
for (const n of roots) this._name(n);
|
|
102
|
+
this._close();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_name(n) {
|
|
106
|
+
if (this.names.has(n)) return;
|
|
107
|
+
this.names.add(n);
|
|
108
|
+
this.pending.push(n);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_reach(d) {
|
|
112
|
+
if (this.reached.has(d)) return;
|
|
113
|
+
this.reached.add(d);
|
|
114
|
+
const failure = d._ensure?.();
|
|
115
|
+
if (failure) this.failures.push(failure);
|
|
116
|
+
const local = new Set();
|
|
117
|
+
collectRefs(d, local, this.fields);
|
|
118
|
+
for (const n of local) this._name(n);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_close() {
|
|
122
|
+
let changed = true;
|
|
123
|
+
while (changed) {
|
|
124
|
+
while (this.pending.length > 0)
|
|
125
|
+
for (const d of this.byName.get(this.pending.pop()) ?? [])
|
|
126
|
+
this._reach(d);
|
|
127
|
+
const size = this.reached.size;
|
|
128
|
+
for (const m of this.methods)
|
|
129
|
+
if (this.names.has(m.recv) && this.fields.has(m.decl.name))
|
|
130
|
+
this._reach(m.decl);
|
|
131
|
+
for (const p of this.programs)
|
|
132
|
+
if (p.decls.some((d) => this.reached.has(d)))
|
|
133
|
+
for (const d of this.inits.get(p) ?? []) this._reach(d);
|
|
134
|
+
changed = this.reached.size !== size || this.pending.length > 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Returns the set of declarations of `stdlibPrograms` reachable from
|
|
141
|
+
* `rootPrograms` (all user code that is emitted). A program's `init` is
|
|
142
|
+
* kept when anything else in that program is reached. A reached body that
|
|
143
|
+
* fails its (lazy) check throws.
|
|
144
|
+
*/
|
|
145
|
+
export function reachableStdlibDecls(rootPrograms, stdlibPrograms) {
|
|
146
|
+
const reach = new StdlibReach(stdlibPrograms);
|
|
147
|
+
reach.add(rootPrograms);
|
|
148
|
+
if (reach.failures.length > 0) throw new Error(reach.failures[0]);
|
|
149
|
+
return reach.reached;
|
|
150
|
+
}
|
package/src/lower/walk.js
CHANGED
|
@@ -23,3 +23,15 @@ export function someChild(node, pred) {
|
|
|
23
23
|
}
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* True when evaluating `node` runs a call or a channel receive (conversions
|
|
29
|
+
* excluded), the only operands Go still evaluates for a constant `len`.
|
|
30
|
+
*/
|
|
31
|
+
export function hasCallOrReceive(node) {
|
|
32
|
+
if (!node || typeof node !== "object") return false;
|
|
33
|
+
if (Array.isArray(node)) return node.some(hasCallOrReceive);
|
|
34
|
+
if (node.kind === "CallExpr" && !node._isTypeConversion) return true;
|
|
35
|
+
if (node.kind === "UnaryExpr" && node.op === "<-") return true;
|
|
36
|
+
return someChild(node, hasCallOrReceive);
|
|
37
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T } from "../tokens.js";
|
|
1
|
+
import { T } from "../lexer/tokens.js";
|
|
2
2
|
|
|
3
3
|
export const declarationParseMethods = {
|
|
4
4
|
parseTopDecl() {
|
|
@@ -34,10 +34,12 @@ export const declarationParseMethods = {
|
|
|
34
34
|
this.expect(T.RPAREN);
|
|
35
35
|
const name = this.expect(T.IDENT).value;
|
|
36
36
|
const { params, returnType } = this.parseSignature();
|
|
37
|
+
const native = this.tokens?.natives?.get(_line - 1) ?? null;
|
|
37
38
|
const body = this.parseBlock();
|
|
38
39
|
this.semi();
|
|
39
40
|
return {
|
|
40
41
|
kind: "MethodDecl",
|
|
42
|
+
...(native && { native }),
|
|
41
43
|
recvName,
|
|
42
44
|
recvType,
|
|
43
45
|
recvPointer,
|
|
@@ -56,7 +58,15 @@ export const declarationParseMethods = {
|
|
|
56
58
|
typeParams = this.parseTypeParamList();
|
|
57
59
|
}
|
|
58
60
|
const { params, returnType } = this.parseSignature();
|
|
59
|
-
|
|
61
|
+
// `//gofront:intrinsic name` on the line before a body-less func: the
|
|
62
|
+
// backend provides the implementation; every pass sees an empty body.
|
|
63
|
+
const intrinsic = this.tokens?.intrinsics?.get(_line - 1) ?? null;
|
|
64
|
+
// `//gofront:native name` marks a func that keeps its Go body; a
|
|
65
|
+
// body-less one parses (and is rejected by the checker).
|
|
66
|
+
const native = this.tokens?.natives?.get(_line - 1) ?? null;
|
|
67
|
+
const bodyless =
|
|
68
|
+
(intrinsic !== null || native !== null) && !this.check(T.LBRACE);
|
|
69
|
+
const body = bodyless ? { kind: "Block", stmts: [] } : this.parseBlock();
|
|
60
70
|
this.semi();
|
|
61
71
|
return {
|
|
62
72
|
kind: "FuncDecl",
|
|
@@ -65,6 +75,8 @@ export const declarationParseMethods = {
|
|
|
65
75
|
params,
|
|
66
76
|
returnType,
|
|
67
77
|
body,
|
|
78
|
+
...(intrinsic && { intrinsic, _bodyless: bodyless }),
|
|
79
|
+
...(native && { native, _bodyless: bodyless }),
|
|
68
80
|
_line,
|
|
69
81
|
};
|
|
70
82
|
},
|
|
@@ -91,17 +103,46 @@ export const declarationParseMethods = {
|
|
|
91
103
|
// Type param lists contain IDENT followed by a constraint (another IDENT or keyword),
|
|
92
104
|
// not expressions like integers or operators.
|
|
93
105
|
_looksLikeTypeParamList() {
|
|
94
|
-
|
|
106
|
+
let i = this.pos + 1; // skip [
|
|
95
107
|
const src = this.tokens;
|
|
96
|
-
// First token must be an IDENT (the type param name)
|
|
97
108
|
if (!src[i] || src[i].type !== T.IDENT) return false;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
while (
|
|
110
|
+
src[i] &&
|
|
111
|
+
src[i].type === T.IDENT &&
|
|
112
|
+
src[i + 1] &&
|
|
113
|
+
src[i + 1].type === T.COMMA
|
|
114
|
+
) {
|
|
115
|
+
i += 2;
|
|
116
|
+
}
|
|
117
|
+
if (!src[i] || src[i].type !== T.IDENT) return false;
|
|
118
|
+
const afterName = src[i + 1];
|
|
119
|
+
if (!afterName) return false;
|
|
120
|
+
if (afterName.type === T.IDENT) return true; // T any, T Stringer
|
|
121
|
+
if (afterName.type === T.TILDE) return true; // T ~int
|
|
122
|
+
if (afterName.type === T.INTERFACE) return true; // T interface{...}
|
|
123
|
+
return false;
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// `Name[…]` followed by `,` or `)` in a result list is an instantiated
|
|
127
|
+
// generic type; a result name is followed by its type (`a []int`).
|
|
128
|
+
_isGenericResultType() {
|
|
129
|
+
return this._isGenericInstanceAt(this.pos);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// True when the IDENT at `at` starts an instantiated type (`Box[int]`) that
|
|
133
|
+
// ends a list entry, rather than `name [N]T` / `name []T`.
|
|
134
|
+
_isGenericInstanceAt(at) {
|
|
135
|
+
if (this.tokens[at + 1]?.type !== T.LBRACKET) return false;
|
|
136
|
+
const src = this.tokens;
|
|
137
|
+
let depth = 0;
|
|
138
|
+
for (let i = at + 1; i < src.length; i++) {
|
|
139
|
+
const t = src[i].type;
|
|
140
|
+
if (t === T.LBRACKET) depth++;
|
|
141
|
+
else if (t === T.RBRACKET && --depth === 0) {
|
|
142
|
+
const next = src[i + 1]?.type;
|
|
143
|
+
return next === T.COMMA || next === T.RPAREN;
|
|
144
|
+
} else if (t === T.EOF || t === T.LBRACE) return false;
|
|
145
|
+
}
|
|
105
146
|
return false;
|
|
106
147
|
},
|
|
107
148
|
|
|
@@ -151,11 +192,13 @@ export const declarationParseMethods = {
|
|
|
151
192
|
// if a type-start token follows — if so, the IDENTs are names; if ) or ,
|
|
152
193
|
// follows, the IDENTs are themselves types.
|
|
153
194
|
_isNamedParam() {
|
|
195
|
+
if (this._isGenericInstanceAt(this.pos)) return false;
|
|
154
196
|
let pos = this.pos + 1; // skip current IDENT
|
|
155
197
|
while (
|
|
156
198
|
this.tokens[pos]?.type === T.COMMA &&
|
|
157
199
|
this.tokens[pos + 1]?.type === T.IDENT
|
|
158
200
|
) {
|
|
201
|
+
if (this._isGenericInstanceAt(pos + 1)) return false;
|
|
159
202
|
pos += 2;
|
|
160
203
|
}
|
|
161
204
|
const next = this.tokens[pos];
|
|
@@ -184,8 +227,16 @@ export const declarationParseMethods = {
|
|
|
184
227
|
this.check(T.EOF)
|
|
185
228
|
)
|
|
186
229
|
return null;
|
|
187
|
-
// Inside a type expression (e.g. func type in a tuple
|
|
188
|
-
|
|
230
|
+
// Inside a type expression (e.g. func type in a tuple or a type-switch
|
|
231
|
+
// `case func():`), `,`, `)` and `:` also end the type
|
|
232
|
+
if (
|
|
233
|
+
inTypeExpr &&
|
|
234
|
+
(this.check(T.COMMA) ||
|
|
235
|
+
this.check(T.RPAREN) ||
|
|
236
|
+
this.check(T.COLON) ||
|
|
237
|
+
this.check(T.STRING) ||
|
|
238
|
+
this.check(T.ASSIGN))
|
|
239
|
+
)
|
|
189
240
|
return null;
|
|
190
241
|
// Multiple return values: (int, string) or named (result int, ok bool)
|
|
191
242
|
if (this.check(T.LPAREN)) {
|
|
@@ -195,7 +246,12 @@ export const declarationParseMethods = {
|
|
|
195
246
|
let isNamed = false;
|
|
196
247
|
do {
|
|
197
248
|
// Named returns: only treat IDENT as a name if peek2 is an actual type token
|
|
198
|
-
|
|
249
|
+
// (`Box[int]` is an instantiated type, not a name followed by `[int]`).
|
|
250
|
+
if (
|
|
251
|
+
this.check(T.IDENT) &&
|
|
252
|
+
this.looksLikeType(this.peek2()) &&
|
|
253
|
+
!this._isGenericResultType()
|
|
254
|
+
) {
|
|
199
255
|
names.push(this.advance().value);
|
|
200
256
|
isNamed = true;
|
|
201
257
|
} else {
|
|
@@ -204,6 +260,17 @@ export const declarationParseMethods = {
|
|
|
204
260
|
types.push(this.parseType());
|
|
205
261
|
} while (this.match(T.COMMA));
|
|
206
262
|
this.expect(T.RPAREN);
|
|
263
|
+
// Grouped names `(r, theta float64)`: a bare identifier before a
|
|
264
|
+
// named entry is a name sharing that entry's type.
|
|
265
|
+
if (isNamed) {
|
|
266
|
+
for (let i = types.length - 2; i >= 0; i--) {
|
|
267
|
+
if (names[i] !== null) continue;
|
|
268
|
+
if (types[i]?.kind !== "TypeName" || types[i].typeArgs?.length)
|
|
269
|
+
continue;
|
|
270
|
+
names[i] = types[i].name;
|
|
271
|
+
types[i] = { ...types[i + 1] };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
207
274
|
const typeNode =
|
|
208
275
|
types.length === 1 ? types[0] : { kind: "TupleType", types };
|
|
209
276
|
// Attach named return info so typechecker can inject them into scope
|
|
@@ -261,6 +328,7 @@ export const declarationParseMethods = {
|
|
|
261
328
|
},
|
|
262
329
|
|
|
263
330
|
parseVarSpec() {
|
|
331
|
+
const at = this.peek();
|
|
264
332
|
const names = this.parseIdentList();
|
|
265
333
|
let type = null;
|
|
266
334
|
let value = null;
|
|
@@ -272,7 +340,7 @@ export const declarationParseMethods = {
|
|
|
272
340
|
type = this.parseType();
|
|
273
341
|
}
|
|
274
342
|
if (this.match(T.ASSIGN)) value = this.parseExprList();
|
|
275
|
-
return { names, type, value };
|
|
343
|
+
return { names, type, value, _line: at.line, _col: at.col };
|
|
276
344
|
},
|
|
277
345
|
|
|
278
346
|
parseConstDecl() {
|