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