gofront 1.8.0 → 1.9.1

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 (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -0,0 +1,204 @@
1
+ // Same-named types in different local packages.
2
+ //
3
+ // Local packages are inlined into one flat JS module, so `a.Pt` and `b.Pt`
4
+ // would both become `class Pt`. A package that declares a type another
5
+ // package already owns gets that type renamed to `Pt$$b` before it is
6
+ // type-checked, the convention Go-source stdlib packages use: diagnostics and
7
+ // `%T` show it as `b.Pt`, and importers reach it through `b.Pt` as usual.
8
+
9
+ import { forEachChild } from "../lower/walk.js";
10
+
11
+ function topLevelTypeNames(programs) {
12
+ const names = [];
13
+ for (const p of programs)
14
+ for (const d of p.decls ?? [])
15
+ if (d.kind === "TypeDecl") names.push(d.name);
16
+ return names;
17
+ }
18
+
19
+ // Registers the package's type names in `owners` (name -> package key) and
20
+ // renames those another package registered first. Returns Map<name, mangled>
21
+ // for the renamed types, empty when nothing collides.
22
+ export function mangleCollidingTypes(programs, pkgKey, pkgName, owners) {
23
+ const renamed = new Map();
24
+ for (const name of topLevelTypeNames(programs)) {
25
+ const owner = owners.get(name);
26
+ if (owner === undefined) owners.set(name, pkgKey);
27
+ else if (owner !== pkgKey) renamed.set(name, `${name}$$${pkgName}`);
28
+ }
29
+ if (renamed.size > 0)
30
+ for (const p of programs)
31
+ for (const d of p.decls ?? [])
32
+ renameTypeRefs(d, renamed, new Set(), new Set());
33
+ return renamed;
34
+ }
35
+
36
+ // Function-local types are scoped to their block, but the checker and both
37
+ // backends key types by bare name. A local type whose name another type of the
38
+ // package also uses (a package-level type or a local of another function)
39
+ // is renamed to `Name__L<n>` for its scope; `%T` and diagnostics drop the
40
+ // suffix (see `displayTypeName`). `reserved` holds type names other packages
41
+ // own: a local type spelled like one is renamed too, so it cannot clash with
42
+ // the imported type in the flat module. Returns whether anything was renamed.
43
+ let localTypeCounter = 0;
44
+
45
+ export function renameLocalTypes(programs, reserved = new Set()) {
46
+ const counts = new Map();
47
+ const count = (node) => {
48
+ if (!node || typeof node !== "object") return;
49
+ if (Array.isArray(node)) return node.forEach(count);
50
+ if (node.kind === "TypeDecl")
51
+ counts.set(node.name, (counts.get(node.name) ?? 0) + 1);
52
+ forEachChild(node, count);
53
+ };
54
+ for (const p of programs) count(p.decls);
55
+ const dup = new Set(
56
+ [...counts].filter(([k, n]) => n > 1 || reserved.has(k)).map(([k]) => k),
57
+ );
58
+ let changed = false;
59
+ // Inner scopes first, so an outer rename never touches a nested
60
+ // declaration that already has its own name. `visible` holds the value
61
+ // names declared so far in the enclosing scopes.
62
+ const scan = (node, visible) => {
63
+ if (!node || typeof node !== "object") return;
64
+ if (Array.isArray(node)) {
65
+ const seen = new Set(visible);
66
+ for (let i = 0; i < node.length; i++) {
67
+ const d = node[i];
68
+ scan(d, seen);
69
+ if (d?.kind === "TypeDecl") {
70
+ // A value of the same name would clash with the type in JS.
71
+ const stop = node.findIndex(
72
+ (s, j) => j > i && declaresValue(s, d.name),
73
+ );
74
+ if (dup.has(d.name) || seen.has(d.name) || stop >= 0) {
75
+ const renamed = new Map([
76
+ [d.name, `${d.name}__L${++localTypeCounter}`],
77
+ ]);
78
+ const end = stop >= 0 ? stop : node.length - 1;
79
+ for (let j = i; j <= end; j++) {
80
+ // The shadowing declaration's own right-hand side and type
81
+ // are evaluated before its new value is in scope.
82
+ const target =
83
+ j === stop && node[j].kind === "DefineStmt"
84
+ ? node[j].rhs
85
+ : node[j];
86
+ renameTypeRefs(target, renamed, new Set(), new Set());
87
+ }
88
+ changed = true;
89
+ }
90
+ } else if (d) for (const n of valueNames(d)) seen.add(n);
91
+ }
92
+ return;
93
+ }
94
+ const here = FUNC_KINDS.has(node.kind)
95
+ ? new Set([...visible, ...declaredParams(node)])
96
+ : visible;
97
+ forEachChild(node, (c) => scan(c, here));
98
+ };
99
+ for (const p of programs)
100
+ for (const d of p.decls ?? [])
101
+ if (d.kind !== "TypeDecl") scan(d, new Set());
102
+ return changed;
103
+ }
104
+
105
+ const VALUE_DECL_KINDS = new Set(["DefineStmt", "VarDecl", "ConstDecl"]);
106
+
107
+ // Names a statement declares as values in its own scope.
108
+ function valueNames(stmt) {
109
+ return VALUE_DECL_KINDS.has(stmt.kind) ? declaredNames(stmt) : [];
110
+ }
111
+
112
+ function declaresValue(stmt, name) {
113
+ return valueNames(stmt).includes(name);
114
+ }
115
+
116
+ function declaredParams(fn) {
117
+ const names = (fn.params ?? []).map((p) => p.name).filter(Boolean);
118
+ if (fn.recvName) names.push(fn.recvName);
119
+ return names;
120
+ }
121
+
122
+ // A package compiled later that declares a type this package only declares
123
+ // inside a function is renamed by `mangleCollidingTypes`, so `Pt` of one package
124
+ // and a local `Pt` of another never meet in the flat module.
125
+ export function registerLocalTypeOwners(programs, pkgKey, owners) {
126
+ if (!owners) return;
127
+ const visit = (node) => {
128
+ if (!node || typeof node !== "object") return;
129
+ if (Array.isArray(node)) return node.forEach(visit);
130
+ if (node.kind === "TypeDecl" && !owners.has(node.name))
131
+ owners.set(node.name, pkgKey);
132
+ forEachChild(node, visit);
133
+ };
134
+ for (const p of programs) visit(p.decls);
135
+ }
136
+
137
+ // `tparams` are type parameters in scope; `values` are names a function
138
+ // declares as parameters or locals, which shadow a same-spelled bare `Ident`.
139
+ function renameTypeRefs(node, renamed, tparams, values) {
140
+ if (!node || typeof node !== "object") return;
141
+ if (Array.isArray(node)) {
142
+ for (const n of node) renameTypeRefs(n, renamed, tparams, values);
143
+ return;
144
+ }
145
+ const tp = withTypeParams(node, tparams);
146
+ const vals = FUNC_KINDS.has(node.kind) ? withLocals(node, values) : values;
147
+ if (isTypeRef(node, vals) && renamed.has(node.name) && !tp.has(node.name))
148
+ node.name = renamed.get(node.name);
149
+ forEachChild(node, (child) => {
150
+ if (node.kind === "KeyValueExpr" && child === node.key) return;
151
+ renameTypeRefs(child, renamed, tp, vals);
152
+ });
153
+ }
154
+
155
+ const FUNC_KINDS = new Set(["FuncDecl", "MethodDecl", "FuncLit"]);
156
+
157
+ // Adds every parameter and local name declared anywhere inside a function.
158
+ function withLocals(fn, values) {
159
+ const out = new Set(values);
160
+ const visit = (node) => {
161
+ if (!node || typeof node !== "object") return;
162
+ if (Array.isArray(node)) return node.forEach(visit);
163
+ for (const n of declaredNames(node)) out.add(n);
164
+ forEachChild(node, visit);
165
+ };
166
+ visit(fn);
167
+ return out;
168
+ }
169
+
170
+ function declaredNames(node) {
171
+ const names = [];
172
+ if (node.kind === "DefineStmt")
173
+ for (const l of node.lhs) if (l.kind === "Ident") names.push(l.name);
174
+ if (node.kind === "VarDecl" || node.kind === "ConstDecl")
175
+ for (const d of node.decls ?? []) names.push(...(d.names ?? []));
176
+ if (node.kind === "TypeSwitchStmt" && node.assign) names.push(node.assign);
177
+ for (const p of node.params ?? []) if (p.name) names.push(p.name);
178
+ if (node.recvName) names.push(node.recvName);
179
+ return names;
180
+ }
181
+
182
+ function withTypeParams(node, tparams) {
183
+ if (!(node.typeParams?.length > 0)) return tparams;
184
+ return new Set([...tparams, ...node.typeParams.map((t) => t.name)]);
185
+ }
186
+
187
+ const TYPE_REF_KINDS = new Set([
188
+ "TypeDecl",
189
+ "TypeName",
190
+ "GenericTypeName",
191
+ "Ident",
192
+ ]);
193
+
194
+ function isTypeRef(node, values) {
195
+ if (node.kind === "Ident") return !values.has(node.name);
196
+ return TYPE_REF_KINDS.has(node.kind);
197
+ }
198
+
199
+ // Importers name a renamed type by its declared name (`b.Pt`).
200
+ export function withOriginalTypeNames(types, renamed) {
201
+ for (const [name, mangled] of renamed)
202
+ if (types.has(mangled)) types.set(name, types.get(mangled));
203
+ return types;
204
+ }
package/src/link/wasm.js CHANGED
@@ -51,7 +51,12 @@ export function registerPackageUnit(options, unit) {
51
51
 
52
52
  function linkWasmUnits(
53
53
  units,
54
- { emitWat = false, rootTarget = "js", sourceMappingURL = null } = {},
54
+ {
55
+ emitWat = false,
56
+ rootTarget = "js",
57
+ sourceMappingURL = null,
58
+ natives = true,
59
+ } = {},
55
60
  ) {
56
61
  if (!units.some((u) => u.target === "wasm")) return null;
57
62
  checkLinkCollisions(units);
@@ -60,6 +65,14 @@ function linkWasmUnits(
60
65
  const bundledPackages = new Set();
61
66
  const programs = [];
62
67
  const stdlibPrograms = [];
68
+ // Check the stdlib bodies the linked code reaches before the ASTs are
69
+ // copied; lazily checked bodies are annotated and mangled in place.
70
+ reachableStdlibDecls(
71
+ units
72
+ .filter((u) => !u.key.startsWith("stdlib:"))
73
+ .flatMap((u) => u.programs),
74
+ units.filter((u) => u.key.startsWith("stdlib:")).flatMap((u) => u.programs),
75
+ );
63
76
  for (const u of units) {
64
77
  for (const b of u.bundledPackages) bundledPackages.add(b);
65
78
  const copies = u.programs.map((p) => cloneAstForWasm(p));
@@ -89,6 +102,7 @@ function linkWasmUnits(
89
102
  return compileWasmModule(programs, checker, lowerRes, {
90
103
  boundary: true,
91
104
  bundledPackages,
105
+ natives,
92
106
  emitWat,
93
107
  callMain: rootTarget === "wasm",
94
108
  sourceMappingURL,
@@ -239,10 +253,16 @@ export function linkIntoBundle(
239
253
  pkgTarget,
240
254
  { sourceMappingURL } = {},
241
255
  ) {
242
- const none = { wasm: null, wat: null, sourceMapInfo: null };
256
+ const none = {
257
+ wasm: null,
258
+ wat: null,
259
+ sourceMapInfo: null,
260
+ stringTable: null,
261
+ };
243
262
  if (options.isDependency) return none;
244
263
  const linked = linkWasmUnits(options.wasmUnits, {
245
264
  emitWat: options.emitWat,
265
+ natives: options.natives,
246
266
  rootTarget: pkgTarget,
247
267
  sourceMappingURL,
248
268
  });
@@ -252,6 +272,7 @@ export function linkIntoBundle(
252
272
  wasm: linked.wasm,
253
273
  wat: linked.wat ?? null,
254
274
  sourceMapInfo: linked.sourceMapInfo ?? null,
275
+ stringTable: linked.stringTable ?? null,
255
276
  };
256
277
  }
257
278
 
@@ -266,6 +287,9 @@ export function lowerStdlibRefs(node) {
266
287
  for (const n of node) lowerStdlibRefs(n);
267
288
  return;
268
289
  }
290
+ // A generated entry replaces the stdlib declaration the checker resolved
291
+ // (errors.As) the call to.
292
+ if (node._genEntry && node._stdName) delete node._stdName;
269
293
  if (node._stdName) {
270
294
  if (node.kind === "SelectorExpr") {
271
295
  delete node.expr;
@@ -276,19 +300,19 @@ export function lowerStdlibRefs(node) {
276
300
  delete node._stdName;
277
301
  }
278
302
  if (node.kind === "CallExpr" && node.func?._jsonStream) {
279
- const entry = node.func._jsonGen;
303
+ const entry = node.func._genEntry;
280
304
  node.args.unshift(node.func.expr);
281
305
  delete node.func.expr;
282
306
  delete node.func.field;
283
307
  delete node.func._jsonStream;
284
- delete node.func._jsonGen;
308
+ delete node.func._genEntry;
285
309
  node.func.kind = "Ident";
286
310
  node.func.name = entry;
287
- } else if (node._jsonGen && node.kind === "SelectorExpr") {
311
+ } else if (node._genEntry && node.kind === "SelectorExpr") {
288
312
  delete node.expr;
289
313
  delete node.field;
290
314
  node.kind = "Ident";
291
- node.name = node._jsonGen;
315
+ node.name = node._genEntry;
292
316
  }
293
317
  forEachChild(node, lowerStdlibRefs);
294
318
  }
@@ -385,6 +385,159 @@ function paramNames(decl) {
385
385
  return names;
386
386
  }
387
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
+
388
541
  /** Marks provably in-range IndexExprs `_inBounds` in every function. */
389
542
  export function markInBoundsIndexes(program) {
390
543
  for (const d of program?.decls ?? []) {
@@ -13,6 +13,9 @@ export class CaptureAnalysis {
13
13
  constructor() {
14
14
  // Map<FuncLit, Set<string>>: free variables captured by each closure
15
15
  this.capturesByClosure = new Map();
16
+ // Map<FuncLit, Map<string, Type>>: the type of each captured variable,
17
+ // read off the closure's first reference to it.
18
+ this.captureTypes = new Map();
16
19
  // Set<string>: variables in the enclosing scope that are captured AND mutated
17
20
  this.mutatedCaptures = new Set();
18
21
  }
@@ -48,48 +51,94 @@ export function collectDeclaredNames(node, names = new Set()) {
48
51
  }
49
52
 
50
53
  /**
51
- * Finds all identifier references in a node that are not shadowed by inner declarations.
54
+ * Finds the identifiers a function literal body reads from outside itself. A
55
+ * name is local only from its declaration onwards and only inside its block,
56
+ * so `m := f(m)` reads the enclosing `m`, and `{ m := 1 }; use(m)` reads it
57
+ * too. `scopes` is the stack of name sets visible inside the literal;
58
+ * `types` receives the type of the first reference to each free name.
52
59
  */
53
- function collectReferencedNames(node, localDecls, refs = new Set()) {
60
+ function collectReferencedNames(node, scopes, refs = new Set(), types) {
54
61
  if (!node || typeof node !== "object") return refs;
55
62
  if (Array.isArray(node)) {
56
- for (const item of node) collectReferencedNames(item, localDecls, refs);
57
- return refs;
58
- }
59
-
60
- // In struct literals, field names in KeyValueExpr are not variable references
61
- if (node.kind === "KeyValueExpr") {
62
- collectReferencedNames(node.value, localDecls, refs);
63
+ for (const item of node) collectReferencedNames(item, scopes, refs, types);
63
64
  return refs;
64
65
  }
66
+ const walk = (child) => collectReferencedNames(child, scopes, refs, types);
67
+ const scoped = (fn) => {
68
+ scopes.push(new Set());
69
+ fn();
70
+ scopes.pop();
71
+ };
72
+ const declare = (name) => {
73
+ if (name && name !== "_") scopes[scopes.length - 1].add(name);
74
+ };
65
75
 
66
- if (node.kind === "FuncLit") {
67
- const innerLocals = new Set(localDecls);
68
- for (const p of node.params ?? []) {
69
- if (p.name && p.name !== "_") innerLocals.add(p.name);
70
- }
71
- for (const r of node.returnType?._namedReturns ?? []) {
72
- if (r.name && r.name !== "_") innerLocals.add(r.name);
76
+ switch (node.kind) {
77
+ // In struct literals, field names in KeyValueExpr are not variable references
78
+ case "KeyValueExpr":
79
+ walk(node.value);
80
+ return refs;
81
+ // Map keys are expressions that may read captured variables; struct
82
+ // field names are not references.
83
+ case "CompositeLit": {
84
+ const t = node._type?.underlying ?? node._type;
85
+ if (t && t.kind !== "struct") {
86
+ for (const el of node.elems ?? []) {
87
+ if (el?.kind === "KeyValueExpr") walk(el.key);
88
+ }
89
+ }
90
+ break;
73
91
  }
74
- collectDeclaredNames(node.body, innerLocals);
75
- forEachChild(node, (child) =>
76
- collectReferencedNames(child, innerLocals, refs),
77
- );
78
- return refs;
79
- }
80
-
81
- if (
82
- node.kind === "Ident" &&
83
- !localDecls.has(node.name) &&
84
- node.name !== "_"
85
- ) {
86
- refs.add(node.name);
92
+ case "FuncLit":
93
+ scoped(() => {
94
+ for (const p of node.params ?? []) declare(p.name);
95
+ for (const r of node.returnType?._namedReturns ?? []) declare(r.name);
96
+ forEachChild(node, walk);
97
+ });
98
+ return refs;
99
+ case "DefineStmt":
100
+ walk(node.rhs);
101
+ for (const l of node.lhs ?? []) {
102
+ if (l.kind === "Ident") declare(l.name);
103
+ else walk(l);
104
+ }
105
+ return refs;
106
+ case "VarDecl":
107
+ case "ConstDecl":
108
+ for (const spec of node.decls ?? []) {
109
+ walk(spec.value ?? spec.init);
110
+ for (const name of spec.names ?? []) declare(name);
111
+ }
112
+ return refs;
113
+ case "TypeSwitchStmt":
114
+ scoped(() => {
115
+ walk(node.expr);
116
+ for (const c of node.cases ?? []) {
117
+ scoped(() => {
118
+ declare(node.assign);
119
+ walk(c.stmts);
120
+ });
121
+ }
122
+ });
123
+ return refs;
124
+ case "Block":
125
+ case "CaseClause":
126
+ case "IfStmt":
127
+ case "ForStmt":
128
+ case "SwitchStmt":
129
+ scoped(() => forEachChild(node, walk));
130
+ return refs;
131
+ case "Ident":
132
+ if (node.name !== "_" && !scopes.some((sc) => sc.has(node.name))) {
133
+ refs.add(node.name);
134
+ if (node._type && types && !types.has(node.name))
135
+ types.set(node.name, node._type);
136
+ }
137
+ return refs;
87
138
  }
88
139
 
89
140
  // For nested closures, free variables are also referenced
90
- forEachChild(node, (child) =>
91
- collectReferencedNames(child, localDecls, refs),
92
- );
141
+ forEachChild(node, walk);
93
142
  return refs;
94
143
  }
95
144
 
@@ -226,6 +275,7 @@ export function analyzeCaptures(fnDecl) {
226
275
  for (const r of node.returnType?._namedReturns ?? []) {
227
276
  if (r.name && r.name !== "_") closureLocals.add(r.name);
228
277
  }
278
+ const paramScope = new Set(closureLocals);
229
279
  collectDeclaredNames(node.body, closureLocals);
230
280
 
231
281
  const allOuterScope = new Set();
@@ -233,14 +283,23 @@ export function analyzeCaptures(fnDecl) {
233
283
  for (const name of scope) allOuterScope.add(name);
234
284
  }
235
285
 
236
- const allRefs = collectReferencedNames(node.body, closureLocals);
286
+ const refTypes = new Map();
287
+ const allRefs = collectReferencedNames(
288
+ node.body,
289
+ [paramScope],
290
+ new Set(),
291
+ refTypes,
292
+ );
237
293
  const captured = new Set();
294
+ const capturedTypes = new Map();
238
295
  for (const ref of allRefs) {
239
296
  if (allOuterScope.has(ref)) {
240
297
  captured.add(ref);
298
+ if (refTypes.has(ref)) capturedTypes.set(ref, refTypes.get(ref));
241
299
  }
242
300
  }
243
301
  result.capturesByClosure.set(node, captured);
302
+ result.captureTypes.set(node, capturedTypes);
244
303
 
245
304
  for (const varName of captured) {
246
305
  if (mutatedVars.has(varName)) {