gofront 1.7.0 → 1.8.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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -1,16 +1,129 @@
1
1
  import {
2
2
  bodyRedeclares,
3
+ forEachChild,
3
4
  isIntRangeType,
4
5
  isMapRangeType,
5
6
  isRangeFor,
6
7
  isSimpleIntRange,
7
8
  isStringRangeType,
9
+ loopVarsCapturedInLoop,
8
10
  nodeAssigns,
11
+ someChild,
9
12
  } from "../../lower/index.js";
10
- import { isComplex } from "../../typechecker/types.js";
13
+ import { isComplex, isTypedArraySlice } from "../../typechecker/types.js";
11
14
 
12
15
  /** @typedef {import('./index.js').CodeGen} CodeGen */
13
16
 
17
+ // The same variable or field chain (`x`, `n.Children`) on both sides.
18
+ const sameChain = (a, b) =>
19
+ a.kind === b.kind &&
20
+ (a.kind === "Ident"
21
+ ? a.name === b.name && a.name !== "_"
22
+ : a.kind === "SelectorExpr" &&
23
+ a.field === b.field &&
24
+ sameChain(a.expr, b.expr));
25
+
26
+ // `x = x[lo:hi]` (no max) with the same chain on both sides.
27
+ const isSelfResliceShape = (stmt) =>
28
+ stmt.op === "=" &&
29
+ stmt.lhs.length === 1 &&
30
+ stmt.rhs.length === 1 &&
31
+ stmt.rhs[0].kind === "SliceExpr" &&
32
+ !stmt.rhs[0].max &&
33
+ sameChain(stmt.lhs[0], stmt.rhs[0].expr);
34
+
35
+ // Pure expressions (the slice chain of a `__rcs` sync) that read the same
36
+ // value after the write as before it: literals, variables and fields not
37
+ // assigned by the statement, `len` and `cap`, and arithmetic over those.
38
+ const isStableIndex = (e, assigned) => {
39
+ switch (e?.kind) {
40
+ case "BasicLit":
41
+ return true;
42
+ case "Ident":
43
+ return !assigned.has(e.name);
44
+ case "SelectorExpr":
45
+ return !e._isMethodValue && isStableIndex(e.expr, assigned);
46
+ case "ParenExpr":
47
+ return isStableIndex(e.expr, assigned);
48
+ case "UnaryExpr":
49
+ return e.op !== "<-" && isStableIndex(e.operand, assigned);
50
+ case "BinaryExpr":
51
+ return (
52
+ isStableIndex(e.left, assigned) && isStableIndex(e.right, assigned)
53
+ );
54
+ case "CallExpr":
55
+ return (
56
+ e.func?.kind === "Ident" &&
57
+ (e.func.name === "len" || e.func.name === "cap") &&
58
+ e.args.every((a) => isStableIndex(a, assigned))
59
+ );
60
+ default:
61
+ return false;
62
+ }
63
+ };
64
+
65
+ const isLenCap = (e) =>
66
+ e.kind === "CallExpr" &&
67
+ e.func?.kind === "Ident" &&
68
+ (e.func.name === "len" || e.func.name === "cap");
69
+
70
+ // A call (other than `len`/`cap`), receive or await in `n` (not inside a
71
+ // function literal): it may change variables and fields.
72
+ const hasCall = (n) => {
73
+ if (Array.isArray(n)) return n.some(hasCall);
74
+ if (!n || typeof n !== "object" || typeof n.kind !== "string") return false;
75
+ if (n.kind === "FuncLit") return false;
76
+ if (n.kind === "AwaitExpr" || (n.kind === "CallExpr" && !isLenCap(n)))
77
+ return true;
78
+ if (n.kind === "UnaryExpr" && n.op === "<-") return true;
79
+ return someChild(n, hasCall);
80
+ };
81
+
82
+ // A call in a left-hand index or on the right-hand side of the statement.
83
+ const stmtHasCall = (lhs, stmt) =>
84
+ lhs.some((e) => hasCall(e.index)) ||
85
+ (stmt.kind === "AssignStmt" && hasCall(stmt.rhs));
86
+
87
+ // A multi-assign with a field or `*p` left-hand side: it may reassign the
88
+ // slice chain of an index write, whose sync must then use the old slice.
89
+ const reassignsChains = (lhs, stmt) =>
90
+ stmt.kind === "AssignStmt" &&
91
+ lhs.length > 1 &&
92
+ lhs.some(
93
+ (e) =>
94
+ e.kind === "SelectorExpr" || (e.kind === "UnaryExpr" && e.op === "*"),
95
+ );
96
+
97
+ // Index of a `__rcs` sync that can be evaluated again after the write:
98
+ // constants, and, when `vars` (a single-LHS statement without calls), unboxed
99
+ // variables, with `len`/`cap` and arithmetic over those. Any other index
100
+ // is bound to a temporary first, so the write and the sync use one value.
101
+ const isReusableIndex = (e, vars, boxed) => {
102
+ if (e?._const || e?.kind === "BasicLit") return true;
103
+ switch (e?.kind) {
104
+ case "Ident":
105
+ return vars && !boxed.has(e.name);
106
+ case "ParenExpr":
107
+ return isReusableIndex(e.expr, vars, boxed);
108
+ case "UnaryExpr":
109
+ return (
110
+ (e.op === "-" || e.op === "+" || e.op === "^") &&
111
+ isReusableIndex(e.operand, vars, boxed)
112
+ );
113
+ case "BinaryExpr":
114
+ return (
115
+ isReusableIndex(e.left, vars, boxed) &&
116
+ isReusableIndex(e.right, vars, boxed)
117
+ );
118
+ case "CallExpr":
119
+ return (
120
+ isLenCap(e) && e.args.every((a) => isReusableIndex(a, vars, boxed))
121
+ );
122
+ default:
123
+ return false;
124
+ }
125
+ };
126
+
14
127
  // Method-name dispatch for genStmt — string values add no static call edges.
15
128
  const STMT_GEN_DELEGATE = {
16
129
  VarDecl: "genVarDecl",
@@ -27,7 +140,53 @@ const STMT_GEN_DELEGATE = {
27
140
  /** @type {ThisType<CodeGen>} */
28
141
  export const statementGenMethods = {
29
142
  genBlock(block) {
30
- for (const stmt of block.stmts) this.genStmt(stmt);
143
+ let nested = 0;
144
+ for (const stmt of block.stmts) {
145
+ if (this._definesShadowedRead(stmt)) {
146
+ this._genShadowingDefine(stmt);
147
+ nested++;
148
+ } else {
149
+ this.genStmt(stmt);
150
+ }
151
+ }
152
+ for (; nested > 0; nested--) {
153
+ this.indent--;
154
+ this.line("}");
155
+ }
156
+ },
157
+
158
+ // `x := f(x)` declaring a new `x` that its own right-hand side reads (the
159
+ // outer `x`): JS `let` would put the read in its temporal dead zone.
160
+ _definesShadowedRead(stmt) {
161
+ if (stmt.kind !== "DefineStmt" || this._isCommaOkMapExpr(stmt, stmt.rhs[0]))
162
+ return false;
163
+ const names = new Set(
164
+ stmt.lhs.filter((l) => !l._redecl && l.name !== "_").map((l) => l.name),
165
+ );
166
+ if (names.size === 0) return false;
167
+ const reads = (n) => {
168
+ if (Array.isArray(n)) return n.some(reads);
169
+ if (!n || typeof n !== "object") return false;
170
+ if (n.kind === "Ident" && names.has(n.name)) return true;
171
+ return someChild(n, reads);
172
+ };
173
+ return stmt.rhs.some(reads);
174
+ },
175
+
176
+ // Evaluates the right-hand side in the enclosing scope, then opens a nested
177
+ // block (closed at the end of the enclosing block) that declares the names.
178
+ _genShadowingDefine(stmt) {
179
+ const rhs = this._defineRhs(stmt);
180
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
181
+ const tmp = `__t${this._tmpCounter}`;
182
+ const all = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
183
+ this.line(`const ${tmp} = ${all};`);
184
+ this.line("{");
185
+ this.indent++;
186
+ this._genDefineStmt(
187
+ stmt,
188
+ rhs.length === 1 ? [tmp] : rhs.map((_, i) => `${tmp}[${i}]`),
189
+ );
31
190
  },
32
191
 
33
192
  // Shared helper for comma-ok map index in both DefineStmt and AssignStmt.
@@ -36,9 +195,15 @@ export const statementGenMethods = {
36
195
  const keyExpr = this.genExpr(rhsNode.index);
37
196
  const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
38
197
  const decl = isDefine ? "let " : "";
198
+ // An array map value is a copy, not the stored array.
199
+ const valType = rhsNode._mapValueType;
200
+ const copyVal = (js) =>
201
+ this._aggregateBase(valType)?.kind === "array"
202
+ ? this._cloneJs(valType, js)
203
+ : js;
39
204
  if (isDefine && !this.strict) {
40
205
  if (vName !== "_") {
41
- this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
206
+ this.line(`let ${vName} = ${copyVal(`${mapExpr}[${keyExpr}]`)};`);
42
207
  }
43
208
  if (okName !== "_") {
44
209
  this.line(`let ${okName} = (${keyExpr}) in ${mapExpr};`);
@@ -54,7 +219,7 @@ export const statementGenMethods = {
54
219
  this.line(`const ${mTmp} = ${mapExpr}, ${kTmp} = ${keyExpr};`);
55
220
  if (vName !== "_") {
56
221
  this.line(
57
- `${decl}${vName} = (${mTmp} && (${kTmp} in ${mTmp})) ? ${mTmp}[${kTmp}] : ${zero};`,
222
+ `${decl}${vName} = (${mTmp} && (${kTmp} in ${mTmp})) ? ${copyVal(`${mTmp}[${kTmp}]`)} : ${zero};`,
58
223
  );
59
224
  }
60
225
  if (okName !== "_") {
@@ -66,11 +231,21 @@ export const statementGenMethods = {
66
231
  // Record source mapping for the first line this statement produces
67
232
  const srcLine = stmt._line ?? null;
68
233
  const _line0 = this.out.length;
234
+ const restore = [];
235
+ const syncs =
236
+ stmt.kind === "AssignStmt"
237
+ ? this._resliceSyncs(stmt.lhs, stmt, false, restore)
238
+ : stmt.kind === "IncDecStmt"
239
+ ? this._resliceSyncs([stmt.expr], stmt, false, restore)
240
+ : null;
69
241
  switch (stmt.kind) {
70
242
  case "TypeDecl":
71
243
  this.genTypeDeclWithMethods(stmt, []);
244
+ this._noteLocalTypeDecl(stmt);
72
245
  break;
73
246
  case "IncDecStmt": {
247
+ this._markIndexLvalues([stmt.expr]);
248
+ this._markDerefOpTarget(stmt.expr);
74
249
  if (stmt.expr.kind === "IndexExpr" && stmt.expr._mapValueType) {
75
250
  const zero = this.zeroValueForType(stmt.expr._mapValueType);
76
251
  const wrapField = this._namedWrapperField(
@@ -87,12 +262,12 @@ export const statementGenMethods = {
87
262
  this._hasCallExpr(stmt.expr.index)
88
263
  ) {
89
264
  this.line(
90
- `((__m, __k) => { __m[__k] = (__m[__k] ?? ${zero}) ${op} 1; })(${base}, ${idx});`,
265
+ `((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = (__m[__k] ?? ${zero}) ${op} 1; })(${base}, ${idx});`,
91
266
  srcLine,
92
267
  );
93
268
  } else {
94
269
  this.line(
95
- `${base}[${idx}] = (${base}[${idx}] ?? ${zero}) ${op} 1;`,
270
+ `${this._genMapWriteBase(base)}[${idx}] = (${base}[${idx}] ?? ${zero}) ${op} 1;`,
96
271
  srcLine,
97
272
  );
98
273
  }
@@ -118,8 +293,14 @@ export const statementGenMethods = {
118
293
  if (stmt.init) {
119
294
  this.line("{");
120
295
  this.indented(() => {
121
- this.genStmt(stmt.init);
296
+ const shadowing = this._definesShadowedRead(stmt.init);
297
+ if (shadowing) this._genShadowingDefine(stmt.init);
298
+ else this.genStmt(stmt.init);
122
299
  this._genIf(stmt);
300
+ if (shadowing) {
301
+ this.indent--;
302
+ this.line("}");
303
+ }
123
304
  });
124
305
  this.line("}");
125
306
  } else {
@@ -130,6 +311,9 @@ export const statementGenMethods = {
130
311
  this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
131
312
  break;
132
313
  case "LabeledStmt":
314
+ if (stmt.body && stmt.body.kind === "ForStmt") {
315
+ stmt.body.label = stmt.label;
316
+ }
133
317
  this.line(`${stmt.label}:`);
134
318
  this.genStmt(stmt.body);
135
319
  break;
@@ -147,6 +331,9 @@ export const statementGenMethods = {
147
331
  throw new Error(`CodeGen: unhandled statement kind '${stmt.kind}'`);
148
332
  }
149
333
  }
334
+ this._emitResliceSyncs(syncs);
335
+ // The AST is shared with the WASM backend: undo the temporaries.
336
+ for (const [node, key, v] of restore) node[key] = v;
150
337
  // Record source mapping: first output line this statement produced
151
338
  if (srcLine != null && this.out.length > _line0) {
152
339
  this._srcMappings.push({
@@ -158,11 +345,14 @@ export const statementGenMethods = {
158
345
  },
159
346
 
160
347
  _isCommaOkMapExpr(stmt, rhsNode) {
348
+ if (stmt._commaOkMap) return true;
349
+ const t = rhsNode?.expr?._type;
350
+ const underlying = t?.kind === "named" ? t.underlying : t;
161
351
  return (
162
352
  stmt.rhs.length === 1 &&
163
353
  stmt.lhs.length === 2 &&
164
354
  rhsNode?.kind === "IndexExpr" &&
165
- rhsNode.expr?._type?.kind === "map"
355
+ underlying?.kind === "map"
166
356
  );
167
357
  },
168
358
 
@@ -178,22 +368,14 @@ export const statementGenMethods = {
178
368
  },
179
369
 
180
370
  _genDefineMixedRedecl(lhs, rhs, redecls) {
181
- if (rhs.length === 1 && lhs.length > 1) {
182
- this._tmpCounter = (this._tmpCounter ?? 0) + 1;
183
- const tmp = `__t${this._tmpCounter}`;
184
- this.line(`const ${tmp} = ${rhs[0]};`);
185
- for (let i = 0; i < lhs.length; i++) {
186
- if (lhs[i] === "_") continue;
187
- this._genDefineSingleVar([lhs[i]], [`${tmp}[${i}]`], [redecls[i]]);
188
- }
189
- } else {
190
- for (let i = 0; i < lhs.length; i++) {
191
- if (lhs[i] === "_") {
192
- this.line(`${rhs[i]};`);
193
- continue;
194
- }
195
- this._genDefineSingleVar([lhs[i]], [rhs[i]], [redecls[i]]);
196
- }
371
+ // `x, y := 2, x` reads the old `x`: evaluate every value first.
372
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
373
+ const tmp = `__t${this._tmpCounter}`;
374
+ const all = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
375
+ this.line(`const ${tmp} = ${all};`);
376
+ for (let i = 0; i < lhs.length; i++) {
377
+ if (lhs[i] === "_") continue;
378
+ this._genDefineSingleVar([lhs[i]], [`${tmp}[${i}]`], [redecls[i]]);
197
379
  }
198
380
  },
199
381
 
@@ -206,19 +388,23 @@ export const statementGenMethods = {
206
388
  }
207
389
  },
208
390
 
209
- _genDefineStmt(stmt) {
210
- const rhsNode = stmt.rhs[0];
211
- if (this._isCommaOkMapExpr(stmt, rhsNode)) {
212
- this._genCommaOkMapIndex(stmt, rhsNode, true);
213
- return;
214
- }
391
+ _defineRhs(stmt) {
215
392
  const paired = stmt.rhs.length === stmt.lhs.length;
216
- const rhs = stmt.rhs.map((e, i) => {
393
+ return stmt.rhs.map((e, i) => {
217
394
  const l = paired ? stmt.lhs[i] : null;
218
395
  if (!l || l.name === "_") return this.genExpr(e);
219
396
  if (l._redecl) return this.genValueExpr(e);
220
397
  return this._genDeclValue(l.name, e);
221
398
  });
399
+ },
400
+
401
+ _genDefineStmt(stmt, preEvaluated = null) {
402
+ const rhsNode = stmt.rhs[0];
403
+ if (this._isCommaOkMapExpr(stmt, rhsNode)) {
404
+ this._genCommaOkMapIndex(stmt, rhsNode, true);
405
+ return;
406
+ }
407
+ const rhs = preEvaluated ?? this._defineRhs(stmt);
222
408
  const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
223
409
  const redecls = stmt.lhs.map((e) => !!e._redecl);
224
410
  const anyRedecl = redecls.some((r) => r);
@@ -249,6 +435,68 @@ export const statementGenMethods = {
249
435
  );
250
436
  },
251
437
 
438
+ // `*p = v` where `p` points at a type parameter: erased, so the pointer is
439
+ // either a struct (copy into it) or a get/set wrapper.
440
+ _isTypeParamDerefAssign(stmt) {
441
+ if (stmt.lhs.length !== 1 || stmt.rhs.length !== 1 || stmt.op !== "=")
442
+ return false;
443
+ const l = stmt.lhs[0];
444
+ return (
445
+ l.kind === "UnaryExpr" &&
446
+ l.op === "*" &&
447
+ l.operand?._type?.base?.kind === "typeParam"
448
+ );
449
+ },
450
+
451
+ _isTypeParamDerefTarget(l) {
452
+ return (
453
+ l.kind === "UnaryExpr" &&
454
+ l.op === "*" &&
455
+ l.operand?._type?.base?.kind === "typeParam"
456
+ );
457
+ },
458
+
459
+ // `*a, *b = *b, *a` with type-parameter pointers: `*p` is not a settable
460
+ // JS destructuring target, so evaluate the right-hand side into temporaries
461
+ // and assign each target on its own.
462
+ _hasTypeParamDerefTarget(stmt) {
463
+ return (
464
+ stmt.op === "=" &&
465
+ stmt.lhs.length > 1 &&
466
+ stmt.lhs.some((l) => this._isTypeParamDerefTarget(l))
467
+ );
468
+ },
469
+
470
+ _genTypeParamDerefMultiAssign(stmt) {
471
+ const n = stmt.lhs.length;
472
+ const names = stmt.lhs.map((_, i) => `__t${i}`);
473
+ this.line("{");
474
+ this.indented(() => {
475
+ if (stmt.rhs.length === n) {
476
+ const vals = stmt.rhs.map((e, i) =>
477
+ stmt.lhs[i].name !== "_" ? this.genValueExpr(e) : this.genExpr(e),
478
+ );
479
+ this.line(`const [${names.join(", ")}] = [${vals.join(", ")}];`);
480
+ } else {
481
+ this.line(
482
+ `const [${names.join(", ")}] = ${this.genExpr(stmt.rhs[0])};`,
483
+ );
484
+ }
485
+ stmt.lhs.forEach((l, i) => {
486
+ if (l.name === "_") return;
487
+ if (this._isTypeParamDerefTarget(l)) {
488
+ const isStruct = this._typeParamIsStructJs(l.operand._type.base.name);
489
+ this.line(
490
+ `((__p, __v) => { if (${isStruct}) Object.assign(__p, __v); else __p.value = __v; })(${this.genExpr(l.operand)}, ${names[i]});`,
491
+ );
492
+ } else {
493
+ this.line(`${this._genAssignLhsExpr(l)} = ${names[i]};`);
494
+ }
495
+ });
496
+ });
497
+ this.line("}");
498
+ },
499
+
252
500
  _isStructDerefAssign(stmt) {
253
501
  if (stmt.lhs.length !== 1 || stmt.rhs.length !== 1 || stmt.op !== "=")
254
502
  return false;
@@ -270,6 +518,13 @@ export const statementGenMethods = {
270
518
  for (const e of exprs) if (e.kind === "IndexExpr") e._lvalue = true;
271
519
  },
272
520
 
521
+ // Op-assign / inc-dec through a type-parameter pointer needs a settable
522
+ // target: the erased pointer is a get/set wrapper (struct instantiations
523
+ // cannot be op-assigned), so target `.value` directly.
524
+ _markDerefOpTarget(e) {
525
+ if (this._isTypeParamDerefTarget(e)) e._derefOpTarget = true;
526
+ },
527
+
273
528
  _genAssignMulti(stmt, lhs, rhs, active, pairs) {
274
529
  const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
275
530
  if (active.length === pairs.length) {
@@ -279,7 +534,132 @@ export const statementGenMethods = {
279
534
  }
280
535
  },
281
536
 
537
+ // `x = x[lo:hi]` with the same variable or field chain on both sides:
538
+ // the old value of `x` is replaced, so `_genSliceExpr` may copy instead
539
+ // of building a slice view (`__reslice`).
540
+ _markSelfReslice(stmt) {
541
+ if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
542
+ return;
543
+ const r = stmt.rhs[0];
544
+ if (r.kind !== "SliceExpr" || r.max) return;
545
+ // A parameter or named result may alias the caller's slice: keep the
546
+ // view. Field chains (`n.Children = n.Children[:k]`) still copy.
547
+ const lhs = stmt.lhs[0];
548
+ if (lhs.kind === "Ident" && this._fnCtx?.params.has(lhs.name)) return;
549
+ if (sameChain(stmt.lhs[0], r.expr)) r._selfReslice = true;
550
+ },
551
+
552
+ // Names of the variables and fields (`.F`) that some `x = x[lo:hi]` in the
553
+ // program may turn into a `__reslice` copy (`_resliceSyncs`).
554
+ _collectResliceNames(program) {
555
+ const names = new Set();
556
+ const visit = (n) => {
557
+ if (Array.isArray(n)) {
558
+ for (const c of n) visit(c);
559
+ return;
560
+ }
561
+ if (!n || typeof n !== "object" || typeof n.kind !== "string") return;
562
+ if (n.kind === "AssignStmt" && isSelfResliceShape(n)) {
563
+ const l = n.lhs[0];
564
+ names.add(l.kind === "Ident" ? l.name : `.${l.field}`);
565
+ }
566
+ forEachChild(n, visit);
567
+ };
568
+ visit(program.decls);
569
+ this._resliceNames = names;
570
+ },
571
+
572
+ // Index writes in `lhs` whose slice may be a `__reslice` copy: each is
573
+ // followed by `__rcs(x, i)` (`_emitResliceSyncs`), which writes the element
574
+ // through to the copy's old array, so writes through the copy and through
575
+ // other slices of that array keep Go's order. An index that the statement
576
+ // could change (`isReusableIndex`) is first bound to a temporary (not in
577
+ // `inline` for-clause statements, which skip the sync: the write-back on
578
+ // extension still applies). Not covered: writes through other references to the copy.
579
+ // Once one index is bound, every left-hand index with a call is bound too,
580
+ // in source order and each after its operand, and so is the slice operand
581
+ // of a synced write in a statement with a call or one that may reassign
582
+ // the chain through a field or `*p` (`reassignsChains`; inline statements
583
+ // skip that sync) — Go evaluates both before the right-hand side.
584
+ // `restore` collects the replaced nodes for the caller to put back.
585
+ _resliceSyncs(lhs, stmt, inline = false, restore = []) {
586
+ if (!this._resliceNames?.size) return null;
587
+ const targets = this._resliceTargets(lhs, stmt);
588
+ if (targets.size === 0) return null;
589
+ const called = stmtHasCall(lhs, stmt);
590
+ const moved = reassignsChains(lhs, stmt);
591
+ const vars = lhs.length === 1 && !called;
592
+ const reusable = (e) => isReusableIndex(e.index, vars, this._boxedVars);
593
+ const out = [...targets].filter((e) => !inline || (!moved && reusable(e)));
594
+ const operands = called || moved;
595
+ if (!inline && (operands || out.some((e) => !reusable(e))))
596
+ this._bindResliceOperands(lhs, targets, operands, reusable, restore);
597
+ if (out.length === 0) return null;
598
+ this.useHelper("slice");
599
+ return out.map((e) => {
600
+ const wrapField = this._namedWrapperField(e.expr._type, e.expr);
601
+ const base = wrapField
602
+ ? `${this.genExpr(e.expr)}.${wrapField}`
603
+ : this.genExpr(e.expr);
604
+ return `__rcs(${base}, ${this.genExpr(e.index)})`;
605
+ });
606
+ },
607
+
608
+ // The `_isResliceSyncTarget` writes in `lhs`.
609
+ _resliceTargets(lhs, stmt) {
610
+ const assigned = new Set();
611
+ if (stmt.kind === "AssignStmt" && stmt.lhs.length > 1)
612
+ for (const e of stmt.lhs) if (e.kind === "Ident") assigned.add(e.name);
613
+ return new Set(lhs.filter((e) => this._isResliceSyncTarget(e, assigned)));
614
+ },
615
+
616
+ // Binds the left-hand operands that `_resliceSyncs` must not evaluate
617
+ // twice to temporaries, in source order: an index after its operand, and
618
+ // the operand of every synced write when `operands`.
619
+ _bindResliceOperands(lhs, targets, operands, reusable, restore) {
620
+ const bind = (e, key) => {
621
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
622
+ const tmp = `__rci${this._tmpCounter}`;
623
+ this.line(`const ${tmp} = ${this.genExpr(e[key])};`);
624
+ restore.push([e, key, e[key]]);
625
+ e[key] = { kind: "Ident", name: tmp, _type: e[key]._type };
626
+ };
627
+ for (const e of lhs) {
628
+ if (e.kind !== "IndexExpr") continue;
629
+ const t = targets.has(e);
630
+ const index = (t && !reusable(e)) || hasCall(e.index);
631
+ if ((t && operands) || index) bind(e, "expr");
632
+ if (index) bind(e, "index");
633
+ }
634
+ },
635
+
636
+ // `x[i]` on a plain slice held by a chain in `_resliceNames` whose root
637
+ // variable the statement does not reassign.
638
+ _isResliceSyncTarget(e, assigned) {
639
+ if (e.kind !== "IndexExpr" || e._mapValueType) return false;
640
+ const c = e.expr;
641
+ if (c.kind !== "Ident" && c.kind !== "SelectorExpr") return false;
642
+ if (!this._resliceNames.has(c.kind === "Ident" ? c.name : `.${c.field}`))
643
+ return false;
644
+ const u = c._type?.kind === "named" ? c._type.underlying : c._type;
645
+ if (u?.kind !== "slice" || isTypedArraySlice(c._type)) return false;
646
+ let root = c;
647
+ while (root.kind === "SelectorExpr") root = root.expr;
648
+ return (
649
+ root.kind === "Ident" &&
650
+ !assigned.has(root.name) &&
651
+ isStableIndex(c, assigned)
652
+ );
653
+ },
654
+
655
+ _emitResliceSyncs(syncs) {
656
+ if (syncs) for (const s of syncs) this.line(`${s};`);
657
+ },
658
+
282
659
  _genAssignStmt(stmt) {
660
+ this._markIndexLvalues(stmt.lhs);
661
+ if (stmt.op !== "=") for (const l of stmt.lhs) this._markDerefOpTarget(l);
662
+ this._markSelfReslice(stmt);
283
663
  if (this._isComplexCompoundAssign(stmt)) {
284
664
  this._genComplexCompoundAssign(stmt);
285
665
  return;
@@ -292,18 +672,38 @@ export const statementGenMethods = {
292
672
  this._genCommaOkTypeAssert(stmt);
293
673
  return;
294
674
  }
675
+ if (this._isTypeParamDerefAssign(stmt)) {
676
+ const ptr = this.genExpr(stmt.lhs[0].operand);
677
+ const val = this.genValueExpr(stmt.rhs[0]);
678
+ const isStruct = this._typeParamIsStructJs(
679
+ stmt.lhs[0].operand._type.base.name,
680
+ );
681
+ this.line(
682
+ `((__p, __v) => { if (${isStruct}) Object.assign(__p, __v); else __p.value = __v; })(${ptr}, ${val});`,
683
+ );
684
+ return;
685
+ }
686
+ if (this._hasTypeParamDerefTarget(stmt)) {
687
+ this._genTypeParamDerefMultiAssign(stmt);
688
+ return;
689
+ }
295
690
  if (this._isStructDerefAssign(stmt)) {
296
691
  this._genStructDerefAssign(stmt);
297
692
  return;
298
693
  }
694
+ if (this._isStableWholeAssign(stmt)) {
695
+ this._genStableWholeAssign(stmt);
696
+ return;
697
+ }
299
698
  const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
300
699
  const rhs = stmt.rhs.map((e, i) =>
301
700
  paired && stmt.lhs[i].name !== "_"
302
701
  ? this.genValueExpr(e)
303
702
  : this.genExpr(e),
304
703
  );
305
- this._markIndexLvalues(stmt.lhs);
704
+ const hoisted = this._hoistLhsOperands(stmt);
306
705
  const lhs = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
706
+ hoisted?.restore();
307
707
  const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
308
708
  const active = pairs.filter((p) => p.l !== "_");
309
709
  if (active.length === 0) {
@@ -329,11 +729,11 @@ export const statementGenMethods = {
329
729
  this._hasCallExpr(stmt.lhs[0].index)
330
730
  ) {
331
731
  this.line(
332
- `((__m, __k) => { __m[__k] = (__m[__k] ?? ${zero}) ${binOp} (${active[0].r}); })(${base}, ${idx});`,
732
+ `((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = (__m[__k] ?? ${zero}) ${binOp} (${active[0].r}); })(${base}, ${idx});`,
333
733
  );
334
734
  } else {
335
735
  this.line(
336
- `${base}[${idx}] = (${base}[${idx}] ?? ${zero}) ${binOp} (${active[0].r});`,
736
+ `${this._genMapWriteBase(base)}[${idx}] = (${base}[${idx}] ?? ${zero}) ${binOp} (${active[0].r});`,
337
737
  );
338
738
  }
339
739
  return;
@@ -351,12 +751,67 @@ export const statementGenMethods = {
351
751
  ) {
352
752
  return;
353
753
  }
354
- this.line(`${active[0].l} ${stmt.op} ${active[0].r};`);
754
+ const targetL =
755
+ active[0].l.startsWith("{") && !active[0].l.startsWith("({")
756
+ ? `(${active[0].l})`
757
+ : active[0].l;
758
+ this.line(`${targetL} ${stmt.op} ${active[0].r};`);
759
+ } else if (hoisted) {
760
+ this.line("{");
761
+ this.indented(() => {
762
+ for (const l of hoisted.lines) this.line(l);
763
+ this._genAssignMulti(stmt, lhs, rhs, active, pairs);
764
+ });
765
+ this.line("}");
355
766
  } else {
356
767
  this._genAssignMulti(stmt, lhs, rhs, active, pairs);
357
768
  }
358
769
  },
359
770
 
771
+ // `k, xs[k] = 1, 5`: Go evaluates the operands of every index and field
772
+ // target before any assignment, but a JS destructuring target reads its
773
+ // operands after the earlier ones are stored. Operands that mention a
774
+ // variable assigned by the same statement are bound to temporaries first.
775
+ _hoistLhsOperands(stmt) {
776
+ if (stmt.op !== "=" || stmt.lhs.length < 2) return null;
777
+ const assigned = new Set(
778
+ stmt.lhs
779
+ .filter((l) => l.kind === "Ident" && l.name !== "_")
780
+ .map((l) => l.name),
781
+ );
782
+ if (assigned.size === 0) return null;
783
+ const mentions = (n) =>
784
+ !!n &&
785
+ typeof n === "object" &&
786
+ ((n.kind === "Ident" && assigned.has(n.name)) || someChild(n, mentions));
787
+ const lines = [];
788
+ const undo = [];
789
+ for (const l of stmt.lhs) {
790
+ let keys;
791
+ if (l.kind === "IndexExpr") keys = ["expr", "index"];
792
+ else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
793
+ else continue;
794
+ for (const key of keys) {
795
+ const operand = l[key];
796
+ if (!mentions(operand)) continue;
797
+ // A struct or array value names the variable's storage in Go.
798
+ if (key === "expr" && this._aggregateBase(operand._type)) continue;
799
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
800
+ const name = `__h${this._tmpCounter}`;
801
+ lines.push(`const ${name} = ${this.genExpr(operand)};`);
802
+ undo.push([l, key, operand]);
803
+ l[key] = { kind: "Ident", name, _type: operand._type };
804
+ }
805
+ }
806
+ if (lines.length === 0) return null;
807
+ return {
808
+ lines,
809
+ restore: () => {
810
+ for (const [node, key, v] of undo) node[key] = v;
811
+ },
812
+ };
813
+ },
814
+
360
815
  _genCommaOkTypeAssert(stmt) {
361
816
  const val = this.genExpr(stmt.rhs[0]);
362
817
  const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
@@ -367,6 +822,68 @@ export const statementGenMethods = {
367
822
  if (okName !== "_") this.line(`${okName} = ${tmp}[1];`);
368
823
  },
369
824
 
825
+ // `a = [3]int{...}` / `s = S{...}` on a variable whose address (or an
826
+ // element's or field's address) is taken must write into the existing
827
+ // storage so those pointers see the new value (Task 0.33).
828
+ _isStableWholeAssign(stmt) {
829
+ if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
830
+ return false;
831
+ const l = stmt.lhs[0];
832
+ if (l.kind === "IndexExpr" && l._mapValueType) return false;
833
+ if (
834
+ l.kind !== "Ident" &&
835
+ l.kind !== "SelectorExpr" &&
836
+ l.kind !== "IndexExpr"
837
+ )
838
+ return false;
839
+ if (l.kind === "Ident" && l.name === "_") return false;
840
+ if (
841
+ !this._aggregateBase(l._type) ||
842
+ !this._aggregateBase(stmt.rhs[0]._type)
843
+ )
844
+ return false;
845
+ const root = this._rootIdentName(l);
846
+ return root !== null && this._fnMutates(root, true);
847
+ },
848
+
849
+ _genStableWholeAssign(stmt) {
850
+ let target = this._genAssignLhsExpr(stmt.lhs[0]);
851
+ const type = stmt.lhs[0]._type;
852
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
853
+ const src = `__s${this._tmpCounter}`;
854
+ this.line(`{ const ${src} = ${this.genExpr(stmt.rhs[0])};`);
855
+ if (stmt.lhs[0].kind === "IndexExpr") {
856
+ // Evaluate the element reference once.
857
+ target = `__d${this._tmpCounter}`;
858
+ this.line(`const ${target} = ${this._genAssignLhsExpr(stmt.lhs[0])};`);
859
+ }
860
+ this._emitCopyInto(target, src, type);
861
+ this.line("}");
862
+ },
863
+
864
+ // Copies aggregate `src` into the storage `dst` element by element, going
865
+ // into nested arrays and structs so pointers to inner parts stay valid.
866
+ _emitCopyInto(dst, src, type) {
867
+ const base = this._aggregateBase(type);
868
+ if (base.kind === "struct") {
869
+ for (const [name, ft] of base.fields) {
870
+ if (name === "_") continue;
871
+ const f = `.${name}`;
872
+ if (this._aggregateBase(ft))
873
+ this._emitCopyInto(`${dst}${f}`, `${src}${f}`, ft);
874
+ else this.line(`${dst}${f} = ${src}${f};`);
875
+ }
876
+ return;
877
+ }
878
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
879
+ const k = `__k${this._tmpCounter}`;
880
+ this.line(`for (let ${k} = 0; ${k} < ${src}.length; ${k}++) {`);
881
+ if (this._aggregateBase(base.elem))
882
+ this._emitCopyInto(`${dst}[${k}]`, `${src}[${k}]`, base.elem);
883
+ else this.line(`${dst}[${k}] = ${src}[${k}];`);
884
+ this.line("}");
885
+ },
886
+
370
887
  _genStructDerefAssign(stmt) {
371
888
  const target = this.genExpr(stmt.lhs[0].operand);
372
889
  const rhsNode = stmt.rhs[0];
@@ -393,31 +910,45 @@ export const statementGenMethods = {
393
910
  },
394
911
 
395
912
  _genComplexCompoundAssign(stmt) {
396
- const lhsStr = stmt.lhs[0].name ?? this.genExpr(stmt.lhs[0]);
913
+ const l = stmt.lhs[0];
914
+ let lhsTarget;
915
+ let lhsRead;
916
+ if (l.kind === "IndexExpr" && l._mapValueType) {
917
+ const wrapField = this._namedWrapperField(l.expr._type, l.expr);
918
+ const base = wrapField
919
+ ? `${this.genExpr(l.expr)}.${wrapField}`
920
+ : this.genExpr(l.expr);
921
+ const idx = this.genExpr(l.index);
922
+ lhsTarget = `${this._genMapWriteBase(base)}[${idx}]`;
923
+ lhsRead = `(${base}[${idx}] ?? { re: 0, im: 0 })`;
924
+ } else {
925
+ lhsTarget = l.name ?? this.genExpr(l);
926
+ lhsRead = lhsTarget;
927
+ }
397
928
  const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
398
929
  const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
399
930
  let result;
400
931
  switch (baseOp) {
401
932
  case "+":
402
- result = `{ re: ${lhsStr}.re + ${rhsExpr}.re, im: ${lhsStr}.im + ${rhsExpr}.im }`;
933
+ result = `{ re: ${lhsRead}.re + ${rhsExpr}.re, im: ${lhsRead}.im + ${rhsExpr}.im }`;
403
934
  break;
404
935
  case "-":
405
- result = `{ re: ${lhsStr}.re - ${rhsExpr}.re, im: ${lhsStr}.im - ${rhsExpr}.im }`;
936
+ result = `{ re: ${lhsRead}.re - ${rhsExpr}.re, im: ${lhsRead}.im - ${rhsExpr}.im }`;
406
937
  break;
407
938
  case "*":
408
939
  this.useHelper("cmul");
409
- result = `__cmul(${lhsStr}, ${rhsExpr})`;
940
+ result = `__cmul(${lhsRead}, ${rhsExpr})`;
410
941
  break;
411
942
  case "/":
412
943
  this.useHelper("cdiv");
413
- result = `__cdiv(${lhsStr}, ${rhsExpr})`;
944
+ result = `__cdiv(${lhsRead}, ${rhsExpr})`;
414
945
  break;
415
946
  default:
416
947
  throw new Error(
417
948
  `unsupported complex compound assignment operator: ${stmt.op}`,
418
949
  );
419
950
  }
420
- this.line(`${lhsStr} = ${result};`);
951
+ this.line(`${lhsTarget} = ${result};`);
421
952
  },
422
953
 
423
954
  _genIteratorReturn(stmt) {
@@ -476,8 +1007,12 @@ export const statementGenMethods = {
476
1007
  }
477
1008
  },
478
1009
 
479
- _genBranchStmt(stmt) {
480
- if (this._inIteratorBody && !stmt.label) {
1010
+ _genIteratorBranchStmt(stmt) {
1011
+ if (!stmt.label) {
1012
+ if (this._iterLoopDepth > 0) {
1013
+ if (stmt.keyword !== "fallthrough") this.line(`${stmt.keyword};`);
1014
+ return;
1015
+ }
481
1016
  if (stmt.keyword === "break") {
482
1017
  this.line(`${this._iterBreakFlag} = true; return false;`);
483
1018
  return;
@@ -486,6 +1021,29 @@ export const statementGenMethods = {
486
1021
  this.line("return true;");
487
1022
  return;
488
1023
  }
1024
+ return;
1025
+ }
1026
+ if (this._innerLoopLabels?.has(stmt.label)) {
1027
+ if (stmt.keyword !== "fallthrough")
1028
+ this.line(`${stmt.keyword} ${stmt.label};`);
1029
+ return;
1030
+ }
1031
+ if (
1032
+ stmt.keyword === "continue" &&
1033
+ stmt.label === this._currentIterLoopLabel
1034
+ ) {
1035
+ this.line("return true;");
1036
+ return;
1037
+ }
1038
+ this.line(
1039
+ `${this._iterTargetFlag} = "${stmt.keyword}$${stmt.label}"; return false;`,
1040
+ );
1041
+ },
1042
+
1043
+ _genBranchStmt(stmt) {
1044
+ if (this._inIteratorBody) {
1045
+ this._genIteratorBranchStmt(stmt);
1046
+ return;
489
1047
  }
490
1048
  if (stmt.keyword !== "fallthrough")
491
1049
  this.line(
@@ -546,31 +1104,73 @@ export const statementGenMethods = {
546
1104
  },
547
1105
 
548
1106
  genFor(stmt) {
549
- if (this.isRangeFor(stmt)) {
550
- if (stmt.init.rhs[0]._isIterator) this.genIteratorFor(stmt);
551
- else this.genRangeFor(stmt);
552
- return;
553
- }
554
- if (stmt.cond?.kind === "RangeExpr") {
555
- this._genRangeExprFor(stmt);
556
- return;
1107
+ const isInner = this._inIteratorBody;
1108
+ if (isInner) {
1109
+ this._iterLoopDepth = (this._iterLoopDepth ?? 0) + 1;
1110
+ if (stmt.label) {
1111
+ this._innerLoopLabels = this._innerLoopLabels ?? new Set();
1112
+ this._innerLoopLabels.add(stmt.label);
1113
+ }
557
1114
  }
558
- if (!stmt.init && !stmt.post) {
559
- this._genSimpleWhileFor(stmt);
560
- return;
1115
+ try {
1116
+ if (this.isRangeFor(stmt)) {
1117
+ if (stmt.init.rhs[0]._isIterator) this.genIteratorFor(stmt);
1118
+ else this.genRangeFor(stmt);
1119
+ return;
1120
+ }
1121
+ if (stmt.cond?.kind === "RangeExpr") {
1122
+ this._genRangeExprFor(stmt);
1123
+ return;
1124
+ }
1125
+ if (!stmt.init && !stmt.post) {
1126
+ this._genSimpleWhileFor(stmt);
1127
+ return;
1128
+ }
1129
+ const init = stmt.init ? this.stmtInline(stmt.init) : "";
1130
+ const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
1131
+ const post = this._perIterationClones(
1132
+ stmt,
1133
+ stmt.post ? this.stmtInline(stmt.post) : "",
1134
+ );
1135
+ this.line(`for (${init}; ${cond}; ${post}) {`);
1136
+ this.indented(() => this.genBlock(stmt.body));
1137
+ this.line("}");
1138
+ } finally {
1139
+ if (isInner) {
1140
+ this._iterLoopDepth--;
1141
+ if (stmt.label) this._innerLoopLabels.delete(stmt.label);
1142
+ }
561
1143
  }
562
- const init = stmt.init ? this.stmtInline(stmt.init) : "";
563
- const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
564
- const post = stmt.post ? this.stmtInline(stmt.post) : "";
565
- this.line(`for (${init}; ${cond}; ${post}) {`);
566
- this.indented(() => this.genBlock(stmt.body));
567
- this.line("}");
568
1144
  },
569
1145
 
570
1146
  isRangeFor(stmt) {
571
1147
  return isRangeFor(stmt);
572
1148
  },
573
1149
 
1150
+ // Go 1.22 per-iteration loop variables. `let` gives each iteration its
1151
+ // own binding, but a struct or array variable is a reference and an
1152
+ // address-taken scalar is a `{ value }` box: replace it at the start of
1153
+ // the post step (which runs in the next iteration's binding), so closures
1154
+ // and pointers from the finished iteration keep their own variable.
1155
+ _perIterationClones(stmt, post) {
1156
+ const clones = [];
1157
+ for (const name of loopVarsCapturedInLoop(stmt)) {
1158
+ const i = stmt.init.lhs.findIndex((e) => e.name === name);
1159
+ const t = stmt.init.lhs[i]?._type ?? stmt.init.rhs?.[i]?._type;
1160
+ if (this._boxedVars?.has(name)) {
1161
+ const v = `${name}.value`;
1162
+ const copy = this._aggregateBase(t) ? this._cloneJs(t, v) : v;
1163
+ clones.push(`${name} = { value: ${copy} }`);
1164
+ continue;
1165
+ }
1166
+ if (!this._aggregateBase(t)) continue;
1167
+ clones.push(`${name} = ${this._cloneJs(t, name)}`);
1168
+ }
1169
+ if (clones.length === 0) return post;
1170
+ const tail = post.replace(/;\s*$/, "");
1171
+ return tail ? `${clones.join(", ")}, ${tail}` : clones.join(", ");
1172
+ },
1173
+
574
1174
  _isIntRangeType(iterType) {
575
1175
  return isIntRangeType(iterType);
576
1176
  },
@@ -629,21 +1229,33 @@ export const statementGenMethods = {
629
1229
  return mutated ? this._cloneJs(elemType, js) : js;
630
1230
  },
631
1231
 
632
- _genSliceRangeFor(stmt, iteree, isAssign) {
1232
+ _genSliceRangeFor(stmt, iteree, isAssign, unwrapView = false) {
633
1233
  this._loopDepth = this._loopDepth ?? 0;
634
1234
  const d = this._loopDepth++;
635
1235
  const [idxNode, valNode] = stmt.init.lhs;
636
1236
  const i = `__i${d}`;
637
1237
  const arr = `__arr${d}`;
638
1238
  const len = `__len${d}`;
639
- this.line(
640
- `for (let ${i} = 0, ${arr} = ${iteree}, ${len} = ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
641
- );
1239
+ let elem = `${arr}[${i}]`;
1240
+ if (unwrapView) {
1241
+ // A partial view of a plain array is a Proxy (`__slice`): read the
1242
+ // elements from its backing array `b` at offset `o` instead.
1243
+ this.useHelper("sv");
1244
+ const [v, b, o] = [`__v${d}`, `__b${d}`, `__o${d}`];
1245
+ this.line(
1246
+ `for (let ${i} = 0, ${arr} = ${iteree}, ${v} = ${arr}?.[__sv], ${b} = ${v} === undefined ? ${arr} : ${v}.b, ${o} = ${v} === undefined ? 0 : ${v}.o, ${len} = ${v} !== undefined ? ${v}.n : ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
1247
+ );
1248
+ elem = `${b}[${o} + ${i}]`;
1249
+ } else {
1250
+ this.line(
1251
+ `for (let ${i} = 0, ${arr} = ${iteree}, ${len} = ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
1252
+ );
1253
+ }
642
1254
  this.indented(() => {
643
1255
  this._emitRangeVar(idxNode, i, isAssign);
644
1256
  this._emitRangeVar(
645
1257
  valNode,
646
- this._rangeElemValue(stmt, valNode, `${arr}[${i}]`, isAssign),
1258
+ this._rangeElemValue(stmt, valNode, elem, isAssign),
647
1259
  isAssign,
648
1260
  );
649
1261
  const declared = isAssign
@@ -739,6 +1351,7 @@ export const statementGenMethods = {
739
1351
 
740
1352
  _genIterFor(init, iterType, iteree, lhs, body) {
741
1353
  const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
1354
+ const keyDecode = this._structKeyRange(iterType);
742
1355
  const binding =
743
1356
  lhs.length === 1
744
1357
  ? lhs[0] === "_"
@@ -747,10 +1360,38 @@ export const statementGenMethods = {
747
1360
  : `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
748
1361
  const kw = init.kind === "AssignStmt" ? "" : "let ";
749
1362
  this.line(`for (${kw}${binding} of ${iterExpr}) {`);
750
- this.indented(() => this.genBlock(body));
1363
+ this.indented(() => {
1364
+ if (keyDecode && lhs[0] !== "_")
1365
+ this.line(`${lhs[0]} = __kd(${lhs[0]}, true);`);
1366
+ this._cloneMapRangeValue(init, iterType, lhs[1]);
1367
+ this.genBlock(body);
1368
+ });
751
1369
  this.line("}");
752
1370
  },
753
1371
 
1372
+ // Ranging over a map whose keys are structs: the key variable is decoded
1373
+ // from its encoded string (see HELPER_STRUCT_KEY).
1374
+ _structKeyRange(iterType) {
1375
+ if (!this._isMapRangeType(iterType)) return false;
1376
+ const base = iterType?.kind === "named" ? iterType.underlying : iterType;
1377
+ const key = base?.kind === "map" ? base.key : null;
1378
+ const keyBase = key?.kind === "named" ? key.underlying : key;
1379
+ if (keyBase?.kind !== "struct" && !this._isIfaceType(key)) return false;
1380
+ this.useHelper("structKey");
1381
+ return true;
1382
+ },
1383
+
1384
+ // A map range value of array or struct type is a copy: clone it when the
1385
+ // body may mutate it (it otherwise aliases the stored value).
1386
+ _cloneMapRangeValue(init, iterType, name) {
1387
+ if (!name || name === "_" || !this._isMapRangeType(iterType)) return;
1388
+ const base = iterType?.kind === "named" ? iterType.underlying : iterType;
1389
+ const valType = base?.value ?? base?.elem;
1390
+ if (!this._aggregateBase(valType)) return;
1391
+ if (init.kind !== "AssignStmt" && !this._fnMutates(name)) return;
1392
+ this.line(`${name} = ${this._cloneJs(valType, name)};`);
1393
+ },
1394
+
754
1395
  genRangeFor(stmt) {
755
1396
  const init = stmt.init;
756
1397
  const range = init.rhs[0];
@@ -776,7 +1417,26 @@ export const statementGenMethods = {
776
1417
  return;
777
1418
  }
778
1419
 
779
- this._genSliceRangeFor(stmt, iteree, init.kind === "AssignStmt");
1420
+ // Go ranges over a copy of an array: the range expression is evaluated
1421
+ // once, so writes to the array inside the body do not reach `v`.
1422
+ const valNode = init.lhs[1];
1423
+ const rangesArrayValue =
1424
+ valNode &&
1425
+ valNode.name !== "_" &&
1426
+ this._aggregateBase(iterType)?.kind === "array";
1427
+ const iterBase =
1428
+ iterType?.kind === "named" ? iterType.underlying : iterType;
1429
+ const unwrapView =
1430
+ valNode?.name !== "_" &&
1431
+ !!valNode &&
1432
+ iterBase?.kind === "slice" &&
1433
+ !isTypedArraySlice(iterType);
1434
+ this._genSliceRangeFor(
1435
+ stmt,
1436
+ rangesArrayValue ? this._cloneJs(iterType, iteree) : iteree,
1437
+ init.kind === "AssignStmt",
1438
+ unwrapView,
1439
+ );
780
1440
  },
781
1441
 
782
1442
  _genRangeIterExpr(iterType, iteree, lhs) {
@@ -789,7 +1449,28 @@ export const statementGenMethods = {
789
1449
  return `__s(${iteree}).entries()`;
790
1450
  },
791
1451
 
792
- _genIteratorLoop(itereeExpr, paramsStr, body) {
1452
+ _emitIterTargetDispatch(targetFlag) {
1453
+ const labels = this._functionLabels ?? [];
1454
+ this.line(`if (${targetFlag}) {`);
1455
+ this.indented(() => {
1456
+ for (const lbl of labels) {
1457
+ this.line(`if (${targetFlag} === "break$${lbl}") break ${lbl};`);
1458
+ this.line(`if (${targetFlag} === "continue$${lbl}") continue ${lbl};`);
1459
+ }
1460
+ if (this._inIteratorBody) {
1461
+ this.line(`${this._iterTargetFlag} = ${targetFlag}; return false;`);
1462
+ }
1463
+ });
1464
+ this.line("}");
1465
+ },
1466
+
1467
+ _genIteratorLoop(
1468
+ itereeExpr,
1469
+ paramsStr,
1470
+ body,
1471
+ assignPreamble = null,
1472
+ loopLabel = null,
1473
+ ) {
793
1474
  let iteree = this.genExpr(itereeExpr);
794
1475
  if (itereeExpr.kind === "FuncLit") {
795
1476
  iteree = `(${iteree})`;
@@ -799,38 +1480,61 @@ export const statementGenMethods = {
799
1480
  const breakFlag = `__broke${d}`;
800
1481
  const retFlag = `__returned${d}`;
801
1482
  const retVar = `__retVal${d}`;
1483
+ const targetFlag = `__target${d}`;
802
1484
 
803
1485
  this.line("{");
804
1486
  this.indented(() => {
805
1487
  this.line(`let ${breakFlag} = false;`);
806
1488
  this.line(`let ${retFlag} = false;`);
807
1489
  this.line(`let ${retVar};`);
1490
+ this.line(`let ${targetFlag} = null;`);
808
1491
 
809
1492
  this.line(`${iteree}(function(${paramsStr}) {`);
810
1493
  this.indented(() => {
811
- this.line(`if (${breakFlag}) return false;`);
1494
+ this.line(`if (${breakFlag} || ${retFlag} || ${targetFlag}) {`);
1495
+ this.indented(() => {
1496
+ if (this.strict) {
1497
+ this.line(
1498
+ `throw new Error("range function continued iteration after function for loop body returned false");`,
1499
+ );
1500
+ } else {
1501
+ this.line("return false;");
1502
+ }
1503
+ });
1504
+ this.line("}");
812
1505
  const prev = {
813
1506
  in: this._inIteratorBody,
814
1507
  break: this._iterBreakFlag,
815
1508
  ret: this._iterReturnFlag,
816
1509
  retVar: this._iterReturnVar,
1510
+ target: this._iterTargetFlag,
1511
+ loopLabel: this._currentIterLoopLabel,
1512
+ loopDepth: this._iterLoopDepth,
817
1513
  };
818
1514
  this._inIteratorBody = true;
819
1515
  this._iterBreakFlag = breakFlag;
820
1516
  this._iterReturnFlag = retFlag;
821
1517
  this._iterReturnVar = retVar;
1518
+ this._iterTargetFlag = targetFlag;
1519
+ this._currentIterLoopLabel = loopLabel;
1520
+ this._iterLoopDepth = 0;
822
1521
 
1522
+ if (assignPreamble) assignPreamble();
823
1523
  this.genBlock(body);
824
1524
 
825
1525
  this._inIteratorBody = prev.in;
826
1526
  this._iterBreakFlag = prev.break;
827
1527
  this._iterReturnFlag = prev.ret;
828
1528
  this._iterReturnVar = prev.retVar;
1529
+ this._iterTargetFlag = prev.target;
1530
+ this._currentIterLoopLabel = prev.loopLabel;
1531
+ this._iterLoopDepth = prev.loopDepth;
829
1532
 
830
1533
  this.line("return true;");
831
1534
  });
832
1535
  this.line("});");
833
1536
  this.line(`if (${retFlag}) return ${retVar};`);
1537
+ this._emitIterTargetDispatch(targetFlag);
834
1538
  });
835
1539
  this.line("}");
836
1540
  this._iterDepth--;
@@ -838,32 +1542,92 @@ export const statementGenMethods = {
838
1542
 
839
1543
  genIteratorFor(stmt) {
840
1544
  const range = stmt.init.rhs[0];
1545
+ const isAssign = stmt.init.kind === "AssignStmt";
841
1546
  const lhs = stmt.init.lhs.map((e) => e.name ?? this.genExpr(e));
1547
+ const yieldTypes = range._yieldParams ?? [];
1548
+ if (isAssign) {
1549
+ const cbParams = lhs.map((_, i) => `__p${i}`);
1550
+ const params = yieldTypes.length === 0 ? "" : cbParams.join(", ");
1551
+ const assignPreamble = () => {
1552
+ for (let i = 0; i < lhs.length; i++) {
1553
+ if (lhs[i] !== "_") {
1554
+ const yType = yieldTypes[i];
1555
+ const isStruct =
1556
+ this._isStructType(yType) ||
1557
+ this._isStructType(stmt.init.lhs[i]?._type);
1558
+ if (isStruct) {
1559
+ this.useHelper("sclone");
1560
+ this.line(`${lhs[i]} = __sclone(${cbParams[i]});`);
1561
+ } else {
1562
+ this.line(`${lhs[i]} = ${cbParams[i]};`);
1563
+ }
1564
+ }
1565
+ }
1566
+ };
1567
+ this._genIteratorLoop(
1568
+ range.expr,
1569
+ params,
1570
+ stmt.body,
1571
+ assignPreamble,
1572
+ stmt.label ?? null,
1573
+ );
1574
+ return;
1575
+ }
842
1576
  const cbParams = lhs.map((n, i) => (n === "_" ? `_$${i}` : n));
843
- const params = range._yieldParams.length === 0 ? "" : cbParams.join(", ");
844
- this._genIteratorLoop(range.expr, params, stmt.body);
1577
+ const params = yieldTypes.length === 0 ? "" : cbParams.join(", ");
1578
+ const clonePreamble = () => {
1579
+ for (let i = 0; i < lhs.length; i++) {
1580
+ if (lhs[i] !== "_") {
1581
+ const yType = yieldTypes[i];
1582
+ const isStruct =
1583
+ this._isStructType(yType) ||
1584
+ this._isStructType(stmt.init.lhs[i]?._type);
1585
+ if (isStruct) {
1586
+ this.useHelper("sclone");
1587
+ this.line(`${cbParams[i]} = __sclone(${cbParams[i]});`);
1588
+ }
1589
+ }
1590
+ }
1591
+ };
1592
+ this._genIteratorLoop(
1593
+ range.expr,
1594
+ params,
1595
+ stmt.body,
1596
+ clonePreamble,
1597
+ stmt.label ?? null,
1598
+ );
845
1599
  },
846
1600
 
847
1601
  // 0-param iterator: for range iter { body }
848
1602
  genIteratorForCond(stmt) {
849
- this._genIteratorLoop(stmt.cond.expr, "", stmt.body);
1603
+ this._genIteratorLoop(
1604
+ stmt.cond.expr,
1605
+ "",
1606
+ stmt.body,
1607
+ null,
1608
+ stmt.label ?? null,
1609
+ );
850
1610
  },
851
1611
 
852
1612
  // Inline a statement as a for-init/post string (no semicolon)
853
1613
  stmtInline(stmt) {
854
1614
  switch (stmt.kind) {
855
- case "DefineStmt": {
856
- const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
857
- const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e)).join(", ");
858
- return `let ${lhs} = ${rhs}`;
859
- }
860
- case "AssignStmt": {
861
- const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
862
- const lhs = stmt.lhs.map((e) => this.genExpr(e)).join(", ");
863
- return `${lhs} ${stmt.op} ${rhs}`;
864
- }
865
- case "IncDecStmt":
866
- return `${this.genExpr(stmt.expr)}${stmt.op}`;
1615
+ case "DefineStmt":
1616
+ return this._inlineDefine(stmt);
1617
+ case "AssignStmt":
1618
+ return this._withInlineSyncs(
1619
+ this._resliceSyncs(stmt.lhs, stmt, true),
1620
+ this._inlineAssign(stmt),
1621
+ );
1622
+ case "IncDecStmt": {
1623
+ const syncs = this._resliceSyncs([stmt.expr], stmt, true);
1624
+ this._markIndexLvalues([stmt.expr]);
1625
+ this._markDerefOpTarget(stmt.expr);
1626
+ return this._withInlineSyncs(
1627
+ syncs,
1628
+ `${this.genExpr(stmt.expr)}${stmt.op}`,
1629
+ );
1630
+ }
867
1631
  case "ExprStmt":
868
1632
  return this.genExpr(stmt.expr);
869
1633
  default:
@@ -871,6 +1635,45 @@ export const statementGenMethods = {
871
1635
  }
872
1636
  },
873
1637
 
1638
+ _withInlineSyncs(syncs, js) {
1639
+ return syncs ? `${js}, ${syncs.join(", ")}` : js;
1640
+ },
1641
+
1642
+ _inlineDefine(stmt) {
1643
+ const boxed =
1644
+ stmt.lhs.length === 1 &&
1645
+ stmt.rhs.length === 1 &&
1646
+ this._boxedVars.has(stmt.lhs[0].name);
1647
+ if (boxed)
1648
+ return `let ${stmt.lhs[0].name} = { value: ${this.genValueExpr(stmt.rhs[0])} }`;
1649
+ const rhs = stmt.rhs.map((e) => this.genExpr(e));
1650
+ const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
1651
+ if (lhs.length === 1) return `let ${lhs[0]} = ${rhs[0]}`;
1652
+ // `for i, j := 0, 10`: one declarator per variable.
1653
+ if (rhs.length === lhs.length) {
1654
+ const decls = lhs.map((n, i) =>
1655
+ this._boxedVars.has(n)
1656
+ ? `${n} = { value: ${rhs[i]} }`
1657
+ : `${n} = ${rhs[i]}`,
1658
+ );
1659
+ return `let ${decls.join(", ")}`;
1660
+ }
1661
+ return `let [${lhs.map((n) => (n === "_" ? "" : n)).join(", ")}] = ${rhs[0]}`;
1662
+ },
1663
+
1664
+ _inlineAssign(stmt) {
1665
+ this._markIndexLvalues(stmt.lhs);
1666
+ const rhs = stmt.rhs.map((e) => this.genExpr(e));
1667
+ if (stmt.lhs.length === 1)
1668
+ return `${this.genExpr(stmt.lhs[0])} ${stmt.op} ${rhs[0]}`;
1669
+ // `i, j = i+1, j-1`: every right-hand side is evaluated first.
1670
+ const lhs = stmt.lhs.map((e) =>
1671
+ e.name === "_" ? "" : this._genAssignLhsExpr(e),
1672
+ );
1673
+ const r = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
1674
+ return `[${lhs.join(", ")}] = ${r}`;
1675
+ },
1676
+
874
1677
  // Interface-typed tags may hold boxed named values; cases compare with __ifeq.
875
1678
  _switchTagAndCases(stmt) {
876
1679
  const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
@@ -953,9 +1756,20 @@ export const statementGenMethods = {
953
1756
  if (
954
1757
  stmt.assign &&
955
1758
  c.types.length === 1 &&
956
- this._isNamedBoxTypeNode(c.types[0])
1759
+ this._assertedValueJs(c.types[0], "__tsw") !== "__tsw"
1760
+ )
1761
+ this.line(
1762
+ `const ${stmt.assign} = ${this._assertedValueJs(c.types[0], "__tsw")};`,
1763
+ );
1764
+ // An array case binds a copy of the boxed value.
1765
+ else if (
1766
+ stmt.assign &&
1767
+ c.types.length === 1 &&
1768
+ this._typeFromNode(c.types[0])?.kind === "array"
957
1769
  )
958
- this.line(`const ${stmt.assign} = __tsw.__gfUnbox();`);
1770
+ this.line(
1771
+ `let ${stmt.assign} = ${this._cloneJs(this._typeFromNode(c.types[0]), "__tsw")};`,
1772
+ );
959
1773
  for (const s of c.stmts) this.genStmt(s);
960
1774
  });
961
1775
  this.line("}");