gofront 1.9.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 (42) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/backend/js/expressions.js +329 -58
  4. package/src/backend/js/index.js +90 -10
  5. package/src/backend/js/runtime.js +51 -9
  6. package/src/backend/js/statements.js +193 -84
  7. package/src/backend/js/type-info.js +123 -8
  8. package/src/backend/shared/fmt-types.js +30 -1
  9. package/src/backend/wasm/emit-builtins.js +250 -9
  10. package/src/backend/wasm/emit-exprs.js +63 -22
  11. package/src/backend/wasm/emit-shared.js +4 -1
  12. package/src/backend/wasm/emit-stmts.js +239 -205
  13. package/src/backend/wasm/emit.js +73 -26
  14. package/src/backend/wasm/globals.js +1 -0
  15. package/src/backend/wasm/index.js +8 -0
  16. package/src/backend/wasm/module-emitter.js +211 -57
  17. package/src/backend/wasm/monomorph.js +36 -2
  18. package/src/backend/wasm/natives.js +4 -0
  19. package/src/backend/wasm/types.js +32 -3
  20. package/src/backend/wasm/variadic.js +6 -1
  21. package/src/compiler.js +6 -1
  22. package/src/lexer/lexer.js +1 -0
  23. package/src/link/stdlib.js +1 -0
  24. package/src/link/type-names.js +72 -16
  25. package/src/lower/embedding.js +43 -15
  26. package/src/lower/fmt-formatters.js +38 -14
  27. package/src/lower/functions.js +223 -7
  28. package/src/lower/gen-types.js +21 -2
  29. package/src/lower/index.js +7 -1
  30. package/src/lower/json-codecs.js +114 -6
  31. package/src/parser/declarations.js +4 -2
  32. package/src/parser/expressions.js +25 -41
  33. package/src/parser/statements.js +107 -0
  34. package/src/parser/types.js +11 -0
  35. package/src/typechecker/assignability.js +94 -12
  36. package/src/typechecker/constants.js +124 -2
  37. package/src/typechecker/expressions.js +171 -17
  38. package/src/typechecker/index.js +158 -29
  39. package/src/typechecker/operand-text.js +1 -0
  40. package/src/typechecker/resolve.js +308 -39
  41. package/src/typechecker/statements.js +171 -21
  42. package/src/typechecker/types.js +84 -2
@@ -720,7 +720,95 @@ export class StmtsEmitter {
720
720
  return idx;
721
721
  }
722
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
+
723
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
+ }
724
812
  const pending = this._pendingRangeBoxes ?? [];
725
813
  this._pendingRangeBoxes = [];
726
814
  for (const { name, idx, wType, goType } of pending) {
@@ -1350,21 +1438,28 @@ export class StmtsEmitter {
1350
1438
  // is the declared type, not its underlying one: a defined string type is
1351
1439
  // boxed apart from `string`, and `error` carries a method set.
1352
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;
1353
1444
  if ((t?.kind !== "TypeName" && t?.kind !== "Ident") || !t.name) return t;
1354
1445
  if (t.name === "nil" || t.name === "any" || t.name === "interface{}")
1355
1446
  return t;
1356
1447
  return this.mod.checker?.types?.get(t.name) ?? t;
1357
1448
  }
1358
1449
 
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)`).
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).
1361
1454
  _isMethodNamedRef(goType) {
1362
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;
1363
1461
  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
- );
1462
+ return isFuncType(t, this.mod.checker);
1368
1463
  }
1369
1464
 
1370
1465
  _emitTypeTest(targetGoType) {
@@ -1423,35 +1518,27 @@ export class StmtsEmitter {
1423
1518
 
1424
1519
  if (isSliceType(targetGoType, this.mod.checker)) {
1425
1520
  const elem = this._getSliceElemType(targetGoType);
1426
- const sInfo = isTaggedSliceType(targetGoType, this.mod.checker)
1427
- ? this.mod.getTaggedSliceType(targetGoType, elem)
1428
- : this.mod.getSliceType(elem);
1429
- if (sInfo) {
1430
- this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
1521
+ if (isTaggedSliceType(targetGoType, this.mod.checker)) {
1522
+ const tagged = this.mod.getTaggedSliceType(targetGoType, elem);
1523
+ this.pushInstruction({ op: "ref.test", typeIndex: tagged.typeIndex });
1431
1524
  return;
1432
1525
  }
1433
- }
1434
-
1435
- if (isArrayType(targetGoType, this.mod.checker)) {
1436
- const elem = this._getArrayElemType(targetGoType);
1437
- const aInfo = this.mod.getArrayType(elem);
1438
- if (aInfo) {
1439
- this._emitArrayTypeTest(targetGoType, aInfo);
1526
+ const sInfo = this.mod.getSliceType(elem);
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 });
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");
1440
1538
  return;
1441
1539
  }
1442
1540
  }
1443
1541
 
1444
- // A map lives in `any` as its bare map struct.
1445
- if (isMapType(targetGoType, this.mod.checker)) {
1446
- const { keyType, valType } = getMapKeyValTypes(
1447
- targetGoType,
1448
- this.mod.checker,
1449
- );
1450
- const mInfo = this.mod.getMapType(keyType, valType);
1451
- this._emitRefTestOrNil(targetGoType, mInfo.typeIndex);
1452
- return;
1453
- }
1454
-
1455
1542
  // Scalar types
1456
1543
  const box = this.mod.getIfaceBoxType(targetGoType);
1457
1544
  if (box) {
@@ -1463,32 +1550,6 @@ export class StmtsEmitter {
1463
1550
  this.pushInstruction({ op: "i32.const", value: 1 });
1464
1551
  }
1465
1552
 
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
1553
  // Whether the interface value on the stack holds a type implementing the
1493
1554
  // non-empty interface: a `ref.test` over every implementing type (a typed
1494
1555
  // nil `*S` marker included).
@@ -1616,28 +1677,6 @@ export class StmtsEmitter {
1616
1677
  }
1617
1678
  }
1618
1679
 
1619
- if (isArrayType(targetGoType, this.mod.checker)) {
1620
- const elem = this._getArrayElemType(targetGoType);
1621
- const aInfo = this.mod.getArrayType(elem);
1622
- if (aInfo) {
1623
- this.pushInstruction({
1624
- op: "ref.cast_null",
1625
- typeIndex: aInfo.typeIndex,
1626
- });
1627
- return;
1628
- }
1629
- }
1630
-
1631
- if (isMapType(targetGoType, this.mod.checker)) {
1632
- const { keyType, valType } = getMapKeyValTypes(
1633
- targetGoType,
1634
- this.mod.checker,
1635
- );
1636
- const mInfo = this.mod.getMapType(keyType, valType);
1637
- this._emitRefCastOrNil(targetGoType, mInfo.typeIndex);
1638
- return;
1639
- }
1640
-
1641
1680
  // Scalar types
1642
1681
  const box = this.mod.getIfaceBoxType(targetGoType);
1643
1682
  if (box) {
@@ -2256,7 +2295,9 @@ export class StmtsEmitter {
2256
2295
  const rangeExpr =
2257
2296
  stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init.rhs[0];
2258
2297
  const isAssign = stmt.init?.kind === "AssignStmt";
2259
- const lhs = stmt.init?.lhs ?? [];
2298
+ const lhs = isAssign
2299
+ ? this._bindRangeAssignTargets(stmt.init.lhs)
2300
+ : (stmt.init?.lhs ?? []);
2260
2301
  const iterExpr = rangeExpr.expr;
2261
2302
  const iterType = iterExpr._type;
2262
2303
 
@@ -2323,21 +2364,21 @@ export class StmtsEmitter {
2323
2364
 
2324
2365
  // Assign loop var if present
2325
2366
  if (idxLocalInfo) {
2326
- this.pushInstruction({ op: "local.get", index: idxTmp });
2327
- if (idxLocalInfo.type === "i64") {
2328
- this.pushInstruction("i64.extend_i32_s");
2329
- }
2330
- if (this.resolveLocal(lhs[0].name)) {
2331
- this.pushInstruction({
2332
- op: "local.set",
2333
- index: idxLocalInfo.index,
2334
- });
2335
- } else {
2336
- this.pushInstruction({
2337
- op: "global.set",
2338
- index: idxLocalInfo.index,
2339
- });
2340
- }
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
+ );
2341
2382
  }
2342
2383
 
2343
2384
  // Emit body
@@ -2448,38 +2489,30 @@ export class StmtsEmitter {
2448
2489
 
2449
2490
  // Assign index variable
2450
2491
  if (idxLocalInfo) {
2451
- this.pushInstruction({ op: "local.get", index: idxTmp });
2452
- this.pushInstruction("i64.extend_i32_s");
2453
- if (this.resolveLocal(lhs[0].name)) {
2454
- this.pushInstruction({
2455
- op: "local.set",
2456
- index: idxLocalInfo.index,
2457
- });
2458
- } else {
2459
- this.pushInstruction({
2460
- op: "global.set",
2461
- index: idxLocalInfo.index,
2462
- });
2463
- }
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
+ );
2464
2501
  }
2465
2502
 
2466
2503
  // Assign rune variable
2467
2504
  if (valLocalInfo) {
2468
- this.pushInstruction({ op: "local.get", index: runeTmp });
2469
- if (valLocalInfo.type === "i64") {
2470
- this.pushInstruction("i64.extend_i32_u");
2471
- }
2472
- if (this.resolveLocal(lhs[1].name)) {
2473
- this.pushInstruction({
2474
- op: "local.set",
2475
- index: valLocalInfo.index,
2476
- });
2477
- } else {
2478
- this.pushInstruction({
2479
- op: "global.set",
2480
- index: valLocalInfo.index,
2481
- });
2482
- }
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
+ );
2483
2516
  }
2484
2517
 
2485
2518
  // Body
@@ -2647,46 +2680,38 @@ export class StmtsEmitter {
2647
2680
 
2648
2681
  // Assign key variable
2649
2682
  if (keyLocalInfo) {
2650
- this.pushInstruction({ op: "local.get", index: entryTmp });
2651
- this.pushInstruction({
2652
- op: "struct.get",
2653
- typeIndex: mapInfo.entryTypeIndex,
2654
- fieldIndex: 0,
2655
- });
2656
- this._emitCopyMapEntry(keyType, mapInfo.keyWType);
2657
- if (this.resolveLocal(lhs[0].name)) {
2658
- this.pushInstruction({
2659
- op: "local.set",
2660
- index: keyLocalInfo.index,
2661
- });
2662
- } else {
2663
- this.pushInstruction({
2664
- op: "global.set",
2665
- index: keyLocalInfo.index,
2666
- });
2667
- }
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
+ );
2668
2697
  }
2669
2698
 
2670
2699
  // Assign val variable
2671
2700
  if (valLocalInfo) {
2672
- this.pushInstruction({ op: "local.get", index: entryTmp });
2673
- this.pushInstruction({
2674
- op: "struct.get",
2675
- typeIndex: mapInfo.entryTypeIndex,
2676
- fieldIndex: 1,
2677
- });
2678
- this._emitCopyMapEntry(valType, mapInfo.valWType);
2679
- if (this.resolveLocal(lhs[1].name)) {
2680
- this.pushInstruction({
2681
- op: "local.set",
2682
- index: valLocalInfo.index,
2683
- });
2684
- } else {
2685
- this.pushInstruction({
2686
- op: "global.set",
2687
- index: valLocalInfo.index,
2688
- });
2689
- }
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
+ );
2690
2715
  }
2691
2716
 
2692
2717
  // Body
@@ -2860,52 +2885,50 @@ export class StmtsEmitter {
2860
2885
 
2861
2886
  // Assign idx
2862
2887
  if (idxLocalInfo) {
2863
- this.pushInstruction({ op: "local.get", index: idxTmp });
2864
- if (idxLocalInfo.type === "i64") {
2865
- this.pushInstruction("i64.extend_i32_s");
2866
- }
2867
- if (this.resolveLocal(lhs[0].name)) {
2868
- this.pushInstruction({ op: "local.set", index: idxLocalInfo.index });
2869
- } else {
2870
- this.pushInstruction({
2871
- op: "global.set",
2872
- index: idxLocalInfo.index,
2873
- });
2874
- }
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
+ );
2875
2899
  }
2876
2900
 
2877
2901
  // Assign val
2878
2902
  if (valLocalInfo) {
2879
- this.pushInstruction({ op: "local.get", index: arrTmp });
2880
- this.pushInstruction({ op: "local.get", index: offTmp });
2881
- this.pushInstruction({ op: "local.get", index: idxTmp });
2882
- this.pushInstruction("i32.add");
2883
- this.pushInstruction({
2884
- op: "array.get",
2885
- typeIndex: arrInfo.typeIndex,
2886
- });
2887
- if (
2888
- isStructType(elemGoType, this.mod.checker, this.mod) &&
2889
- !isPointerToStruct(elemGoType, this.mod.checker, this.mod)
2890
- ) {
2891
- const sInfo = this._resolveStructInfo({ _type: elemGoType });
2892
- if (sInfo) {
2893
- this.emitCloneStruct(sInfo, arrInfo.elemWType);
2894
- }
2895
- } else if (
2896
- elemGoType?.kind !== "pointer" &&
2897
- isArrayType(elemGoType, this.mod.checker)
2898
- ) {
2899
- this.emitCopyArray(elemGoType);
2900
- }
2901
- if (this.resolveLocal(lhs[1].name)) {
2902
- this.pushInstruction({ op: "local.set", index: valLocalInfo.index });
2903
- } else {
2904
- this.pushInstruction({
2905
- op: "global.set",
2906
- index: valLocalInfo.index,
2907
- });
2908
- }
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
+ );
2909
2932
  }
2910
2933
 
2911
2934
  // Body
@@ -3627,13 +3650,24 @@ export class StmtsEmitter {
3627
3650
  this.pushInstruction("i64.eq");
3628
3651
  this.pushInstruction({ op: "if", blockType: "void" });
3629
3652
  if (target.role !== "break" || stmt.label !== target.label) {
3630
- 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) {
3631
3668
  this._propagateYieldExit(BigInt(target.tag));
3632
3669
  } else {
3633
- const depth = this.resolveBranchDepth(
3634
- target.label,
3635
- target.role === "continue",
3636
- );
3670
+ const depth = this.resolveBranchDepth(target.label, isContinue);
3637
3671
  this.pushInstruction({ op: "br", depth });
3638
3672
  }
3639
3673
  }
@@ -3758,7 +3792,7 @@ export class StmtsEmitter {
3758
3792
  }
3759
3793
 
3760
3794
  emitBranchStmt(stmt) {
3761
- const alias = stmt.label && this._labelAliases?.get(stmt.label);
3795
+ const alias = stmt.label && this._stackedLabelAlias(stmt.label);
3762
3796
  if (alias) stmt = { ...stmt, label: alias };
3763
3797
  if (this._tryEmitYieldBranch(stmt)) return;
3764
3798
  const isContinue = stmt.keyword === "continue";