gofront 1.8.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 (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  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 +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  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 +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -0,0 +1,148 @@
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`). Returns whether anything was renamed.
41
+ let localTypeCounter = 0;
42
+
43
+ export function renameLocalTypes(programs) {
44
+ const counts = new Map();
45
+ const count = (node) => {
46
+ if (!node || typeof node !== "object") return;
47
+ if (Array.isArray(node)) return node.forEach(count);
48
+ if (node.kind === "TypeDecl")
49
+ counts.set(node.name, (counts.get(node.name) ?? 0) + 1);
50
+ forEachChild(node, count);
51
+ };
52
+ for (const p of programs) count(p.decls);
53
+ const dup = new Set([...counts].filter(([, n]) => n > 1).map(([k]) => k));
54
+ if (dup.size === 0) return false;
55
+ let changed = false;
56
+ // Inner scopes first, so an outer rename never touches a nested
57
+ // declaration that already has its own name.
58
+ const scan = (node) => {
59
+ if (!node || typeof node !== "object") return;
60
+ if (Array.isArray(node)) {
61
+ node.forEach(scan);
62
+ for (let i = 0; i < node.length; i++) {
63
+ const d = node[i];
64
+ if (d?.kind !== "TypeDecl" || !dup.has(d.name)) continue;
65
+ const renamed = new Map([
66
+ [d.name, `${d.name}__L${++localTypeCounter}`],
67
+ ]);
68
+ for (let j = i; j < node.length; j++)
69
+ renameTypeRefs(node[j], renamed, new Set(), new Set());
70
+ changed = true;
71
+ }
72
+ return;
73
+ }
74
+ forEachChild(node, scan);
75
+ };
76
+ for (const p of programs)
77
+ for (const d of p.decls ?? []) if (d.kind !== "TypeDecl") scan(d);
78
+ return changed;
79
+ }
80
+
81
+ // `tparams` are type parameters in scope; `values` are names a function
82
+ // declares as parameters or locals, which shadow a same-spelled bare `Ident`.
83
+ function renameTypeRefs(node, renamed, tparams, values) {
84
+ if (!node || typeof node !== "object") return;
85
+ if (Array.isArray(node)) {
86
+ for (const n of node) renameTypeRefs(n, renamed, tparams, values);
87
+ return;
88
+ }
89
+ const tp = withTypeParams(node, tparams);
90
+ const vals = FUNC_KINDS.has(node.kind) ? withLocals(node, values) : values;
91
+ if (isTypeRef(node, vals) && renamed.has(node.name) && !tp.has(node.name))
92
+ node.name = renamed.get(node.name);
93
+ forEachChild(node, (child) => {
94
+ if (node.kind === "KeyValueExpr" && child === node.key) return;
95
+ renameTypeRefs(child, renamed, tp, vals);
96
+ });
97
+ }
98
+
99
+ const FUNC_KINDS = new Set(["FuncDecl", "MethodDecl", "FuncLit"]);
100
+
101
+ // Adds every parameter and local name declared anywhere inside a function.
102
+ function withLocals(fn, values) {
103
+ const out = new Set(values);
104
+ const visit = (node) => {
105
+ if (!node || typeof node !== "object") return;
106
+ if (Array.isArray(node)) return node.forEach(visit);
107
+ for (const n of declaredNames(node)) out.add(n);
108
+ forEachChild(node, visit);
109
+ };
110
+ visit(fn);
111
+ return out;
112
+ }
113
+
114
+ function declaredNames(node) {
115
+ const names = [];
116
+ if (node.kind === "DefineStmt")
117
+ for (const l of node.lhs) if (l.kind === "Ident") names.push(l.name);
118
+ if (node.kind === "VarDecl" || node.kind === "ConstDecl")
119
+ for (const d of node.decls ?? []) names.push(...(d.names ?? []));
120
+ if (node.kind === "TypeSwitchStmt" && node.assign) names.push(node.assign);
121
+ for (const p of node.params ?? []) if (p.name) names.push(p.name);
122
+ if (node.recvName) names.push(node.recvName);
123
+ return names;
124
+ }
125
+
126
+ function withTypeParams(node, tparams) {
127
+ if (!(node.typeParams?.length > 0)) return tparams;
128
+ return new Set([...tparams, ...node.typeParams.map((t) => t.name)]);
129
+ }
130
+
131
+ const TYPE_REF_KINDS = new Set([
132
+ "TypeDecl",
133
+ "TypeName",
134
+ "GenericTypeName",
135
+ "Ident",
136
+ ]);
137
+
138
+ function isTypeRef(node, values) {
139
+ if (node.kind === "Ident") return !values.has(node.name);
140
+ return TYPE_REF_KINDS.has(node.kind);
141
+ }
142
+
143
+ // Importers name a renamed type by its declared name (`b.Pt`).
144
+ export function withOriginalTypeNames(types, renamed) {
145
+ for (const [name, mangled] of renamed)
146
+ if (types.has(mangled)) types.set(name, types.get(mangled));
147
+ return types;
148
+ }
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)) {
@@ -1,6 +1,15 @@
1
1
  // src/lower/embedding.js
2
2
  // Computes promoted methods and delegation stubs for embedded structs.
3
3
 
4
+ import { embeddedFieldName } from "../typechecker/types.js";
5
+
6
+ // A method promoted from an embedded struct value but declared on its
7
+ // pointer receiver is outside the method set of the outer struct's value.
8
+ function isPtrOnlyPromotion(rawEmbed, embed, mName, mType) {
9
+ if (rawEmbed.kind === "pointer") return false;
10
+ return Boolean((mType ?? embed.methods?.get(mName))?._ptrRecv);
11
+ }
12
+
4
13
  export function computeEmbeddedStubs(name, methodDecls, checker) {
5
14
  if (!checker) return [];
6
15
  const resolvedType = checker.types.get(name)?.underlying;
@@ -9,18 +18,38 @@ export function computeEmbeddedStubs(name, methodDecls, checker) {
9
18
  const declared = new Set(methodDecls.map((m) => m.name));
10
19
  const stubs = [];
11
20
 
12
- for (const rawEmbed of resolvedType._embeds) {
21
+ const embedded = (resolvedType._structFields ?? []).filter((f) => f.embedded);
22
+ for (const [i, rawEmbed] of resolvedType._embeds.entries()) {
23
+ const fieldName = embedded[i] ? embeddedFieldName(embedded[i]) : null;
13
24
  const embed = rawEmbed.kind === "pointer" ? rawEmbed.base : rawEmbed;
14
25
  const embedName = embed.kind === "named" ? embed.name : null;
15
26
  if (!embedName) continue;
16
27
  const embedBase = embed.kind === "named" ? embed.underlying : embed;
17
- if (embedBase?.kind !== "struct" || !embedBase.methods) continue;
28
+ if (embedBase?.kind !== "struct" || !embedBase.methods) {
29
+ // A named non-struct type (`time.Duration`) promotes its value
30
+ // methods; the stub passes the embedded value as the receiver.
31
+ if (rawEmbed.kind === "pointer" || embedBase?.kind === "interface")
32
+ continue;
33
+ for (const [mName, mType] of embed.methods?.entries() ?? []) {
34
+ if (declared.has(mName) || mType?._ptrRecv) continue;
35
+ stubs.push({
36
+ methodName: mName,
37
+ embedName,
38
+ fieldName,
39
+ valueEmbed: true,
40
+ });
41
+ declared.add(mName);
42
+ }
43
+ continue;
44
+ }
18
45
 
19
- for (const [mName] of embedBase.methods.entries()) {
46
+ for (const [mName, mType] of embedBase.methods.entries()) {
20
47
  if (!declared.has(mName)) {
21
48
  stubs.push({
22
49
  methodName: mName,
23
50
  embedName,
51
+ fieldName,
52
+ ptrOnly: isPtrOnlyPromotion(rawEmbed, embed, mName, mType),
24
53
  });
25
54
  declared.add(mName); // Prevent duplicate stubs for diamond embeds
26
55
  }