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
@@ -13,6 +13,7 @@ import {
13
13
  import {
14
14
  is64BitInt,
15
15
  isComplex,
16
+ isComplex64,
16
17
  isTypedArraySlice,
17
18
  } from "../../typechecker/types.js";
18
19
 
@@ -421,10 +422,25 @@ export const statementGenMethods = {
421
422
  labels.push(body.label);
422
423
  body = body.body;
423
424
  }
424
- if (body?.kind === "ForStmt") body.label = labels[labels.length - 1];
425
+ if (body?.kind === "ForStmt") {
426
+ body.label = labels[labels.length - 1];
427
+ body.labels = labels;
428
+ }
425
429
  const hoisted = this._hoistForInit(body);
426
430
  for (const label of labels) this.line(`${label}:`);
427
- this.genStmt(body);
431
+ // Labels in scope of the JS function being emitted (see
432
+ // `_emitIterTargetDispatch`).
433
+ const prevLabels = this._jsLabels ?? [];
434
+ const loop = body?.kind === "ForStmt";
435
+ this._jsLabels = [
436
+ ...prevLabels,
437
+ ...labels.map((name) => ({ name, loop })),
438
+ ];
439
+ try {
440
+ this.genStmt(body);
441
+ } finally {
442
+ this._jsLabels = prevLabels;
443
+ }
428
444
  if (hoisted) this._closeHoistedForInit();
429
445
  break;
430
446
  }
@@ -767,6 +783,14 @@ export const statementGenMethods = {
767
783
  if (syncs) for (const s of syncs) this.line(`${s};`);
768
784
  },
769
785
 
786
+ // An untyped constant operand of a 64-bit compound assignment is a Number
787
+ // in the output; the operator needs it as a BigInt.
788
+ _untypedTo64(rhsVal, rhsNode) {
789
+ if (rhsNode?._type?.kind !== "untyped" || rhsVal.endsWith("n"))
790
+ return rhsVal;
791
+ return /^-?\d+$/.test(rhsVal) ? `${rhsVal}n` : `BigInt(${rhsVal})`;
792
+ },
793
+
770
794
  _genAssignStmt(stmt) {
771
795
  this._markIndexLvalues(stmt.lhs);
772
796
  if (stmt.op !== "=") for (const l of stmt.lhs) this._markDerefOpTarget(l);
@@ -841,14 +865,7 @@ export const statementGenMethods = {
841
865
  const binOp = stmt.op.slice(0, -1);
842
866
  const is64Val = is64BitInt(stmt.lhs[0]._mapValueType);
843
867
  let rhsVal = active[0].r;
844
- if (
845
- is64Val &&
846
- stmt.rhs[0]?._type?.kind === "untyped" &&
847
- !rhsVal.endsWith("n") &&
848
- /^-?\d+$/.test(rhsVal)
849
- ) {
850
- rhsVal = `${rhsVal}n`;
851
- }
868
+ if (is64Val) rhsVal = this._untypedTo64(rhsVal, stmt.rhs[0]);
852
869
  const rhsExpr = is64Val
853
870
  ? this._gen64BitBinary(
854
871
  binOp,
@@ -881,14 +898,7 @@ export const statementGenMethods = {
881
898
  }
882
899
  const is64 = is64BitInt(stmt.lhs[0]._type);
883
900
  let rhsVal = active[0].r;
884
- if (
885
- is64 &&
886
- stmt.rhs[0]?._type?.kind === "untyped" &&
887
- !rhsVal.endsWith("n") &&
888
- /^-?\d+$/.test(rhsVal)
889
- ) {
890
- rhsVal = `${rhsVal}n`;
891
- }
901
+ if (is64) rhsVal = this._untypedTo64(rhsVal, stmt.rhs[0]);
892
902
  if (
893
903
  this._needsStrictCompoundStmt(stmt, is64) &&
894
904
  this._genStrictCompound(
@@ -1134,27 +1144,16 @@ export const statementGenMethods = {
1134
1144
  }
1135
1145
  const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
1136
1146
  const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
1137
- let result;
1138
- switch (baseOp) {
1139
- case "+":
1140
- result = `{ re: ${lhsRead}.re + ${rhsExpr}.re, im: ${lhsRead}.im + ${rhsExpr}.im }`;
1141
- break;
1142
- case "-":
1143
- result = `{ re: ${lhsRead}.re - ${rhsExpr}.re, im: ${lhsRead}.im - ${rhsExpr}.im }`;
1144
- break;
1145
- case "*":
1146
- this.useHelper("cmul");
1147
- result = `__cmul(${lhsRead}, ${rhsExpr})`;
1148
- break;
1149
- case "/":
1150
- this.useHelper("cdiv");
1151
- result = `__cdiv(${lhsRead}, ${rhsExpr})`;
1152
- break;
1153
- default:
1154
- throw new Error(
1155
- `unsupported complex compound assignment operator: ${stmt.op}`,
1156
- );
1157
- }
1147
+ if (!["+", "-", "*", "/"].includes(baseOp))
1148
+ throw new Error(
1149
+ `unsupported complex compound assignment operator: ${stmt.op}`,
1150
+ );
1151
+ const result = this._genComplexArith(
1152
+ baseOp,
1153
+ lhsRead,
1154
+ rhsExpr,
1155
+ isComplex64(l._type),
1156
+ );
1158
1157
  this.line(`${lhsTarget} = ${result};`);
1159
1158
  },
1160
1159
 
@@ -1163,10 +1162,21 @@ export const statementGenMethods = {
1163
1162
  this.line(`${this._iterReturnFlag} = true; return false;`);
1164
1163
  return;
1165
1164
  }
1166
- const vals =
1165
+ let vals =
1167
1166
  stmt.values.length > 0
1168
1167
  ? stmt.values.map((v) => this._genReturnValue(v))
1169
1168
  : (this.namedReturnVars ?? []);
1169
+ const named = this.namedReturnVars ?? [];
1170
+ if (stmt.values.length > 0 && named.length > 0) {
1171
+ // Deferred calls read the named results after the loop returns.
1172
+ const rhs =
1173
+ vals.length === named.length && named.length > 1
1174
+ ? `[${vals.join(", ")}]`
1175
+ : vals[0];
1176
+ const lhs = named.length === 1 ? named[0] : `[${named.join(", ")}]`;
1177
+ this.line(`${lhs} = ${rhs};`);
1178
+ vals = named;
1179
+ }
1170
1180
  const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
1171
1181
  this.line(
1172
1182
  `${this._iterReturnFlag} = true; ${this._iterReturnVar} = ${stored}; return false;`,
@@ -1235,11 +1245,10 @@ export const statementGenMethods = {
1235
1245
  this.line(`${stmt.keyword} ${stmt.label};`);
1236
1246
  return;
1237
1247
  }
1238
- if (
1239
- stmt.keyword === "continue" &&
1240
- stmt.label === this._currentIterLoopLabel
1241
- ) {
1242
- this.line("return true;");
1248
+ if (this._currentIterLoopLabels?.includes(stmt.label)) {
1249
+ if (stmt.keyword === "continue") this.line("return true;");
1250
+ else if (stmt.keyword === "break")
1251
+ this.line(`${this._iterBreakFlag} = true; return false;`);
1243
1252
  return;
1244
1253
  }
1245
1254
  this.line(
@@ -1360,10 +1369,8 @@ export const statementGenMethods = {
1360
1369
  const isInner = this._inIteratorBody;
1361
1370
  if (isInner) {
1362
1371
  this._iterLoopDepth = (this._iterLoopDepth ?? 0) + 1;
1363
- if (stmt.label) {
1364
- this._innerLoopLabels = this._innerLoopLabels ?? new Set();
1365
- this._innerLoopLabels.add(stmt.label);
1366
- }
1372
+ this._innerLoopLabels = this._innerLoopLabels ?? new Set();
1373
+ for (const l of this._loopLabels(stmt)) this._innerLoopLabels.add(l);
1367
1374
  }
1368
1375
  try {
1369
1376
  if (this.isRangeFor(stmt)) {
@@ -1391,7 +1398,7 @@ export const statementGenMethods = {
1391
1398
  } finally {
1392
1399
  if (isInner) {
1393
1400
  this._iterLoopDepth--;
1394
- if (stmt.label) this._innerLoopLabels.delete(stmt.label);
1401
+ for (const l of this._loopLabels(stmt)) this._innerLoopLabels.delete(l);
1395
1402
  }
1396
1403
  }
1397
1404
  },
@@ -1447,10 +1454,26 @@ export const statementGenMethods = {
1447
1454
  return `${this._genAssignLhsExpr(e)} = `;
1448
1455
  },
1449
1456
 
1457
+ // The value a range assignment stores into an interface-typed variable,
1458
+ // boxed as in any conversion to an interface.
1459
+ _boxRangeAssigned(e, valueJs) {
1460
+ const box = e._rangeBox;
1461
+ if (!box) return valueJs;
1462
+ if (this._isNamedBoxKind(box))
1463
+ return this._genNamedIfaceBox(
1464
+ valueJs,
1465
+ box._ifaceNamedType,
1466
+ box._ifaceBox === "named_ptr",
1467
+ );
1468
+ this.useHelper("ifaceBox");
1469
+ return this._ifaceBoxWrap(valueJs, box._ifaceBox, box._ifacePtrFields);
1470
+ },
1471
+
1450
1472
  _emitRangeVar(e, valueJs, isAssign) {
1451
1473
  if (!e) return;
1452
1474
  const target = this._rangeVarTarget(e, isAssign);
1453
1475
  if (target === null) return;
1476
+ if (isAssign) valueJs = this._boxRangeAssigned(e, valueJs);
1454
1477
  if (typeof target === "string") {
1455
1478
  this.line(`${target}${valueJs};`);
1456
1479
  return;
@@ -1605,13 +1628,38 @@ export const statementGenMethods = {
1605
1628
  this._loopDepth--;
1606
1629
  },
1607
1630
 
1608
- _genIterFor(init, iterType, iteree, lhs, body) {
1631
+ // `for _, v = range m` into an address-taken variable (or an element or
1632
+ // field) cannot destructure into the target: bind temporaries and store
1633
+ // through the variable's box after decoding. Fills `stores` with the
1634
+ // [target, temporary] pairs and returns the names to bind.
1635
+ _rangeAssignTemps(init, names, stores) {
1636
+ if (init.kind !== "AssignStmt") return names;
1637
+ // `let [..]` declares every name, so once one target needs a temporary
1638
+ // all the others go through temporaries too.
1639
+ const needs = (n, i) => {
1640
+ const e = init.lhs[i];
1641
+ return (
1642
+ n !== "_" &&
1643
+ (e._rangeBox || !(e.kind === "Ident" && !this._boxedVars.has(e.name)))
1644
+ );
1645
+ };
1646
+ const any = names.some(needs);
1647
+ return names.map((n, i) => {
1648
+ if (n === "_" || !any) return n;
1649
+ stores.push([init.lhs[i], `__r${i}$`]);
1650
+ return `__r${i}$`;
1651
+ });
1652
+ },
1653
+
1654
+ _genIterFor(init, iterType, iteree, lhsNames, body) {
1655
+ const isAssign = init.kind === "AssignStmt";
1656
+ const stores = [];
1657
+ const lhs = this._rangeAssignTemps(init, lhsNames, stores);
1609
1658
  const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
1610
1659
  const keyDecode = this._structKeyRange(iterType);
1611
1660
  const paramKey = this._typeParamKeyRange(iterType);
1612
1661
  const is64Key = this._is64BitMapRange(iterType);
1613
1662
  const scalarKey = this._scalarKeyRange(iterType);
1614
- const isAssign = init.kind === "AssignStmt";
1615
1663
  // `for _, v = range m` assigns existing variables, where a blank is a
1616
1664
  // hole in the destructuring pattern; a lone blank still needs a binding.
1617
1665
  const blank = isAssign && lhs.length > 1 ? "" : "_$";
@@ -1621,7 +1669,7 @@ export const statementGenMethods = {
1621
1669
  ? "_$"
1622
1670
  : lhs[0]
1623
1671
  : `[${lhs.map((n) => (n === "_" ? blank : n)).join(", ")}]`;
1624
- const kw = !isAssign || binding === "_$" ? "let " : "";
1672
+ const kw = !isAssign || binding === "_$" || stores.length > 0 ? "let " : "";
1625
1673
  this.line(`for (${kw}${binding} of ${iterExpr}) {`);
1626
1674
  this.indented(() => {
1627
1675
  if (paramKey && lhs[0] !== "_")
@@ -1637,6 +1685,7 @@ export const statementGenMethods = {
1637
1685
  : `${lhs[0]} = Number(${lhs[0]});`,
1638
1686
  );
1639
1687
  this._cloneMapRangeValue(init, iterType, lhs[1]);
1688
+ for (const [e, tmp] of stores) this._emitRangeVar(e, tmp, true);
1640
1689
  this.genBlock(body);
1641
1690
  });
1642
1691
  this.line("}");
@@ -1775,17 +1824,40 @@ export const statementGenMethods = {
1775
1824
  return `__s(${iteree}).entries()`;
1776
1825
  },
1777
1826
 
1778
- _emitIterTargetDispatch(targetFlag) {
1779
- const labels = this._functionLabels ?? [];
1827
+ // Every label of a loop: stacked labels (`a: b: for ...`) all name it.
1828
+ _loopLabels(stmt) {
1829
+ return stmt.labels ?? (stmt.label ? [stmt.label] : []);
1830
+ },
1831
+
1832
+ _emitIterTargetDispatch(targetFlag, ownLabels = []) {
1833
+ // The loop's own label is handled inside the callback (`break` and
1834
+ // `continue` of the range-over-func loop), and a JS `continue` cannot
1835
+ // name the labeled block that wraps it. Only the labels of statements
1836
+ // enclosing this loop in the same JS function are in scope (`_jsLabels`):
1837
+ // inside another iterator's callback, the enclosing iterator loop's
1838
+ // label is handled like a branch in its body, and any other label is
1839
+ // passed on to the next dispatcher.
1840
+ const inIter = this._inIteratorBody;
1841
+ const labels = (this._jsLabels ?? []).filter(
1842
+ (l) => !ownLabels.includes(l.name),
1843
+ );
1780
1844
  this.line(`if (${targetFlag}) {`);
1781
1845
  this.indented(() => {
1782
- for (const lbl of labels) {
1783
- this.line(`if (${targetFlag} === "break$${lbl}") break ${lbl};`);
1784
- this.line(`if (${targetFlag} === "continue$${lbl}") continue ${lbl};`);
1846
+ for (const { name, loop } of labels) {
1847
+ this.line(`if (${targetFlag} === "break$${name}") break ${name};`);
1848
+ if (loop)
1849
+ this.line(
1850
+ `if (${targetFlag} === "continue$${name}") continue ${name};`,
1851
+ );
1785
1852
  }
1786
- if (this._inIteratorBody) {
1787
- this.line(`${this._iterTargetFlag} = ${targetFlag}; return false;`);
1853
+ if (!inIter) return;
1854
+ for (const encl of this._currentIterLoopLabels ?? []) {
1855
+ this.line(`if (${targetFlag} === "continue$${encl}") return true;`);
1856
+ this.line(
1857
+ `if (${targetFlag} === "break$${encl}") { ${this._iterBreakFlag} = true; return false; }`,
1858
+ );
1788
1859
  }
1860
+ this.line(`${this._iterTargetFlag} = ${targetFlag}; return false;`);
1789
1861
  });
1790
1862
  this.line("}");
1791
1863
  },
@@ -1795,7 +1867,7 @@ export const statementGenMethods = {
1795
1867
  paramsStr,
1796
1868
  body,
1797
1869
  assignPreamble = null,
1798
- loopLabel = null,
1870
+ loopLabels = [],
1799
1871
  ) {
1800
1872
  let iteree = this.genExpr(itereeExpr);
1801
1873
  if (itereeExpr.kind === "FuncLit") {
@@ -1834,16 +1906,21 @@ export const statementGenMethods = {
1834
1906
  ret: this._iterReturnFlag,
1835
1907
  retVar: this._iterReturnVar,
1836
1908
  target: this._iterTargetFlag,
1837
- loopLabel: this._currentIterLoopLabel,
1909
+ loopLabels: this._currentIterLoopLabels,
1838
1910
  loopDepth: this._iterLoopDepth,
1911
+ innerLabels: this._innerLoopLabels,
1912
+ jsLabels: this._jsLabels,
1839
1913
  };
1840
1914
  this._inIteratorBody = true;
1841
1915
  this._iterBreakFlag = breakFlag;
1842
1916
  this._iterReturnFlag = retFlag;
1843
1917
  this._iterReturnVar = retVar;
1844
1918
  this._iterTargetFlag = targetFlag;
1845
- this._currentIterLoopLabel = loopLabel;
1919
+ this._currentIterLoopLabels = loopLabels;
1846
1920
  this._iterLoopDepth = 0;
1921
+ // Loops of an enclosing callback are not JS labels in this one.
1922
+ this._innerLoopLabels = new Set();
1923
+ this._jsLabels = [];
1847
1924
 
1848
1925
  if (assignPreamble) assignPreamble();
1849
1926
  this.genBlock(body);
@@ -1853,14 +1930,16 @@ export const statementGenMethods = {
1853
1930
  this._iterReturnFlag = prev.ret;
1854
1931
  this._iterReturnVar = prev.retVar;
1855
1932
  this._iterTargetFlag = prev.target;
1856
- this._currentIterLoopLabel = prev.loopLabel;
1933
+ this._currentIterLoopLabels = prev.loopLabels;
1857
1934
  this._iterLoopDepth = prev.loopDepth;
1935
+ this._innerLoopLabels = prev.innerLabels;
1936
+ this._jsLabels = prev.jsLabels;
1858
1937
 
1859
1938
  this.line("return true;");
1860
1939
  });
1861
1940
  this.line("});");
1862
1941
  this.line(`if (${retFlag}) return ${retVar};`);
1863
- this._emitIterTargetDispatch(targetFlag);
1942
+ this._emitIterTargetDispatch(targetFlag, loopLabels);
1864
1943
  });
1865
1944
  this.line("}");
1866
1945
  this._iterDepth--;
@@ -1869,7 +1948,8 @@ export const statementGenMethods = {
1869
1948
  genIteratorFor(stmt) {
1870
1949
  const range = stmt.init.rhs[0];
1871
1950
  const isAssign = stmt.init.kind === "AssignStmt";
1872
- const lhs = stmt.init.lhs.map((e) => e.name ?? this.genExpr(e));
1951
+ // A non-identifier target is stored through _emitRangeVar, not by name.
1952
+ const lhs = stmt.init.lhs.map((e) => e.name ?? "_$target");
1873
1953
  const yieldTypes = range._yieldParams ?? [];
1874
1954
  if (isAssign) {
1875
1955
  const cbParams = lhs.map((_, i) => `__p${i}`);
@@ -1881,12 +1961,9 @@ export const statementGenMethods = {
1881
1961
  const isStruct =
1882
1962
  this._isStructType(yType) ||
1883
1963
  this._isStructType(stmt.init.lhs[i]?._type);
1884
- if (isStruct) {
1885
- this.useHelper("sclone");
1886
- this.line(`${lhs[i]} = __sclone(${cbParams[i]});`);
1887
- } else {
1888
- this.line(`${lhs[i]} = ${cbParams[i]};`);
1889
- }
1964
+ if (isStruct) this.useHelper("sclone");
1965
+ const value = isStruct ? `__sclone(${cbParams[i]})` : cbParams[i];
1966
+ this._emitRangeVar(stmt.init.lhs[i], value, true);
1890
1967
  }
1891
1968
  }
1892
1969
  };
@@ -1895,7 +1972,7 @@ export const statementGenMethods = {
1895
1972
  params,
1896
1973
  stmt.body,
1897
1974
  assignPreamble,
1898
- stmt.label ?? null,
1975
+ this._loopLabels(stmt),
1899
1976
  );
1900
1977
  return;
1901
1978
  }
@@ -1920,7 +1997,7 @@ export const statementGenMethods = {
1920
1997
  params,
1921
1998
  stmt.body,
1922
1999
  clonePreamble,
1923
- stmt.label ?? null,
2000
+ this._loopLabels(stmt),
1924
2001
  );
1925
2002
  },
1926
2003
 
@@ -1931,7 +2008,7 @@ export const statementGenMethods = {
1931
2008
  "",
1932
2009
  stmt.body,
1933
2010
  null,
1934
- stmt.label ?? null,
2011
+ this._loopLabels(stmt),
1935
2012
  );
1936
2013
  },
1937
2014
 
@@ -2032,14 +2109,8 @@ export const statementGenMethods = {
2032
2109
  if (stmt.lhs.length === 1) {
2033
2110
  if (stmt.op !== "=" && is64BitInt(stmt.lhs[0]._type)) {
2034
2111
  const binOp = stmt.op.slice(0, -1);
2035
- let r = rhs[0];
2036
- if (
2037
- stmt.rhs[0]?._type?.kind === "untyped" &&
2038
- !r.endsWith("n") &&
2039
- /^-?\d+$/.test(r)
2040
- ) {
2041
- r = `${r}n`;
2042
- }
2112
+ // Same operand coercion as the statement path (`_genAssignStmt`).
2113
+ const r = this._untypedTo64(rhs[0], stmt.rhs[0]);
2043
2114
  const l = this.genExpr(stmt.lhs[0]);
2044
2115
  return `${l} = ${this._gen64BitBinary(binOp, l, r, stmt.lhs[0]._type)}`;
2045
2116
  }
@@ -2059,6 +2130,11 @@ export const statementGenMethods = {
2059
2130
  return `[${lhs.join(", ")}] = ${r}`;
2060
2131
  },
2061
2132
 
2133
+ _isMapTag(tag) {
2134
+ const t = tag._type?.kind === "named" ? tag._type.underlying : tag._type;
2135
+ return t?.kind === "map" || tag._coreType?.kind === "map";
2136
+ },
2137
+
2062
2138
  // Interface-typed tags may hold boxed named values; cases compare with __ifeq.
2063
2139
  _switchTagAndCases(stmt) {
2064
2140
  const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
@@ -2073,6 +2149,37 @@ export const statementGenMethods = {
2073
2149
  caseExpr: (e) => `__equal(${tmp}, ${this.genExpr(e)})`,
2074
2150
  };
2075
2151
  }
2152
+ if (stmt.tag && isComplex(stmt.tag._type)) {
2153
+ // A complex tag is an object: cases compare its parts.
2154
+ this._swCounter = (this._swCounter ?? 0) + 1;
2155
+ const tmp = `__sw${this._swCounter}`;
2156
+ this.line(`const ${tmp} = ${tag};`);
2157
+ return {
2158
+ tag: "true",
2159
+ caseExpr: (e) =>
2160
+ this._genComplexEquality(
2161
+ "===",
2162
+ "&&",
2163
+ tmp,
2164
+ this._genComplexOperand(e),
2165
+ ),
2166
+ };
2167
+ }
2168
+ if (stmt.tag && this._isMapTag(stmt.tag)) {
2169
+ // A nil map is a sentinel object in JS, not `null`: `case nil`
2170
+ // goes through `__isnm`, as `m == nil` does.
2171
+ this.useHelper(this.strict ? "nilMap" : "nm");
2172
+ this._swCounter = (this._swCounter ?? 0) + 1;
2173
+ const tmp = `__sw${this._swCounter}`;
2174
+ this.line(`const ${tmp} = ${tag};`);
2175
+ return {
2176
+ tag: "true",
2177
+ caseExpr: (e) =>
2178
+ this._isNilLiteral(e)
2179
+ ? `__isnm(${tmp})`
2180
+ : `${tmp} === ${this.genExpr(e)}`,
2181
+ };
2182
+ }
2076
2183
  if (!stmt.tag || !this._isIfaceType(stmt.tag._type))
2077
2184
  return { tag, caseExpr: (e) => this.genExpr(e) };
2078
2185
  this.useHelper("ifeq");
@@ -2125,7 +2232,9 @@ export const statementGenMethods = {
2125
2232
  } else if (
2126
2233
  stmt.tag &&
2127
2234
  (this._isStructOrArrayType(stmt.tag._type) ||
2128
- this._isIfaceType(stmt.tag._type))
2235
+ this._isIfaceType(stmt.tag._type) ||
2236
+ isComplex(stmt.tag._type) ||
2237
+ this._isMapTag(stmt.tag))
2129
2238
  ) {
2130
2239
  // The tag temporary is a `const`, which cannot directly follow a label.
2131
2240
  this.line("{");
@@ -13,7 +13,8 @@ import {
13
13
  typedArrayConstructorForElem,
14
14
  typeStr,
15
15
  } from "../../typechecker/types.js";
16
- import { mentionsTypeParam } from "./expressions.js";
16
+ import { goTypeName } from "../shared/fmt-types.js";
17
+ import { isGenericComposite, mentionsTypeParam } from "./expressions.js";
17
18
 
18
19
  // True when type node `node` names one of the type parameters `names`.
19
20
  function typeNodeMentions(node, names) {
@@ -87,6 +88,8 @@ export const typeInfoGenMethods = {
87
88
 
88
89
  _nodeUsesTypeInfo(node, names) {
89
90
  if (node._ifaceKeyDyn && names.has(node._ifaceKeyParam)) return true;
91
+ // A map or slice entering an interface is tagged by its instantiated type.
92
+ if (isGenericComposite(node._ifaceSrc)) return true;
90
93
  if (node._ifaceSrc?.kind === "typeParam" && names.has(node._ifaceSrc.name))
91
94
  return true;
92
95
  switch (node.kind) {
@@ -180,20 +183,33 @@ export const typeInfoGenMethods = {
180
183
  const unbox = this._boxesInInterface(t) ? "true" : "false";
181
184
  const box = this._ifaceKeyBox(t) ?? this._ptrMethodBox(t);
182
185
  const fmtBox = this._fmtBoxFor(t);
183
- const tail = fmtBox
184
- ? `, ${box ?? "null"}, ${fmtBox}`
185
- : box
186
- ? `, ${box}`
187
- : "";
186
+ const tail = this._typeInfoTail(t, box, fmtBox);
188
187
  return `__ti(${base}, ${JSON.stringify(k)}, ${JSON.stringify(typeStr(t))}, ${is}, ${unbox}${tail})`;
189
188
  },
190
189
 
190
+ // The optional trailing arguments of `__ti` (`b`, `fb`, `nm`). `typeStr`
191
+ // drops the `...` of a variadic func type, so a func type also carries its
192
+ // spelled name (see `__tnm`).
193
+ _typeInfoTail(t, box, fmtBox) {
194
+ if (t.kind === "func")
195
+ return `, ${box ?? "null"}, ${fmtBox ?? "null"}, ${JSON.stringify(this._dynTypeName(t))}`;
196
+ if (fmtBox) return `, ${box ?? "null"}, ${fmtBox}`;
197
+ return box ? `, ${box}` : "";
198
+ },
199
+
191
200
  // How `__ik` tells the dynamic type of an interface map key apart where
192
201
  // the JS value cannot: the numeric type name for types sharing a JS
193
202
  // representation with `int`/`float64`/`int64`, `*` for pointers (keyed by
194
203
  // identity) and `!type` for types Go cannot hash.
195
204
  _ifaceKeyTag(t) {
196
- if (!t || t.kind === "named" || mentionsTypeParam(t)) return null;
205
+ if (!t || mentionsTypeParam(t)) return null;
206
+ if (t.kind === "named") {
207
+ // A defined slice, map or func type: a literal of it carries no tag.
208
+ const u = this._underlyingOf(t)?.kind;
209
+ return u === "slice" || u === "map" || u === "func"
210
+ ? `!${goTypeName(t)}`
211
+ : null;
212
+ }
197
213
  if (t.kind === "pointer") return "*";
198
214
  if (t.kind === "slice" || t.kind === "map" || t.kind === "func")
199
215
  return `!${this._dynTypeName(t)}`;
@@ -227,7 +243,7 @@ export const typeInfoGenMethods = {
227
243
  const tag = this._dynTypeTag(t);
228
244
  if (!tag) return base;
229
245
  this.useHelper("ifaceBox");
230
- const ptr = t.kind === "pointer" ? ", true" : "";
246
+ const ptr = this._dynTagFlags(t);
231
247
  return `(__v) => __ity(${base ? `(${base})(__v)` : "__v"}, ${JSON.stringify(tag)}${ptr})`;
232
248
  },
233
249
 
@@ -239,6 +255,12 @@ export const typeInfoGenMethods = {
239
255
  this.useHelper("ifaceBox");
240
256
  return "(__v) => __ifp(__v)";
241
257
  }
258
+ // A struct value is marked as a value, so a pointer boxed earlier by the
259
+ // same generic code does not make it read as `*T`.
260
+ if (u?.kind === "struct" && t.kind !== "pointer") {
261
+ this.useHelper("ifaceBox");
262
+ return "(__v) => __ifv(__v)";
263
+ }
242
264
  if (u?.kind === "map") {
243
265
  this.useHelper("ifaceBox");
244
266
  return "(__v) => __ifm(__v)";
@@ -247,6 +269,62 @@ export const typeInfoGenMethods = {
247
269
  return null;
248
270
  },
249
271
 
272
+ // Extra `__ity` arguments: `, true` marks a pointer and `, false, true` a
273
+ // defined slice type, whose nil value keeps its type as a tagged sentinel.
274
+ _dynTagFlags(t) {
275
+ if (t?.kind === "pointer") return ", true";
276
+ if (this._isNamedComposite(t) && this._underlyingOf(t)?.kind === "slice")
277
+ return ", false, true";
278
+ return "";
279
+ },
280
+
281
+ // JS expression for the run-time type tag of a map, slice or array type `t`
282
+ // that mentions type parameters, built from their type information (see
283
+ // `__ti`); null when some part cannot be spelled.
284
+ _genericMapTag(t) {
285
+ const part = (x) => {
286
+ if (!mentionsTypeParam(x)) return JSON.stringify(this._dynTypeName(x));
287
+ switch (x.kind) {
288
+ case "typeParam": {
289
+ const f = this._typeParamZeroFactory(x.name);
290
+ return f ? `__tnm(${f})` : null;
291
+ }
292
+ case "slice": {
293
+ const e = part(x.elem);
294
+ return e && `"[]" + ${e}`;
295
+ }
296
+ case "array": {
297
+ const e = part(x.elem);
298
+ return e && `"[${x.size}]" + ${e}`;
299
+ }
300
+ case "pointer": {
301
+ const e = part(x.base);
302
+ return e && `"*" + ${e}`;
303
+ }
304
+ case "map": {
305
+ const k = part(x.key);
306
+ const v = part(x.value);
307
+ return k && v && `"map[" + ${k} + "]" + ${v}`;
308
+ }
309
+ default:
310
+ return null;
311
+ }
312
+ };
313
+ return part(t);
314
+ },
315
+
316
+ // A defined func type whose values carry their name as a run-time tag
317
+ // (one with methods is boxed as a class instead).
318
+ _isTaggedFuncType(t) {
319
+ return (
320
+ t?.kind === "named" &&
321
+ !mentionsTypeParam(t) &&
322
+ !t.typeArgs?.length &&
323
+ this._underlyingOf(t)?.kind === "func" &&
324
+ !this._namedBoxInfo(t)
325
+ );
326
+ },
327
+
250
328
  // The run-time type tag of an unnamed slice, map or non-struct pointer
251
329
  // entering an interface, or null when the value needs none. The tag lets
252
330
  // assertions and type switches tell `[]int32` from `[]uint8`, `*int` from
@@ -272,6 +350,27 @@ export const typeInfoGenMethods = {
272
350
  return null;
273
351
  },
274
352
 
353
+ // Whether Go cannot hash a value of type `t` (it contains a slice, map or
354
+ // func), so using it as an interface map key panics.
355
+ _isUnhashableType(t, depth = 0) {
356
+ if (!t || depth > 8 || mentionsTypeParam(t)) return false;
357
+ const u = this._underlyingOf(t) ?? t;
358
+ switch (u.kind) {
359
+ case "slice":
360
+ case "map":
361
+ case "func":
362
+ return true;
363
+ case "array":
364
+ return this._isUnhashableType(u.elem, depth + 1);
365
+ case "struct":
366
+ return [...(u.fields?.values() ?? [])].some((f) =>
367
+ this._isUnhashableType(f, depth + 1),
368
+ );
369
+ default:
370
+ return false;
371
+ }
372
+ },
373
+
275
374
  // A named slice, array or map type: its values are plain JS arrays and
276
375
  // objects, told apart in an interface by a tag carrying the type's name.
277
376
  _isNamedComposite(t) {
@@ -302,6 +401,22 @@ export const typeInfoGenMethods = {
302
401
  return `map[${s(t.key)}]${s(t.value)}`;
303
402
  case "pointer":
304
403
  return `*${s(t.base)}`;
404
+ case "func": {
405
+ // Go's spelling: no result is `func()`, not `func() void`.
406
+ const rs = (t.returns ?? []).filter(
407
+ (r) => !(r?.kind === "basic" && r.name === "void"),
408
+ );
409
+ const ps = (t.params ?? []).map((p, i) =>
410
+ t.variadic && i === t.params.length - 1 ? `...${s(p)}` : s(p),
411
+ );
412
+ const res =
413
+ rs.length === 0
414
+ ? ""
415
+ : rs.length === 1
416
+ ? ` ${s(rs[0])}`
417
+ : ` (${rs.map(s).join(", ")})`;
418
+ return `func(${ps.join(", ")})${res}`;
419
+ }
305
420
  case "struct":
306
421
  if (t.name || !t._structFields) return typeStr(t);
307
422
  return `struct{${t._structFields