gofront 1.9.0 → 1.9.2

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 (43) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  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/cli/minifier.js +20 -6
  22. package/src/compiler.js +6 -1
  23. package/src/lexer/lexer.js +1 -0
  24. package/src/link/stdlib.js +1 -0
  25. package/src/link/type-names.js +72 -16
  26. package/src/lower/embedding.js +43 -15
  27. package/src/lower/fmt-formatters.js +38 -14
  28. package/src/lower/functions.js +223 -7
  29. package/src/lower/gen-types.js +21 -2
  30. package/src/lower/index.js +7 -1
  31. package/src/lower/json-codecs.js +114 -6
  32. package/src/parser/declarations.js +4 -2
  33. package/src/parser/expressions.js +25 -41
  34. package/src/parser/statements.js +107 -0
  35. package/src/parser/types.js +11 -0
  36. package/src/typechecker/assignability.js +94 -12
  37. package/src/typechecker/constants.js +124 -2
  38. package/src/typechecker/expressions.js +171 -17
  39. package/src/typechecker/index.js +158 -29
  40. package/src/typechecker/operand-text.js +1 -0
  41. package/src/typechecker/resolve.js +308 -39
  42. package/src/typechecker/statements.js +171 -21
  43. package/src/typechecker/types.js +84 -2
@@ -213,6 +213,35 @@ export class FunctionEmitter {
213
213
  return targets;
214
214
  }
215
215
 
216
+ // Outer stacked labels (`a: b: for`) alias the innermost one. Collected
217
+ // once per root function so the yield closures of a range-over-func loop
218
+ // (emitted by their own emitter) resolve them too.
219
+ _stackedLabelAlias(label) {
220
+ const root = this.rootFuncDecl;
221
+ if (root && !root._labelAliases) {
222
+ const aliases = new Map();
223
+ const scan = (node) => {
224
+ if (!node || typeof node !== "object") return;
225
+ if (Array.isArray(node)) {
226
+ for (const item of node) scan(item);
227
+ return;
228
+ }
229
+ if (node.kind === "LabeledStmt" && node.body?.kind === "LabeledStmt") {
230
+ let inner = node.body;
231
+ while (inner.body?.kind === "LabeledStmt") inner = inner.body;
232
+ aliases.set(node.label, inner.label);
233
+ }
234
+ for (const k of Object.keys(node)) {
235
+ if (k.startsWith("_")) continue;
236
+ scan(node[k]);
237
+ }
238
+ };
239
+ scan(root.body);
240
+ root._labelAliases = aliases;
241
+ }
242
+ return root?._labelAliases?.get(label) ?? this._labelAliases?.get(label);
243
+ }
244
+
216
245
  _findLabelTarget(label, role) {
217
246
  for (const target of this.rootFuncDecl?._labelTargets?.values() ?? []) {
218
247
  if (target.label === label && target.role === role) return target;
@@ -1434,31 +1463,9 @@ export class FunctionEmitter {
1434
1463
  typeIndex: cand.testTypeIndex,
1435
1464
  fieldIndex: 0,
1436
1465
  });
1437
- if (cand.embedPath) {
1438
- for (const step of cand.embedPath) {
1439
- if (step.castTypeIndex) {
1440
- this.pushInstruction({
1441
- op: "ref.cast",
1442
- typeIndex: step.castTypeIndex,
1443
- });
1444
- }
1445
- this.pushInstruction({
1446
- op: "struct.get",
1447
- typeIndex: step.parentTypeIndex,
1448
- fieldIndex: step.fieldIndex,
1449
- });
1450
- }
1451
- }
1466
+ if (cand.embedPath) this._emitEmbedPath(cand);
1452
1467
  } else {
1453
- if (cand.embedPath) {
1454
- for (const step of cand.embedPath) {
1455
- this.pushInstruction({
1456
- op: "struct.get",
1457
- typeIndex: step.parentTypeIndex,
1458
- fieldIndex: step.fieldIndex,
1459
- });
1460
- }
1461
- }
1468
+ if (cand.embedPath) this._emitEmbedPath(cand);
1462
1469
  if (cand.needsValueDeref && cand.structInfo) {
1463
1470
  this.emitCloneStruct(cand.structInfo, cand.targetRecvWType);
1464
1471
  }
@@ -1469,7 +1476,10 @@ export class FunctionEmitter {
1469
1476
  this.pushInstruction({ op: "local.get", index: p });
1470
1477
  }
1471
1478
 
1472
- this.pushInstruction({ op: "call", funcIndex: cand.funcIndex });
1479
+ this.pushInstruction({
1480
+ op: "call",
1481
+ funcIndex: cand.selfDispatch ? fn._globalFuncIndex : cand.funcIndex,
1482
+ });
1473
1483
  this.pushInstruction("return");
1474
1484
  this.pushInstruction("end");
1475
1485
  }
@@ -1477,12 +1487,49 @@ export class FunctionEmitter {
1477
1487
  emitMiss("interface conversion: nil or unmatched type for method call");
1478
1488
  }
1479
1489
 
1490
+ // Walks `cand.embedPath` from the embedder on the stack to the embedded
1491
+ // value. A step through a nil embedded pointer panics, except that the last
1492
+ // step may be nil for a pointer-receiver method (`nilCheckLast` is unset).
1493
+ _emitEmbedPath(cand) {
1494
+ const steps = cand.embedPath;
1495
+ for (const [i, step] of steps.entries()) {
1496
+ if (step.castTypeIndex) {
1497
+ this.pushInstruction({ op: "ref.cast", typeIndex: step.castTypeIndex });
1498
+ }
1499
+ this.pushInstruction({
1500
+ op: "struct.get",
1501
+ typeIndex: step.parentTypeIndex,
1502
+ fieldIndex: step.fieldIndex,
1503
+ });
1504
+ if (step.ptrWType && (i < steps.length - 1 || cand.nilCheckLast))
1505
+ this._emitNilPanicIfNull(step.ptrWType);
1506
+ }
1507
+ }
1508
+
1509
+ // Panics with a nil dereference when the reference on the stack is null,
1510
+ // leaving it on the stack otherwise.
1511
+ _emitNilPanicIfNull(wType) {
1512
+ const tmp = this.acquireTemp(wType);
1513
+ this.pushInstruction({ op: "local.tee", index: tmp });
1514
+ this.pushInstruction("ref.is_null");
1515
+ this.pushInstruction({ op: "if", blockType: "void" });
1516
+ this.emitPanic(
1517
+ "runtime error: invalid memory address or nil pointer dereference",
1518
+ );
1519
+ this.pushInstruction("end");
1520
+ this.pushInstruction({ op: "local.get", index: tmp });
1521
+ this.releaseTemp(tmp, wType);
1522
+ }
1523
+
1480
1524
  // A typed nil `*S` inside the interface is a marker object (see
1481
1525
  // getNilBoxType): a pointer-receiver method runs with a nil receiver, as in
1482
1526
  // Go; a value-receiver or promoted method must read through the nil pointer.
1527
+ // A promoted candidate tests the outer struct but carries the embedded
1528
+ // struct's info, so it is recognised by its embed path.
1483
1529
  _emitNilPtrCandidate(cand, fn, recvLocal) {
1484
1530
  if (cand.isBoxedValue || !cand.structInfo) return;
1485
- if (cand.testTypeIndex !== cand.structInfo.typeIndex) return;
1531
+ if (!cand.embedPath && cand.testTypeIndex !== cand.structInfo.typeIndex)
1532
+ return;
1486
1533
  const nilBox = this.mod.getNilBoxType({
1487
1534
  kind: "pointer",
1488
1535
  base: { kind: "named", name: cand.recvTypeName },
@@ -104,6 +104,7 @@ function _zeroFieldConst(f, mod) {
104
104
  const w = f.wType;
105
105
  if (w === "i64") return [{ op: "i64.const", value: 0n }];
106
106
  if (w === "f32" || w === "f64") return [{ op: `${w}.const`, value: 0 }];
107
+ if (w === "v128") return [{ op: "v128.const", bytes: new Array(16).fill(0) }];
107
108
  if (w === "externref") return [{ op: "ref.null", heapType: "extern" }];
108
109
  if (w === "anyref") return [{ op: "ref.null", heapType: "any" }];
109
110
  if (typeof w !== "object" || w === null)
@@ -259,6 +259,7 @@ export function compileWasmModule(
259
259
  _isClosure: true,
260
260
  _isYieldClosure: Boolean(node._isYieldClosure),
261
261
  _iterStateName: node._iterStateName ?? null,
262
+ _forStmt: node._forStmt ?? null,
262
263
  _funcLit: node,
263
264
  _rootFuncDecl: rootFn,
264
265
  _srcFile: rootFn?._srcFile ?? null,
@@ -701,6 +702,13 @@ export function compileWasmModule(
701
702
  ),
702
703
  );
703
704
  }
705
+ // fmt's %p identity of a reference in an `any`: a defined map or func type
706
+ // is boxed there, so the helper hands the host the boxed reference.
707
+ if (funcDecls.some((fn) => fn.intrinsic === "fmt_ptr_id")) {
708
+ funcDecls.push(
709
+ helper("__any_ref", [{ name: "a", type: ANY_T }], ANY_T, "any_ref"),
710
+ );
711
+ }
704
712
  if (mod.hasErrorType) {
705
713
  const errPtr = {
706
714
  kind: "pointer",
@@ -576,6 +576,7 @@ export class ModuleEmitter {
576
576
  const known = this.taggedSliceTypes.get(key);
577
577
  if (known) return known;
578
578
  const base = this.getSliceType(elemGoType);
579
+ const root = this.getTaggedSliceRoot(base);
579
580
  const tagIndex = this.taggedSliceTypes.size + 1;
580
581
  const tagTypeIndex = this._getTotalTypeCount();
581
582
  this._pushType({
@@ -589,7 +590,7 @@ export class ModuleEmitter {
589
590
  this._pushType({
590
591
  form: "struct",
591
592
  sub: true,
592
- superTypeIndex: base.typeIndex,
593
+ superTypeIndex: root.typeIndex,
593
594
  fields: [
594
595
  ...base.typeEntry.fields,
595
596
  {
@@ -603,6 +604,23 @@ export class ModuleEmitter {
603
604
  return info;
604
605
  }
605
606
 
607
+ // The common supertype of every tagged subtype of the slice type `base`. An
608
+ // unnamed `[]int` in an interface is the bare slice type, which every
609
+ // tagged subtype (`type S []int`) also passes `ref.test` for; testing
610
+ // "base and not root" is what keeps `any(S{}).([]int)` from matching.
611
+ getTaggedSliceRoot(base) {
612
+ if (base.tagRoot) return base.tagRoot;
613
+ const typeIndex = this._getTotalTypeCount();
614
+ this._pushType({
615
+ form: "struct",
616
+ sub: true,
617
+ superTypeIndex: base.typeIndex,
618
+ fields: [...base.typeEntry.fields],
619
+ });
620
+ base.tagRoot = { typeIndex };
621
+ return base.tagRoot;
622
+ }
623
+
606
624
  getSliceTypeByIndex(typeIndex) {
607
625
  return this.sliceTypesByIndex?.get(typeIndex) ?? null;
608
626
  }
@@ -1330,61 +1348,60 @@ export class ModuleEmitter {
1330
1348
  }
1331
1349
 
1332
1350
  // Promoted methods on sName
1333
- if (sInfo.embeds && sInfo.embeds.length > 0) {
1334
- for (const embed of sInfo.embeds) {
1335
- const embedType = embed.typeName ?? embed.name;
1336
- const embedMethodName = `${embedType}.${methodName}`;
1337
- if (this.funcMap.has(embedMethodName)) {
1338
- const fIdx = this.resolveFuncIndex(embedMethodName);
1339
- const paramGoTypes = this.getFuncParamGoTypes(embedMethodName);
1340
- const paramWTypes = this.getFuncParamTypes(embedMethodName);
1341
- const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
1342
- const embedSInfo = this.getStructType(embedType);
1343
-
1344
- const ptrKey = `ptr:${sName}`;
1345
- if (!seenKeys.has(ptrKey)) {
1346
- seenKeys.add(ptrKey);
1347
- candidates.push({
1348
- recvTypeName: sName,
1349
- testTypeIndex: sInfo.typeIndex,
1350
- isBoxedValue: false,
1351
- needsValueDeref: !isPtrRecv,
1352
- embedPath: [
1353
- {
1354
- parentTypeIndex: sInfo.typeIndex,
1355
- fieldIndex: embed.fieldIndex,
1356
- },
1357
- ],
1358
- funcIndex: fIdx,
1359
- targetParamTypes: paramWTypes,
1360
- targetRecvWType: paramWTypes[0],
1361
- structInfo: embedSInfo,
1362
- });
1363
- }
1351
+ const promoted = this._findPromotedMethod(sInfo, methodName);
1352
+ if (promoted?.iface) {
1353
+ this._addIfaceEmbedCandidates(
1354
+ candidates,
1355
+ seenKeys,
1356
+ sName,
1357
+ sInfo,
1358
+ promoted.embedPath,
1359
+ );
1360
+ } else if (promoted) {
1361
+ const { embedType, embedPath, viaPointer } = promoted;
1362
+ const embedMethodName = `${embedType}.${methodName}`;
1363
+ const fIdx = this.resolveFuncIndex(embedMethodName);
1364
+ const paramGoTypes = this.getFuncParamGoTypes(embedMethodName);
1365
+ const paramWTypes = this.getFuncParamTypes(embedMethodName);
1366
+ const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
1367
+ const embedSInfo = this.getStructType(embedType);
1368
+
1369
+ const ptrKey = `ptr:${sName}`;
1370
+ if (!seenKeys.has(ptrKey)) {
1371
+ seenKeys.add(ptrKey);
1372
+ candidates.push({
1373
+ recvTypeName: sName,
1374
+ testTypeIndex: sInfo.typeIndex,
1375
+ isBoxedValue: false,
1376
+ needsValueDeref: !isPtrRecv,
1377
+ embedPath,
1378
+ nilCheckLast: !isPtrRecv,
1379
+ funcIndex: fIdx,
1380
+ targetParamTypes: paramWTypes,
1381
+ targetRecvWType: paramWTypes[0],
1382
+ structInfo: embedSInfo,
1383
+ });
1384
+ }
1364
1385
 
1365
- if (!isPtrRecv) {
1366
- const box = this.getIfaceBoxType({ kind: "named", name: sName });
1367
- const valKey = `val:${sName}`;
1368
- if (box && !seenKeys.has(valKey)) {
1369
- seenKeys.add(valKey);
1370
- candidates.push({
1371
- recvTypeName: sName,
1372
- testTypeIndex: box.typeIndex,
1373
- isBoxedValue: true,
1374
- needsValueDeref: false,
1375
- embedPath: [
1376
- {
1377
- parentTypeIndex: sInfo.typeIndex,
1378
- fieldIndex: embed.fieldIndex,
1379
- },
1380
- ],
1381
- funcIndex: fIdx,
1382
- targetParamTypes: paramWTypes,
1383
- targetRecvWType: paramWTypes[0],
1384
- structInfo: embedSInfo,
1385
- });
1386
- }
1387
- }
1386
+ // A pointer-receiver method is in the value's method set only
1387
+ // when it is reached through an embedded pointer.
1388
+ if (!isPtrRecv || viaPointer) {
1389
+ const box = this.getIfaceBoxType({ kind: "named", name: sName });
1390
+ const valKey = `val:${sName}`;
1391
+ if (box && !seenKeys.has(valKey)) {
1392
+ seenKeys.add(valKey);
1393
+ candidates.push({
1394
+ recvTypeName: sName,
1395
+ testTypeIndex: box.typeIndex,
1396
+ isBoxedValue: true,
1397
+ needsValueDeref: false,
1398
+ embedPath,
1399
+ nilCheckLast: !isPtrRecv,
1400
+ funcIndex: fIdx,
1401
+ targetParamTypes: paramWTypes,
1402
+ targetRecvWType: paramWTypes[0],
1403
+ structInfo: embedSInfo,
1404
+ });
1388
1405
  }
1389
1406
  }
1390
1407
  }
@@ -1495,6 +1512,116 @@ export class ModuleEmitter {
1495
1512
  return candidates;
1496
1513
  }
1497
1514
 
1515
+ // Whether `goType`, the type of an embedded field, is an interface whose
1516
+ // method set has `methodName`.
1517
+ embeddedInterfaceHas(goType, methodName) {
1518
+ let t = goType;
1519
+ for (let i = 0; i < 8 && t; i++) {
1520
+ if (t.kind === "TypeName" || t.kind === "Ident") {
1521
+ const named = this.checker?.types?.get(t.name);
1522
+ if (!named) return t.name === "error" && methodName === "Error";
1523
+ t = named;
1524
+ } else if (t.kind === "named") {
1525
+ t = t.underlying ?? this.checker?.types?.get(t.name)?.underlying;
1526
+ } else if (t.kind === "interface") {
1527
+ return t.methods?.has?.(methodName) ?? false;
1528
+ } else if (t.kind === "InterfaceType") {
1529
+ return (t.methods ?? []).some((m) => (m.name ?? m) === methodName);
1530
+ } else {
1531
+ return false;
1532
+ }
1533
+ }
1534
+ return false;
1535
+ }
1536
+
1537
+ // Dispatch candidates for a method `sName` promotes from an embedded
1538
+ // interface: the embedded value is loaded and dispatched again through the
1539
+ // same dispatcher (`selfDispatch`).
1540
+ _addIfaceEmbedCandidates(candidates, seenKeys, sName, sInfo, embedPath) {
1541
+ const base = {
1542
+ recvTypeName: sName,
1543
+ needsValueDeref: false,
1544
+ embedPath,
1545
+ selfDispatch: true,
1546
+ structInfo: sInfo,
1547
+ };
1548
+ const ptrKey = `ptr:${sName}`;
1549
+ if (!seenKeys.has(ptrKey)) {
1550
+ seenKeys.add(ptrKey);
1551
+ candidates.push({
1552
+ ...base,
1553
+ testTypeIndex: sInfo.typeIndex,
1554
+ isBoxedValue: false,
1555
+ });
1556
+ }
1557
+ const box = this.getIfaceBoxType({ kind: "named", name: sName });
1558
+ const valKey = `val:${sName}`;
1559
+ if (box && !seenKeys.has(valKey)) {
1560
+ seenKeys.add(valKey);
1561
+ candidates.push({
1562
+ ...base,
1563
+ testTypeIndex: box.typeIndex,
1564
+ isBoxedValue: true,
1565
+ });
1566
+ }
1567
+ }
1568
+
1569
+ // The shallowest embedded type of `sInfo` (any depth) that declares
1570
+ // `methodName`: its name, the field path to reach it from an `sInfo`
1571
+ // struct, and whether that path goes through an embedded pointer. Null
1572
+ // when two embeds at that depth declare it (ambiguous, as in Go).
1573
+ _findPromotedMethod(sInfo, methodName) {
1574
+ let level = [{ info: sInfo, path: [], viaPointer: false }];
1575
+ const seen = new Set([sInfo]);
1576
+ while (level.length > 0) {
1577
+ const next = [];
1578
+ const hits = [];
1579
+ for (const { info, path, viaPointer } of level) {
1580
+ for (const embed of info.embeds ?? []) {
1581
+ const embedType = embed.typeName ?? embed.name;
1582
+ const goType = info.fields?.[embed.fieldIndex]?.goType;
1583
+ const isPtr =
1584
+ goType?.kind === "PointerType" || goType?.kind === "pointer";
1585
+ const step = {
1586
+ path: [
1587
+ ...path,
1588
+ {
1589
+ parentTypeIndex: info.typeIndex,
1590
+ fieldIndex: embed.fieldIndex,
1591
+ // An embedded pointer may be nil: reading through it
1592
+ // is a recoverable nil dereference.
1593
+ ...(isPtr && {
1594
+ ptrWType: info.fields[embed.fieldIndex].wType,
1595
+ }),
1596
+ },
1597
+ ],
1598
+ viaPointer: viaPointer || isPtr,
1599
+ };
1600
+ if (this.embeddedInterfaceHas(goType, methodName)) {
1601
+ hits.push({ iface: true, embedPath: step.path, viaPointer: false });
1602
+ continue;
1603
+ }
1604
+ if (this.funcMap.has(`${embedType}.${methodName}`)) {
1605
+ hits.push({
1606
+ embedType,
1607
+ embedPath: step.path,
1608
+ viaPointer: step.viaPointer,
1609
+ });
1610
+ continue;
1611
+ }
1612
+ // A type reached twice at one depth stays twice in the next
1613
+ // level, so a diamond's method is ambiguous there too.
1614
+ const inner = this.structTypes.get(embedType);
1615
+ if (inner && !seen.has(inner)) next.push({ info: inner, ...step });
1616
+ }
1617
+ }
1618
+ if (hits.length > 0) return hits.length === 1 ? hits[0] : null;
1619
+ for (const n of next) seen.add(n.info);
1620
+ level = next;
1621
+ }
1622
+ return null;
1623
+ }
1624
+
1498
1625
  // A value-receiver method of a named scalar is also in the method set of
1499
1626
  // the pointer: `var g Getter = &x` holds a pointer box whose target is read
1500
1627
  // before the call.
@@ -1560,7 +1687,7 @@ export class ModuleEmitter {
1560
1687
  }
1561
1688
 
1562
1689
  // Check struct value (boxed)
1563
- if (this.checker?.implements?.(namedType, ifaceResolved)) {
1690
+ if (this._valueImplements(namedType, ifaceResolved)) {
1564
1691
  const box = this.getIfaceBoxType(namedType);
1565
1692
  if (box && !seen.has(box.typeIndex)) {
1566
1693
  seen.add(box.typeIndex);
@@ -1578,7 +1705,7 @@ export class ModuleEmitter {
1578
1705
  tVal.underlying?.kind !== "interface"
1579
1706
  ) {
1580
1707
  if (this.checker?.implements?.(tVal, ifaceResolved)) {
1581
- const box = this.getIfaceBoxType(tVal);
1708
+ const box = this._namedValueBox(tVal);
1582
1709
  if (box && !seen.has(box.typeIndex)) {
1583
1710
  seen.add(box.typeIndex);
1584
1711
  candidates.push({ name: tName, typeIndex: box.typeIndex });
@@ -1598,6 +1725,33 @@ export class ModuleEmitter {
1598
1725
  return candidates;
1599
1726
  }
1600
1727
 
1728
+ // The WebAssembly type a value of the named non-struct type `tVal` takes
1729
+ // inside an interface: a defined slice type is a tagged slice (see
1730
+ // getTaggedSliceType), anything else an interface box.
1731
+ _namedValueBox(tVal) {
1732
+ if (!isSliceType(tVal, this.checker)) return this.getIfaceBoxType(tVal);
1733
+ const elem = getSliceElemType(tVal, this.checker);
1734
+ return elem ? this.getTaggedSliceType(tVal, elem) : null;
1735
+ }
1736
+
1737
+ // Whether the struct value `namedType` implements `iface`, including when its
1738
+ // pointer-receiver methods are promoted through an embedded pointer, which
1739
+ // puts them in the value's method set (`struct{ *Inner }`).
1740
+ _valueImplements(namedType, iface) {
1741
+ const checker = this.checker;
1742
+ if (checker?.implements?.(namedType, iface)) return true;
1743
+ const ptr = { kind: "pointer", base: namedType };
1744
+ if (!checker?.implements?.(ptr, iface)) return false;
1745
+ const st = checker.resolveType(namedType.underlying);
1746
+ const methods = st?.methods?.size ? st.methods : namedType.methods;
1747
+ const names = [...(iface.methods?.keys() ?? [])];
1748
+ return names.every(
1749
+ (n) =>
1750
+ !methods?.get(n)?._ptrRecv ||
1751
+ checker._promotedThroughPointer(namedType, n),
1752
+ );
1753
+ }
1754
+
1601
1755
  // `*T` of a named scalar or slice is boxed apart from `T`.
1602
1756
  _addPtrNamedImplementor(tVal, tName, ifaceResolved, seen, candidates) {
1603
1757
  const ptrType = { kind: "pointer", base: tVal };
@@ -73,9 +73,20 @@ function rawTypeKey(t) {
73
73
  if (t.kind === "map" || t.kind === "MapType") {
74
74
  return `map_${typeKey(t.key)}_${typeKey(t.value)}`;
75
75
  }
76
- if (t.kind === "func" || t.kind === "FuncType") {
76
+ if (t.kind === "func") {
77
77
  const params = (t.params ?? []).map(typeKey).join("_");
78
- const rets = (t.returns ?? []).map(typeKey).join("_");
78
+ const rets = (t.returns ?? [])
79
+ .filter((r) => !(r?.kind === "basic" && r.name === "void"))
80
+ .map(typeKey)
81
+ .join("_");
82
+ return `fn_${params}_ret_${rets}`;
83
+ }
84
+ if (t.kind === "FuncType") {
85
+ const params = (t.params ?? []).map((p) => typeKey(p.type)).join("_");
86
+ const rt = t.returnType;
87
+ const rets = rt
88
+ ? (rt.kind === "TupleType" ? rt.types : [rt]).map(typeKey).join("_")
89
+ : "";
79
90
  return `fn_${params}_ret_${rets}`;
80
91
  }
81
92
  if (t.name) return t.name;
@@ -135,9 +146,32 @@ export function typeToTypeNode(t) {
135
146
  };
136
147
  }
137
148
  if (t.kind === "typeParam") return { kind: "TypeName", name: t.name };
149
+ // Without this a func type argument fell through to `any`, so a
150
+ // `map[string]V` built in `mk[func()]` was a `map[string]any`.
151
+ if (t.kind === "func") return funcTypeNode(t);
138
152
  return { kind: "TypeName", name: t.name ?? "any" };
139
153
  }
140
154
 
155
+ // A checker func type as a `FuncType` node (several results as a `TupleType`).
156
+ function funcTypeNode(t) {
157
+ // A variadic func lists its last parameter by element type.
158
+ const params = (t.params ?? []).map((p, i, all) => ({
159
+ name: "_",
160
+ type: typeToTypeNode(p),
161
+ variadic: !!t.variadic && i === all.length - 1,
162
+ }));
163
+ const rets = (t.returns ?? []).filter(
164
+ (r) => !(r?.kind === "basic" && r.name === "void"),
165
+ );
166
+ // Several results come as one tuple type or as a list.
167
+ const results = rets.length === 1 && rets[0]?.types ? rets[0].types : rets;
168
+ let returnType = null;
169
+ if (results.length === 1) returnType = typeToTypeNode(results[0]);
170
+ else if (results.length > 1)
171
+ returnType = { kind: "TupleType", types: results.map(typeToTypeNode) };
172
+ return { kind: "FuncType", params, returnType };
173
+ }
174
+
141
175
  // Replaces the type parameters inside a checker type (`[]E`, `map[K]V`,
142
176
  // `Box[E]`) by their type arguments; the result mixes checker types and the
143
177
  // AST type nodes of the substitution, which `typeKey` and `typeToTypeNode`
@@ -175,6 +175,10 @@ const WASM_INTRINSICS = {
175
175
  mod.getOrAddFuncImport("env", "fmt_ptr_id", [EXT], ["i32"]),
176
176
  emit: (e) => {
177
177
  e.pushInstruction({ op: "local.get", index: 0 });
178
+ e.pushInstruction({
179
+ op: "call",
180
+ funcIndex: e.mod.resolveFuncIndex("__any_ref"),
181
+ });
178
182
  e.pushInstruction("extern.convert_any");
179
183
  e.pushInstruction({
180
184
  op: "call",
@@ -407,6 +407,24 @@ export function goTypeKey(t) {
407
407
  if (t.kind === "map" || t.kind === "MapType") {
408
408
  return `map_${goTypeKey(t.key ?? t.keyType)}_${goTypeKey(t.value ?? t.val ?? t.elem ?? t.valueType ?? t.valType)}`;
409
409
  }
410
+ // Func types differ by signature (`[2]func()` is not `[2]func(int) bool`).
411
+ // A checker func lists the element type of a variadic parameter, like a
412
+ // parsed `FuncType`.
413
+ if (t.kind === "func" || t.kind === "FuncType") {
414
+ const parsed = t.kind === "FuncType";
415
+ const params = (t.params ?? []).map((p) => goTypeKey(parsed ? p.type : p));
416
+ const variadic = parsed ? t.params?.at(-1)?.variadic : t.variadic;
417
+ const rets = parsed
418
+ ? t.returnType
419
+ ? [t.returnType]
420
+ : []
421
+ : (t.returns ?? []).filter(
422
+ (r) => !(r?.kind === "basic" && r.name === "void"),
423
+ );
424
+ return `fn_${variadic ? "v" : ""}${params.join(",")}_${rets.map(goTypeKey).join(",")}`;
425
+ }
426
+ if (t.kind === "tuple" || t.kind === "TupleType")
427
+ return `tup_${(t.types ?? []).map(goTypeKey).join(",")}`;
410
428
  if (t.kind === "struct" || t.kind === "StructType") {
411
429
  if (t.name) return t.name;
412
430
  const rawFields = [];
@@ -480,6 +498,7 @@ function isReplaceableRef(goType, checker) {
480
498
  return (
481
499
  isSliceType(goType, checker) ||
482
500
  isMapType(goType, checker) ||
501
+ isComplex(goType) ||
483
502
  t?.kind === "pointer" ||
484
503
  t?.kind === "PointerType"
485
504
  );
@@ -523,8 +542,14 @@ export function isSliceType(goType, checker = null) {
523
542
  }
524
543
 
525
544
  // Element kinds whose slices share one WebAssembly representation (a slice of
526
- // i32), so `[]bool`, `[]byte` and `[]rune` cannot be told apart by `ref.test`.
545
+ // i32, or of i64 for the 64-bit integers), so `[]bool`, `[]byte`, `[]rune` and
546
+ // `[]int` vs `[]int64` cannot be told apart by `ref.test`.
527
547
  const TAGGED_SLICE_ELEMS = new Set([
548
+ "int",
549
+ "int64",
550
+ "uint",
551
+ "uint64",
552
+ "uintptr",
528
553
  "bool",
529
554
  "int8",
530
555
  "int16",
@@ -536,8 +561,8 @@ const TAGGED_SLICE_ELEMS = new Set([
536
561
  "rune",
537
562
  ]);
538
563
 
539
- // True for a defined slice type or a slice of booleans or integers of at
540
- // most 32 bits. In an
564
+ // True for a defined slice type, a slice of booleans or integers, or a slice
565
+ // of a method interface. In an
541
566
  // interface such a value carries a per-Go-type tag (see getTaggedSliceType).
542
567
  export function isTaggedSliceType(goType, checker = null) {
543
568
  if (!isSliceType(goType, checker)) return false;
@@ -560,6 +585,10 @@ export function isTaggedSliceType(goType, checker = null) {
560
585
  };
561
586
  else break;
562
587
  }
588
+ // A slice of a method interface (`[]error`) is a slice of `anyref` like
589
+ // `[]any`, and `[]fmt.Stringer` another one. `[]any` and `[]interface{}`
590
+ // are the same type, so neither is tagged.
591
+ if (isNonEmptyInterface(elem, checker)) return true;
563
592
  return elem?.kind === "basic" && TAGGED_SLICE_ELEMS.has(elem.name);
564
593
  }
565
594
 
@@ -26,13 +26,17 @@ function isStdlibCall(call) {
26
26
  }
27
27
 
28
28
  function packCallArgs(call) {
29
- if (call._multiForward || isStdlibCall(call)) return false;
29
+ // A call node can be reachable twice (`defer f(x)` lowers to a wrapper
30
+ // whose body lists the same call under `stmts` and `list`): pack it once.
31
+ if (call._variadicPacked || call._multiForward || isStdlibCall(call))
32
+ return false;
30
33
  const fn = variadicFuncType(call.func?._type);
31
34
  if (!fn) return false;
32
35
  const n = fn.params.length;
33
36
  const args = call.args ?? [];
34
37
  if (args.length === n && args[n - 1]?._spread) {
35
38
  args[n - 1]._spread = false;
39
+ call._variadicPacked = true;
36
40
  return true;
37
41
  }
38
42
  if (args.length < n - 1) return false;
@@ -62,6 +66,7 @@ function packCallArgs(call) {
62
66
  _col: call._col,
63
67
  },
64
68
  ];
69
+ call._variadicPacked = true;
65
70
  return true;
66
71
  }
67
72