gofront 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -1,16 +1,20 @@
1
1
  import {
2
2
  bodyRedeclares,
3
3
  forEachChild,
4
+ hasCallOrReceive,
4
5
  isIntRangeType,
5
6
  isMapRangeType,
6
7
  isRangeFor,
7
8
  isSimpleIntRange,
8
9
  isStringRangeType,
9
10
  loopVarsCapturedInLoop,
10
- nodeAssigns,
11
11
  someChild,
12
12
  } from "../../lower/index.js";
13
- import { isComplex, isTypedArraySlice } from "../../typechecker/types.js";
13
+ import {
14
+ is64BitInt,
15
+ isComplex,
16
+ isTypedArraySlice,
17
+ } from "../../typechecker/types.js";
14
18
 
15
19
  /** @typedef {import('./index.js').CodeGen} CodeGen */
16
20
 
@@ -124,6 +128,20 @@ const isReusableIndex = (e, vars, boxed) => {
124
128
  }
125
129
  };
126
130
 
131
+ // Adds the name of every identifier under `n` to `into`.
132
+ const collectIdentNames = (n, into) => {
133
+ if (Array.isArray(n)) {
134
+ for (const c of n) collectIdentNames(c, into);
135
+ return;
136
+ }
137
+ if (!n || typeof n !== "object") return;
138
+ if (n.kind === "Ident") into.add(n.name);
139
+ someChild(n, (c) => {
140
+ collectIdentNames(c, into);
141
+ return false;
142
+ });
143
+ };
144
+
127
145
  // Method-name dispatch for genStmt — string values add no static call edges.
128
146
  const STMT_GEN_DELEGATE = {
129
147
  VarDecl: "genVarDecl",
@@ -135,19 +153,27 @@ const STMT_GEN_DELEGATE = {
135
153
  SwitchStmt: "genSwitch",
136
154
  TypeSwitchStmt: "genTypeSwitch",
137
155
  BranchStmt: "_genBranchStmt",
156
+ TemplMount: "_genTemplMountStmt",
138
157
  };
139
158
 
140
159
  /** @type {ThisType<CodeGen>} */
141
160
  export const statementGenMethods = {
142
161
  genBlock(block) {
143
162
  let nested = 0;
163
+ const earlier = new Set();
144
164
  for (const stmt of block.stmts) {
145
165
  if (this._definesShadowedRead(stmt)) {
146
166
  this._genShadowingDefine(stmt);
147
167
  nested++;
168
+ } else if (this._definesAfterOuterUse(stmt, earlier)) {
169
+ this.line("{");
170
+ this.indent++;
171
+ nested++;
172
+ this.genStmt(stmt);
148
173
  } else {
149
174
  this.genStmt(stmt);
150
175
  }
176
+ collectIdentNames(stmt, earlier);
151
177
  }
152
178
  for (; nested > 0; nested--) {
153
179
  this.indent--;
@@ -155,11 +181,20 @@ export const statementGenMethods = {
155
181
  }
156
182
  },
157
183
 
184
+ // `err = nil; _, err := g()` where `err` is an outer variable used earlier
185
+ // in this block: JS `let` would put those uses in its temporal dead zone,
186
+ // so the rest of the block moves into a nested block.
187
+ _definesAfterOuterUse(stmt, earlier) {
188
+ if (stmt.kind !== "DefineStmt" || earlier.size === 0) return false;
189
+ return stmt.lhs.some(
190
+ (l) => !l._redecl && l.name !== "_" && earlier.has(l.name),
191
+ );
192
+ },
193
+
158
194
  // `x := f(x)` declaring a new `x` that its own right-hand side reads (the
159
195
  // outer `x`): JS `let` would put the read in its temporal dead zone.
160
196
  _definesShadowedRead(stmt) {
161
- if (stmt.kind !== "DefineStmt" || this._isCommaOkMapExpr(stmt, stmt.rhs[0]))
162
- return false;
197
+ if (stmt.kind !== "DefineStmt") return false;
163
198
  const names = new Set(
164
199
  stmt.lhs.filter((l) => !l._redecl && l.name !== "_").map((l) => l.name),
165
200
  );
@@ -173,9 +208,35 @@ export const statementGenMethods = {
173
208
  return stmt.rhs.some(reads);
174
209
  },
175
210
 
211
+ // Emits an `if`/`switch` init statement, then `body`; an init that reads the
212
+ // name it declares goes through `_genShadowingDefine`.
213
+ _genScopedInit(init, body) {
214
+ const shadowing = this._definesShadowedRead(init);
215
+ if (shadowing) this._genShadowingDefine(init);
216
+ else this.genStmt(init);
217
+ body();
218
+ if (shadowing) {
219
+ this.indent--;
220
+ this.line("}");
221
+ }
222
+ },
223
+
176
224
  // Evaluates the right-hand side in the enclosing scope, then opens a nested
177
225
  // block (closed at the end of the enclosing block) that declares the names.
178
226
  _genShadowingDefine(stmt) {
227
+ if (this._isCommaOkMapExpr(stmt, stmt.rhs[0])) {
228
+ const node = stmt.rhs[0];
229
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
230
+ const m = `__sm${this._tmpCounter}`;
231
+ const k = `__sk${this._tmpCounter}`;
232
+ this.line(
233
+ `const ${m} = ${this.genExpr(node.expr)}, ${k} = ${this.genExpr(node.index)};`,
234
+ );
235
+ this.line("{");
236
+ this.indent++;
237
+ this._genCommaOkMapIndex(stmt, node, true, { map: m, key: k });
238
+ return;
239
+ }
179
240
  const rhs = this._defineRhs(stmt);
180
241
  this._tmpCounter = (this._tmpCounter ?? 0) + 1;
181
242
  const tmp = `__t${this._tmpCounter}`;
@@ -189,24 +250,44 @@ export const statementGenMethods = {
189
250
  );
190
251
  },
191
252
 
253
+ // `m[k]` of a non-strict comma-ok: a missing key reads as the scalar zero
254
+ // value, not `undefined`.
255
+ _nonStrictMapRead(mapExpr, keyExpr, valType, copyVal) {
256
+ const zero = valType ? this.zeroValueForType(valType) : "";
257
+ const read = `${mapExpr}[${keyExpr}]`;
258
+ if (/^(0n?|""|false)$/.test(zero)) return `(${read} ?? ${zero})`;
259
+ return copyVal(
260
+ this._aggregateBase(valType) ? `(${read} ?? ${zero})` : read,
261
+ );
262
+ },
263
+
192
264
  // Shared helper for comma-ok map index in both DefineStmt and AssignStmt.
193
- _genCommaOkMapIndex(stmt, rhsNode, isDefine) {
194
- const mapExpr = this.genExpr(rhsNode.expr);
195
- const keyExpr = this.genExpr(rhsNode.index);
265
+ // `pre` carries the map and key already evaluated (a shadowing declaration
266
+ // reads them before its names exist).
267
+ _genCommaOkMapIndex(stmt, rhsNode, isDefine, pre = null) {
268
+ const mapExpr = pre?.map ?? this.genExpr(rhsNode.expr);
269
+ const keyExpr = pre?.key ?? this.genExpr(rhsNode.index);
196
270
  const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
197
- const decl = isDefine ? "let " : "";
198
- // An array map value is a copy, not the stored array.
271
+ // A name `:=` reuses is assigned, not declared again; an address-taken
272
+ // variable lives in a `{ value }` box.
273
+ const bind = (l, name, js) => {
274
+ const boxed = this._boxedVars.has(name);
275
+ if (isDefine && !l._redecl)
276
+ return `let ${name} = ${boxed ? `{ value: ${js} }` : js};`;
277
+ return `${name}${boxed ? ".value" : ""} = ${js};`;
278
+ };
279
+ const [vL, okL] = stmt.lhs;
280
+ // A struct or array map value is a copy, not the stored value.
199
281
  const valType = rhsNode._mapValueType;
200
282
  const copyVal = (js) =>
201
- this._aggregateBase(valType)?.kind === "array"
202
- ? this._cloneJs(valType, js)
203
- : js;
283
+ this._aggregateBase(valType) ? this._cloneJs(valType, js) : js;
204
284
  if (isDefine && !this.strict) {
205
285
  if (vName !== "_") {
206
- this.line(`let ${vName} = ${copyVal(`${mapExpr}[${keyExpr}]`)};`);
286
+ const val = this._nonStrictMapRead(mapExpr, keyExpr, valType, copyVal);
287
+ this.line(bind(vL, vName, val));
207
288
  }
208
289
  if (okName !== "_") {
209
- this.line(`let ${okName} = (${keyExpr}) in ${mapExpr};`);
290
+ this.line(bind(okL, okName, `(${keyExpr}) in ${mapExpr}`));
210
291
  }
211
292
  return;
212
293
  }
@@ -219,11 +300,15 @@ export const statementGenMethods = {
219
300
  this.line(`const ${mTmp} = ${mapExpr}, ${kTmp} = ${keyExpr};`);
220
301
  if (vName !== "_") {
221
302
  this.line(
222
- `${decl}${vName} = (${mTmp} && (${kTmp} in ${mTmp})) ? ${copyVal(`${mTmp}[${kTmp}]`)} : ${zero};`,
303
+ bind(
304
+ vL,
305
+ vName,
306
+ `(${mTmp} && (${kTmp} in ${mTmp})) ? ${copyVal(`${mTmp}[${kTmp}]`)} : ${zero}`,
307
+ ),
223
308
  );
224
309
  }
225
310
  if (okName !== "_") {
226
- this.line(`${decl}${okName} = Boolean(${mTmp} && (${kTmp} in ${mTmp}));`);
311
+ this.line(bind(okL, okName, `Boolean(${mTmp} && (${kTmp} in ${mTmp}))`));
227
312
  }
228
313
  },
229
314
 
@@ -240,8 +325,11 @@ export const statementGenMethods = {
240
325
  : null;
241
326
  switch (stmt.kind) {
242
327
  case "TypeDecl":
243
- this.genTypeDeclWithMethods(stmt, []);
244
- this._noteLocalTypeDecl(stmt);
328
+ {
329
+ const start = this.out.length;
330
+ this.genTypeDeclWithMethods(stmt, []);
331
+ this._hoistLocalClass(stmt, start);
332
+ }
245
333
  break;
246
334
  case "IncDecStmt": {
247
335
  this._markIndexLvalues([stmt.expr]);
@@ -257,28 +345,47 @@ export const statementGenMethods = {
257
345
  : this.genExpr(stmt.expr.expr);
258
346
  const idx = this.genExpr(stmt.expr.index);
259
347
  const op = stmt.op === "++" ? "+" : "-";
348
+ const is64Val = is64BitInt(stmt.expr._mapValueType);
349
+ const delta = is64Val ? "1n" : "1";
260
350
  if (
261
351
  this._hasCallExpr(stmt.expr.expr) ||
262
352
  this._hasCallExpr(stmt.expr.index)
263
353
  ) {
354
+ const rhs = is64Val
355
+ ? this._gen64BitBinary(
356
+ op,
357
+ `(__m[__k] ?? ${zero})`,
358
+ delta,
359
+ stmt.expr._mapValueType,
360
+ )
361
+ : `(__m[__k] ?? ${zero}) ${op} 1`;
264
362
  this.line(
265
- `((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = (__m[__k] ?? ${zero}) ${op} 1; })(${base}, ${idx});`,
363
+ `((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = ${rhs}; })(${base}, ${idx});`,
266
364
  srcLine,
267
365
  );
268
366
  } else {
367
+ const rhs = is64Val
368
+ ? this._gen64BitBinary(
369
+ op,
370
+ `(${base}[${idx}] ?? ${zero})`,
371
+ delta,
372
+ stmt.expr._mapValueType,
373
+ )
374
+ : `(${base}[${idx}] ?? ${zero}) ${op} 1`;
269
375
  this.line(
270
- `${this._genMapWriteBase(base)}[${idx}] = (${base}[${idx}] ?? ${zero}) ${op} 1;`,
376
+ `${this._genMapWriteBase(base)}[${idx}] = ${rhs};`,
271
377
  srcLine,
272
378
  );
273
379
  }
274
380
  break;
275
381
  }
382
+ const is64 = is64BitInt(stmt.expr._type);
276
383
  if (
277
- this.strict &&
384
+ (this.strict || is64 || this._isFloat32(stmt.expr._type)) &&
278
385
  this._genStrictCompound(
279
386
  stmt.expr,
280
387
  stmt.op === "++" ? "+" : "-",
281
- "1",
388
+ is64 ? "1n" : "1",
282
389
  srcLine,
283
390
  )
284
391
  )
@@ -293,14 +400,7 @@ export const statementGenMethods = {
293
400
  if (stmt.init) {
294
401
  this.line("{");
295
402
  this.indented(() => {
296
- const shadowing = this._definesShadowedRead(stmt.init);
297
- if (shadowing) this._genShadowingDefine(stmt.init);
298
- else this.genStmt(stmt.init);
299
- this._genIf(stmt);
300
- if (shadowing) {
301
- this.indent--;
302
- this.line("}");
303
- }
403
+ this._genScopedInit(stmt.init, () => this._genIf(stmt));
304
404
  });
305
405
  this.line("}");
306
406
  } else {
@@ -308,15 +408,26 @@ export const statementGenMethods = {
308
408
  }
309
409
  break;
310
410
  case "DeferStmt":
311
- this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
411
+ // A deferred native call runs the Go body.
412
+ this._withoutNatives(() =>
413
+ this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`),
414
+ );
312
415
  break;
313
- case "LabeledStmt":
314
- if (stmt.body && stmt.body.kind === "ForStmt") {
315
- stmt.body.label = stmt.label;
416
+ case "LabeledStmt": {
417
+ // Stacked labels (`a: b: for ...`) all stay attached to the loop.
418
+ const labels = [stmt.label];
419
+ let body = stmt.body;
420
+ while (body?.kind === "LabeledStmt") {
421
+ labels.push(body.label);
422
+ body = body.body;
316
423
  }
317
- this.line(`${stmt.label}:`);
318
- this.genStmt(stmt.body);
424
+ if (body?.kind === "ForStmt") body.label = labels[labels.length - 1];
425
+ const hoisted = this._hoistForInit(body);
426
+ for (const label of labels) this.line(`${label}:`);
427
+ this.genStmt(body);
428
+ if (hoisted) this._closeHoistedForInit();
319
429
  break;
430
+ }
320
431
  case "Block":
321
432
  this.line("{");
322
433
  this.indented(() => this.genBlock(stmt));
@@ -695,6 +806,10 @@ export const statementGenMethods = {
695
806
  this._genStableWholeAssign(stmt);
696
807
  return;
697
808
  }
809
+ if (this._isStableTupleAssign(stmt)) {
810
+ this._genStableTupleAssign(stmt);
811
+ return;
812
+ }
698
813
  const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
699
814
  const rhs = stmt.rhs.map((e, i) =>
700
815
  paired && stmt.lhs[i].name !== "_"
@@ -724,29 +839,65 @@ export const statementGenMethods = {
724
839
  : this.genExpr(stmt.lhs[0].expr);
725
840
  const idx = this.genExpr(stmt.lhs[0].index);
726
841
  const binOp = stmt.op.slice(0, -1);
842
+ const is64Val = is64BitInt(stmt.lhs[0]._mapValueType);
843
+ let rhsVal = active[0].r;
844
+ if (
845
+ is64Val &&
846
+ stmt.rhs[0]?._type?.kind === "untyped" &&
847
+ !rhsVal.endsWith("n") &&
848
+ /^-?\d+$/.test(rhsVal)
849
+ ) {
850
+ rhsVal = `${rhsVal}n`;
851
+ }
852
+ const rhsExpr = is64Val
853
+ ? this._gen64BitBinary(
854
+ binOp,
855
+ `(__m[__k] ?? ${zero})`,
856
+ rhsVal,
857
+ stmt.lhs[0]._mapValueType,
858
+ )
859
+ : `(__m[__k] ?? ${zero}) ${binOp === "&^" ? "& ~" : binOp} (${rhsVal})`;
860
+ const rhsExprDirect = is64Val
861
+ ? this._gen64BitBinary(
862
+ binOp,
863
+ `(${base}[${idx}] ?? ${zero})`,
864
+ rhsVal,
865
+ stmt.lhs[0]._mapValueType,
866
+ )
867
+ : `(${base}[${idx}] ?? ${zero}) ${binOp === "&^" ? "& ~" : binOp} (${rhsVal})`;
727
868
  if (
728
869
  this._hasCallExpr(stmt.lhs[0].expr) ||
729
870
  this._hasCallExpr(stmt.lhs[0].index)
730
871
  ) {
731
872
  this.line(
732
- `((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = (__m[__k] ?? ${zero}) ${binOp} (${active[0].r}); })(${base}, ${idx});`,
873
+ `((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = ${rhsExpr}; })(${base}, ${idx});`,
733
874
  );
734
875
  } else {
735
876
  this.line(
736
- `${this._genMapWriteBase(base)}[${idx}] = (${base}[${idx}] ?? ${zero}) ${binOp} (${active[0].r});`,
877
+ `${this._genMapWriteBase(base)}[${idx}] = ${rhsExprDirect};`,
737
878
  );
738
879
  }
739
880
  return;
740
881
  }
882
+ const is64 = is64BitInt(stmt.lhs[0]._type);
883
+ let rhsVal = active[0].r;
741
884
  if (
742
- this.strict &&
743
- stmt.op !== "=" &&
885
+ is64 &&
886
+ stmt.rhs[0]?._type?.kind === "untyped" &&
887
+ !rhsVal.endsWith("n") &&
888
+ /^-?\d+$/.test(rhsVal)
889
+ ) {
890
+ rhsVal = `${rhsVal}n`;
891
+ }
892
+ if (
893
+ this._needsStrictCompoundStmt(stmt, is64) &&
744
894
  this._genStrictCompound(
745
895
  stmt.lhs[0],
746
896
  stmt.op.slice(0, -1),
747
- active[0].r,
897
+ rhsVal,
748
898
  null,
749
899
  active[0].l,
900
+ stmt.rhs[0]?._type,
750
901
  )
751
902
  ) {
752
903
  return;
@@ -755,7 +906,8 @@ export const statementGenMethods = {
755
906
  active[0].l.startsWith("{") && !active[0].l.startsWith("({")
756
907
  ? `(${active[0].l})`
757
908
  : active[0].l;
758
- this.line(`${targetL} ${stmt.op} ${active[0].r};`);
909
+ if (stmt.op === "&^=") this.line(`${targetL} &= ~(${rhsVal});`);
910
+ else this.line(`${targetL} ${stmt.op} ${rhsVal};`);
759
911
  } else if (hoisted) {
760
912
  this.line("{");
761
913
  this.indented(() => {
@@ -786,23 +938,28 @@ export const statementGenMethods = {
786
938
  ((n.kind === "Ident" && assigned.has(n.name)) || someChild(n, mentions));
787
939
  const lines = [];
788
940
  const undo = [];
789
- for (const l of stmt.lhs) {
941
+ const visit = (l) => {
790
942
  let keys;
791
943
  if (l.kind === "IndexExpr") keys = ["expr", "index"];
792
944
  else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
793
- else continue;
945
+ else return;
794
946
  for (const key of keys) {
795
947
  const operand = l[key];
796
948
  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;
949
+ // A struct or array value names the variable's storage in Go: hoist
950
+ // the operands inside it (`ps[n].A`) instead of the value.
951
+ if (key === "expr" && this._aggregateBase(operand._type)) {
952
+ visit(operand);
953
+ continue;
954
+ }
799
955
  this._tmpCounter = (this._tmpCounter ?? 0) + 1;
800
956
  const name = `__h${this._tmpCounter}`;
801
957
  lines.push(`const ${name} = ${this.genExpr(operand)};`);
802
958
  undo.push([l, key, operand]);
803
959
  l[key] = { kind: "Ident", name, _type: operand._type };
804
960
  }
805
- }
961
+ };
962
+ for (const l of stmt.lhs) visit(l);
806
963
  if (lines.length === 0) return null;
807
964
  return {
808
965
  lines,
@@ -814,7 +971,7 @@ export const statementGenMethods = {
814
971
 
815
972
  _genCommaOkTypeAssert(stmt) {
816
973
  const val = this.genExpr(stmt.rhs[0]);
817
- const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
974
+ const [vName, okName] = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
818
975
  this._tmpCounter = (this._tmpCounter ?? 0) + 1;
819
976
  const tmp = `__ta${this._tmpCounter}`;
820
977
  this.line(`const ${tmp} = ${val};`);
@@ -828,7 +985,13 @@ export const statementGenMethods = {
828
985
  _isStableWholeAssign(stmt) {
829
986
  if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
830
987
  return false;
831
- const l = stmt.lhs[0];
988
+ return this._isStableTarget(stmt.lhs[0], stmt.rhs[0]);
989
+ },
990
+
991
+ // `l = rhs` writes into the existing storage of `l` (see above). When the
992
+ // program takes `&xs[i]` anywhere, an element of a slice does too: any
993
+ // callee may hold the pointer.
994
+ _isStableTarget(l, rhs) {
832
995
  if (l.kind === "IndexExpr" && l._mapValueType) return false;
833
996
  if (
834
997
  l.kind !== "Ident" &&
@@ -837,15 +1000,59 @@ export const statementGenMethods = {
837
1000
  )
838
1001
  return false;
839
1002
  if (l.kind === "Ident" && l.name === "_") return false;
840
- if (
841
- !this._aggregateBase(l._type) ||
842
- !this._aggregateBase(stmt.rhs[0]._type)
843
- )
1003
+ if (!this._aggregateBase(l._type) || !this._aggregateBase(rhs._type))
844
1004
  return false;
1005
+ if (this._elemAddrTaken && this._isSliceElemTarget(l)) return true;
845
1006
  const root = this._rootIdentName(l);
846
1007
  return root !== null && this._fnMutates(root, true);
847
1008
  },
848
1009
 
1010
+ // `xs[i]` on a plain slice (not a typed array, array or map).
1011
+ _isSliceElemTarget(l) {
1012
+ if (l.kind !== "IndexExpr" || l._mapValueType) return false;
1013
+ const u =
1014
+ l.expr._type?.kind === "named" ? l.expr._type.underlying : l.expr._type;
1015
+ return u?.kind === "slice" && !isTypedArraySlice(l.expr._type);
1016
+ },
1017
+
1018
+ // `a, b = x, y` where a target is stored in place: every right-hand value
1019
+ // is evaluated (and copied) first, then each target is assigned in order.
1020
+ _isStableTupleAssign(stmt) {
1021
+ return (
1022
+ stmt.op === "=" &&
1023
+ stmt.lhs.length > 1 &&
1024
+ stmt.rhs.length === stmt.lhs.length &&
1025
+ stmt.lhs.some((l, i) => this._isStableTarget(l, stmt.rhs[i]))
1026
+ );
1027
+ },
1028
+
1029
+ _genStableTupleAssign(stmt) {
1030
+ const hoisted = this._hoistLhsOperands(stmt);
1031
+ const stable = stmt.lhs.map((l, i) => this._isStableTarget(l, stmt.rhs[i]));
1032
+ this.line("{");
1033
+ this.indented(() => {
1034
+ for (const l of hoisted?.lines ?? []) this.line(l);
1035
+ const base = (this._tmpCounter ?? 0) + 1;
1036
+ this._tmpCounter = base + stmt.lhs.length;
1037
+ const val = (i) => `__s${base + i}`;
1038
+ const dst = (i) => `__d${base + i}`;
1039
+ stmt.rhs.forEach((r, i) => {
1040
+ this.line(`const ${val(i)} = ${this.genValueExpr(r)};`);
1041
+ });
1042
+ stmt.lhs.forEach((l, i) => {
1043
+ if (stable[i])
1044
+ this.line(`const ${dst(i)} = ${this._genAssignLhsExpr(l)};`);
1045
+ });
1046
+ stmt.lhs.forEach((l, i) => {
1047
+ if (stable[i]) this._emitCopyInto(dst(i), val(i), l._type);
1048
+ else if (!(l.kind === "Ident" && l.name === "_"))
1049
+ this.line(`${this._genAssignLhsExpr(l)} = ${val(i)};`);
1050
+ });
1051
+ });
1052
+ this.line("}");
1053
+ hoisted?.restore();
1054
+ },
1055
+
849
1056
  _genStableWholeAssign(stmt) {
850
1057
  let target = this._genAssignLhsExpr(stmt.lhs[0]);
851
1058
  const type = stmt.lhs[0]._type;
@@ -1063,7 +1270,12 @@ export const statementGenMethods = {
1063
1270
  this.line("}");
1064
1271
  return;
1065
1272
  }
1066
- if (elseBody.kind === "IfStmt") {
1273
+ if (elseBody.kind === "IfStmt" && elseBody.init) {
1274
+ // `else if init; cond`: the init runs only when this branch is taken.
1275
+ this.line("} else {");
1276
+ this.indented(() => this.genStmt(elseBody));
1277
+ this.line("}");
1278
+ } else if (elseBody.kind === "IfStmt") {
1067
1279
  this.line(`} else if (${this.genExpr(elseBody.cond)}) {`);
1068
1280
  this.indented(() => this.genBlock(elseBody.body));
1069
1281
  this._genElse(elseBody.elseBody);
@@ -1076,7 +1288,8 @@ export const statementGenMethods = {
1076
1288
 
1077
1289
  _rangeExprLimit(iterType, iteree) {
1078
1290
  if (this._isIntRangeType(iterType)) return iteree;
1079
- if (this._isMapRangeType(iterType)) return `Object.keys(${iteree}).length`;
1291
+ if (this._isMapRangeType(iterType))
1292
+ return `Object.keys(${iteree} ?? {}).length`;
1080
1293
  return `(${iteree} ? ${iteree}.length : 0)`;
1081
1294
  },
1082
1295
 
@@ -1085,7 +1298,7 @@ export const statementGenMethods = {
1085
1298
  this.genIteratorForCond(stmt);
1086
1299
  return;
1087
1300
  }
1088
- const iterType = stmt.cond.expr._type;
1301
+ const iterType = stmt.cond.expr._coreType ?? stmt.cond.expr._type;
1089
1302
  const iteree = this.genExpr(stmt.cond.expr);
1090
1303
  const limit = this._rangeExprLimit(iterType, iteree);
1091
1304
  this.line(`for (let _$ = 0; _$ < ${limit}; _$++) {`);
@@ -1103,7 +1316,47 @@ export const statementGenMethods = {
1103
1316
  this.line("}");
1104
1317
  },
1105
1318
 
1319
+ // `for n := n; ...` declares a loop variable its own initialiser reads: the
1320
+ // initialiser values are evaluated into temporaries ahead of the loop.
1321
+ _hoistForInit(stmt) {
1322
+ const init = stmt.kind === "ForStmt" && stmt.init;
1323
+ if (
1324
+ !init ||
1325
+ stmt._initPre ||
1326
+ init.kind !== "DefineStmt" ||
1327
+ this.isRangeFor(stmt) ||
1328
+ stmt.cond?.kind === "RangeExpr" ||
1329
+ !this._definesShadowedRead(init)
1330
+ )
1331
+ return false;
1332
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
1333
+ const values = this._defineRhs(init);
1334
+ const names = values.map((_, i) => `__fi${this._tmpCounter}_${i}`);
1335
+ this.line("{");
1336
+ this.indent++;
1337
+ this.line(
1338
+ `const ${values.map((v, i) => `${names[i]} = ${v}`).join(", ")};`,
1339
+ );
1340
+ stmt._initPre = names;
1341
+ return true;
1342
+ },
1343
+
1344
+ _closeHoistedForInit() {
1345
+ this.indent--;
1346
+ this.line("}");
1347
+ },
1348
+
1106
1349
  genFor(stmt) {
1350
+ const hoisted = this._hoistForInit(stmt);
1351
+ try {
1352
+ this._genForLoop(stmt);
1353
+ } finally {
1354
+ delete stmt._initPre;
1355
+ }
1356
+ if (hoisted) this._closeHoistedForInit();
1357
+ },
1358
+
1359
+ _genForLoop(stmt) {
1107
1360
  const isInner = this._inIteratorBody;
1108
1361
  if (isInner) {
1109
1362
  this._iterLoopDepth = (this._iterLoopDepth ?? 0) + 1;
@@ -1126,7 +1379,7 @@ export const statementGenMethods = {
1126
1379
  this._genSimpleWhileFor(stmt);
1127
1380
  return;
1128
1381
  }
1129
- const init = stmt.init ? this.stmtInline(stmt.init) : "";
1382
+ const init = stmt.init ? this.stmtInline(stmt.init, stmt._initPre) : "";
1130
1383
  const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
1131
1384
  const post = this._perIterationClones(
1132
1385
  stmt,
@@ -1217,10 +1470,14 @@ export const statementGenMethods = {
1217
1470
  const iterType = iterNode._type;
1218
1471
  const base = iterType?.kind === "named" ? iterType.underlying : iterType;
1219
1472
  const elemType = base?.elem;
1220
- if (!valNode || !this._aggregateBase(elemType)) return js;
1221
- if (isAssign || valNode.kind !== "Ident")
1473
+ if (
1474
+ !valNode ||
1475
+ (!this._aggregateBase(elemType) && !this._typeParamClone(elemType, js))
1476
+ )
1477
+ return js;
1478
+ if (valNode.kind === "Ident" && valNode.name === "_") return js;
1479
+ if (isAssign || valNode.kind !== "Ident" || elemType?.kind === "typeParam")
1222
1480
  return this._cloneJs(elemType, js);
1223
- if (valNode.name === "_") return js;
1224
1481
  const root = this._rootIdentName(iterNode);
1225
1482
  const mutated =
1226
1483
  this._fnMutates(valNode.name) ||
@@ -1311,11 +1568,6 @@ export const statementGenMethods = {
1311
1568
  this._loopDepth--;
1312
1569
  },
1313
1570
 
1314
- // True when `node` contains an assignment or ++/-- targeting identifier `name`.
1315
- _nodeAssigns(node, name) {
1316
- return nodeAssigns(node, name);
1317
- },
1318
-
1319
1571
  _genStringRangeFor(stmt, iteree, isAssign) {
1320
1572
  this._loopDepth = this._loopDepth ?? 0;
1321
1573
  const d = this._loopDepth++;
@@ -1323,13 +1575,17 @@ export const statementGenMethods = {
1323
1575
  const s = `__s${d}`;
1324
1576
  const i = `__i${d}`;
1325
1577
  const cp = `__cp${d}`;
1578
+ const w = `__w${d}`;
1579
+ // The index is a UTF-16 offset (an astral rune is two units) so `s[i:]`
1580
+ // agrees with the WebAssembly string layout.
1326
1581
  this.line(
1327
- `for (let ${i} = 0, ${s} = Array.from(${iteree} ?? ""); ${i} < ${s}.length; ${i}++) {`,
1582
+ `for (let ${i} = 0, ${s} = ${iteree} ?? "", ${w} = 0; ${i} < ${s}.length; ${i} += ${w}) {`,
1328
1583
  );
1329
1584
  this.indented(() => {
1585
+ this.line(`const ${cp} = ${s}.codePointAt(${i});`);
1586
+ this.line(`${w} = ${cp} > 0xffff ? 2 : 1;`);
1330
1587
  this._emitRangeVar(idxNode, i, isAssign);
1331
1588
  if (valNode && valNode.name !== "_") {
1332
- this.line(`const ${cp} = ${s}[${i}].codePointAt(0);`);
1333
1589
  this._emitRangeVar(valNode, cp, isAssign);
1334
1590
  }
1335
1591
  const declared = isAssign
@@ -1352,23 +1608,73 @@ export const statementGenMethods = {
1352
1608
  _genIterFor(init, iterType, iteree, lhs, body) {
1353
1609
  const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
1354
1610
  const keyDecode = this._structKeyRange(iterType);
1611
+ const paramKey = this._typeParamKeyRange(iterType);
1612
+ const is64Key = this._is64BitMapRange(iterType);
1613
+ const scalarKey = this._scalarKeyRange(iterType);
1614
+ const isAssign = init.kind === "AssignStmt";
1615
+ // `for _, v = range m` assigns existing variables, where a blank is a
1616
+ // hole in the destructuring pattern; a lone blank still needs a binding.
1617
+ const blank = isAssign && lhs.length > 1 ? "" : "_$";
1355
1618
  const binding =
1356
1619
  lhs.length === 1
1357
1620
  ? lhs[0] === "_"
1358
1621
  ? "_$"
1359
1622
  : lhs[0]
1360
- : `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
1361
- const kw = init.kind === "AssignStmt" ? "" : "let ";
1623
+ : `[${lhs.map((n) => (n === "_" ? blank : n)).join(", ")}]`;
1624
+ const kw = !isAssign || binding === "_$" ? "let " : "";
1362
1625
  this.line(`for (${kw}${binding} of ${iterExpr}) {`);
1363
1626
  this.indented(() => {
1364
- if (keyDecode && lhs[0] !== "_")
1627
+ if (paramKey && lhs[0] !== "_")
1628
+ this.line(`${lhs[0]} = __kdt(${paramKey}, ${lhs[0]});`);
1629
+ else if (keyDecode && lhs[0] !== "_")
1365
1630
  this.line(`${lhs[0]} = __kd(${lhs[0]}, true);`);
1631
+ else if (is64Key && lhs[0] !== "_")
1632
+ this.line(`${lhs[0]} = BigInt(${lhs[0]});`);
1633
+ else if (scalarKey && lhs[0] !== "_")
1634
+ this.line(
1635
+ scalarKey === "bool"
1636
+ ? `${lhs[0]} = ${lhs[0]} === "true";`
1637
+ : `${lhs[0]} = Number(${lhs[0]});`,
1638
+ );
1366
1639
  this._cloneMapRangeValue(init, iterType, lhs[1]);
1367
1640
  this.genBlock(body);
1368
1641
  });
1369
1642
  this.line("}");
1370
1643
  },
1371
1644
 
1645
+ _is64BitMapRange(iterType) {
1646
+ if (!this._isMapRangeType(iterType)) return false;
1647
+ const base = iterType?.kind === "named" ? iterType.underlying : iterType;
1648
+ const key = base?.kind === "map" ? base.key : null;
1649
+ return is64BitInt(key);
1650
+ },
1651
+
1652
+ // Ranging over a map with plain int, float or bool keys: object property
1653
+ // names are strings, so the key variable is converted back.
1654
+ _scalarKeyRange(iterType) {
1655
+ if (!this._isMapRangeType(iterType)) return null;
1656
+ const base = iterType?.kind === "named" ? iterType.underlying : iterType;
1657
+ const key = base?.kind === "map" ? base.key : null;
1658
+ if (!key || key.kind === "typeParam") return null;
1659
+ const u = this._underlyingOf(key);
1660
+ if (u?.kind !== "basic" || this._is64BitInt(key)) return null;
1661
+ if (u.name === "bool") return "bool";
1662
+ return /^(u?int\d*|uintptr|float(32|64))$/.test(u.name) ? "num" : null;
1663
+ },
1664
+
1665
+ // Ranging over a map whose key type is a type parameter: the key is decoded
1666
+ // from its property string by the instantiated key type's zero factory.
1667
+ _typeParamKeyRange(iterType) {
1668
+ if (!this._isMapRangeType(iterType)) return null;
1669
+ const base = iterType?.kind === "named" ? iterType.underlying : iterType;
1670
+ const key = base?.kind === "map" ? base.key : null;
1671
+ if (key?.kind !== "typeParam") return null;
1672
+ const factory = this._typeParamZeroFactory(key.name);
1673
+ if (!factory) return null;
1674
+ this.useHelper("structKey");
1675
+ return factory;
1676
+ },
1677
+
1372
1678
  // Ranging over a map whose keys are structs: the key variable is decoded
1373
1679
  // from its encoded string (see HELPER_STRUCT_KEY).
1374
1680
  _structKeyRange(iterType) {
@@ -1376,7 +1682,13 @@ export const statementGenMethods = {
1376
1682
  const base = iterType?.kind === "named" ? iterType.underlying : iterType;
1377
1683
  const key = base?.kind === "map" ? base.key : null;
1378
1684
  const keyBase = key?.kind === "named" ? key.underlying : key;
1379
- if (keyBase?.kind !== "struct" && !this._isIfaceType(key)) return false;
1685
+ if (
1686
+ keyBase?.kind !== "struct" &&
1687
+ keyBase?.kind !== "array" &&
1688
+ keyBase?.kind !== "pointer" &&
1689
+ !this._isIfaceType(key)
1690
+ )
1691
+ return false;
1380
1692
  this.useHelper("structKey");
1381
1693
  return true;
1382
1694
  },
@@ -1396,7 +1708,7 @@ export const statementGenMethods = {
1396
1708
  const init = stmt.init;
1397
1709
  const range = init.rhs[0];
1398
1710
  const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
1399
- const iterType = range.expr._type;
1711
+ const iterType = range.expr._coreType ?? range.expr._type;
1400
1712
  const wrapField = this._namedWrapperField(iterType, range.expr);
1401
1713
  const iteree = wrapField
1402
1714
  ? `${this.genExpr(range.expr)}.${wrapField}`
@@ -1431,9 +1743,22 @@ export const statementGenMethods = {
1431
1743
  !!valNode &&
1432
1744
  iterBase?.kind === "slice" &&
1433
1745
  !isTypedArraySlice(iterType);
1746
+ // Ranging over an array for its indexes only does not evaluate the
1747
+ // array (`range p` on a nil array pointer is valid): the length is the
1748
+ // type's.
1749
+ const arrayLen = this._aggregateBase(iterType)?.size;
1750
+ const indexOnlyArray =
1751
+ (!valNode || valNode.name === "_") &&
1752
+ typeof arrayLen === "number" &&
1753
+ this._aggregateBase(iterType)?.kind === "array" &&
1754
+ !hasCallOrReceive(range.expr);
1434
1755
  this._genSliceRangeFor(
1435
1756
  stmt,
1436
- rangesArrayValue ? this._cloneJs(iterType, iteree) : iteree,
1757
+ indexOnlyArray
1758
+ ? `({ length: ${arrayLen} })`
1759
+ : rangesArrayValue
1760
+ ? this._cloneJs(iterType, iteree)
1761
+ : iteree,
1437
1762
  init.kind === "AssignStmt",
1438
1763
  unwrapView,
1439
1764
  );
@@ -1441,8 +1766,9 @@ export const statementGenMethods = {
1441
1766
 
1442
1767
  _genRangeIterExpr(iterType, iteree, lhs) {
1443
1768
  if (this._isMapRangeType(iterType)) {
1444
- if (lhs.length === 1) return `Object.keys(${iteree})`;
1445
- return `Object.entries(${iteree})`;
1769
+ // A nil map is `null`; ranging over it runs zero iterations.
1770
+ if (lhs.length === 1) return `Object.keys(${iteree} ?? {})`;
1771
+ return `Object.entries(${iteree} ?? {})`;
1446
1772
  }
1447
1773
  this.useHelper("sliceGuard");
1448
1774
  if (lhs.length === 1) return `__s(${iteree}).keys()`;
@@ -1610,10 +1936,10 @@ export const statementGenMethods = {
1610
1936
  },
1611
1937
 
1612
1938
  // Inline a statement as a for-init/post string (no semicolon)
1613
- stmtInline(stmt) {
1939
+ stmtInline(stmt, pre = null) {
1614
1940
  switch (stmt.kind) {
1615
1941
  case "DefineStmt":
1616
- return this._inlineDefine(stmt);
1942
+ return this._inlineDefine(stmt, pre);
1617
1943
  case "AssignStmt":
1618
1944
  return this._withInlineSyncs(
1619
1945
  this._resliceSyncs(stmt.lhs, stmt, true),
@@ -1623,6 +1949,14 @@ export const statementGenMethods = {
1623
1949
  const syncs = this._resliceSyncs([stmt.expr], stmt, true);
1624
1950
  this._markIndexLvalues([stmt.expr]);
1625
1951
  this._markDerefOpTarget(stmt.expr);
1952
+ if (is64BitInt(stmt.expr._type)) {
1953
+ const l = this.genExpr(stmt.expr);
1954
+ const binOp = stmt.op === "++" ? "+" : "-";
1955
+ return this._withInlineSyncs(
1956
+ syncs,
1957
+ `${l} = ${this._gen64BitBinary(binOp, l, "1n", stmt.expr._type)}`,
1958
+ );
1959
+ }
1626
1960
  return this._withInlineSyncs(
1627
1961
  syncs,
1628
1962
  `${this.genExpr(stmt.expr)}${stmt.op}`,
@@ -1639,14 +1973,14 @@ export const statementGenMethods = {
1639
1973
  return syncs ? `${js}, ${syncs.join(", ")}` : js;
1640
1974
  },
1641
1975
 
1642
- _inlineDefine(stmt) {
1976
+ _inlineDefine(stmt, pre = null) {
1643
1977
  const boxed =
1644
1978
  stmt.lhs.length === 1 &&
1645
1979
  stmt.rhs.length === 1 &&
1646
1980
  this._boxedVars.has(stmt.lhs[0].name);
1647
1981
  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));
1982
+ return `let ${stmt.lhs[0].name} = { value: ${pre ? pre[0] : this.genValueExpr(stmt.rhs[0])} }`;
1983
+ const rhs = pre ?? stmt.rhs.map((e) => this.genExpr(e));
1650
1984
  const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
1651
1985
  if (lhs.length === 1) return `let ${lhs[0]} = ${rhs[0]}`;
1652
1986
  // `for i, j := 0, 10`: one declarator per variable.
@@ -1661,11 +1995,62 @@ export const statementGenMethods = {
1661
1995
  return `let [${lhs.map((n) => (n === "_" ? "" : n)).join(", ")}] = ${rhs[0]}`;
1662
1996
  },
1663
1997
 
1998
+ // Compound assignments that need more than the JS operator: strict
1999
+ // numeric wrapping, 64-bit and float32 arithmetic, and integer `/=`.
2000
+ _needsStrictCompoundStmt(stmt, is64) {
2001
+ if (stmt.op === "=") return false;
2002
+ const t = stmt.lhs[0]._type;
2003
+ return (
2004
+ this.strict ||
2005
+ is64 ||
2006
+ this._isFloat32(t) ||
2007
+ (stmt.op === "/=" && this.isIntType(t))
2008
+ );
2009
+ },
2010
+
2011
+ // The single-expression form of `_genStrictCompound` for `for` post
2012
+ // statements: the emitted statement is lifted back out of the output.
2013
+ _inlineStrictCompound(stmt, rhsJs) {
2014
+ const mark = this.out.length;
2015
+ const handled = this._genStrictCompound(
2016
+ stmt.lhs[0],
2017
+ stmt.op.slice(0, -1),
2018
+ rhsJs,
2019
+ null,
2020
+ null,
2021
+ stmt.rhs[0]?._type,
2022
+ );
2023
+ const emitted = this.out.splice(mark).map((l) => l.trim());
2024
+ if (!handled) return null;
2025
+ const js = emitted.join(" ").replace(/;$/, "");
2026
+ return js.startsWith("{") ? `(() => ${js})()` : js;
2027
+ },
2028
+
1664
2029
  _inlineAssign(stmt) {
1665
2030
  this._markIndexLvalues(stmt.lhs);
1666
2031
  const rhs = stmt.rhs.map((e) => this.genExpr(e));
1667
- if (stmt.lhs.length === 1)
2032
+ if (stmt.lhs.length === 1) {
2033
+ if (stmt.op !== "=" && is64BitInt(stmt.lhs[0]._type)) {
2034
+ const binOp = stmt.op.slice(0, -1);
2035
+ let r = rhs[0];
2036
+ if (
2037
+ stmt.rhs[0]?._type?.kind === "untyped" &&
2038
+ !r.endsWith("n") &&
2039
+ /^-?\d+$/.test(r)
2040
+ ) {
2041
+ r = `${r}n`;
2042
+ }
2043
+ const l = this.genExpr(stmt.lhs[0]);
2044
+ return `${l} = ${this._gen64BitBinary(binOp, l, r, stmt.lhs[0]._type)}`;
2045
+ }
2046
+ if (this._needsStrictCompoundStmt(stmt, is64BitInt(stmt.lhs[0]._type))) {
2047
+ const strict = this._inlineStrictCompound(stmt, rhs[0]);
2048
+ if (strict) return strict;
2049
+ }
2050
+ if (stmt.op === "&^=")
2051
+ return `${this.genExpr(stmt.lhs[0])} &= ~(${rhs[0]})`;
1668
2052
  return `${this.genExpr(stmt.lhs[0])} ${stmt.op} ${rhs[0]}`;
2053
+ }
1669
2054
  // `i, j = i+1, j-1`: every right-hand side is evaluated first.
1670
2055
  const lhs = stmt.lhs.map((e) =>
1671
2056
  e.name === "_" ? "" : this._genAssignLhsExpr(e),
@@ -1677,6 +2062,17 @@ export const statementGenMethods = {
1677
2062
  // Interface-typed tags may hold boxed named values; cases compare with __ifeq.
1678
2063
  _switchTagAndCases(stmt) {
1679
2064
  const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
2065
+ if (stmt.tag && this._isStructOrArrayType(stmt.tag._type)) {
2066
+ // Struct and array tags compare by value, not by reference.
2067
+ this.useHelper("equal");
2068
+ this._swCounter = (this._swCounter ?? 0) + 1;
2069
+ const tmp = `__sw${this._swCounter}`;
2070
+ this.line(`const ${tmp} = ${tag};`);
2071
+ return {
2072
+ tag: "true",
2073
+ caseExpr: (e) => `__equal(${tmp}, ${this.genExpr(e)})`,
2074
+ };
2075
+ }
1680
2076
  if (!stmt.tag || !this._isIfaceType(stmt.tag._type))
1681
2077
  return { tag, caseExpr: (e) => this.genExpr(e) };
1682
2078
  this.useHelper("ifeq");
@@ -1704,7 +2100,7 @@ export const statementGenMethods = {
1704
2100
  // across sibling cases (JS switch shares one block scope).
1705
2101
  this.line("{");
1706
2102
  this.indented(() => {
1707
- for (const s of c.stmts) this.genStmt(s);
2103
+ this.genBlock(c);
1708
2104
  // Go doesn't fall through by default — add break unless last stmt is return/break
1709
2105
  const last = c.stmts[c.stmts.length - 1];
1710
2106
  if (
@@ -1723,10 +2119,18 @@ export const statementGenMethods = {
1723
2119
  if (stmt.init) {
1724
2120
  this.line("{");
1725
2121
  this.indented(() => {
1726
- this.genStmt(stmt.init);
1727
- genBody();
2122
+ this._genScopedInit(stmt.init, genBody);
1728
2123
  });
1729
2124
  this.line("}");
2125
+ } else if (
2126
+ stmt.tag &&
2127
+ (this._isStructOrArrayType(stmt.tag._type) ||
2128
+ this._isIfaceType(stmt.tag._type))
2129
+ ) {
2130
+ // The tag temporary is a `const`, which cannot directly follow a label.
2131
+ this.line("{");
2132
+ this.indented(genBody);
2133
+ this.line("}");
1730
2134
  } else {
1731
2135
  genBody();
1732
2136
  }
@@ -1737,7 +2141,13 @@ export const statementGenMethods = {
1737
2141
  this.indented(() => {
1738
2142
  const val = this.genExpr(stmt.expr);
1739
2143
  this.line(`const __tsw = ${val};`);
1740
- if (stmt.assign) this.line(`let ${stmt.assign} = __tsw;`);
2144
+ // The binding is declared per clause, after the switch expression
2145
+ // was evaluated (`switch x := any(x).(type)` reads the outer `x`).
2146
+ const boxedBind = stmt.assign && this._boxedVars.has(stmt.assign);
2147
+ const bind = (valueJs) =>
2148
+ this.line(
2149
+ `let ${stmt.assign} = ${boxedBind ? `{ value: ${valueJs} }` : valueJs};`,
2150
+ );
1741
2151
 
1742
2152
  let first = true;
1743
2153
  let hasDefault = false;
@@ -1752,24 +2162,16 @@ export const statementGenMethods = {
1752
2162
  .join(" || ");
1753
2163
  this.line(`${first ? "if" : "else if"} (${cond}) {`);
1754
2164
  this.indented(() => {
1755
- // A single named non-struct case binds the unboxed value.
1756
- if (
1757
- stmt.assign &&
1758
- c.types.length === 1 &&
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"
1769
- )
1770
- this.line(
1771
- `let ${stmt.assign} = ${this._cloneJs(this._typeFromNode(c.types[0]), "__tsw")};`,
1772
- );
2165
+ if (stmt.assign) {
2166
+ const only = c.types.length === 1 ? c.types[0] : null;
2167
+ // A single named non-struct case binds the unboxed value.
2168
+ if (only && this._assertedValueJs(only, "__tsw") !== "__tsw")
2169
+ bind(this._assertedValueJs(only, "__tsw"));
2170
+ // An array case binds a copy of the boxed value.
2171
+ else if (only && this._typeFromNode(only)?.kind === "array")
2172
+ bind(this._cloneJs(this._typeFromNode(only), "__tsw"));
2173
+ else bind("__tsw");
2174
+ }
1773
2175
  for (const s of c.stmts) this.genStmt(s);
1774
2176
  });
1775
2177
  this.line("}");
@@ -1778,6 +2180,7 @@ export const statementGenMethods = {
1778
2180
  if (hasDefault) {
1779
2181
  this.line(first ? "{" : "else {");
1780
2182
  this.indented(() => {
2183
+ if (stmt.assign) bind("__tsw");
1781
2184
  for (const s of hasDefault.stmts) this.genStmt(s);
1782
2185
  });
1783
2186
  this.line("}");