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
@@ -3,6 +3,7 @@
3
3
  // are mutated and thus require heap-allocated environment cells for WASM closures.
4
4
 
5
5
  import { collectMutatedVars } from "./ownership.js";
6
+ import { getOrCreateIteratorFuncLit, isIteratorRangeFor } from "./range.js";
6
7
  import { forEachChild } from "./walk.js";
7
8
 
8
9
  /**
@@ -12,6 +13,9 @@ export class CaptureAnalysis {
12
13
  constructor() {
13
14
  // Map<FuncLit, Set<string>>: free variables captured by each closure
14
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();
15
19
  // Set<string>: variables in the enclosing scope that are captured AND mutated
16
20
  this.mutatedCaptures = new Set();
17
21
  }
@@ -40,58 +44,151 @@ export function collectDeclaredNames(node, names = new Set()) {
40
44
  }
41
45
 
42
46
  // Do not descend into nested functions when collecting local declarations
43
- if (node.kind === "FuncLit") return names;
47
+ if (node.kind === "FuncLit" || isIteratorRangeFor(node)) return names;
44
48
 
45
49
  forEachChild(node, (child) => collectDeclaredNames(child, names));
46
50
  return names;
47
51
  }
48
52
 
49
53
  /**
50
- * 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.
51
59
  */
52
- function collectReferencedNames(node, localDecls, refs = new Set()) {
60
+ function collectReferencedNames(node, scopes, refs = new Set(), types) {
53
61
  if (!node || typeof node !== "object") return refs;
54
62
  if (Array.isArray(node)) {
55
- for (const item of node) collectReferencedNames(item, localDecls, refs);
56
- return refs;
57
- }
58
-
59
- // In struct literals, field names in KeyValueExpr are not variable references
60
- if (node.kind === "KeyValueExpr") {
61
- collectReferencedNames(node.value, localDecls, refs);
63
+ for (const item of node) collectReferencedNames(item, scopes, refs, types);
62
64
  return refs;
63
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
+ };
64
75
 
65
- if (node.kind === "FuncLit") {
66
- const innerLocals = new Set(localDecls);
67
- for (const p of node.params ?? []) {
68
- if (p.name && p.name !== "_") innerLocals.add(p.name);
69
- }
70
- for (const r of node.returnType?._namedReturns ?? []) {
71
- 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;
72
91
  }
73
- collectDeclaredNames(node.body, innerLocals);
74
- forEachChild(node, (child) =>
75
- collectReferencedNames(child, innerLocals, refs),
76
- );
77
- return refs;
78
- }
79
-
80
- if (
81
- node.kind === "Ident" &&
82
- !localDecls.has(node.name) &&
83
- node.name !== "_"
84
- ) {
85
- 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;
86
138
  }
87
139
 
88
140
  // For nested closures, free variables are also referenced
89
- forEachChild(node, (child) =>
90
- collectReferencedNames(child, localDecls, refs),
91
- );
141
+ forEachChild(node, walk);
92
142
  return refs;
93
143
  }
94
144
 
145
+ function hasReturnInNode(n) {
146
+ let found = false;
147
+ const check = (x) => {
148
+ if (!x || typeof x !== "object" || found) return;
149
+ if (Array.isArray(x)) {
150
+ for (const elem of x) check(elem);
151
+ return;
152
+ }
153
+ if (x.kind === "ReturnStmt") {
154
+ found = true;
155
+ return;
156
+ }
157
+ if (x.kind === "FuncLit" && !x._isYieldClosure) return;
158
+ for (const k of Object.keys(x)) {
159
+ if (k.startsWith("_")) continue;
160
+ check(x[k]);
161
+ }
162
+ };
163
+ check(n);
164
+ return found;
165
+ }
166
+
167
+ function hasIteratorWithReturn(body) {
168
+ let found = false;
169
+ const check = (x) => {
170
+ if (!x || typeof x !== "object" || found) return;
171
+ if (Array.isArray(x)) {
172
+ for (const elem of x) check(elem);
173
+ return;
174
+ }
175
+ if (x.kind === "FuncLit") return;
176
+ if (isIteratorRangeFor(x)) {
177
+ const lit = getOrCreateIteratorFuncLit(x);
178
+ if (hasReturnInNode(lit?.body)) {
179
+ found = true;
180
+ return;
181
+ }
182
+ }
183
+ for (const k of Object.keys(x)) {
184
+ if (k.startsWith("_")) continue;
185
+ check(x[k]);
186
+ }
187
+ };
188
+ check(body);
189
+ return found;
190
+ }
191
+
95
192
  /**
96
193
  * Analyzes a function or method declaration for closures and their captured variables.
97
194
  * @param {Object} fnDecl AST FuncDecl or FuncLit node
@@ -101,6 +198,26 @@ export function analyzeCaptures(fnDecl) {
101
198
  const result = new CaptureAnalysis();
102
199
  if (!fnDecl?.body) return result;
103
200
 
201
+ // Synthesize named returns if not already present, so iterator returns can share boxed slots
202
+ if (
203
+ fnDecl.returnType &&
204
+ !fnDecl.returnType._namedReturns &&
205
+ hasIteratorWithReturn(fnDecl.body)
206
+ ) {
207
+ const ret = fnDecl.returnType;
208
+ if (ret.name !== "void") {
209
+ const types =
210
+ ret.kind === "TupleType" || ret.kind === "tuple" ? ret.types : [ret];
211
+ if (types && types.length > 0) {
212
+ fnDecl.returnType._namedReturns = types.map((t, i) => ({
213
+ name: `__retVal$${i}`,
214
+ type: { ...t, _namedReturns: undefined },
215
+ _synthesized: true,
216
+ }));
217
+ }
218
+ }
219
+ }
220
+
104
221
  // Collect parameters, receiver and named returns of the root function
105
222
  const rootScope = new Set();
106
223
  if (fnDecl.recvName && fnDecl.recvName !== "_") {
@@ -117,46 +234,97 @@ export function analyzeCaptures(fnDecl) {
117
234
  // Fast pre-collected set of all mutated variables in the entire function
118
235
  const mutatedVars = collectMutatedVars(fnDecl.body);
119
236
 
120
- // Traverse closures with lexical scope stack to properly handle arbitrary nesting
121
- const walkClosures = (node, scopeStack) => {
122
- if (!node || typeof node !== "object") return;
123
- if (Array.isArray(node)) {
124
- for (const item of node) walkClosures(item, scopeStack);
125
- return;
237
+ const handleIteratorRange = (node, scopeStack) => {
238
+ const funcLit = getOrCreateIteratorFuncLit(node);
239
+ if (node._iterStateName) {
240
+ rootScope.add(node._iterStateName);
241
+ result.mutatedCaptures.add(node._iterStateName);
126
242
  }
127
-
128
- if (node.kind === "FuncLit") {
129
- const closureLocals = new Set();
130
- for (const p of node.params ?? []) {
131
- if (p.name && p.name !== "_") closureLocals.add(p.name);
243
+ const rootNamed = fnDecl.returnType?._namedReturns ?? [];
244
+ const hasRet = hasReturnInNode(funcLit.body);
245
+ if (hasRet && rootNamed.length > 0) {
246
+ for (const r of rootNamed) {
247
+ if (r.name && r.name !== "_") {
248
+ rootScope.add(r.name);
249
+ result.mutatedCaptures.add(r.name);
250
+ }
132
251
  }
133
- for (const r of node.returnType?._namedReturns ?? []) {
134
- if (r.name && r.name !== "_") closureLocals.add(r.name);
252
+ }
253
+ walkClosures(funcLit, scopeStack);
254
+ // A func literal ranged over directly is a closure of its own.
255
+ walkClosures(node.cond?.expr ?? node.init?.rhs?.[0]?.expr, scopeStack);
256
+ const captured = result.capturesByClosure.get(funcLit) ?? new Set();
257
+ if (node._iterStateName) {
258
+ captured.add(node._iterStateName);
259
+ }
260
+ if (hasRet && rootNamed.length > 0) {
261
+ for (const r of rootNamed) {
262
+ if (r.name && r.name !== "_") {
263
+ captured.add(r.name);
264
+ }
135
265
  }
136
- collectDeclaredNames(node.body, closureLocals);
266
+ }
267
+ result.capturesByClosure.set(funcLit, captured);
268
+ };
137
269
 
138
- // Visible outer variables from all enclosing ancestor scopes
139
- const allOuterScope = new Set();
140
- for (const scope of scopeStack) {
141
- for (const name of scope) allOuterScope.add(name);
142
- }
270
+ const handleFuncLit = (node, scopeStack) => {
271
+ const closureLocals = new Set();
272
+ for (const p of node.params ?? []) {
273
+ if (p.name && p.name !== "_") closureLocals.add(p.name);
274
+ }
275
+ for (const r of node.returnType?._namedReturns ?? []) {
276
+ if (r.name && r.name !== "_") closureLocals.add(r.name);
277
+ }
278
+ const paramScope = new Set(closureLocals);
279
+ collectDeclaredNames(node.body, closureLocals);
143
280
 
144
- const allRefs = collectReferencedNames(node.body, closureLocals);
145
- const captured = new Set();
146
- for (const ref of allRefs) {
147
- if (allOuterScope.has(ref)) {
148
- captured.add(ref);
149
- }
281
+ const allOuterScope = new Set();
282
+ for (const scope of scopeStack) {
283
+ for (const name of scope) allOuterScope.add(name);
284
+ }
285
+
286
+ const refTypes = new Map();
287
+ const allRefs = collectReferencedNames(
288
+ node.body,
289
+ [paramScope],
290
+ new Set(),
291
+ refTypes,
292
+ );
293
+ const captured = new Set();
294
+ const capturedTypes = new Map();
295
+ for (const ref of allRefs) {
296
+ if (allOuterScope.has(ref)) {
297
+ captured.add(ref);
298
+ if (refTypes.has(ref)) capturedTypes.set(ref, refTypes.get(ref));
150
299
  }
151
- result.capturesByClosure.set(node, captured);
300
+ }
301
+ result.capturesByClosure.set(node, captured);
302
+ result.captureTypes.set(node, capturedTypes);
152
303
 
153
- for (const varName of captured) {
154
- if (mutatedVars.has(varName)) {
155
- result.mutatedCaptures.add(varName);
156
- }
304
+ for (const varName of captured) {
305
+ if (mutatedVars.has(varName)) {
306
+ result.mutatedCaptures.add(varName);
157
307
  }
308
+ }
309
+
310
+ walkClosures(node.body, [...scopeStack, closureLocals]);
311
+ };
312
+
313
+ // Traverse closures with lexical scope stack to properly handle arbitrary nesting
314
+ const walkClosures = (node, scopeStack) => {
315
+ if (!node || typeof node !== "object") return;
316
+ if (Array.isArray(node)) {
317
+ for (const item of node) walkClosures(item, scopeStack);
318
+ return;
319
+ }
158
320
 
159
- walkClosures(node.body, [...scopeStack, closureLocals]);
321
+ if (isIteratorRangeFor(node)) {
322
+ handleIteratorRange(node, scopeStack);
323
+ return;
324
+ }
325
+
326
+ if (node.kind === "FuncLit") {
327
+ handleFuncLit(node, scopeStack);
160
328
  return;
161
329
  }
162
330
 
@@ -166,3 +334,38 @@ export function analyzeCaptures(fnDecl) {
166
334
  walkClosures(fnDecl.body, [rootScope]);
167
335
  return result;
168
336
  }
337
+
338
+ /**
339
+ * Go 1.22 per-iteration loop variables: the names declared by a three-clause
340
+ * `for` init (`for i := …`) that a closure inside the loop (body, cond or
341
+ * post) refers to, or whose address is taken there (`&i`, `&p.X`): a pointer
342
+ * from one iteration must keep pointing at that iteration's variable.
343
+ * Conservative by name. Only these need a fresh copy per iteration; the
344
+ * others stay plain locals.
345
+ */
346
+ export function loopVarsCapturedInLoop(stmt) {
347
+ const out = new Set();
348
+ if (stmt?.init?.kind !== "DefineStmt") return out;
349
+ const names = new Set(
350
+ (stmt.init.lhs ?? [])
351
+ .filter((l) => l.kind === "Ident" && l.name !== "_")
352
+ .map((l) => l.name),
353
+ );
354
+ if (names.size === 0) return out;
355
+ const walk = (node, inClosure) => {
356
+ if (!node || typeof node !== "object") return;
357
+ if (Array.isArray(node)) {
358
+ for (const n of node) walk(n, inClosure);
359
+ return;
360
+ }
361
+ if (inClosure && node.kind === "Ident" && names.has(node.name))
362
+ out.add(node.name);
363
+ const nowIn = inClosure || node.kind === "FuncLit";
364
+ forEachChild(node, (child) => walk(child, nowIn));
365
+ };
366
+ const loop = [stmt.cond, stmt.post, stmt.body];
367
+ walk(loop, false);
368
+ for (const name of collectMutatedVars(loop, new Set(), true))
369
+ if (names.has(name)) out.add(name);
370
+ return out;
371
+ }
@@ -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
  }
@@ -0,0 +1,196 @@
1
+ // src/lower/errors-as.js
2
+ // errors.As as generated Go source.
3
+ //
4
+ // `errors.As(err, &x)` stores through a pointer whose type only the checker
5
+ // knows. Each call site gets a function generated for its static target
6
+ // type; it walks the error tree like the real package and honours custom
7
+ // `As(any) bool` methods. The functions are ordinary Go, parsed and checked
8
+ // as part of the package's generated file (src/link/stdlib.js), so both
9
+ // backends compile the same code.
10
+
11
+ import {
12
+ importsFor,
13
+ mentionsTypeParam,
14
+ TypeSpeller,
15
+ walkNodes,
16
+ } from "./gen-types.js";
17
+
18
+ const ERRORS_PATH = "errors";
19
+ const NOT_POINTER =
20
+ "errors.As: second argument must be a non-nil pointer to a type that implements error or to any interface type";
21
+
22
+ // The names the file imports `errors` as.
23
+ function errorsAliases(program) {
24
+ const aliases = new Set();
25
+ for (const imp of program.imports ?? [])
26
+ for (const spec of imp.imports)
27
+ if (spec.path === ERRORS_PATH && spec.alias !== "_")
28
+ aliases.add(spec.alias ?? "errors");
29
+ return aliases;
30
+ }
31
+
32
+ const isAsRef = (node, aliases) =>
33
+ node.kind === "SelectorExpr" &&
34
+ node.expr?.kind === "Ident" &&
35
+ node.expr._type?.kind === "namespace" &&
36
+ aliases.has(node.expr.name) &&
37
+ node.field === "As";
38
+
39
+ const isAsCall = (node, aliases) =>
40
+ node.kind === "CallExpr" && isAsRef(node.func, aliases);
41
+
42
+ // Marks `call` to call the generated function `name`. The function takes the
43
+ // typed pointer: the target is not boxed into an interface the way
44
+ // errors.As's `any` parameter asked.
45
+ function markCall(call, name) {
46
+ call.func._genEntry = name;
47
+ const arg = call.args[1];
48
+ arg._ifaceBox = undefined;
49
+ arg._ifaceSrc = undefined;
50
+ arg._ifaceNamedType = undefined;
51
+ arg._ifacePtrFields = undefined;
52
+ }
53
+
54
+ // One function per distinct target type; `tparams` ("[T any]") makes it the
55
+ // shared function for targets that are a type parameter.
56
+ function asFunction(name, target, tparams = "") {
57
+ return `func ${name}${tparams}(err error, target *${target}) bool {
58
+ if target == nil {
59
+ panic("errors: target must be a non-nil pointer")
60
+ }
61
+ for err != nil {
62
+ if v, ok := err.(${target}); ok {
63
+ *target = v
64
+ return true
65
+ }
66
+ if a, ok := err.(interface{ As(any) bool }); ok && a.As(target) {
67
+ return true
68
+ }
69
+ switch u := err.(type) {
70
+ case interface{ Unwrap() error }:
71
+ err = u.Unwrap()
72
+ case interface{ Unwrap() []error }:
73
+ for _, e := range u.Unwrap() {
74
+ if e != nil && ${name}${tparams ? "[T]" : ""}(e, target) {
75
+ return true
76
+ }
77
+ }
78
+ return false
79
+ default:
80
+ return false
81
+ }
82
+ }
83
+ return false
84
+ }`;
85
+ }
86
+
87
+ // The `errors.As` calls of the programs, and a diagnostic for each use of
88
+ // errors.As that is not a call.
89
+ function findSites(programs) {
90
+ const sites = [];
91
+ const diagnostics = [];
92
+ for (const program of programs) {
93
+ const aliases = errorsAliases(program);
94
+ if (aliases.size === 0) continue;
95
+ const callees = new Set();
96
+ const refs = [];
97
+ walkNodes(program.decls, (node) => {
98
+ if (isAsCall(node, aliases)) {
99
+ sites.push({ call: node, program });
100
+ callees.add(node.func);
101
+ } else if (isAsRef(node, aliases)) {
102
+ refs.push(node);
103
+ }
104
+ });
105
+ for (const node of refs)
106
+ if (!callees.has(node))
107
+ diagnostics.push({
108
+ program,
109
+ node: node.expr,
110
+ message: "errors.As cannot be used as a value",
111
+ hint: "call errors.As directly",
112
+ });
113
+ }
114
+ return { sites, diagnostics };
115
+ }
116
+
117
+ // What a call's target type allows: { base } for a target to generate for, or
118
+ // { message, hint } for one the generator rejects.
119
+ function classifyTarget(call) {
120
+ const argType = call.args[1]?._type;
121
+ if (argType?.kind !== "pointer") return { message: NOT_POINTER };
122
+ const base = argType.base;
123
+ if (base?.kind === "interface" && !base.name && base.methods?.size > 0)
124
+ return {
125
+ message:
126
+ "errors.As with a target of an anonymous interface type is not yet available",
127
+ hint: "declare a named interface type for the target",
128
+ };
129
+ if (base?.kind !== "typeParam" && mentionsTypeParam(base))
130
+ return {
131
+ message:
132
+ "errors.As with a target that mentions a type parameter is not yet available",
133
+ hint: "use a target that is a type parameter itself, or a concrete type",
134
+ };
135
+ return { base };
136
+ }
137
+
138
+ // Generates the `errors.As` functions for a package's call sites. Returns
139
+ // null when it has none; otherwise { body, imports, diagnostics, link } and
140
+ // marks each call's SelectorExpr with `_genEntry`.
141
+ export function generateErrorsAs(programs, _checker, pkgName) {
142
+ const { sites, diagnostics } = findSites(programs);
143
+ if (sites.length + diagnostics.length === 0) return null;
144
+ const qualifiers = new Map();
145
+ const speller = new TypeSpeller(pkgName, qualifiers, programs);
146
+ const prefix = `gfe_${String(pkgName).replace(/[^A-Za-z0-9_]/g, "_")}_as_`;
147
+ const names = new Map(); // spelled target -> function name
148
+ const out = [];
149
+ const sharedSites = []; // targets that are a type parameter
150
+ for (const { call, program } of sites) {
151
+ const { base, message, hint } = classifyTarget(call);
152
+ let target;
153
+ try {
154
+ if (base?.kind === "typeParam") sharedSites.push(call);
155
+ else if (base) target = speller.spell(base);
156
+ } catch (e) {
157
+ diagnostics.push({
158
+ program,
159
+ node: call.func.expr,
160
+ message: `errors.As: ${e.message}`,
161
+ });
162
+ continue;
163
+ }
164
+ if (!base) {
165
+ diagnostics.push({ program, node: call.func.expr, message, hint });
166
+ continue;
167
+ }
168
+ if (target === undefined) continue;
169
+ if (!names.has(target)) {
170
+ names.set(target, `${prefix}${names.size}`);
171
+ out.push(asFunction(names.get(target), target));
172
+ }
173
+ markCall(call, names.get(target));
174
+ }
175
+ const sharedName = `${prefix}t`;
176
+ if (sharedSites.length > 0) out.push(asFunction(sharedName, "T", "[T any]"));
177
+ for (const call of sharedSites) markCall(call, sharedName);
178
+ // The shared function is generic: once it is checked, its call sites take
179
+ // the type arguments the checker would have inferred.
180
+ const link = (chk) => {
181
+ const generic = chk.globals.symbols.get(sharedName);
182
+ for (const call of sharedSites) {
183
+ const typeArgs = [call.args[1]._type.base];
184
+ for (const n of [call, call.func]) {
185
+ n._typeArgs = typeArgs;
186
+ n._genericFnType = generic;
187
+ }
188
+ }
189
+ };
190
+ return {
191
+ body: out.join("\n\n"),
192
+ imports: importsFor(qualifiers),
193
+ diagnostics,
194
+ link,
195
+ };
196
+ }