gofront 1.8.0 → 1.9.1

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 (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  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 +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  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 +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -9,7 +9,7 @@ import {
9
9
  isIteratorRangeFor,
10
10
  isRangeFor,
11
11
  } from "../../lower/range.js";
12
- import { someChild } from "../../lower/walk.js";
12
+ import { hasCallOrReceive, someChild } from "../../lower/walk.js";
13
13
  import {
14
14
  getMapKeyValTypes,
15
15
  isArrayType,
@@ -24,6 +24,7 @@ import {
24
24
  isSliceType,
25
25
  isStringType,
26
26
  isStructType,
27
+ isTaggedSliceType,
27
28
  } from "./types.js";
28
29
 
29
30
  function hasReturnInBlock(n) {
@@ -61,6 +62,15 @@ export class StmtsEmitter {
61
62
  const names = spec.names ?? (spec.name ? [spec.name] : []);
62
63
  const values = spec.value ?? (spec.init ? [spec.init] : []);
63
64
 
65
+ // `var a, b = f()` / `var v, ok = m[k]` bind like `a, b := ...`.
66
+ if (names.length > 1 && values.length === 1) {
67
+ this.emitDefineStmt({
68
+ lhs: names.map((name) => ({ kind: "Ident", name })),
69
+ rhs: values,
70
+ });
71
+ continue;
72
+ }
73
+
64
74
  for (let i = 0; i < names.length; i++) {
65
75
  const name = names[i];
66
76
  const rawType = spec.type ?? values[i]?._type;
@@ -301,21 +311,24 @@ export class StmtsEmitter {
301
311
  if (assigned.size === 0) return null;
302
312
  const undo = [];
303
313
  const names = [];
304
- for (const l of lhsNodes) {
314
+ const visit = (l) => {
305
315
  let keys;
306
316
  if (l.kind === "IndexExpr") keys = ["expr", "index"];
307
317
  else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
308
- else continue;
318
+ else return;
309
319
  for (const key of keys) {
310
320
  const operand = l[key];
311
321
  if (!this._mentions(operand, assigned)) continue;
312
322
  const goType = operand._type ?? this._resolveExprGoType(operand);
313
- // A struct or array value names the variable's storage in Go.
323
+ // A struct or array value names the variable's storage in Go: hoist
324
+ // the operands inside it (`ps[n].A`) instead of the value.
314
325
  if (
315
326
  key === "expr" &&
316
327
  (this._isValueStruct(goType) || this._isValueArray(goType))
317
- )
328
+ ) {
329
+ visit(operand);
318
330
  continue;
331
+ }
319
332
  const wType = this.toWasmType(goType);
320
333
  const name = `__lhs_tmp_${this.tempCounter++}`;
321
334
  const idx = this.allocLocal(name, wType, goType, true);
@@ -325,7 +338,8 @@ export class StmtsEmitter {
325
338
  names.push(name);
326
339
  l[key] = { kind: "Ident", name, _type: goType, _parTmp: true };
327
340
  }
328
- }
341
+ };
342
+ for (const l of lhsNodes) visit(l);
329
343
  if (undo.length === 0) return null;
330
344
  return () => {
331
345
  for (const [node, key, v] of undo) node[key] = v;
@@ -664,6 +678,10 @@ export class StmtsEmitter {
664
678
  // A whole struct or array stored into an element whose address is
665
679
  // taken writes into the existing element, so `p := &xs[0]` stays valid.
666
680
  if (this._needsStableStore(l, elemGoType)) {
681
+ if (sliceInfo) {
682
+ this._emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo);
683
+ return;
684
+ }
667
685
  this._emitStableStore(elemGoType, r, () => {
668
686
  this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
669
687
  this.pushInstruction({
@@ -702,7 +720,95 @@ export class StmtsEmitter {
702
720
  return idx;
703
721
  }
704
722
 
723
+ // Stores the value `emitValue` pushes into the variable `name` that a range
724
+ // assignment (`for k, v = range m`) targets: a plain local, a boxed local
725
+ // (its address is taken or a closure mutates it), or a global. When the
726
+ // target is an interface, `valueGoType` is the Go type of the ranged
727
+ // element and the value is boxed into the interface representation;
728
+ // `emitValue` is then called with the wasm type of `valueGoType`.
729
+ _emitRangeAssignStore(name, info, emitValue, valueGoType = null) {
730
+ const local = this.resolveLocal(name);
731
+ const boxIface =
732
+ valueGoType && this._rangeStoreWType(name, info) === "anyref";
733
+ const pushValue = () => {
734
+ if (boxIface) this._emitRangeValueToAny(emitValue, valueGoType);
735
+ else emitValue();
736
+ };
737
+ if (!local) {
738
+ pushValue();
739
+ this.pushInstruction({ op: "global.set", index: info.index });
740
+ return;
741
+ }
742
+ if (local.isBoxed) {
743
+ this.pushInstruction({ op: "local.get", index: local.index });
744
+ pushValue();
745
+ this.pushInstruction({
746
+ op: "struct.set",
747
+ typeIndex: local.boxInfo.typeIndex,
748
+ fieldIndex: 0,
749
+ });
750
+ return;
751
+ }
752
+ pushValue();
753
+ this.pushInstruction({ op: "local.set", index: info.index });
754
+ }
755
+
756
+ // Pushes a ranged element of Go type `goType` as an interface value.
757
+ // `emitValue(wType)` pushes the element in the wasm type of `goType`; an
758
+ // element that is already an interface needs no boxing.
759
+ _emitRangeValueToAny(emitValue, goType) {
760
+ const wType = this.toWasmType(goType);
761
+ if (wType === "anyref") {
762
+ emitValue(wType);
763
+ return;
764
+ }
765
+ const name = `__rangeval$${this.tempCounter++}`;
766
+ const idx = this.allocLocal(name, wType, goType, true);
767
+ emitValue(wType);
768
+ this.pushInstruction({ op: "local.set", index: idx });
769
+ this.emitExpr(
770
+ { kind: "Ident", name, _type: goType, _parTmp: true },
771
+ "anyref",
772
+ );
773
+ this.locals.delete(name);
774
+ }
775
+
776
+ // The wasm type of the value a range assignment stores into `info`: the
777
+ // box's field type when the target local is boxed.
778
+ _rangeStoreWType(name, info) {
779
+ const local = this.resolveLocal(name);
780
+ return local?.isBoxed ? local.boxInfo.wType : info.type;
781
+ }
782
+
783
+ // `for _, out[0] = range xs`: an index, selector or dereference target is
784
+ // not a variable the loop can store into directly. Each such target gets
785
+ // a hidden local that the loop writes (so conversions and interface
786
+ // boxing work as for a variable), and `_emitRangeBoxes` assigns it to the
787
+ // real target at the top of the body. Returns `lhs` with those targets
788
+ // replaced by identifiers of the hidden locals.
789
+ _bindRangeAssignTargets(lhs) {
790
+ return lhs.map((node) => {
791
+ if (!node || node.kind === "Ident" || !node._type) return node;
792
+ const name = `__rangetarget$${this.tempCounter++}`;
793
+ const wType = this.toWasmType(node._type);
794
+ this.allocLocal(name, wType, node._type, true);
795
+ if (!this._pendingRangeTargets) this._pendingRangeTargets = [];
796
+ this._pendingRangeTargets.push({ node, name, goType: node._type });
797
+ return { kind: "Ident", name, _type: node._type };
798
+ });
799
+ }
800
+
705
801
  _emitRangeBoxes() {
802
+ const targets = this._pendingRangeTargets ?? [];
803
+ this._pendingRangeTargets = [];
804
+ for (const { node, name, goType } of targets) {
805
+ this._emitAssignSingle(
806
+ node,
807
+ { kind: "Ident", name, _type: goType, _parTmp: true },
808
+ "=",
809
+ );
810
+ this.locals.delete(name);
811
+ }
706
812
  const pending = this._pendingRangeBoxes ?? [];
707
813
  this._pendingRangeBoxes = [];
708
814
  for (const { name, idx, wType, goType } of pending) {
@@ -823,6 +929,10 @@ export class StmtsEmitter {
823
929
  )
824
930
  )
825
931
  return false;
932
+ // Any callee may hold `&xs[i]` of a slice element, so when the program
933
+ // takes one, a whole-value store into an element writes in place.
934
+ if (this.mod.lowerResult?.elemAddrTaken && this._isSliceElemTarget(l))
935
+ return true;
826
936
  const root = rootIdentName(l);
827
937
  if (!root) return false;
828
938
  // Any code may hold the address of a package-level variable.
@@ -832,6 +942,45 @@ export class StmtsEmitter {
832
942
  return Boolean(ctx?.fnMutates(root, true));
833
943
  }
834
944
 
945
+ // `xs[i] = v` into a slice element in place. Capacity beyond the length
946
+ // may hold null elements (a reslice exposes them), so a null element is
947
+ // first replaced by a zero value that the store then fills.
948
+ _emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo) {
949
+ const arrT = {
950
+ kind: "ref",
951
+ nullable: true,
952
+ typeIndex: arrInfo.typeIndex,
953
+ };
954
+ const arr = this.acquireTemp(arrT);
955
+ const idx = this.acquireTemp("i32");
956
+ this._emitElemAddr(l.expr, l.index, sliceInfo, arrInfo);
957
+ this.pushInstruction({ op: "local.set", index: idx });
958
+ this.pushInstruction({ op: "local.set", index: arr });
959
+ const getElem = () => {
960
+ this.pushInstruction({ op: "local.get", index: arr });
961
+ this.pushInstruction({ op: "local.get", index: idx });
962
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
963
+ };
964
+ getElem();
965
+ this.pushInstruction("ref.is_null");
966
+ this.pushInstruction({ op: "if", blockType: "void" });
967
+ this.pushInstruction({ op: "local.get", index: arr });
968
+ this.pushInstruction({ op: "local.get", index: idx });
969
+ this.emitZeroValue(elemGoType, arrInfo.elemWType);
970
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
971
+ this.pushInstruction("end");
972
+ this._emitStableStore(elemGoType, r, getElem);
973
+ this.releaseTemp(idx, "i32");
974
+ this.releaseTemp(arr, arrT);
975
+ }
976
+
977
+ // True when `l` is `xs[i]` on a slice (not an array or map).
978
+ _isSliceElemTarget(l) {
979
+ if (l.kind !== "IndexExpr") return false;
980
+ const t = l.expr._type ?? this._resolveExprGoType(l.expr);
981
+ return isSliceType(t, this.mod.checker) && !this._sharedInfo(t);
982
+ }
983
+
835
984
  _emitStableStore(goType, r, emitDst) {
836
985
  if (this._isValueArray(goType)) {
837
986
  this._emitArrayStoreInto(goType, r, emitDst);
@@ -921,6 +1070,7 @@ export class StmtsEmitter {
921
1070
  this.pushInstruction({ op: "local.set", index: tmpL });
922
1071
  this.emitExpr(r, baseWType);
923
1072
  this.pushInstruction({ op: "local.set", index: tmpR });
1073
+ this._emitNilGuard(tmpL, operand._type);
924
1074
  for (let i = 0; i < structInfo.fields.length; i++) {
925
1075
  this.pushInstruction({ op: "local.get", index: tmpL });
926
1076
  this.pushInstruction({ op: "local.get", index: tmpR });
@@ -939,6 +1089,16 @@ export class StmtsEmitter {
939
1089
  this.releaseTemp(tmpL, baseWType);
940
1090
  }
941
1091
 
1092
+ // Raises Go's nil-dereference panic when the pointer held in `tmp` is nil
1093
+ // (a plain cast or field write would trap, which `recover` cannot see).
1094
+ _emitNilGuard(tmp, goType) {
1095
+ if (goType?.kind !== "pointer") return;
1096
+ if (this.resolveBranchDepthToRole("nil") == null) return;
1097
+ this.pushInstruction({ op: "local.get", index: tmp });
1098
+ this._emitNilBranch();
1099
+ this.pushInstruction("drop");
1100
+ }
1101
+
942
1102
  _emitDerefAssign(l, r) {
943
1103
  const operand = l.operand ?? l.expr;
944
1104
  const structInfo = this._derefStructInfo(operand);
@@ -975,6 +1135,7 @@ export class StmtsEmitter {
975
1135
  const valTmp = this.acquireTemp(box.wType);
976
1136
  this.emitExpr(operand, ptrWType);
977
1137
  this.pushInstruction({ op: "local.set", index: ptrTmp });
1138
+ this._emitNilGuard(ptrTmp, operandGoType);
978
1139
  this.emitExpr(r, box.wType);
979
1140
  this.pushInstruction({ op: "local.set", index: valTmp });
980
1141
 
@@ -996,8 +1157,26 @@ export class StmtsEmitter {
996
1157
  this.releaseTemp(ptrTmp, ptrWType);
997
1158
  }
998
1159
 
1160
+ // A plain variable target of interface type receiving a concrete result
1161
+ // (`var x any; x, y = two()`) needs the value converted first.
1162
+ _needsTupleConversion(lhsNodes, tupleTypes) {
1163
+ return lhsNodes.some((l, i) => {
1164
+ if (l.kind !== "Ident" || l.name === "_") return false;
1165
+ const dest = this.resolveLocal(l.name) ?? this.mod.resolveGlobal(l.name);
1166
+ const checker = this.mod.checker;
1167
+ return (
1168
+ isInterfaceType(dest?.goType ?? l._type, checker) &&
1169
+ !isInterfaceType(tupleTypes[i], checker)
1170
+ );
1171
+ });
1172
+ }
1173
+
999
1174
  _emitTupleAssign(lhsNodes, rhsNode, op) {
1000
- if (op === "=" && lhsNodes.some((l) => !this._isPlainTupleTarget(l))) {
1175
+ if (
1176
+ op === "=" &&
1177
+ (lhsNodes.some((l) => !this._isPlainTupleTarget(l)) ||
1178
+ this._needsTupleConversion(lhsNodes, rhsNode._type.types))
1179
+ ) {
1001
1180
  this._emitTupleViaTemps(
1002
1181
  lhsNodes,
1003
1182
  rhsNode,
@@ -1093,16 +1272,79 @@ export class StmtsEmitter {
1093
1272
  this.releaseTemp(okTmp, "i32");
1094
1273
  }
1095
1274
 
1096
- // Pushes a comma-ok result (map value / asserted value). An array is a
1097
- // value: the stored one must not alias the map entry or boxed interface.
1275
+ // Copies the struct or array a map entry holds (a range key or value is a
1276
+ // copy, not the stored value).
1277
+ _emitCopyMapEntry(goType, wType) {
1278
+ const isStruct =
1279
+ isStructType(goType, this.mod.checker, this.mod) &&
1280
+ !isPointerToStruct(goType, this.mod.checker, this.mod);
1281
+ if (isStruct) {
1282
+ const sInfo = this._resolveStructInfo({ _type: goType });
1283
+ if (sInfo) this.emitCloneStruct(sInfo, wType);
1284
+ } else if (
1285
+ goType?.kind !== "pointer" &&
1286
+ isArrayType(goType, this.mod.checker)
1287
+ ) {
1288
+ this.emitCopyArray(goType);
1289
+ }
1290
+ }
1291
+
1292
+ // Pushes a comma-ok result (map value / asserted value). An array or
1293
+ // struct is a value: the stored one must not alias the map entry or boxed
1294
+ // interface.
1098
1295
  _emitGetTempValue(tempIdx, goType) {
1099
1296
  this.pushInstruction({ op: "local.get", index: tempIdx });
1100
1297
  if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker))
1101
1298
  this.emitCopyArray(goType);
1299
+ else if (
1300
+ isStructType(goType, this.mod.checker, this.mod) &&
1301
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
1302
+ ) {
1303
+ // A struct read out of a map or an interface is a copy.
1304
+ const info =
1305
+ this._resolveStructInfo({ _type: goType }) ??
1306
+ this.mod.getStructType(goType?.name);
1307
+ if (info) this.emitCloneStruct(info, this.toWasmType(goType));
1308
+ }
1309
+ }
1310
+
1311
+ _storeTargetWType(lhsNode) {
1312
+ if (lhsNode.kind !== "Ident") return null;
1313
+ const local = this.resolveLocal(lhsNode.name);
1314
+ if (local)
1315
+ return local.isBoxed
1316
+ ? local.boxInfo?.typeEntry?.fields?.[0]?.type
1317
+ : local.type;
1318
+ return this.mod.resolveGlobal(lhsNode.name)?.type ?? null;
1102
1319
  }
1103
1320
 
1104
1321
  _emitStoreLhsFromTemp(lhsNode, tempIdx, wType, goType, isDefine) {
1105
- if (lhsNode.kind !== "Ident" || lhsNode.name === "_") return;
1322
+ if (lhsNode.kind === "Ident" && lhsNode.name === "_") return;
1323
+ const redeclared =
1324
+ isDefine && lhsNode._redecl && this.resolveLocal(lhsNode.name);
1325
+ // `var x any; x, ok = m[k]`: a scalar or struct result is boxed on the
1326
+ // way into the interface-typed variable.
1327
+ const target = isDefine ? null : this._storeTargetWType(lhsNode);
1328
+ const boxesIntoAny = target === "anyref" && wType !== "anyref";
1329
+ if (lhsNode.kind !== "Ident" || redeclared || boxesIntoAny) {
1330
+ // An index, field or dereference target (or a name `:=` redeclares):
1331
+ // assign from a named view of the temporary.
1332
+ const name = `__ok_tmp_${this.tempCounter++}`;
1333
+ this.locals.set(name, {
1334
+ index: tempIdx,
1335
+ type: wType,
1336
+ goType,
1337
+ isParam: false,
1338
+ isBoxed: false,
1339
+ });
1340
+ this._emitAssignSingle(
1341
+ lhsNode,
1342
+ { kind: "Ident", name, _type: goType, _parTmp: true },
1343
+ "=",
1344
+ );
1345
+ this.locals.delete(name);
1346
+ return;
1347
+ }
1106
1348
  if (isDefine) {
1107
1349
  const idx = this.allocLocal(lhsNode.name, wType, goType);
1108
1350
  const local = this.locals.get(lhsNode.name);
@@ -1192,7 +1434,36 @@ export class StmtsEmitter {
1192
1434
  );
1193
1435
  }
1194
1436
 
1437
+ // A bare type name in a case list or assertion (`case L:`, `case error:`)
1438
+ // is the declared type, not its underlying one: a defined string type is
1439
+ // boxed apart from `string`, and `error` carries a method set.
1440
+ _resolveTestTarget(t) {
1441
+ // A composite type-switch case (`case func(int) string:`) carries the
1442
+ // checker's type, which keys typed-nil markers like the boxed value.
1443
+ if (t?._assertType) return t._assertType;
1444
+ if ((t?.kind !== "TypeName" && t?.kind !== "Ident") || !t.name) return t;
1445
+ if (t.name === "nil" || t.name === "any" || t.name === "interface{}")
1446
+ return t;
1447
+ return this.mod.checker?.types?.get(t.name) ?? t;
1448
+ }
1449
+
1450
+ // A defined func, map or array type lives in `any` as a box (so
1451
+ // `iter.Seq[int]` is not a plain `func(func(int) bool)`, `type M
1452
+ // map[string]int` keeps its name in `%T` and unhashable-key panics, and
1453
+ // `type A [2]int` is not a `[2]int` in a type switch).
1454
+ _isMethodNamedRef(goType) {
1455
+ const t = this._resolveTestTarget(goType);
1456
+ if (
1457
+ t &&
1458
+ (isArrayType(t, this.mod.checker) || isMapType(t, this.mod.checker))
1459
+ )
1460
+ return true;
1461
+ if (t?.kind !== "named") return false;
1462
+ return isFuncType(t, this.mod.checker);
1463
+ }
1464
+
1195
1465
  _emitTypeTest(targetGoType) {
1466
+ targetGoType = this._resolveTestTarget(targetGoType);
1196
1467
  if (!targetGoType) {
1197
1468
  this.pushInstruction("drop");
1198
1469
  this.pushInstruction({ op: "i32.const", value: 1 });
@@ -1211,31 +1482,7 @@ export class StmtsEmitter {
1211
1482
  }
1212
1483
 
1213
1484
  if (isNonEmptyInterface(targetGoType, this.mod.checker)) {
1214
- const candidates = this.mod.getTypesImplementingInterface(targetGoType);
1215
- if (candidates.length === 0) {
1216
- this.pushInstruction("drop");
1217
- this.pushInstruction({ op: "i32.const", value: 0 });
1218
- return;
1219
- }
1220
- const anyTmp = this.acquireTemp("anyref");
1221
- this.pushInstruction({ op: "local.set", index: anyTmp });
1222
-
1223
- this.pushInstruction({ op: "local.get", index: anyTmp });
1224
- this.pushInstruction("ref.is_null");
1225
- this.pushInstruction("i32.eqz");
1226
-
1227
- for (let i = 0; i < candidates.length; i++) {
1228
- this.pushInstruction({ op: "local.get", index: anyTmp });
1229
- this.pushInstruction({
1230
- op: "ref.test",
1231
- typeIndex: candidates[i].typeIndex,
1232
- });
1233
- if (i > 0) {
1234
- this.pushInstruction("i32.or");
1235
- }
1236
- }
1237
- this.pushInstruction("i32.and");
1238
- this.releaseTemp(anyTmp, "anyref");
1485
+ this._emitInterfaceTypeTest(targetGoType);
1239
1486
  return;
1240
1487
  }
1241
1488
 
@@ -1247,6 +1494,12 @@ export class StmtsEmitter {
1247
1494
  return;
1248
1495
  }
1249
1496
 
1497
+ if (this._isMethodNamedRef(targetGoType)) {
1498
+ const box = this.mod.getIfaceBoxType(targetGoType);
1499
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
1500
+ return;
1501
+ }
1502
+
1250
1503
  if (isFuncType(targetGoType, this.mod.checker)) {
1251
1504
  const closureInfo = this.mod.getClosureType(targetGoType);
1252
1505
  this._emitRefTestOrNil(targetGoType, closureInfo.typeIndex);
@@ -1265,33 +1518,27 @@ export class StmtsEmitter {
1265
1518
 
1266
1519
  if (isSliceType(targetGoType, this.mod.checker)) {
1267
1520
  const elem = this._getSliceElemType(targetGoType);
1521
+ if (isTaggedSliceType(targetGoType, this.mod.checker)) {
1522
+ const tagged = this.mod.getTaggedSliceType(targetGoType, elem);
1523
+ this.pushInstruction({ op: "ref.test", typeIndex: tagged.typeIndex });
1524
+ return;
1525
+ }
1268
1526
  const sInfo = this.mod.getSliceType(elem);
1269
1527
  if (sInfo) {
1528
+ // The unnamed slice type is the bare one, not a tagged subtype.
1529
+ const root = this.mod.getTaggedSliceRoot(sInfo);
1530
+ const tmp = this.acquireTemp("anyref");
1531
+ this.pushInstruction({ op: "local.tee", index: tmp });
1270
1532
  this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
1533
+ this.pushInstruction({ op: "local.get", index: tmp });
1534
+ this.pushInstruction({ op: "ref.test", typeIndex: root.typeIndex });
1535
+ this.pushInstruction("i32.eqz");
1536
+ this.pushInstruction("i32.and");
1537
+ this.releaseTemp(tmp, "anyref");
1271
1538
  return;
1272
1539
  }
1273
1540
  }
1274
1541
 
1275
- if (isArrayType(targetGoType, this.mod.checker)) {
1276
- const elem = this._getArrayElemType(targetGoType);
1277
- const aInfo = this.mod.getArrayType(elem);
1278
- if (aInfo) {
1279
- this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
1280
- return;
1281
- }
1282
- }
1283
-
1284
- // A map lives in `any` as its bare map struct.
1285
- if (isMapType(targetGoType, this.mod.checker)) {
1286
- const { keyType, valType } = getMapKeyValTypes(
1287
- targetGoType,
1288
- this.mod.checker,
1289
- );
1290
- const mInfo = this.mod.getMapType(keyType, valType);
1291
- this._emitRefTestOrNil(targetGoType, mInfo.typeIndex);
1292
- return;
1293
- }
1294
-
1295
1542
  // Scalar types
1296
1543
  const box = this.mod.getIfaceBoxType(targetGoType);
1297
1544
  if (box) {
@@ -1303,6 +1550,45 @@ export class StmtsEmitter {
1303
1550
  this.pushInstruction({ op: "i32.const", value: 1 });
1304
1551
  }
1305
1552
 
1553
+ // Whether the interface value on the stack holds a type implementing the
1554
+ // non-empty interface: a `ref.test` over every implementing type (a typed
1555
+ // nil `*S` marker included).
1556
+ _emitInterfaceTypeTest(targetGoType) {
1557
+ const candidates = this.mod.getTypesImplementingInterface(targetGoType);
1558
+ if (candidates.length === 0) {
1559
+ this.pushInstruction("drop");
1560
+ this.pushInstruction({ op: "i32.const", value: 0 });
1561
+ return;
1562
+ }
1563
+ const anyTmp = this.acquireTemp("anyref");
1564
+ this.pushInstruction({ op: "local.set", index: anyTmp });
1565
+
1566
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1567
+ this.pushInstruction("ref.is_null");
1568
+ this.pushInstruction("i32.eqz");
1569
+
1570
+ for (let i = 0; i < candidates.length; i++) {
1571
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1572
+ this.pushInstruction({
1573
+ op: "ref.test",
1574
+ typeIndex: candidates[i].typeIndex,
1575
+ });
1576
+ if (candidates[i].nilTypeIndex !== undefined) {
1577
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1578
+ this.pushInstruction({
1579
+ op: "ref.test",
1580
+ typeIndex: candidates[i].nilTypeIndex,
1581
+ });
1582
+ this.pushInstruction("i32.or");
1583
+ }
1584
+ if (i > 0) {
1585
+ this.pushInstruction("i32.or");
1586
+ }
1587
+ }
1588
+ this.pushInstruction("i32.and");
1589
+ this.releaseTemp(anyTmp, "anyref");
1590
+ }
1591
+
1306
1592
  // `ref.test` for a map, func or `*T`, which also accepts the typed-nil
1307
1593
  // marker of the same Go type (see getNilBoxType).
1308
1594
  _emitRefTestOrNil(goType, typeIndex) {
@@ -1337,10 +1623,19 @@ export class StmtsEmitter {
1337
1623
  this.releaseTemp(tmp, "anyref");
1338
1624
  }
1339
1625
 
1626
+ // An interface target keeps the value as is; a boxed defined func or map
1627
+ // type is unboxed. True when the cast is done.
1628
+ _emitCastNoRepack(targetGoType) {
1629
+ if (isInterfaceType(targetGoType, this.mod.checker)) return true;
1630
+ if (!this._isMethodNamedRef(targetGoType)) return false;
1631
+ this._emitUnbox(
1632
+ this.mod.getIfaceBoxType(this._resolveTestTarget(targetGoType)),
1633
+ );
1634
+ return true;
1635
+ }
1636
+
1340
1637
  _emitTypeCast(targetGoType, targetWType) {
1341
- if (isInterfaceType(targetGoType, this.mod.checker)) {
1342
- return;
1343
- }
1638
+ if (this._emitCastNoRepack(targetGoType)) return;
1344
1639
 
1345
1640
  if (isBareStringType(targetGoType, this.mod.checker)) {
1346
1641
  this.pushInstruction("extern.convert_any");
@@ -1382,28 +1677,6 @@ export class StmtsEmitter {
1382
1677
  }
1383
1678
  }
1384
1679
 
1385
- if (isArrayType(targetGoType, this.mod.checker)) {
1386
- const elem = this._getArrayElemType(targetGoType);
1387
- const aInfo = this.mod.getArrayType(elem);
1388
- if (aInfo) {
1389
- this.pushInstruction({
1390
- op: "ref.cast_null",
1391
- typeIndex: aInfo.typeIndex,
1392
- });
1393
- return;
1394
- }
1395
- }
1396
-
1397
- if (isMapType(targetGoType, this.mod.checker)) {
1398
- const { keyType, valType } = getMapKeyValTypes(
1399
- targetGoType,
1400
- this.mod.checker,
1401
- );
1402
- const mInfo = this.mod.getMapType(keyType, valType);
1403
- this._emitRefCastOrNil(targetGoType, mInfo.typeIndex);
1404
- return;
1405
- }
1406
-
1407
1680
  // Scalar types
1408
1681
  const box = this.mod.getIfaceBoxType(targetGoType);
1409
1682
  if (box) {
@@ -1632,6 +1905,7 @@ export class StmtsEmitter {
1632
1905
  const ptrTmp = this.acquireTemp(ptrWType);
1633
1906
  this.emitExpr(operand, ptrWType);
1634
1907
  this.pushInstruction({ op: "local.set", index: ptrTmp });
1908
+ this._emitNilGuard(ptrTmp, operandGoType);
1635
1909
 
1636
1910
  this.pushInstruction({ op: "local.get", index: ptrTmp });
1637
1911
  this.pushInstruction({
@@ -1770,6 +2044,7 @@ export class StmtsEmitter {
1770
2044
  const ptrTmp = this.acquireTemp(ptrWType);
1771
2045
  this.emitExpr(operand, ptrWType);
1772
2046
  this.pushInstruction({ op: "local.set", index: ptrTmp });
2047
+ this._emitNilGuard(ptrTmp, operandGoType);
1773
2048
 
1774
2049
  this.pushInstruction({ op: "local.get", index: ptrTmp });
1775
2050
  this.pushInstruction({
@@ -2020,7 +2295,9 @@ export class StmtsEmitter {
2020
2295
  const rangeExpr =
2021
2296
  stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init.rhs[0];
2022
2297
  const isAssign = stmt.init?.kind === "AssignStmt";
2023
- const lhs = stmt.init?.lhs ?? [];
2298
+ const lhs = isAssign
2299
+ ? this._bindRangeAssignTargets(stmt.init.lhs)
2300
+ : (stmt.init?.lhs ?? []);
2024
2301
  const iterExpr = rangeExpr.expr;
2025
2302
  const iterType = iterExpr._type;
2026
2303
 
@@ -2087,21 +2364,21 @@ export class StmtsEmitter {
2087
2364
 
2088
2365
  // Assign loop var if present
2089
2366
  if (idxLocalInfo) {
2090
- this.pushInstruction({ op: "local.get", index: idxTmp });
2091
- if (idxLocalInfo.type === "i64") {
2092
- this.pushInstruction("i64.extend_i32_s");
2093
- }
2094
- if (this.resolveLocal(lhs[0].name)) {
2095
- this.pushInstruction({
2096
- op: "local.set",
2097
- index: idxLocalInfo.index,
2098
- });
2099
- } else {
2100
- this.pushInstruction({
2101
- op: "global.set",
2102
- index: idxLocalInfo.index,
2103
- });
2104
- }
2367
+ const name = lhs[0].name;
2368
+ const wType = this._rangeStoreWType(name, idxLocalInfo);
2369
+ const idxGoType =
2370
+ iterType?.kind === "untyped"
2371
+ ? { kind: "basic", name: "int" }
2372
+ : iterType;
2373
+ this._emitRangeAssignStore(
2374
+ name,
2375
+ idxLocalInfo,
2376
+ (want = wType) => {
2377
+ this.pushInstruction({ op: "local.get", index: idxTmp });
2378
+ if (want === "i64") this.pushInstruction("i64.extend_i32_s");
2379
+ },
2380
+ idxGoType,
2381
+ );
2105
2382
  }
2106
2383
 
2107
2384
  // Emit body
@@ -2212,38 +2489,30 @@ export class StmtsEmitter {
2212
2489
 
2213
2490
  // Assign index variable
2214
2491
  if (idxLocalInfo) {
2215
- this.pushInstruction({ op: "local.get", index: idxTmp });
2216
- this.pushInstruction("i64.extend_i32_s");
2217
- if (this.resolveLocal(lhs[0].name)) {
2218
- this.pushInstruction({
2219
- op: "local.set",
2220
- index: idxLocalInfo.index,
2221
- });
2222
- } else {
2223
- this.pushInstruction({
2224
- op: "global.set",
2225
- index: idxLocalInfo.index,
2226
- });
2227
- }
2492
+ this._emitRangeAssignStore(
2493
+ lhs[0].name,
2494
+ idxLocalInfo,
2495
+ () => {
2496
+ this.pushInstruction({ op: "local.get", index: idxTmp });
2497
+ this.pushInstruction("i64.extend_i32_s");
2498
+ },
2499
+ { kind: "basic", name: "int" },
2500
+ );
2228
2501
  }
2229
2502
 
2230
2503
  // Assign rune variable
2231
2504
  if (valLocalInfo) {
2232
- this.pushInstruction({ op: "local.get", index: runeTmp });
2233
- if (valLocalInfo.type === "i64") {
2234
- this.pushInstruction("i64.extend_i32_u");
2235
- }
2236
- if (this.resolveLocal(lhs[1].name)) {
2237
- this.pushInstruction({
2238
- op: "local.set",
2239
- index: valLocalInfo.index,
2240
- });
2241
- } else {
2242
- this.pushInstruction({
2243
- op: "global.set",
2244
- index: valLocalInfo.index,
2245
- });
2246
- }
2505
+ const name = lhs[1].name;
2506
+ const wType = this._rangeStoreWType(name, valLocalInfo);
2507
+ this._emitRangeAssignStore(
2508
+ name,
2509
+ valLocalInfo,
2510
+ (want = wType) => {
2511
+ this.pushInstruction({ op: "local.get", index: runeTmp });
2512
+ if (want === "i64") this.pushInstruction("i64.extend_i32_u");
2513
+ },
2514
+ { kind: "basic", name: "int32" },
2515
+ );
2247
2516
  }
2248
2517
 
2249
2518
  // Body
@@ -2411,58 +2680,38 @@ export class StmtsEmitter {
2411
2680
 
2412
2681
  // Assign key variable
2413
2682
  if (keyLocalInfo) {
2414
- this.pushInstruction({ op: "local.get", index: entryTmp });
2415
- this.pushInstruction({
2416
- op: "struct.get",
2417
- typeIndex: mapInfo.entryTypeIndex,
2418
- fieldIndex: 0,
2419
- });
2420
- if (this.resolveLocal(lhs[0].name)) {
2421
- this.pushInstruction({
2422
- op: "local.set",
2423
- index: keyLocalInfo.index,
2424
- });
2425
- } else {
2426
- this.pushInstruction({
2427
- op: "global.set",
2428
- index: keyLocalInfo.index,
2429
- });
2430
- }
2683
+ this._emitRangeAssignStore(
2684
+ lhs[0].name,
2685
+ keyLocalInfo,
2686
+ () => {
2687
+ this.pushInstruction({ op: "local.get", index: entryTmp });
2688
+ this.pushInstruction({
2689
+ op: "struct.get",
2690
+ typeIndex: mapInfo.entryTypeIndex,
2691
+ fieldIndex: 0,
2692
+ });
2693
+ this._emitCopyMapEntry(keyType, mapInfo.keyWType);
2694
+ },
2695
+ keyType,
2696
+ );
2431
2697
  }
2432
2698
 
2433
2699
  // Assign val variable
2434
2700
  if (valLocalInfo) {
2435
- this.pushInstruction({ op: "local.get", index: entryTmp });
2436
- this.pushInstruction({
2437
- op: "struct.get",
2438
- typeIndex: mapInfo.entryTypeIndex,
2439
- fieldIndex: 1,
2440
- });
2441
- const isValStruct =
2442
- isStructType(valType, this.mod.checker, this.mod) &&
2443
- !isPointerToStruct(valType, this.mod.checker, this.mod);
2444
- if (isValStruct) {
2445
- const sInfo = this._resolveStructInfo({ _type: valType });
2446
- if (sInfo) {
2447
- this.emitCloneStruct(sInfo, mapInfo.valWType);
2448
- }
2449
- } else if (
2450
- valType?.kind !== "pointer" &&
2451
- isArrayType(valType, this.mod.checker)
2452
- ) {
2453
- this.emitCopyArray(valType);
2454
- }
2455
- if (this.resolveLocal(lhs[1].name)) {
2456
- this.pushInstruction({
2457
- op: "local.set",
2458
- index: valLocalInfo.index,
2459
- });
2460
- } else {
2461
- this.pushInstruction({
2462
- op: "global.set",
2463
- index: valLocalInfo.index,
2464
- });
2465
- }
2701
+ this._emitRangeAssignStore(
2702
+ lhs[1].name,
2703
+ valLocalInfo,
2704
+ () => {
2705
+ this.pushInstruction({ op: "local.get", index: entryTmp });
2706
+ this.pushInstruction({
2707
+ op: "struct.get",
2708
+ typeIndex: mapInfo.entryTypeIndex,
2709
+ fieldIndex: 1,
2710
+ });
2711
+ this._emitCopyMapEntry(valType, mapInfo.valWType);
2712
+ },
2713
+ valType,
2714
+ );
2466
2715
  }
2467
2716
 
2468
2717
  // Body
@@ -2551,6 +2800,15 @@ export class StmtsEmitter {
2551
2800
  });
2552
2801
  }
2553
2802
  this.releaseTemp(sliceTmp, sliceWType);
2803
+ } else if (!hasVal && !hasCallOrReceive(iterExpr)) {
2804
+ // Ranging over an array for its indexes only does not evaluate it
2805
+ // (`range p` on a nil array pointer is valid): the length is the
2806
+ // type's.
2807
+ this.pushInstruction({
2808
+ op: "i32.const",
2809
+ value: this._getArraySize(isPtrToArr ? iterType.base : iterType),
2810
+ });
2811
+ this.pushInstruction({ op: "local.set", index: lenTmp });
2554
2812
  } else {
2555
2813
  this.emitExpr(iterExpr, arrWType);
2556
2814
  // Go ranges over a copy of an array (the range expression is
@@ -2627,52 +2885,50 @@ export class StmtsEmitter {
2627
2885
 
2628
2886
  // Assign idx
2629
2887
  if (idxLocalInfo) {
2630
- this.pushInstruction({ op: "local.get", index: idxTmp });
2631
- if (idxLocalInfo.type === "i64") {
2632
- this.pushInstruction("i64.extend_i32_s");
2633
- }
2634
- if (this.resolveLocal(lhs[0].name)) {
2635
- this.pushInstruction({ op: "local.set", index: idxLocalInfo.index });
2636
- } else {
2637
- this.pushInstruction({
2638
- op: "global.set",
2639
- index: idxLocalInfo.index,
2640
- });
2641
- }
2888
+ const name = lhs[0].name;
2889
+ const wType = this._rangeStoreWType(name, idxLocalInfo);
2890
+ this._emitRangeAssignStore(
2891
+ name,
2892
+ idxLocalInfo,
2893
+ (want = wType) => {
2894
+ this.pushInstruction({ op: "local.get", index: idxTmp });
2895
+ if (want === "i64") this.pushInstruction("i64.extend_i32_s");
2896
+ },
2897
+ { kind: "basic", name: "int" },
2898
+ );
2642
2899
  }
2643
2900
 
2644
2901
  // Assign val
2645
2902
  if (valLocalInfo) {
2646
- this.pushInstruction({ op: "local.get", index: arrTmp });
2647
- this.pushInstruction({ op: "local.get", index: offTmp });
2648
- this.pushInstruction({ op: "local.get", index: idxTmp });
2649
- this.pushInstruction("i32.add");
2650
- this.pushInstruction({
2651
- op: "array.get",
2652
- typeIndex: arrInfo.typeIndex,
2653
- });
2654
- if (
2655
- isStructType(elemGoType, this.mod.checker, this.mod) &&
2656
- !isPointerToStruct(elemGoType, this.mod.checker, this.mod)
2657
- ) {
2658
- const sInfo = this._resolveStructInfo({ _type: elemGoType });
2659
- if (sInfo) {
2660
- this.emitCloneStruct(sInfo, arrInfo.elemWType);
2661
- }
2662
- } else if (
2663
- elemGoType?.kind !== "pointer" &&
2664
- isArrayType(elemGoType, this.mod.checker)
2665
- ) {
2666
- this.emitCopyArray(elemGoType);
2667
- }
2668
- if (this.resolveLocal(lhs[1].name)) {
2669
- this.pushInstruction({ op: "local.set", index: valLocalInfo.index });
2670
- } else {
2671
- this.pushInstruction({
2672
- op: "global.set",
2673
- index: valLocalInfo.index,
2674
- });
2675
- }
2903
+ this._emitRangeAssignStore(
2904
+ lhs[1].name,
2905
+ valLocalInfo,
2906
+ () => {
2907
+ this.pushInstruction({ op: "local.get", index: arrTmp });
2908
+ this.pushInstruction({ op: "local.get", index: offTmp });
2909
+ this.pushInstruction({ op: "local.get", index: idxTmp });
2910
+ this.pushInstruction("i32.add");
2911
+ this.pushInstruction({
2912
+ op: "array.get",
2913
+ typeIndex: arrInfo.typeIndex,
2914
+ });
2915
+ if (
2916
+ isStructType(elemGoType, this.mod.checker, this.mod) &&
2917
+ !isPointerToStruct(elemGoType, this.mod.checker, this.mod)
2918
+ ) {
2919
+ const sInfo = this._resolveStructInfo({ _type: elemGoType });
2920
+ if (sInfo) {
2921
+ this.emitCloneStruct(sInfo, arrInfo.elemWType);
2922
+ }
2923
+ } else if (
2924
+ elemGoType?.kind !== "pointer" &&
2925
+ isArrayType(elemGoType, this.mod.checker)
2926
+ ) {
2927
+ this.emitCopyArray(elemGoType);
2928
+ }
2929
+ },
2930
+ elemGoType,
2931
+ );
2676
2932
  }
2677
2933
 
2678
2934
  // Body
@@ -3134,16 +3390,7 @@ export class StmtsEmitter {
3134
3390
  defaultIdx = i;
3135
3391
  continue;
3136
3392
  }
3137
- for (let j = 0; j < vals.length; j++) {
3138
- if (stmt.tag) {
3139
- this.pushInstruction({ op: "local.get", index: tagTmp });
3140
- this.emitExpr(vals[j], tagWType);
3141
- this.emitBinaryOp("==", tagWType, tagGoType);
3142
- } else {
3143
- this.emitExpr(vals[j], "i32");
3144
- }
3145
- if (j > 0) this.pushInstruction("i32.or");
3146
- }
3393
+ this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
3147
3394
  // The innermost block ends the first clause, so clause i is depth i.
3148
3395
  this.pushInstruction({ op: "br_if", depth: i });
3149
3396
  }
@@ -3162,6 +3409,26 @@ export class StmtsEmitter {
3162
3409
  this.pushInstruction("end");
3163
3410
  }
3164
3411
 
3412
+ // Leaves an i32 on the stack: whether any expression of a case list matches.
3413
+ // Later expressions are not evaluated once one matches, as in Go.
3414
+ _emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType) {
3415
+ for (let j = 0; j < vals.length; j++) {
3416
+ if (stmt.tag) {
3417
+ this.pushInstruction({ op: "local.get", index: tagTmp });
3418
+ this.emitExpr(vals[j], tagWType);
3419
+ this._emitElemEq(tagGoType, tagWType);
3420
+ } else {
3421
+ this.emitExpr(vals[j], "i32");
3422
+ }
3423
+ if (j < vals.length - 1) {
3424
+ this.pushInstruction({ op: "if", blockType: "i32" });
3425
+ this.pushInstruction({ op: "i32.const", value: 1 });
3426
+ this.pushInstruction("else");
3427
+ }
3428
+ }
3429
+ for (let j = 0; j < vals.length - 1; j++) this.pushInstruction("end");
3430
+ }
3431
+
3165
3432
  emitSwitchStmt(stmt) {
3166
3433
  if (
3167
3434
  (stmt.cases ?? []).some((c) => {
@@ -3202,16 +3469,7 @@ export class StmtsEmitter {
3202
3469
  let ifCount = 0;
3203
3470
  for (const c of nonDefaultCases) {
3204
3471
  const vals = getCaseVals(c);
3205
- for (let i = 0; i < vals.length; i++) {
3206
- if (stmt.tag) {
3207
- this.pushInstruction({ op: "local.get", index: tagTmp });
3208
- this.emitExpr(vals[i], tagWType);
3209
- this.emitBinaryOp("==", tagWType, tagGoType);
3210
- } else {
3211
- this.emitExpr(vals[i], "i32");
3212
- }
3213
- if (i > 0) this.pushInstruction("i32.or");
3214
- }
3472
+ this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
3215
3473
  this.pushInstruction({ op: "if", blockType: "void" });
3216
3474
  ifCount++;
3217
3475
 
@@ -3233,6 +3491,17 @@ export class StmtsEmitter {
3233
3491
  this.pushInstruction("end");
3234
3492
  }
3235
3493
 
3494
+ // Wraps the value on the stack in a box when the just-declared local `name`
3495
+ // is captured and mutated.
3496
+ _emitBoxIfCaptured(name) {
3497
+ const info = this.locals.get(name);
3498
+ if (info?.isBoxed)
3499
+ this.pushInstruction({
3500
+ op: "struct.new",
3501
+ typeIndex: info.boxInfo.typeIndex,
3502
+ });
3503
+ }
3504
+
3236
3505
  emitTypeSwitchStmt(stmt) {
3237
3506
  this.pushInstruction({ op: "block", blockType: "void" });
3238
3507
  this.pushControl("break", stmt.label);
@@ -3273,7 +3542,7 @@ export class StmtsEmitter {
3273
3542
  const targetGoType = isNilCase
3274
3543
  ? (stmt.expr._type ?? { kind: "basic", name: "any" })
3275
3544
  : types.length === 1
3276
- ? (types[0]._type ?? types[0])
3545
+ ? (types[0]._type ?? this._resolveTestTarget(types[0]))
3277
3546
  : { kind: "basic", name: "any" };
3278
3547
  const targetWType = this.toWasmType(targetGoType);
3279
3548
  const assignLocal = this.allocLocal(
@@ -3293,6 +3562,7 @@ export class StmtsEmitter {
3293
3562
  } else {
3294
3563
  this.pushInstruction({ op: "local.get", index: tswTmp });
3295
3564
  }
3565
+ this._emitBoxIfCaptured(stmt.assign);
3296
3566
  this.pushInstruction({ op: "local.set", index: assignLocal });
3297
3567
  }
3298
3568
 
@@ -3308,6 +3578,7 @@ export class StmtsEmitter {
3308
3578
  name: "any",
3309
3579
  });
3310
3580
  this.pushInstruction({ op: "local.get", index: tswTmp });
3581
+ this._emitBoxIfCaptured(stmt.assign);
3311
3582
  this.pushInstruction({ op: "local.set", index: assignLocal });
3312
3583
  }
3313
3584
  this._emitClauseBody(defaultCase.stmts);
@@ -3379,13 +3650,24 @@ export class StmtsEmitter {
3379
3650
  this.pushInstruction("i64.eq");
3380
3651
  this.pushInstruction({ op: "if", blockType: "void" });
3381
3652
  if (target.role !== "break" || stmt.label !== target.label) {
3382
- if (this.funcDecl._isYieldClosure) {
3653
+ const isContinue = target.role === "continue";
3654
+ const inScope = this.controlStack.some(
3655
+ (c) => c.label === target.label && c.role === target.role,
3656
+ );
3657
+ if (
3658
+ !inScope &&
3659
+ isContinue &&
3660
+ this.funcDecl._isYieldClosure &&
3661
+ target.label === this.funcDecl._forStmt?.label
3662
+ ) {
3663
+ // `continue L` where L labels the iterator loop whose body this
3664
+ // yield closure is: resume that iterator.
3665
+ this.pushInstruction({ op: "i32.const", value: 1 });
3666
+ this.pushInstruction({ op: "return" });
3667
+ } else if (!inScope && this.funcDecl._isYieldClosure) {
3383
3668
  this._propagateYieldExit(BigInt(target.tag));
3384
3669
  } else {
3385
- const depth = this.resolveBranchDepth(
3386
- target.label,
3387
- target.role === "continue",
3388
- );
3670
+ const depth = this.resolveBranchDepth(target.label, isContinue);
3389
3671
  this.pushInstruction({ op: "br", depth });
3390
3672
  }
3391
3673
  }
@@ -3510,6 +3792,8 @@ export class StmtsEmitter {
3510
3792
  }
3511
3793
 
3512
3794
  emitBranchStmt(stmt) {
3795
+ const alias = stmt.label && this._stackedLabelAlias(stmt.label);
3796
+ if (alias) stmt = { ...stmt, label: alias };
3513
3797
  if (this._tryEmitYieldBranch(stmt)) return;
3514
3798
  const isContinue = stmt.keyword === "continue";
3515
3799
  const depth = this.resolveBranchDepth(stmt.label, isContinue);