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
@@ -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({
@@ -823,6 +841,10 @@ export class StmtsEmitter {
823
841
  )
824
842
  )
825
843
  return false;
844
+ // Any callee may hold `&xs[i]` of a slice element, so when the program
845
+ // takes one, a whole-value store into an element writes in place.
846
+ if (this.mod.lowerResult?.elemAddrTaken && this._isSliceElemTarget(l))
847
+ return true;
826
848
  const root = rootIdentName(l);
827
849
  if (!root) return false;
828
850
  // Any code may hold the address of a package-level variable.
@@ -832,6 +854,45 @@ export class StmtsEmitter {
832
854
  return Boolean(ctx?.fnMutates(root, true));
833
855
  }
834
856
 
857
+ // `xs[i] = v` into a slice element in place. Capacity beyond the length
858
+ // may hold null elements (a reslice exposes them), so a null element is
859
+ // first replaced by a zero value that the store then fills.
860
+ _emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo) {
861
+ const arrT = {
862
+ kind: "ref",
863
+ nullable: true,
864
+ typeIndex: arrInfo.typeIndex,
865
+ };
866
+ const arr = this.acquireTemp(arrT);
867
+ const idx = this.acquireTemp("i32");
868
+ this._emitElemAddr(l.expr, l.index, sliceInfo, arrInfo);
869
+ this.pushInstruction({ op: "local.set", index: idx });
870
+ this.pushInstruction({ op: "local.set", index: arr });
871
+ const getElem = () => {
872
+ this.pushInstruction({ op: "local.get", index: arr });
873
+ this.pushInstruction({ op: "local.get", index: idx });
874
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
875
+ };
876
+ getElem();
877
+ this.pushInstruction("ref.is_null");
878
+ this.pushInstruction({ op: "if", blockType: "void" });
879
+ this.pushInstruction({ op: "local.get", index: arr });
880
+ this.pushInstruction({ op: "local.get", index: idx });
881
+ this.emitZeroValue(elemGoType, arrInfo.elemWType);
882
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
883
+ this.pushInstruction("end");
884
+ this._emitStableStore(elemGoType, r, getElem);
885
+ this.releaseTemp(idx, "i32");
886
+ this.releaseTemp(arr, arrT);
887
+ }
888
+
889
+ // True when `l` is `xs[i]` on a slice (not an array or map).
890
+ _isSliceElemTarget(l) {
891
+ if (l.kind !== "IndexExpr") return false;
892
+ const t = l.expr._type ?? this._resolveExprGoType(l.expr);
893
+ return isSliceType(t, this.mod.checker) && !this._sharedInfo(t);
894
+ }
895
+
835
896
  _emitStableStore(goType, r, emitDst) {
836
897
  if (this._isValueArray(goType)) {
837
898
  this._emitArrayStoreInto(goType, r, emitDst);
@@ -921,6 +982,7 @@ export class StmtsEmitter {
921
982
  this.pushInstruction({ op: "local.set", index: tmpL });
922
983
  this.emitExpr(r, baseWType);
923
984
  this.pushInstruction({ op: "local.set", index: tmpR });
985
+ this._emitNilGuard(tmpL, operand._type);
924
986
  for (let i = 0; i < structInfo.fields.length; i++) {
925
987
  this.pushInstruction({ op: "local.get", index: tmpL });
926
988
  this.pushInstruction({ op: "local.get", index: tmpR });
@@ -939,6 +1001,16 @@ export class StmtsEmitter {
939
1001
  this.releaseTemp(tmpL, baseWType);
940
1002
  }
941
1003
 
1004
+ // Raises Go's nil-dereference panic when the pointer held in `tmp` is nil
1005
+ // (a plain cast or field write would trap, which `recover` cannot see).
1006
+ _emitNilGuard(tmp, goType) {
1007
+ if (goType?.kind !== "pointer") return;
1008
+ if (this.resolveBranchDepthToRole("nil") == null) return;
1009
+ this.pushInstruction({ op: "local.get", index: tmp });
1010
+ this._emitNilBranch();
1011
+ this.pushInstruction("drop");
1012
+ }
1013
+
942
1014
  _emitDerefAssign(l, r) {
943
1015
  const operand = l.operand ?? l.expr;
944
1016
  const structInfo = this._derefStructInfo(operand);
@@ -975,6 +1047,7 @@ export class StmtsEmitter {
975
1047
  const valTmp = this.acquireTemp(box.wType);
976
1048
  this.emitExpr(operand, ptrWType);
977
1049
  this.pushInstruction({ op: "local.set", index: ptrTmp });
1050
+ this._emitNilGuard(ptrTmp, operandGoType);
978
1051
  this.emitExpr(r, box.wType);
979
1052
  this.pushInstruction({ op: "local.set", index: valTmp });
980
1053
 
@@ -996,8 +1069,26 @@ export class StmtsEmitter {
996
1069
  this.releaseTemp(ptrTmp, ptrWType);
997
1070
  }
998
1071
 
1072
+ // A plain variable target of interface type receiving a concrete result
1073
+ // (`var x any; x, y = two()`) needs the value converted first.
1074
+ _needsTupleConversion(lhsNodes, tupleTypes) {
1075
+ return lhsNodes.some((l, i) => {
1076
+ if (l.kind !== "Ident" || l.name === "_") return false;
1077
+ const dest = this.resolveLocal(l.name) ?? this.mod.resolveGlobal(l.name);
1078
+ const checker = this.mod.checker;
1079
+ return (
1080
+ isInterfaceType(dest?.goType ?? l._type, checker) &&
1081
+ !isInterfaceType(tupleTypes[i], checker)
1082
+ );
1083
+ });
1084
+ }
1085
+
999
1086
  _emitTupleAssign(lhsNodes, rhsNode, op) {
1000
- if (op === "=" && lhsNodes.some((l) => !this._isPlainTupleTarget(l))) {
1087
+ if (
1088
+ op === "=" &&
1089
+ (lhsNodes.some((l) => !this._isPlainTupleTarget(l)) ||
1090
+ this._needsTupleConversion(lhsNodes, rhsNode._type.types))
1091
+ ) {
1001
1092
  this._emitTupleViaTemps(
1002
1093
  lhsNodes,
1003
1094
  rhsNode,
@@ -1093,16 +1184,79 @@ export class StmtsEmitter {
1093
1184
  this.releaseTemp(okTmp, "i32");
1094
1185
  }
1095
1186
 
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.
1187
+ // Copies the struct or array a map entry holds (a range key or value is a
1188
+ // copy, not the stored value).
1189
+ _emitCopyMapEntry(goType, wType) {
1190
+ const isStruct =
1191
+ isStructType(goType, this.mod.checker, this.mod) &&
1192
+ !isPointerToStruct(goType, this.mod.checker, this.mod);
1193
+ if (isStruct) {
1194
+ const sInfo = this._resolveStructInfo({ _type: goType });
1195
+ if (sInfo) this.emitCloneStruct(sInfo, wType);
1196
+ } else if (
1197
+ goType?.kind !== "pointer" &&
1198
+ isArrayType(goType, this.mod.checker)
1199
+ ) {
1200
+ this.emitCopyArray(goType);
1201
+ }
1202
+ }
1203
+
1204
+ // Pushes a comma-ok result (map value / asserted value). An array or
1205
+ // struct is a value: the stored one must not alias the map entry or boxed
1206
+ // interface.
1098
1207
  _emitGetTempValue(tempIdx, goType) {
1099
1208
  this.pushInstruction({ op: "local.get", index: tempIdx });
1100
1209
  if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker))
1101
1210
  this.emitCopyArray(goType);
1211
+ else if (
1212
+ isStructType(goType, this.mod.checker, this.mod) &&
1213
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
1214
+ ) {
1215
+ // A struct read out of a map or an interface is a copy.
1216
+ const info =
1217
+ this._resolveStructInfo({ _type: goType }) ??
1218
+ this.mod.getStructType(goType?.name);
1219
+ if (info) this.emitCloneStruct(info, this.toWasmType(goType));
1220
+ }
1221
+ }
1222
+
1223
+ _storeTargetWType(lhsNode) {
1224
+ if (lhsNode.kind !== "Ident") return null;
1225
+ const local = this.resolveLocal(lhsNode.name);
1226
+ if (local)
1227
+ return local.isBoxed
1228
+ ? local.boxInfo?.typeEntry?.fields?.[0]?.type
1229
+ : local.type;
1230
+ return this.mod.resolveGlobal(lhsNode.name)?.type ?? null;
1102
1231
  }
1103
1232
 
1104
1233
  _emitStoreLhsFromTemp(lhsNode, tempIdx, wType, goType, isDefine) {
1105
- if (lhsNode.kind !== "Ident" || lhsNode.name === "_") return;
1234
+ if (lhsNode.kind === "Ident" && lhsNode.name === "_") return;
1235
+ const redeclared =
1236
+ isDefine && lhsNode._redecl && this.resolveLocal(lhsNode.name);
1237
+ // `var x any; x, ok = m[k]`: a scalar or struct result is boxed on the
1238
+ // way into the interface-typed variable.
1239
+ const target = isDefine ? null : this._storeTargetWType(lhsNode);
1240
+ const boxesIntoAny = target === "anyref" && wType !== "anyref";
1241
+ if (lhsNode.kind !== "Ident" || redeclared || boxesIntoAny) {
1242
+ // An index, field or dereference target (or a name `:=` redeclares):
1243
+ // assign from a named view of the temporary.
1244
+ const name = `__ok_tmp_${this.tempCounter++}`;
1245
+ this.locals.set(name, {
1246
+ index: tempIdx,
1247
+ type: wType,
1248
+ goType,
1249
+ isParam: false,
1250
+ isBoxed: false,
1251
+ });
1252
+ this._emitAssignSingle(
1253
+ lhsNode,
1254
+ { kind: "Ident", name, _type: goType, _parTmp: true },
1255
+ "=",
1256
+ );
1257
+ this.locals.delete(name);
1258
+ return;
1259
+ }
1106
1260
  if (isDefine) {
1107
1261
  const idx = this.allocLocal(lhsNode.name, wType, goType);
1108
1262
  const local = this.locals.get(lhsNode.name);
@@ -1192,7 +1346,29 @@ export class StmtsEmitter {
1192
1346
  );
1193
1347
  }
1194
1348
 
1349
+ // A bare type name in a case list or assertion (`case L:`, `case error:`)
1350
+ // is the declared type, not its underlying one: a defined string type is
1351
+ // boxed apart from `string`, and `error` carries a method set.
1352
+ _resolveTestTarget(t) {
1353
+ if ((t?.kind !== "TypeName" && t?.kind !== "Ident") || !t.name) return t;
1354
+ if (t.name === "nil" || t.name === "any" || t.name === "interface{}")
1355
+ return t;
1356
+ return this.mod.checker?.types?.get(t.name) ?? t;
1357
+ }
1358
+
1359
+ // A defined func type, or a defined map type that has methods, lives in
1360
+ // `any` as a box (so `iter.Seq[int]` is not a plain `func(func(int) bool)`).
1361
+ _isMethodNamedRef(goType) {
1362
+ const t = this._resolveTestTarget(goType);
1363
+ if (t?.kind !== "named") return false;
1364
+ return (
1365
+ isFuncType(t, this.mod.checker) ||
1366
+ (t.methods?.size > 0 && isMapType(t, this.mod.checker))
1367
+ );
1368
+ }
1369
+
1195
1370
  _emitTypeTest(targetGoType) {
1371
+ targetGoType = this._resolveTestTarget(targetGoType);
1196
1372
  if (!targetGoType) {
1197
1373
  this.pushInstruction("drop");
1198
1374
  this.pushInstruction({ op: "i32.const", value: 1 });
@@ -1211,31 +1387,7 @@ export class StmtsEmitter {
1211
1387
  }
1212
1388
 
1213
1389
  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");
1390
+ this._emitInterfaceTypeTest(targetGoType);
1239
1391
  return;
1240
1392
  }
1241
1393
 
@@ -1247,6 +1399,12 @@ export class StmtsEmitter {
1247
1399
  return;
1248
1400
  }
1249
1401
 
1402
+ if (this._isMethodNamedRef(targetGoType)) {
1403
+ const box = this.mod.getIfaceBoxType(targetGoType);
1404
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
1405
+ return;
1406
+ }
1407
+
1250
1408
  if (isFuncType(targetGoType, this.mod.checker)) {
1251
1409
  const closureInfo = this.mod.getClosureType(targetGoType);
1252
1410
  this._emitRefTestOrNil(targetGoType, closureInfo.typeIndex);
@@ -1265,7 +1423,9 @@ export class StmtsEmitter {
1265
1423
 
1266
1424
  if (isSliceType(targetGoType, this.mod.checker)) {
1267
1425
  const elem = this._getSliceElemType(targetGoType);
1268
- const sInfo = this.mod.getSliceType(elem);
1426
+ const sInfo = isTaggedSliceType(targetGoType, this.mod.checker)
1427
+ ? this.mod.getTaggedSliceType(targetGoType, elem)
1428
+ : this.mod.getSliceType(elem);
1269
1429
  if (sInfo) {
1270
1430
  this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
1271
1431
  return;
@@ -1276,7 +1436,7 @@ export class StmtsEmitter {
1276
1436
  const elem = this._getArrayElemType(targetGoType);
1277
1437
  const aInfo = this.mod.getArrayType(elem);
1278
1438
  if (aInfo) {
1279
- this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
1439
+ this._emitArrayTypeTest(targetGoType, aInfo);
1280
1440
  return;
1281
1441
  }
1282
1442
  }
@@ -1303,6 +1463,71 @@ export class StmtsEmitter {
1303
1463
  this.pushInstruction({ op: "i32.const", value: 1 });
1304
1464
  }
1305
1465
 
1466
+ // `[3]int` and `[2]int` share one wasm array type, so the test also compares
1467
+ // the length.
1468
+ _emitArrayTypeTest(goType, aInfo) {
1469
+ let t = goType?._assertType ?? goType;
1470
+ for (let i = 0; i < 16 && t && t.kind !== "array"; i++)
1471
+ t = t.underlying ?? this.mod.checker?.types?.get(t.name);
1472
+ const size = typeof t?.size === "number" ? t.size : null;
1473
+ if (size === null) {
1474
+ this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
1475
+ return;
1476
+ }
1477
+ const tmp = this.acquireTemp("anyref");
1478
+ this.pushInstruction({ op: "local.tee", index: tmp });
1479
+ this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
1480
+ this.pushInstruction({ op: "if", blockType: "i32" });
1481
+ this.pushInstruction({ op: "local.get", index: tmp });
1482
+ this.pushInstruction({ op: "ref.cast", typeIndex: aInfo.typeIndex });
1483
+ this.pushInstruction("array.len");
1484
+ this.pushInstruction({ op: "i32.const", value: size });
1485
+ this.pushInstruction("i32.eq");
1486
+ this.pushInstruction("else");
1487
+ this.pushInstruction({ op: "i32.const", value: 0 });
1488
+ this.pushInstruction("end");
1489
+ this.releaseTemp(tmp, "anyref");
1490
+ }
1491
+
1492
+ // Whether the interface value on the stack holds a type implementing the
1493
+ // non-empty interface: a `ref.test` over every implementing type (a typed
1494
+ // nil `*S` marker included).
1495
+ _emitInterfaceTypeTest(targetGoType) {
1496
+ const candidates = this.mod.getTypesImplementingInterface(targetGoType);
1497
+ if (candidates.length === 0) {
1498
+ this.pushInstruction("drop");
1499
+ this.pushInstruction({ op: "i32.const", value: 0 });
1500
+ return;
1501
+ }
1502
+ const anyTmp = this.acquireTemp("anyref");
1503
+ this.pushInstruction({ op: "local.set", index: anyTmp });
1504
+
1505
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1506
+ this.pushInstruction("ref.is_null");
1507
+ this.pushInstruction("i32.eqz");
1508
+
1509
+ for (let i = 0; i < candidates.length; i++) {
1510
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1511
+ this.pushInstruction({
1512
+ op: "ref.test",
1513
+ typeIndex: candidates[i].typeIndex,
1514
+ });
1515
+ if (candidates[i].nilTypeIndex !== undefined) {
1516
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1517
+ this.pushInstruction({
1518
+ op: "ref.test",
1519
+ typeIndex: candidates[i].nilTypeIndex,
1520
+ });
1521
+ this.pushInstruction("i32.or");
1522
+ }
1523
+ if (i > 0) {
1524
+ this.pushInstruction("i32.or");
1525
+ }
1526
+ }
1527
+ this.pushInstruction("i32.and");
1528
+ this.releaseTemp(anyTmp, "anyref");
1529
+ }
1530
+
1306
1531
  // `ref.test` for a map, func or `*T`, which also accepts the typed-nil
1307
1532
  // marker of the same Go type (see getNilBoxType).
1308
1533
  _emitRefTestOrNil(goType, typeIndex) {
@@ -1337,10 +1562,19 @@ export class StmtsEmitter {
1337
1562
  this.releaseTemp(tmp, "anyref");
1338
1563
  }
1339
1564
 
1565
+ // An interface target keeps the value as is; a boxed defined func or map
1566
+ // type is unboxed. True when the cast is done.
1567
+ _emitCastNoRepack(targetGoType) {
1568
+ if (isInterfaceType(targetGoType, this.mod.checker)) return true;
1569
+ if (!this._isMethodNamedRef(targetGoType)) return false;
1570
+ this._emitUnbox(
1571
+ this.mod.getIfaceBoxType(this._resolveTestTarget(targetGoType)),
1572
+ );
1573
+ return true;
1574
+ }
1575
+
1340
1576
  _emitTypeCast(targetGoType, targetWType) {
1341
- if (isInterfaceType(targetGoType, this.mod.checker)) {
1342
- return;
1343
- }
1577
+ if (this._emitCastNoRepack(targetGoType)) return;
1344
1578
 
1345
1579
  if (isBareStringType(targetGoType, this.mod.checker)) {
1346
1580
  this.pushInstruction("extern.convert_any");
@@ -1632,6 +1866,7 @@ export class StmtsEmitter {
1632
1866
  const ptrTmp = this.acquireTemp(ptrWType);
1633
1867
  this.emitExpr(operand, ptrWType);
1634
1868
  this.pushInstruction({ op: "local.set", index: ptrTmp });
1869
+ this._emitNilGuard(ptrTmp, operandGoType);
1635
1870
 
1636
1871
  this.pushInstruction({ op: "local.get", index: ptrTmp });
1637
1872
  this.pushInstruction({
@@ -1770,6 +2005,7 @@ export class StmtsEmitter {
1770
2005
  const ptrTmp = this.acquireTemp(ptrWType);
1771
2006
  this.emitExpr(operand, ptrWType);
1772
2007
  this.pushInstruction({ op: "local.set", index: ptrTmp });
2008
+ this._emitNilGuard(ptrTmp, operandGoType);
1773
2009
 
1774
2010
  this.pushInstruction({ op: "local.get", index: ptrTmp });
1775
2011
  this.pushInstruction({
@@ -2417,6 +2653,7 @@ export class StmtsEmitter {
2417
2653
  typeIndex: mapInfo.entryTypeIndex,
2418
2654
  fieldIndex: 0,
2419
2655
  });
2656
+ this._emitCopyMapEntry(keyType, mapInfo.keyWType);
2420
2657
  if (this.resolveLocal(lhs[0].name)) {
2421
2658
  this.pushInstruction({
2422
2659
  op: "local.set",
@@ -2438,20 +2675,7 @@ export class StmtsEmitter {
2438
2675
  typeIndex: mapInfo.entryTypeIndex,
2439
2676
  fieldIndex: 1,
2440
2677
  });
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
- }
2678
+ this._emitCopyMapEntry(valType, mapInfo.valWType);
2455
2679
  if (this.resolveLocal(lhs[1].name)) {
2456
2680
  this.pushInstruction({
2457
2681
  op: "local.set",
@@ -2551,6 +2775,15 @@ export class StmtsEmitter {
2551
2775
  });
2552
2776
  }
2553
2777
  this.releaseTemp(sliceTmp, sliceWType);
2778
+ } else if (!hasVal && !hasCallOrReceive(iterExpr)) {
2779
+ // Ranging over an array for its indexes only does not evaluate it
2780
+ // (`range p` on a nil array pointer is valid): the length is the
2781
+ // type's.
2782
+ this.pushInstruction({
2783
+ op: "i32.const",
2784
+ value: this._getArraySize(isPtrToArr ? iterType.base : iterType),
2785
+ });
2786
+ this.pushInstruction({ op: "local.set", index: lenTmp });
2554
2787
  } else {
2555
2788
  this.emitExpr(iterExpr, arrWType);
2556
2789
  // Go ranges over a copy of an array (the range expression is
@@ -3134,16 +3367,7 @@ export class StmtsEmitter {
3134
3367
  defaultIdx = i;
3135
3368
  continue;
3136
3369
  }
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
- }
3370
+ this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
3147
3371
  // The innermost block ends the first clause, so clause i is depth i.
3148
3372
  this.pushInstruction({ op: "br_if", depth: i });
3149
3373
  }
@@ -3162,6 +3386,26 @@ export class StmtsEmitter {
3162
3386
  this.pushInstruction("end");
3163
3387
  }
3164
3388
 
3389
+ // Leaves an i32 on the stack: whether any expression of a case list matches.
3390
+ // Later expressions are not evaluated once one matches, as in Go.
3391
+ _emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType) {
3392
+ for (let j = 0; j < vals.length; j++) {
3393
+ if (stmt.tag) {
3394
+ this.pushInstruction({ op: "local.get", index: tagTmp });
3395
+ this.emitExpr(vals[j], tagWType);
3396
+ this._emitElemEq(tagGoType, tagWType);
3397
+ } else {
3398
+ this.emitExpr(vals[j], "i32");
3399
+ }
3400
+ if (j < vals.length - 1) {
3401
+ this.pushInstruction({ op: "if", blockType: "i32" });
3402
+ this.pushInstruction({ op: "i32.const", value: 1 });
3403
+ this.pushInstruction("else");
3404
+ }
3405
+ }
3406
+ for (let j = 0; j < vals.length - 1; j++) this.pushInstruction("end");
3407
+ }
3408
+
3165
3409
  emitSwitchStmt(stmt) {
3166
3410
  if (
3167
3411
  (stmt.cases ?? []).some((c) => {
@@ -3202,16 +3446,7 @@ export class StmtsEmitter {
3202
3446
  let ifCount = 0;
3203
3447
  for (const c of nonDefaultCases) {
3204
3448
  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
- }
3449
+ this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
3215
3450
  this.pushInstruction({ op: "if", blockType: "void" });
3216
3451
  ifCount++;
3217
3452
 
@@ -3233,6 +3468,17 @@ export class StmtsEmitter {
3233
3468
  this.pushInstruction("end");
3234
3469
  }
3235
3470
 
3471
+ // Wraps the value on the stack in a box when the just-declared local `name`
3472
+ // is captured and mutated.
3473
+ _emitBoxIfCaptured(name) {
3474
+ const info = this.locals.get(name);
3475
+ if (info?.isBoxed)
3476
+ this.pushInstruction({
3477
+ op: "struct.new",
3478
+ typeIndex: info.boxInfo.typeIndex,
3479
+ });
3480
+ }
3481
+
3236
3482
  emitTypeSwitchStmt(stmt) {
3237
3483
  this.pushInstruction({ op: "block", blockType: "void" });
3238
3484
  this.pushControl("break", stmt.label);
@@ -3273,7 +3519,7 @@ export class StmtsEmitter {
3273
3519
  const targetGoType = isNilCase
3274
3520
  ? (stmt.expr._type ?? { kind: "basic", name: "any" })
3275
3521
  : types.length === 1
3276
- ? (types[0]._type ?? types[0])
3522
+ ? (types[0]._type ?? this._resolveTestTarget(types[0]))
3277
3523
  : { kind: "basic", name: "any" };
3278
3524
  const targetWType = this.toWasmType(targetGoType);
3279
3525
  const assignLocal = this.allocLocal(
@@ -3293,6 +3539,7 @@ export class StmtsEmitter {
3293
3539
  } else {
3294
3540
  this.pushInstruction({ op: "local.get", index: tswTmp });
3295
3541
  }
3542
+ this._emitBoxIfCaptured(stmt.assign);
3296
3543
  this.pushInstruction({ op: "local.set", index: assignLocal });
3297
3544
  }
3298
3545
 
@@ -3308,6 +3555,7 @@ export class StmtsEmitter {
3308
3555
  name: "any",
3309
3556
  });
3310
3557
  this.pushInstruction({ op: "local.get", index: tswTmp });
3558
+ this._emitBoxIfCaptured(stmt.assign);
3311
3559
  this.pushInstruction({ op: "local.set", index: assignLocal });
3312
3560
  }
3313
3561
  this._emitClauseBody(defaultCase.stmts);
@@ -3510,6 +3758,8 @@ export class StmtsEmitter {
3510
3758
  }
3511
3759
 
3512
3760
  emitBranchStmt(stmt) {
3761
+ const alias = stmt.label && this._labelAliases?.get(stmt.label);
3762
+ if (alias) stmt = { ...stmt, label: alias };
3513
3763
  if (this._tryEmitYieldBranch(stmt)) return;
3514
3764
  const isContinue = stmt.keyword === "continue";
3515
3765
  const depth = this.resolveBranchDepth(stmt.label, isContinue);