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,547 @@
|
|
|
1
|
+
// src/lower/bounds.js
|
|
2
|
+
// Index-check elision for strict mode (`both` packages in JS). Marks an
|
|
3
|
+
// IndexExpr `_inBounds` when its index is provably within the operand's
|
|
4
|
+
// length, so the backend emits a plain `xs[i]` instead of `__at`/`__ix`:
|
|
5
|
+
//
|
|
6
|
+
// - `for i := range xs { … xs[i] … }`
|
|
7
|
+
// - `for i := K; i < len(xs); i++ { … xs[i] … }` with a constant K >= 0
|
|
8
|
+
// - a constant index into an array (the checker rejects out-of-range ones)
|
|
9
|
+
// - constant indexes into a slice after a length proof: the hint `_ = xs[K]`,
|
|
10
|
+
// `if len(xs) > K { … }`, or `if len(xs) <= K { return }` (also `>=`, `<`
|
|
11
|
+
// and the mirrored `K < len(xs)` forms); proves `xs[0]` through `xs[K]`
|
|
12
|
+
//
|
|
13
|
+
|
|
14
|
+
// The loop forms need `xs` to be a local slice or array that the body never
|
|
15
|
+
// assigns or shadows, `i` to be written only by the loop, and neither to be
|
|
16
|
+
// address-taken, used as a method receiver, or assigned inside any closure
|
|
17
|
+
// in the function. Indexes inside func literals in the body are not marked:
|
|
18
|
+
// a closure may run after the loop, when `xs` has changed.
|
|
19
|
+
|
|
20
|
+
import { constIntValue } from "../typechecker/const-fold.js";
|
|
21
|
+
import { forEachChild } from "./walk.js";
|
|
22
|
+
|
|
23
|
+
function underlying(t) {
|
|
24
|
+
return t?.kind === "named" ? underlying(t.underlying) : t;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isSliceOrArray(t) {
|
|
28
|
+
const u = underlying(t);
|
|
29
|
+
return u?.kind === "slice" || u?.kind === "array";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function constInt(node) {
|
|
33
|
+
const v = node?._const ? constIntValue(node._const) : null;
|
|
34
|
+
return typeof v === "bigint" ? v : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isIdent(node, name) {
|
|
38
|
+
return node?.kind === "Ident" && (name === undefined || node.name === name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Names a node makes unsafe to reason about: taken by address, or used as a
|
|
42
|
+
// selector base (a possible pointer-receiver method call).
|
|
43
|
+
function unsafeNamesOf(node) {
|
|
44
|
+
if (node.kind === "UnaryExpr" && node.op === "&" && isIdent(node.operand))
|
|
45
|
+
return [node.operand.name];
|
|
46
|
+
if (node.kind === "SelectorExpr" && isIdent(node.expr))
|
|
47
|
+
return [node.expr.name];
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Names that are unsafe anywhere in the function: see unsafeNamesOf, plus
|
|
52
|
+
// every name assigned inside a closure.
|
|
53
|
+
function collectUnsafeNames(fnBody) {
|
|
54
|
+
const unsafe = new Set();
|
|
55
|
+
const visit = (node, inClosure) => {
|
|
56
|
+
if (!node || typeof node !== "object") return;
|
|
57
|
+
if (Array.isArray(node)) {
|
|
58
|
+
for (const n of node) visit(n, inClosure);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const names = inClosure
|
|
62
|
+
? [...unsafeNamesOf(node), ...assignedNames(node)]
|
|
63
|
+
: unsafeNamesOf(node);
|
|
64
|
+
for (const n of names) unsafe.add(n);
|
|
65
|
+
forEachChild(node, (c) => visit(c, inClosure || node.kind === "FuncLit"));
|
|
66
|
+
};
|
|
67
|
+
visit(fnBody, false);
|
|
68
|
+
return unsafe;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Identifiers a single node assigns to (not declarations).
|
|
72
|
+
function assignedNames(node) {
|
|
73
|
+
if (node.kind === "AssignStmt")
|
|
74
|
+
return node.lhs.filter((l) => isIdent(l)).map((l) => l.name);
|
|
75
|
+
if (node.kind === "IncDecStmt" && isIdent(node.expr)) return [node.expr.name];
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Identifiers a single node declares.
|
|
80
|
+
function declaredNames(node) {
|
|
81
|
+
switch (node.kind) {
|
|
82
|
+
case "DefineStmt":
|
|
83
|
+
return node.lhs.filter((l) => isIdent(l)).map((l) => l.name);
|
|
84
|
+
case "VarDecl":
|
|
85
|
+
return (node.decls ?? []).flatMap((d) =>
|
|
86
|
+
(d.names ?? []).map((n) => (typeof n === "string" ? n : n?.name)),
|
|
87
|
+
);
|
|
88
|
+
case "FuncLit":
|
|
89
|
+
return (node.params ?? []).map((p) => p.name).filter(Boolean);
|
|
90
|
+
case "TypeSwitchStmt":
|
|
91
|
+
return node.assign ? [node.assign] : [];
|
|
92
|
+
default:
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// True when `body` assigns or redeclares any of `names`.
|
|
98
|
+
function touches(body, names) {
|
|
99
|
+
let hit = false;
|
|
100
|
+
const visit = (node) => {
|
|
101
|
+
if (hit || !node || typeof node !== "object") return;
|
|
102
|
+
if (Array.isArray(node)) {
|
|
103
|
+
for (const n of node) visit(n);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const n of [...assignedNames(node), ...declaredNames(node)])
|
|
107
|
+
if (names.includes(n)) hit = true;
|
|
108
|
+
forEachChild(node, visit);
|
|
109
|
+
};
|
|
110
|
+
visit(body);
|
|
111
|
+
return hit;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Marks `xs[i]` in `body`, outside func literals.
|
|
115
|
+
function markIndexes(body, xs, i) {
|
|
116
|
+
const visit = (node) => {
|
|
117
|
+
if (!node || typeof node !== "object") return;
|
|
118
|
+
if (Array.isArray(node)) {
|
|
119
|
+
for (const n of node) visit(n);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (node.kind === "FuncLit") return;
|
|
123
|
+
if (
|
|
124
|
+
node.kind === "IndexExpr" &&
|
|
125
|
+
isIdent(node.expr, xs) &&
|
|
126
|
+
isIdent(node.index, i)
|
|
127
|
+
)
|
|
128
|
+
node._inBounds = true;
|
|
129
|
+
forEachChild(node, visit);
|
|
130
|
+
};
|
|
131
|
+
visit(body);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// `len(xs)` over a slice or array identifier: its name, or null.
|
|
135
|
+
function lenOfName(node) {
|
|
136
|
+
const ok =
|
|
137
|
+
node?.kind === "CallExpr" &&
|
|
138
|
+
isIdent(node.func, "len") &&
|
|
139
|
+
node.args?.length === 1 &&
|
|
140
|
+
isIdent(node.args[0]) &&
|
|
141
|
+
isSliceOrArray(node.args[0]._type);
|
|
142
|
+
return ok ? node.args[0].name : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// for i := range xs / for i, v := range xs
|
|
146
|
+
function rangeLoop(init, i) {
|
|
147
|
+
const rhs = init.rhs?.[0];
|
|
148
|
+
if (!isIdent(rhs.expr) || !isSliceOrArray(rhs.expr._type)) return null;
|
|
149
|
+
// `for i, xs := range xs`: the body's xs is the element, not the slice.
|
|
150
|
+
if (isIdent(init.lhs[1], rhs.expr.name)) return null;
|
|
151
|
+
return { xs: rhs.expr.name, i };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// for i := K; i < len(xs); i++ with a constant K >= 0
|
|
155
|
+
function countingLoop(stmt, i) {
|
|
156
|
+
const { init, cond, post } = stmt;
|
|
157
|
+
const k = constInt(init.rhs?.[0]);
|
|
158
|
+
if (init.lhs.length !== 1 || k === null || k < 0n) return null;
|
|
159
|
+
if (cond?.kind !== "BinaryExpr" || cond.op !== "<" || !isIdent(cond.left, i))
|
|
160
|
+
return null;
|
|
161
|
+
const xs = lenOfName(cond.right);
|
|
162
|
+
const step =
|
|
163
|
+
post?.kind === "IncDecStmt" && post.op === "++" && isIdent(post.expr, i);
|
|
164
|
+
return xs && step ? { xs, i } : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// The (slice name, index name) a for statement proves in range, or null.
|
|
168
|
+
function inBoundsLoop(stmt) {
|
|
169
|
+
const init = stmt.init;
|
|
170
|
+
if (init?.kind !== "DefineStmt" || !isIdent(init.lhs?.[0])) return null;
|
|
171
|
+
const i = init.lhs[0].name;
|
|
172
|
+
return init.rhs?.[0]?.kind === "RangeExpr"
|
|
173
|
+
? rangeLoop(init, i)
|
|
174
|
+
: countingLoop(stmt, i);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Marks the loop's `xs[i]` when its slice is a local the loop cannot change.
|
|
178
|
+
function markLoop(stmt, scope, unsafe) {
|
|
179
|
+
const loop = inBoundsLoop(stmt);
|
|
180
|
+
if (!loop || !scope.has(loop.xs) || scope.has("len")) return;
|
|
181
|
+
if (unsafe.has(loop.xs) || unsafe.has(loop.i)) return;
|
|
182
|
+
if (touches(stmt.body, [loop.xs, loop.i])) return;
|
|
183
|
+
markIndexes(stmt.body, loop.xs, loop.i);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const SCOPE_KINDS = new Set([
|
|
187
|
+
"Block",
|
|
188
|
+
"ForStmt",
|
|
189
|
+
"IfStmt",
|
|
190
|
+
"FuncLit",
|
|
191
|
+
"CaseClause",
|
|
192
|
+
"TypeSwitchStmt",
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
// How often each name is declared in the function (parameters included) and
|
|
196
|
+
// which names are assigned anywhere. A slice declared once and never
|
|
197
|
+
// assigned keeps its length for the whole function.
|
|
198
|
+
function nameStats(params, body) {
|
|
199
|
+
const declared = new Map();
|
|
200
|
+
const assigned = new Set();
|
|
201
|
+
const bump = (n) => n && declared.set(n, (declared.get(n) ?? 0) + 1);
|
|
202
|
+
for (const n of params) bump(n);
|
|
203
|
+
const visit = (node) => {
|
|
204
|
+
if (!node || typeof node !== "object") return;
|
|
205
|
+
if (Array.isArray(node)) {
|
|
206
|
+
for (const n of node) visit(n);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
for (const n of declaredNames(node)) bump(n);
|
|
210
|
+
for (const n of assignedNames(node)) assigned.add(n);
|
|
211
|
+
forEachChild(node, visit);
|
|
212
|
+
};
|
|
213
|
+
visit(body);
|
|
214
|
+
return { declared, assigned };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// A slice whose length no statement in the function can change.
|
|
218
|
+
function isStableSlice(node, stats, unsafe) {
|
|
219
|
+
if (!isIdent(node) || underlying(node._type)?.kind !== "slice") return false;
|
|
220
|
+
const { name } = node;
|
|
221
|
+
return (
|
|
222
|
+
stats.declared.get(name) === 1 &&
|
|
223
|
+
!stats.assigned.has(name) &&
|
|
224
|
+
!unsafe.has(name)
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// `_ = xs[K]` with a constant K >= 0: the slice and K, or null.
|
|
229
|
+
function hintOf(stmt, stats, unsafe) {
|
|
230
|
+
if (stmt?.kind !== "AssignStmt" || stmt.op !== "=") return null;
|
|
231
|
+
const [lhs] = stmt.lhs;
|
|
232
|
+
const rhs = stmt.rhs?.[0];
|
|
233
|
+
if (stmt.lhs.length !== 1 || !isIdent(lhs, "_")) return null;
|
|
234
|
+
if (rhs?.kind !== "IndexExpr" || !isStableSlice(rhs.expr, stats, unsafe))
|
|
235
|
+
return null;
|
|
236
|
+
const k = constInt(rhs.index);
|
|
237
|
+
return k !== null && k >= 0n ? { xs: rhs.expr.name, k } : null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const FLIPPED = { ">": "<", ">=": "<=", "<": ">", "<=": ">=" };
|
|
241
|
+
|
|
242
|
+
// `len(xs) op K` (or `K op len(xs)`) over a stable slice: [xs, op, K] or null.
|
|
243
|
+
function lenComparison(cond, stats, unsafe) {
|
|
244
|
+
if (cond?.kind !== "BinaryExpr" || !(cond.op in FLIPPED)) return null;
|
|
245
|
+
const direct = cond.left?.kind === "CallExpr";
|
|
246
|
+
const call = direct ? cond.left : cond.right;
|
|
247
|
+
const k = constInt(direct ? cond.right : cond.left);
|
|
248
|
+
const arg = call?.args?.[0];
|
|
249
|
+
const ok =
|
|
250
|
+
k !== null &&
|
|
251
|
+
isIdent(call?.func, "len") &&
|
|
252
|
+
call.args.length === 1 &&
|
|
253
|
+
isStableSlice(arg, stats, unsafe);
|
|
254
|
+
if (!ok || stats.declared.has("len")) return null;
|
|
255
|
+
return [arg.name, direct ? cond.op : FLIPPED[cond.op], k];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Highest index `len(xs) op K` proves when it holds (`holds`) or fails.
|
|
259
|
+
function provenMax(op, k, holds) {
|
|
260
|
+
let max = null;
|
|
261
|
+
if (holds && op === ">") max = k;
|
|
262
|
+
else if (holds && op === ">=") max = k - 1n;
|
|
263
|
+
else if (!holds && op === "<=") max = k;
|
|
264
|
+
else if (!holds && op === "<") max = k - 1n;
|
|
265
|
+
return max !== null && max >= 0n ? max : null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function isTerminating(stmt) {
|
|
269
|
+
if (stmt?.kind === "ReturnStmt") return true;
|
|
270
|
+
const call = stmt?.kind === "ExprStmt" ? stmt.expr : null;
|
|
271
|
+
return call?.kind === "CallExpr" && isIdent(call.func, "panic");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Marks `xs[0]` … `xs[max]` (constant indexes) anywhere under `root`.
|
|
275
|
+
function markProven(root, xs, max) {
|
|
276
|
+
const visit = (node) => {
|
|
277
|
+
if (!node || typeof node !== "object") return;
|
|
278
|
+
if (Array.isArray(node)) {
|
|
279
|
+
for (const n of node) visit(n);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (node.kind === "IndexExpr" && isIdent(node.expr, xs)) {
|
|
283
|
+
const k = constInt(node.index);
|
|
284
|
+
if (k !== null && k >= 0n && k <= max) node._inBounds = true;
|
|
285
|
+
}
|
|
286
|
+
forEachChild(node, visit);
|
|
287
|
+
};
|
|
288
|
+
visit(root);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Length facts a statement establishes for what follows it in its block.
|
|
292
|
+
function factAfter(stmt, stats, unsafe) {
|
|
293
|
+
const hint = hintOf(stmt, stats, unsafe);
|
|
294
|
+
if (hint) return { xs: hint.xs, max: hint.k };
|
|
295
|
+
const exits =
|
|
296
|
+
stmt?.kind === "IfStmt" &&
|
|
297
|
+
!stmt.init &&
|
|
298
|
+
!stmt.elseBody &&
|
|
299
|
+
isTerminating(stmt.body?.stmts?.at(-1));
|
|
300
|
+
const cmp = exits ? lenComparison(stmt.cond, stats, unsafe) : null;
|
|
301
|
+
const max = cmp ? provenMax(cmp[1], cmp[2], false) : null;
|
|
302
|
+
return max === null ? null : { xs: cmp[0], max };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Slice length proofs: hints and guards over every statement list.
|
|
306
|
+
function markSliceFacts(body, stats, unsafe) {
|
|
307
|
+
const visit = (node) => {
|
|
308
|
+
if (!node || typeof node !== "object") return;
|
|
309
|
+
if (Array.isArray(node)) {
|
|
310
|
+
for (const n of node) visit(n);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (node.kind === "IfStmt") {
|
|
314
|
+
const cmp = lenComparison(node.cond, stats, unsafe);
|
|
315
|
+
const max = cmp ? provenMax(cmp[1], cmp[2], true) : null;
|
|
316
|
+
if (max !== null) markProven(node.body, cmp[0], max);
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(node.stmts)) {
|
|
319
|
+
node.stmts.forEach((stmt, i) => {
|
|
320
|
+
const fact = factAfter(stmt, stats, unsafe);
|
|
321
|
+
if (fact) markProven(node.stmts.slice(i + 1), fact.xs, fact.max);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
forEachChild(node, visit);
|
|
325
|
+
};
|
|
326
|
+
visit(body);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function analyzeFunc(params, body) {
|
|
330
|
+
const unsafe = collectUnsafeNames(body);
|
|
331
|
+
// Walk in source order with a scope chain of local names, so `xs` counts
|
|
332
|
+
// as local only when a declaration in an enclosing scope precedes the loop.
|
|
333
|
+
const visit = (node, scope) => {
|
|
334
|
+
if (!node || typeof node !== "object") return;
|
|
335
|
+
if (Array.isArray(node)) {
|
|
336
|
+
for (const n of node) visit(n, scope);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (node.kind === "IndexExpr") markConstArrayIndex(node);
|
|
340
|
+
if (node.kind === "ForStmt") markLoop(node, scope, unsafe);
|
|
341
|
+
const inner = SCOPE_KINDS.has(node.kind) ? scope.child() : scope;
|
|
342
|
+
const declared = declaredNames(node);
|
|
343
|
+
// Func literal parameters and a type switch binding belong to its
|
|
344
|
+
// body; other declarations follow the node in its enclosing scope.
|
|
345
|
+
const ownsDecls = node.kind === "FuncLit" || node.kind === "TypeSwitchStmt";
|
|
346
|
+
const target = ownsDecls ? inner : null;
|
|
347
|
+
if (target) for (const n of declared) target.add(n);
|
|
348
|
+
forEachChild(node, (c) => visit(c, inner));
|
|
349
|
+
if (!target) for (const n of declared) scope.add(n);
|
|
350
|
+
};
|
|
351
|
+
const root = new LocalScope(null);
|
|
352
|
+
for (const n of params) root.add(n);
|
|
353
|
+
visit(body, root);
|
|
354
|
+
markSliceFacts(body, nameStats(params, body), unsafe);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function markConstArrayIndex(node) {
|
|
358
|
+
const u = underlying(node.expr?._type);
|
|
359
|
+
if (u?.kind !== "array" || typeof u.size !== "number") return;
|
|
360
|
+
const k = constInt(node.index);
|
|
361
|
+
if (k !== null && k >= 0n && k < BigInt(u.size)) node._inBounds = true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
class LocalScope {
|
|
365
|
+
constructor(parent) {
|
|
366
|
+
this.parent = parent;
|
|
367
|
+
this.names = new Set();
|
|
368
|
+
}
|
|
369
|
+
child() {
|
|
370
|
+
return new LocalScope(this);
|
|
371
|
+
}
|
|
372
|
+
add(name) {
|
|
373
|
+
if (name && name !== "_") this.names.add(name);
|
|
374
|
+
}
|
|
375
|
+
has(name) {
|
|
376
|
+
return this.names.has(name) || (this.parent?.has(name) ?? false);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function paramNames(decl) {
|
|
381
|
+
const names = (decl.params ?? []).map((p) => p.name);
|
|
382
|
+
if (decl.recvName) names.push(decl.recvName);
|
|
383
|
+
for (const r of decl.returnType?._namedReturns ?? [])
|
|
384
|
+
names.push(typeof r === "string" ? r : r?.name);
|
|
385
|
+
return names;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── Nil-check elision ───────────────────────────────────────────
|
|
389
|
+
//
|
|
390
|
+
// A pointer parameter or receiver that is declared once and never assigned
|
|
391
|
+
// or address-taken stays the same pointer for the whole function. Strict mode
|
|
392
|
+
// checks it for nil at a field access (`__nilcall`); marking later accesses
|
|
393
|
+
// `_nonNil` drops the repeats, and a receiver of a method that never compares
|
|
394
|
+
// itself to nil cannot be nil at all (a nil receiver fails at the call).
|
|
395
|
+
|
|
396
|
+
function isPointerIdent(node, names) {
|
|
397
|
+
return (
|
|
398
|
+
isIdent(node) &&
|
|
399
|
+
names.has(node.name) &&
|
|
400
|
+
underlying(node._type)?.kind === "pointer"
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function derefBase(node) {
|
|
405
|
+
if (node.kind === "SelectorExpr") return node.expr;
|
|
406
|
+
if (node.kind === "UnaryExpr" && node.op === "*") return node.operand;
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Names whose address is taken anywhere under `body`.
|
|
411
|
+
function addressTakenNames(body) {
|
|
412
|
+
const taken = new Set();
|
|
413
|
+
const visit = (node) => {
|
|
414
|
+
if (!node || typeof node !== "object") return;
|
|
415
|
+
if (Array.isArray(node)) {
|
|
416
|
+
for (const n of node) visit(n);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (node.kind === "UnaryExpr" && node.op === "&" && isIdent(node.operand))
|
|
420
|
+
taken.add(node.operand.name);
|
|
421
|
+
forEachChild(node, visit);
|
|
422
|
+
};
|
|
423
|
+
visit(body);
|
|
424
|
+
return taken;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function hasLabelOrGoto(body) {
|
|
428
|
+
let hit = false;
|
|
429
|
+
const visit = (node) => {
|
|
430
|
+
if (hit || !node || typeof node !== "object") return;
|
|
431
|
+
if (Array.isArray(node)) {
|
|
432
|
+
for (const n of node) visit(n);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (
|
|
436
|
+
node.kind === "LabeledStmt" ||
|
|
437
|
+
(node.kind === "BranchStmt" && node.tok === "goto")
|
|
438
|
+
)
|
|
439
|
+
hit = true;
|
|
440
|
+
forEachChild(node, visit);
|
|
441
|
+
};
|
|
442
|
+
visit(body);
|
|
443
|
+
return hit;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// The children of `node` that run whenever `node` runs, or null when all do.
|
|
447
|
+
function alwaysRunChildren(node) {
|
|
448
|
+
switch (node.kind) {
|
|
449
|
+
case "FuncLit":
|
|
450
|
+
case "DeferStmt":
|
|
451
|
+
case "GoStmt":
|
|
452
|
+
return [];
|
|
453
|
+
case "IfStmt":
|
|
454
|
+
return [node.init, node.cond];
|
|
455
|
+
case "ForStmt":
|
|
456
|
+
return [node.init];
|
|
457
|
+
case "SwitchStmt":
|
|
458
|
+
case "TypeSwitchStmt":
|
|
459
|
+
return [node.init, node.tag];
|
|
460
|
+
case "BinaryExpr":
|
|
461
|
+
return node.op === "&&" || node.op === "||" ? [node.left] : null;
|
|
462
|
+
default:
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Marks derefs of `names` under `root` that run whenever `root` runs and
|
|
468
|
+
// follow the first one per name (`seen`) or are in `always`.
|
|
469
|
+
function markRepeats(root, names, seen, always) {
|
|
470
|
+
const visit = (node) => {
|
|
471
|
+
if (!node || typeof node !== "object") return;
|
|
472
|
+
if (Array.isArray(node)) {
|
|
473
|
+
for (const n of node) visit(n);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const kids = alwaysRunChildren(node);
|
|
477
|
+
if (kids) {
|
|
478
|
+
for (const k of kids) visit(k);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const base = derefBase(node);
|
|
482
|
+
if (base && isPointerIdent(base, names)) {
|
|
483
|
+
if (seen.has(base.name) || always.has(base.name)) node._nonNil = true;
|
|
484
|
+
else seen.add(base.name);
|
|
485
|
+
}
|
|
486
|
+
forEachChild(node, visit);
|
|
487
|
+
};
|
|
488
|
+
visit(root);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Marks every deref of `names` under `root` (receivers that cannot be nil).
|
|
492
|
+
function markAll(root, names) {
|
|
493
|
+
const visit = (node) => {
|
|
494
|
+
if (!node || typeof node !== "object") return;
|
|
495
|
+
if (Array.isArray(node)) {
|
|
496
|
+
for (const n of node) visit(n);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const base = derefBase(node);
|
|
500
|
+
if (base && isPointerIdent(base, names)) node._nonNil = true;
|
|
501
|
+
forEachChild(node, visit);
|
|
502
|
+
};
|
|
503
|
+
visit(root);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function markNilFunc(decl, nilSafeRecv) {
|
|
507
|
+
const body = decl.body;
|
|
508
|
+
const params = paramNames(decl);
|
|
509
|
+
const stats = nameStats(params, body);
|
|
510
|
+
const taken = addressTakenNames(body);
|
|
511
|
+
const stable = (n) =>
|
|
512
|
+
n && stats.declared.get(n) === 1 && !stats.assigned.has(n) && !taken.has(n);
|
|
513
|
+
const always = new Set();
|
|
514
|
+
if (decl.kind === "MethodDecl" && decl.recvPointer && !nilSafeRecv(decl))
|
|
515
|
+
if (stable(decl.recvName)) always.add(decl.recvName);
|
|
516
|
+
const candidates = new Set(
|
|
517
|
+
(decl.params ?? []).map((p) => p.name).filter(stable),
|
|
518
|
+
);
|
|
519
|
+
for (const n of always) candidates.add(n);
|
|
520
|
+
if (candidates.size === 0) return;
|
|
521
|
+
if (always.size > 0) markAll(body, always);
|
|
522
|
+
if (hasLabelOrGoto(body)) return;
|
|
523
|
+
const seen = new Set();
|
|
524
|
+
for (const stmt of body.stmts ?? [])
|
|
525
|
+
markRepeats(stmt, candidates, seen, always);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Marks pointer derefs that need no nil check in strict mode: those of a
|
|
530
|
+
* receiver that cannot be nil and repeats after the first unconditional
|
|
531
|
+
* deref of an unchanged pointer parameter. `nilSafeRecv(decl)` tells
|
|
532
|
+
* whether a method accepts a nil receiver.
|
|
533
|
+
*/
|
|
534
|
+
export function markRedundantNilChecks(program, nilSafeRecv) {
|
|
535
|
+
for (const d of program?.decls ?? []) {
|
|
536
|
+
if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body)
|
|
537
|
+
markNilFunc(d, nilSafeRecv);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Marks provably in-range IndexExprs `_inBounds` in every function. */
|
|
542
|
+
export function markInBoundsIndexes(program) {
|
|
543
|
+
for (const d of program?.decls ?? []) {
|
|
544
|
+
if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body)
|
|
545
|
+
analyzeFunc(paramNames(d), d.body);
|
|
546
|
+
}
|
|
547
|
+
}
|
package/src/lower/boxing.js
CHANGED
|
@@ -6,7 +6,7 @@ import { forEachChild } from "./walk.js";
|
|
|
6
6
|
export function isReferenceType(t) {
|
|
7
7
|
if (!t) return false;
|
|
8
8
|
const base = t.kind === "named" ? t.underlying : t;
|
|
9
|
-
return base?.kind === "struct"
|
|
9
|
+
return base?.kind === "struct";
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
// Scan AST node for _addressTaken idents on scalars and populate a Set of boxed variable names.
|