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.
Files changed (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /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
+ }
@@ -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" || base?.kind === "func";
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.