gofront 1.6.1 → 1.8.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 (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -4,16 +4,82 @@ import {
4
4
  nodeMutatesVar,
5
5
  nodeWritesVar,
6
6
  rootIdentName,
7
+ someChild,
7
8
  } from "../../lower/index.js";
9
+ import { constIntValue } from "../../typechecker/const-fold.js";
8
10
  import {
11
+ BROWSER_GLOBALS,
12
+ compositeLitIndices,
9
13
  ERROR,
14
+ isAny,
10
15
  isComplex,
11
16
  isTypedArraySlice,
12
17
  typedArrayConstructorForElem,
18
+ typeStr,
13
19
  } from "../../typechecker/types.js";
14
20
 
21
+ // True when `node` compares the identifier `name` with nil (`l == nil`): a
22
+ // pointer-receiver method that does so is meant to run on a nil receiver.
23
+ export function comparesToNil(node, name) {
24
+ if (Array.isArray(node)) return node.some((n) => comparesToNil(n, name));
25
+ if (!node || typeof node !== "object") return false;
26
+ if (node.kind === "BinaryExpr" && (node.op === "==" || node.op === "!=")) {
27
+ const isRecv = (e) => e?.kind === "Ident" && e.name === name;
28
+ const isNil = (e) =>
29
+ (e?.kind === "Ident" && e.name === "nil") ||
30
+ (e?.kind === "BasicLit" && e.litKind === "NIL");
31
+ if (
32
+ (isRecv(node.left) && isNil(node.right)) ||
33
+ (isRecv(node.right) && isNil(node.left))
34
+ )
35
+ return true;
36
+ }
37
+ return someChild(node, (c) => comparesToNil(c, name));
38
+ }
39
+
15
40
  /** @typedef {import('./index.js').CodeGen} CodeGen */
16
41
 
42
+ // True when type `t` mentions a type parameter anywhere.
43
+ export function mentionsTypeParam(t, depth = 0) {
44
+ if (!t || depth > 16) return false;
45
+ switch (t.kind) {
46
+ case "typeParam":
47
+ return true;
48
+ case "slice":
49
+ case "array":
50
+ return mentionsTypeParam(t.elem, depth + 1);
51
+ case "pointer":
52
+ return mentionsTypeParam(t.base, depth + 1);
53
+ case "map":
54
+ return (
55
+ mentionsTypeParam(t.key, depth + 1) ||
56
+ mentionsTypeParam(t.value, depth + 1)
57
+ );
58
+ case "func":
59
+ return [...t.params, ...t.returns].some((x) =>
60
+ mentionsTypeParam(x, depth + 1),
61
+ );
62
+ case "named":
63
+ return (t.typeArgs ?? []).some((x) => mentionsTypeParam(x, depth + 1));
64
+ default:
65
+ return false;
66
+ }
67
+ }
68
+
69
+ // How an instance of a generic struct is identified at run time: the type
70
+ // arguments' printed form, kept after the zero-value factories in the
71
+ // instance's `__tz` array. Null when an argument is a type parameter (the
72
+ // instantiation is not known inside erased generic code).
73
+ export function instanceId(typeArgs) {
74
+ if (!typeArgs?.length || typeArgs.some((t) => mentionsTypeParam(t)))
75
+ return null;
76
+ return typeArgs.map(typeStr).join(",");
77
+ }
78
+
79
+ // Array literals longer than this that leave most slots zero are emitted as
80
+ // the zero array plus assignments (_genSparseArrayLit).
81
+ const SPARSE_ARRAY_MIN = 32;
82
+
17
83
  const STRICT_NUMERIC_TYPES = new Set([
18
84
  "float32",
19
85
  "int8",
@@ -26,6 +92,25 @@ const STRICT_NUMERIC_TYPES = new Set([
26
92
  "uint32",
27
93
  ]);
28
94
 
95
+ // Unsigned types narrower than 64 bits: `-x` and `^x` wrap back into range.
96
+ const SMALL_UNSIGNED_WRAP = {
97
+ uint8: "& 0xFF",
98
+ byte: "& 0xFF",
99
+ uint16: "& 0xFFFF",
100
+ uint32: ">>> 0",
101
+ };
102
+
103
+ // Signed types narrower than 64 bits: `-x` wraps back into range.
104
+ const SIGNED_NEG_WRAP = {
105
+ int8: "<< 24 >> 24",
106
+ int16: "<< 16 >> 16",
107
+ int32: "| 0",
108
+ rune: "| 0",
109
+ };
110
+
111
+ // Integer types whose JS shifts must follow Go's 64-bit semantics.
112
+ const WIDE_INT_NAMES = new Set(["int", "int64", "uint", "uint64", "uintptr"]);
113
+
29
114
  const STRICT_OPS = new Set([
30
115
  "+",
31
116
  "-",
@@ -40,6 +125,21 @@ const STRICT_OPS = new Set([
40
125
  ">>",
41
126
  ]);
42
127
 
128
+ function collectFunctionLabels(node, labels = new Set()) {
129
+ if (!node || typeof node !== "object") return labels;
130
+ if (Array.isArray(node)) {
131
+ for (const item of node) collectFunctionLabels(item, labels);
132
+ return labels;
133
+ }
134
+ if (node.kind === "LabeledStmt" && node.label) labels.add(node.label);
135
+ if (node.kind === "ForStmt" && node.label) labels.add(node.label);
136
+ for (const k of Object.keys(node)) {
137
+ if (k.startsWith("_")) continue;
138
+ collectFunctionLabels(node[k], labels);
139
+ }
140
+ return labels;
141
+ }
142
+
43
143
  const NAMED_ZERO_TABLE = {
44
144
  "time.Time": [null, "{ _d: new Date(0), _isZero: true }"],
45
145
  "strings.Builder": [null, '{ _buf: "" }'],
@@ -55,6 +155,50 @@ const NAMED_ZERO_TABLE = {
55
155
  "encoding/binary.BigEndian": ["binary", "__binary_be"],
56
156
  "base64.Encoding": ["base64", "__base64_std"],
57
157
  "encoding/base64.Encoding": ["base64", "__base64_std"],
158
+ "simd.F32x4": [null, "Object.freeze([0, 0, 0, 0])"],
159
+ F32x4: [null, "Object.freeze([0, 0, 0, 0])"],
160
+ "simd.Mask32x4": [null, "Object.freeze([false, false, false, false])"],
161
+ Mask32x4: [null, "Object.freeze([false, false, false, false])"],
162
+ };
163
+
164
+ // gofront/simd receiver methods -> runtime helper; the receiver is passed first.
165
+ const SIMD_VECTOR_HELPERS = {
166
+ Add: "__simd_add",
167
+ Sub: "__simd_sub",
168
+ Mul: "__simd_mul",
169
+ Div: "__simd_div",
170
+ Min: "__simd_min",
171
+ Max: "__simd_max",
172
+ MulAdd: "__simd_muladd",
173
+ Neg: "__simd_neg",
174
+ Abs: "__simd_abs",
175
+ Sqrt: "__simd_sqrt",
176
+ Floor: "__simd_floor",
177
+ Ceil: "__simd_ceil",
178
+ Trunc: "__simd_trunc",
179
+ RoundToEven: "__simd_nearest",
180
+ Dot3: "__simd_dot3",
181
+ Dot4: "__simd_dot4",
182
+ Sum: "__simd_sum",
183
+ WithLane: "__simd_with_lane",
184
+ Swizzle: "__simd_swizzle",
185
+ Equal: "__simd_eq",
186
+ NotEqual: "__simd_ne",
187
+ Less: "__simd_lt",
188
+ LessEqual: "__simd_le",
189
+ Greater: "__simd_gt",
190
+ GreaterEqual: "__simd_ge",
191
+ };
192
+
193
+ const SIMD_MASK_HELPERS = {
194
+ And: "__simd_mask_and",
195
+ Or: "__simd_mask_or",
196
+ Xor: "__simd_mask_xor",
197
+ AndNot: "__simd_mask_andnot",
198
+ Not: "__simd_mask_not",
199
+ Any: "__simd_mask_any",
200
+ All: "__simd_mask_all",
201
+ Bits: "__simd_mask_bits",
58
202
  };
59
203
 
60
204
  // Splits a generated JS lvalue at its outermost trailing access:
@@ -128,6 +272,9 @@ const NS_CONSTANTS = {
128
272
  ErrSyntax: "__strconv_err_syntax",
129
273
  ErrRange: "__strconv_err_range",
130
274
  },
275
+ bits: {
276
+ UintSize: "64",
277
+ },
131
278
  path: {
132
279
  ErrBadPattern: "__path_err_bad_pattern",
133
280
  },
@@ -161,7 +308,12 @@ const NS_CONSTANTS = {
161
308
  rand: {
162
309
  Reader: "__crypto_rand_reader",
163
310
  },
164
- os: { Args: "process.argv" },
311
+ os: {
312
+ Args: "process.argv",
313
+ Stdout: "__os_stdout",
314
+ Stderr: "__os_stderr",
315
+ Stdin: "__os_stdin",
316
+ },
165
317
  time: {
166
318
  Nanosecond: "1",
167
319
  Microsecond: "1000",
@@ -255,6 +407,14 @@ const INT_TYPE_NAMES = new Set([
255
407
  "rune",
256
408
  ]);
257
409
 
410
+ // Zero values with a shared factory in HELPER_GENERICS.
411
+ const SHARED_ZERO_FACTORIES = {
412
+ null: "__zn",
413
+ 0: "__z0",
414
+ '""': "__zs",
415
+ false: "__zf",
416
+ };
417
+
258
418
  // Zero-value JS literals for basic Go types.
259
419
  const ZERO_FOR_BASIC = {
260
420
  int: "0",
@@ -285,18 +445,38 @@ const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
285
445
  const BUILTIN_GEN = {
286
446
  append: (s, e) => s.genAppend(e),
287
447
  len: (s, e) => s._genBuiltinLen(e),
288
- cap: (s, e) => `(${s.genExpr(e.args[0])}?.length ?? 0)`,
448
+ cap: (s, e) => {
449
+ s.useHelper("slice");
450
+ return `__cap(${s.genExpr(e.args[0])})`;
451
+ },
289
452
  make: (s, e) => s.genMake(e),
290
453
  delete: (s, e) => {
291
454
  const [m, k] = e.args.map((a) => s.genExpr(a));
292
455
  return `(delete ${m}[${k}])`;
293
456
  },
294
457
  copy: (s, e) => {
295
- const [dst, src] = e.args.map((a) => s.genExpr(a));
296
- return `((__cd, __cs) => { if (!__cd || !__cs) return 0; const n = Math.min(__cd.length, __cs.length); if (__cd.set) { __cd.set(__cs.length === n ? __cs : (__cs.subarray ? __cs.subarray(0, n) : __cs.slice(0, n))); } else { __cd.splice(0, n, ...(__cs.slice ? __cs.slice(0, n) : __cs.subarray(0, n))); } return n; })(${dst}, ${src})`;
458
+ const dst = s.genExpr(e.args[0]);
459
+ const src = s._genSliceSource(e.args[1]);
460
+ // Array elements are values: the copied elements must not alias src.
461
+ const t = e.args[1]._type;
462
+ const elem = (t?.kind === "named" ? t.underlying : t)?.elem;
463
+ const clone =
464
+ s._aggregateBase(elem)?.kind === "array"
465
+ ? s._cloneJs(elem, "__e")
466
+ : "__e";
467
+ const items =
468
+ clone === "__e"
469
+ ? "(__cs.slice ? __cs.slice(0, n) : __cs.subarray(0, n))"
470
+ : `__cs.slice(0, n).map((__e) => ${clone})`;
471
+ return `((__cd, __cs) => { if (!__cd || !__cs) return 0; const n = Math.min(__cd.length, __cs.length); if (__cd.set) { __cd.set(__cs.length === n ? __cs : (__cs.subarray ? __cs.subarray(0, n) : __cs.slice(0, n))); } else { __cd.splice(0, n, ...${items}); } return n; })(${dst}, ${src})`;
297
472
  },
298
473
  new: (s, e) => {
299
474
  const arg = e.args[0];
475
+ const base = e._type?.kind === "pointer" ? e._type.base : null;
476
+ if (base?.kind === "named" && base.typeArgs?.length) {
477
+ const inst = s._genericStructNew(base.name, base.typeArgs);
478
+ if (inst) return inst;
479
+ }
300
480
  const typeName = arg.name ?? s.getTypeName(arg);
301
481
  if (typeName && s.structNames.has(typeName)) {
302
482
  return `new ${typeName}()`;
@@ -306,7 +486,12 @@ const BUILTIN_GEN = {
306
486
  const name = t.name ?? (t.kind === "named" ? t.name : null);
307
487
  if (name) return `new ${name}()`;
308
488
  }
309
- return `{ value: ${s.zeroValueForExpr(arg)} }`;
489
+ // `new([4]byte)`: the type argument is wrapped in a TypeExpr node.
490
+ const zero = s.zeroValueForExpr(arg.kind === "TypeExpr" ? arg.type : arg);
491
+ // Erased type parameter: a struct instantiation is its own pointer.
492
+ if (base?.kind === "typeParam")
493
+ return `((__v) => (${s._typeParamIsStructJs(base.name)} ? __v : { value: __v }))(${zero})`;
494
+ return `{ value: ${zero} }`;
310
495
  },
311
496
  // `print` writes without a trailing newline (Go semantics); the wasm glue
312
497
  // buffers the same way, so output parity holds across backends.
@@ -323,8 +508,8 @@ const BUILTIN_GEN = {
323
508
  s.useHelper("error");
324
509
  return `__error(${s.genExpr(e.args[0])})`;
325
510
  },
326
- min: (s, e) => `Math.min(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
327
- max: (s, e) => `Math.max(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
511
+ min: (s, e) => genMinMax(s, e, "min", "<"),
512
+ max: (s, e) => genMinMax(s, e, "max", ">"),
328
513
  clear: (s, e) => s._genBuiltinClear(e),
329
514
  complex: (s, e) =>
330
515
  `{ re: ${s.genExpr(e.args[0])}, im: ${s.genExpr(e.args[1])} }`,
@@ -332,6 +517,53 @@ const BUILTIN_GEN = {
332
517
  imag: (s, e) => `(${s.genExpr(e.args[0])}).im`,
333
518
  };
334
519
 
520
+ // Math.min/max coerce strings to NaN, so string operands fold with `<`/`>`.
521
+ // A type parameter may be a string or a number: pick the form at run time
522
+ // (Math.min/max keeps NaN propagation and -0 for floats).
523
+ function genMinMax(s, e, fn, cmp) {
524
+ const args = e.args.map((a) => s.genExpr(a));
525
+ const fold = (xs) => `${xs}.reduce((a, b) => (b ${cmp} a ? b : a))`;
526
+ if (args.length === 1) return args[0];
527
+ if (e._type?.kind === "typeParam")
528
+ return `((...v) => (typeof v[0] === "string" ? ${fold("v")} : Math.${fn}(...v)))(${args.join(", ")})`;
529
+ if (!s._isStringExprType(e._type)) return `Math.${fn}(${args.join(", ")})`;
530
+ return fold(`[${args.join(", ")}]`);
531
+ }
532
+
533
+ // Expression kinds whose folded constant value (`_const`, attached by the
534
+ // checker — see typechecker/const-fold.js) is emitted as a literal. Literals
535
+ // and constant names already emit their value.
536
+ const FOLDED_CONST_KINDS = new Set([
537
+ "UnaryExpr",
538
+ "BinaryExpr",
539
+ "ParenExpr",
540
+ "CallExpr",
541
+ "TypeConversion",
542
+ ]);
543
+
544
+ const JS_SAFE_INT = 2n ** 53n;
545
+
546
+ // `-1.0` already emits its exact value; keep it as written.
547
+ const isSignedLit = (e) =>
548
+ e.kind === "UnaryExpr" &&
549
+ (e.op === "-" || e.op === "+") &&
550
+ e.operand?.kind === "BasicLit";
551
+
552
+ // Negative literals are parenthesised so they splice safely into operators.
553
+ const signed = (n, text) => (n < 0 || Object.is(n, -0) ? `(${text})` : text);
554
+
555
+ // JS literal per folded constant kind; null when JS cannot represent it.
556
+ const CONST_LITS = {
557
+ STRING: (v) => JSON.stringify(v),
558
+ BOOL: (v) => (v ? "true" : "false"),
559
+ FLOAT: (v) =>
560
+ Number.isFinite(v) ? signed(v, Object.is(v, -0) ? "-0" : String(v)) : null,
561
+ INT: (v) => {
562
+ const n = Number(v);
563
+ return Number.isFinite(n) ? signed(n, BigInt(n).toString()) : null;
564
+ },
565
+ };
566
+
335
567
  // Method-name dispatch for genExpr — string values add no static call edges.
336
568
  const EXPR_GEN_DELEGATE = {
337
569
  UnaryExpr: "_genUnaryExpr",
@@ -347,35 +579,78 @@ const EXPR_GEN_DELEGATE = {
347
579
  };
348
580
 
349
581
  /** @type {ThisType<CodeGen>} */
582
+ const stdIdent = (name, type) => ({ kind: "Ident", name, _type: type });
583
+
584
+ // True when a func type mentions a type parameter (a generic stdlib function).
585
+ function hasTypeParamType(t) {
586
+ return JSON.stringify([t.params, t.returns], (k, v) =>
587
+ k.startsWith("_") || k === "underlying" ? undefined : v,
588
+ ).includes('"typeParam"');
589
+ }
590
+
350
591
  export const expressionGenMethods = {
351
592
  genExpr(expr) {
352
- if (expr._ifaceBox && !expr._ifaceBoxing) {
593
+ if (expr._ifaceKeyDyn && !expr._ifaceKeying) {
594
+ // A key typed by a type parameter: encode it for the instantiated type.
595
+ const f = this._typeParamZeroFactory(expr._ifaceKeyParam);
596
+ if (f) {
597
+ expr._ifaceKeying = true;
598
+ try {
599
+ this.useHelper("structKey");
600
+ const box = expr._ifaceKeyDyn === "box" ? ", true" : "";
601
+ return `__ikt(${f}, ${this.genExpr(expr)}${box})`;
602
+ } finally {
603
+ expr._ifaceKeying = false;
604
+ }
605
+ }
606
+ }
607
+ if (expr._ifaceKey && !expr._ifaceKeying) {
608
+ // A string key is its own property key; any other dynamic value is
609
+ // encoded with its type (see HELPER_STRUCT_KEY).
610
+ const plain =
611
+ !this._isNamedBoxKind(expr) && this._isStringExprType(expr._type);
612
+ if (!plain) {
613
+ expr._ifaceKeying = true;
614
+ try {
615
+ this.useHelper("structKey");
616
+ return `__ik(${this.genExpr(expr)})`;
617
+ } finally {
618
+ expr._ifaceKeying = false;
619
+ }
620
+ }
621
+ }
622
+ if (this._isNamedIfaceBox(expr)) return this._genNamedIfaceBoxExpr(expr);
623
+ if (expr._ifaceSrc && !expr._fmtDescing && !this._isNamedBoxKind(expr)) {
624
+ const desc = this._fmtDesc(expr._ifaceSrc);
625
+ if (desc) {
626
+ expr._fmtDescing = true;
627
+ try {
628
+ return this._fmtDescWrap(this.genExpr(expr), desc);
629
+ } finally {
630
+ expr._fmtDescing = false;
631
+ }
632
+ }
633
+ }
634
+ if (expr._ifaceBox && !expr._ifaceBoxing && !this._isNamedBoxKind(expr)) {
353
635
  expr._ifaceBoxing = true;
354
636
  try {
355
637
  this.useHelper("ifaceBox");
356
- const fn =
357
- expr._ifaceBox === "ptr"
358
- ? "__ifp"
359
- : expr._ifaceBox === "ptr_slice"
360
- ? "__ifps"
361
- : expr._ifaceBox === "ptr_map"
362
- ? "__ifpm"
363
- : expr._ifaceBox === "map"
364
- ? "__ifm"
365
- : expr._ifaceBox === "slice"
366
- ? "__ifs"
367
- : "__ifv";
368
- const extra = expr._ifacePtrFields?.length
369
- ? `, ${JSON.stringify(expr._ifacePtrFields)}`
370
- : "";
371
- return `${fn}(${this.genExpr(expr)}${extra})`;
638
+ return this._ifaceBoxWrap(
639
+ this.genExpr(expr),
640
+ expr._ifaceBox,
641
+ expr._ifacePtrFields,
642
+ );
372
643
  } finally {
373
644
  expr._ifaceBoxing = false;
374
645
  }
375
646
  }
647
+ const folded = this._genFoldedConst(expr);
648
+ if (folded !== null) return folded;
376
649
  switch (expr.kind) {
377
650
  case "BasicLit":
378
651
  if (expr.litKind === "STRING") return JSON.stringify(expr.value);
652
+ if (expr._const?.k === "INT")
653
+ this._warnConstPrecision(expr._const, expr);
379
654
  return expr.value; // int, float, bool, nil(null)
380
655
  case "ImagLit":
381
656
  return `{ re: 0, im: ${expr.value} }`;
@@ -386,6 +661,7 @@ export const expressionGenMethods = {
386
661
  case "AwaitExpr":
387
662
  return `await ${this.genExpr(expr.expr)}`;
388
663
  case "InstantiationExpr":
664
+ return this._genInstantiationExpr(expr);
389
665
  case "RangeExpr":
390
666
  return this.genExpr(expr.expr);
391
667
  default: {
@@ -396,6 +672,80 @@ export const expressionGenMethods = {
396
672
  }
397
673
  },
398
674
 
675
+ // The literal for a folded constant expression (`1 << 40` →
676
+ // `1099511627776`), or null. Integers outside ±2⁵³ emit the nearest
677
+ // Number and a precision warning.
678
+ _genFoldedConst(expr) {
679
+ const val = expr._const;
680
+ if (!val) return null;
681
+ // Constant names emit themselves, but an integer use of one still warns
682
+ // (an integer constant declared in this package warned at its declaration).
683
+ if (expr.kind === "Ident" || expr.kind === "SelectorExpr") {
684
+ const declared = expr.kind === "Ident" && val.k === "INT";
685
+ if (expr._intConst && !declared) this._warnConstPrecision(val, expr);
686
+ return null;
687
+ }
688
+ if (!FOLDED_CONST_KINDS.has(expr.kind) || isSignedLit(expr)) return null;
689
+ if (val.k === "INT" || expr._intConst) this._warnConstPrecision(val, expr);
690
+ return CONST_LITS[val.k]?.(val.v) ?? null;
691
+ },
692
+
693
+ // Warns when an integer constant outside ±2⁵³ has no exact Number (once
694
+ // per node per generate run: some forms generate the same node more than
695
+ // once, and cached ASTs are generated again by later compiles).
696
+ _warnConstPrecision(val, expr) {
697
+ const i = constIntValue(val);
698
+ if (i === null || (i <= JS_SAFE_INT && i >= -JS_SAFE_INT)) return;
699
+ if (BigInt(Number(i)) !== i && !this._precisionWarned.has(expr)) {
700
+ this._precisionWarned.add(expr);
701
+ this._warn(`constant ${i} exceeds JS Number precision`, expr);
702
+ }
703
+ },
704
+
705
+ // `js` boxed as `box` (a checker _ifaceBoxMeta kind) for an interface.
706
+ _ifaceBoxWrap(js, box, ptrFields) {
707
+ this.useHelper("ifaceBox");
708
+ const fn =
709
+ {
710
+ ptr: "__ifp",
711
+ ptr_slice: "__ifps",
712
+ ptr_map: "__ifpm",
713
+ map: "__ifm",
714
+ slice: "__ifs",
715
+ }[box] ?? "__ifv";
716
+ const extra = ptrFields?.length ? `, ${JSON.stringify(ptrFields)}` : "";
717
+ return `${fn}(${js}${extra})`;
718
+ },
719
+
720
+ // `js` tagged with the fmt descriptor source `desc` (see _fmtDesc).
721
+ _fmtDescWrap(js, desc) {
722
+ this.useHelper("ifd");
723
+ return `__ifd(${js}, ${desc})`;
724
+ },
725
+
726
+ _isNamedBoxKind(expr) {
727
+ return expr._ifaceBox === "named" || expr._ifaceBox === "named_ptr";
728
+ },
729
+
730
+ _isNamedIfaceBox(expr) {
731
+ return (
732
+ this._isNamedBoxKind(expr) && !expr._ifaceBoxing && !expr._noNamedBox
733
+ );
734
+ },
735
+
736
+ _genNamedIfaceBoxExpr(expr) {
737
+ expr._ifaceBoxing = true;
738
+ try {
739
+ return this._genNamedIfaceBox(
740
+ this.genExpr(expr),
741
+ expr._ifaceNamedType,
742
+ expr._ifaceBox === "named_ptr",
743
+ );
744
+ } finally {
745
+ expr._ifaceBoxing = false;
746
+ }
747
+ },
748
+
399
749
  _isStructType(t) {
400
750
  if (!t) return false;
401
751
  if (t.name && this.structNames.has(t.name)) return true;
@@ -417,6 +767,29 @@ export const expressionGenMethods = {
417
767
  return this._isStructType(pt.base);
418
768
  },
419
769
 
770
+ _isScalarPointerType(t) {
771
+ if (!t) return false;
772
+ const pt =
773
+ t.kind === "named"
774
+ ? (t.underlying ?? this.checker?.types.get(t.name)?.underlying)
775
+ : t;
776
+ if (pt?.kind !== "pointer") return false;
777
+ return !this._isStructType(pt.base);
778
+ },
779
+
780
+ _genPointerEquality(expr) {
781
+ if (expr.op !== "==" && expr.op !== "!=") return null;
782
+ if (
783
+ !this._isScalarPointerType(expr.left._type) &&
784
+ !this._isScalarPointerType(expr.right._type)
785
+ ) {
786
+ return null;
787
+ }
788
+ this.useHelper("ptreq");
789
+ const cmp = `__ptreq(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
790
+ return expr.op === "==" ? cmp : `!${cmp}`;
791
+ },
792
+
420
793
  _isStructExpr(expr) {
421
794
  if (!expr) return false;
422
795
  if (expr.kind === "CompositeLit") {
@@ -453,7 +826,20 @@ export const expressionGenMethods = {
453
826
  return null;
454
827
  },
455
828
 
829
+ // A value of type-parameter type is copied at run time when its type
830
+ // argument is a struct or array (decision: runtime check on the zero-value
831
+ // factory, so one erased function body serves every instantiation).
832
+ _typeParamClone(t, js) {
833
+ if (t?.kind !== "typeParam") return null;
834
+ const factory = this._typeParamZeroFactory(t.name);
835
+ if (!factory) return null;
836
+ this.useHelper("vclone");
837
+ return `__vclone(${factory}, ${js})`;
838
+ },
839
+
456
840
  _cloneJs(t, js) {
841
+ const tp = this._typeParamClone(t, js);
842
+ if (tp) return tp;
457
843
  const base = this._aggregateBase(t);
458
844
  if (!base) return js;
459
845
  if (base.kind === "struct") {
@@ -489,7 +875,12 @@ export const expressionGenMethods = {
489
875
  genValueExpr(expr) {
490
876
  const js = this.genExpr(expr);
491
877
  if (expr._ifaceBox) return js;
492
- if (!this._aggregateBase(expr._type) || !this._isAddressableExpr(expr))
878
+ if (
879
+ !(
880
+ this._aggregateBase(expr._type) || this._typeParamClone(expr._type, js)
881
+ ) ||
882
+ !this._isAddressableExpr(expr)
883
+ )
493
884
  return js;
494
885
  return this._cloneJs(expr._type, js);
495
886
  },
@@ -514,16 +905,26 @@ export const expressionGenMethods = {
514
905
 
515
906
  _withFnCtx(body, fn) {
516
907
  const prev = this._fnCtx;
908
+ const prevLabels = this._functionLabels;
909
+ const labels = collectFunctionLabels(
910
+ body,
911
+ new Set(this._functionLabels ?? []),
912
+ );
913
+ this._functionLabels = Array.from(labels);
517
914
  this._fnCtx = {
518
915
  body,
519
916
  mut: new Map(),
520
917
  owned: new Set(),
521
918
  borrowed: new Set(),
919
+ // Parameters, receivers and named results, including the enclosing
920
+ // functions' (closures): `x = x[lo:hi]` on them must not copy.
921
+ params: new Set(prev?.params),
522
922
  };
523
923
  try {
524
924
  fn();
525
925
  } finally {
526
926
  this._fnCtx = prev;
927
+ this._functionLabels = prevLabels;
527
928
  }
528
929
  },
529
930
 
@@ -541,9 +942,23 @@ export const expressionGenMethods = {
541
942
  // Copies aggregate params/receivers that the function body mutates.
542
943
  _emitParamCopies(params) {
543
944
  for (const p of params) {
945
+ if (p.name) this._fnCtx?.params.add(p.name);
946
+ if (p.variadic && p.name && p.name !== "_") {
947
+ this.useHelper("va");
948
+ // Only a function that tests the parameter against nil needs an
949
+ // empty call to be nil; others keep an empty slice so stdlib
950
+ // helpers and printing see `[]`.
951
+ const nilTested = comparesToNil(this._fnCtx?.body, p.name);
952
+ this.line(`${p.name} = __va(${p.name}${nilTested ? ", true" : ""});`);
953
+ }
544
954
  if (p.variadic || !p.name || p.name === "_") continue;
955
+ if (this._boxedVars.has(p.name)) {
956
+ // Address-taken parameter: rebind it to a { value } box.
957
+ this.line(`${p.name} = { value: ${p.name} };`);
958
+ continue;
959
+ }
545
960
  const t = this._typeFromNode(p.type);
546
- if (!t || this._boxedVars.has(p.name)) continue;
961
+ if (!t) continue;
547
962
  if (this._fnMutates(p.name)) {
548
963
  this.line(`${p.name} = ${this._cloneJs(t, p.name)};`);
549
964
  this._markOwnership(p.name, true);
@@ -589,12 +1004,26 @@ export const expressionGenMethods = {
589
1004
  ) {
590
1005
  return this.genExpr(expr.operand);
591
1006
  }
592
- if (
593
- expr.operand.kind === "SelectorExpr" ||
594
- expr.operand.kind === "IndexExpr"
595
- ) {
596
- const inner = this.genExpr(expr.operand);
597
- return `{ get value() { return ${inner}; }, set value(v) { ${inner} = v; } }`;
1007
+ if (expr.operand.kind === "IndexExpr") {
1008
+ const obj = this.genExpr(expr.operand.expr);
1009
+ const idx = this.genExpr(expr.operand.index);
1010
+ const check = this._genStrictIndex(
1011
+ { ...expr.operand, _lvalue: false },
1012
+ "__o",
1013
+ "__i",
1014
+ );
1015
+ // Erased generic element: a struct instantiation is its own
1016
+ // pointer, anything else gets the get/set wrapper.
1017
+ if (expr.operand._type?.kind === "typeParam")
1018
+ return `((__o, __i) => { ${check ? `${check}; ` : ""}if (${this._typeParamIsStructJs(expr.operand._type.name)}) return __o[__i]; return Object.defineProperties({ get value() { return __o[__i]; }, set value(v) { __o[__i] = v; } }, { __target: { value: __o }, __index: { value: __i } }); })(${obj}, ${idx})`;
1019
+ if (check)
1020
+ return `((__o, __i) => (${check}, Object.defineProperties({ get value() { return __o[__i]; }, set value(v) { __o[__i] = v; } }, { __target: { value: __o }, __index: { value: __i } })))(${obj}, ${idx})`;
1021
+ return `((__o, __i) => Object.defineProperties({ get value() { return __o[__i]; }, set value(v) { __o[__i] = v; } }, { __target: { value: __o }, __index: { value: __i } }))(${obj}, ${idx})`;
1022
+ }
1023
+ if (expr.operand.kind === "SelectorExpr") {
1024
+ const obj = this.genExpr(expr.operand.expr);
1025
+ const field = expr.operand.field;
1026
+ return `((__o) => Object.defineProperties({ get value() { return __o.${field}; }, set value(v) { __o.${field} = v; } }, { __target: { value: __o }, __index: { value: "${field}" } }))(${obj})`;
598
1027
  }
599
1028
  // For non-boxed vars (primitives, etc.), wrap in { value: x }
600
1029
  return `{ value: ${this.genExpr(expr.operand)} }`;
@@ -602,7 +1031,11 @@ export const expressionGenMethods = {
602
1031
  // Dereference: *p
603
1032
  if (op === "*") {
604
1033
  if (this._isStructPointerType(expr.operand._type)) {
605
- return this.genExpr(expr.operand);
1034
+ return this._genNilChecked(this.genExpr(expr.operand));
1035
+ }
1036
+ if (expr.operand._type?.base?.kind === "typeParam") {
1037
+ if (expr._derefOpTarget) return `${this.genExpr(expr.operand)}.value`;
1038
+ return `((__p) => (${this._typeParamIsStructJs(expr.operand._type.base.name)} ? __p : __p.value))(${this.genExpr(expr.operand)})`;
606
1039
  }
607
1040
  return `${this.genExpr(expr.operand)}.value`;
608
1041
  }
@@ -613,6 +1046,16 @@ export const expressionGenMethods = {
613
1046
  return inner;
614
1047
  }
615
1048
  const code = this.genExpr(expr.operand);
1049
+ const wrap = this.strict
1050
+ ? SMALL_UNSIGNED_WRAP[this._typeName(expr._type)]
1051
+ : null;
1052
+ if (wrap && (op === "-" || op === "~")) return `(${op}(${code}) ${wrap})`;
1053
+ // -MinInt wraps back to itself for signed narrow integers.
1054
+ const signedWrap =
1055
+ this.strict && op === "-"
1056
+ ? SIGNED_NEG_WRAP[this._typeName(expr._type)]
1057
+ : null;
1058
+ if (signedWrap) return `(-(${code}) ${signedWrap})`;
616
1059
  const needsParens =
617
1060
  this._emittedBinaryOp(expr.operand) !== null ||
618
1061
  ((op === "-" || op === "+") && /^[-+]/.test(code));
@@ -620,9 +1063,16 @@ export const expressionGenMethods = {
620
1063
  },
621
1064
 
622
1065
  _genSelectorExpr(expr) {
1066
+ // Go-source stdlib member: bundled under its mangled top-level name.
1067
+ if (expr._stdName) return expr._stdName;
1068
+ if (this._isStdlibFuncValue(expr)) return this._genStdlibFuncValue(expr);
623
1069
  if (expr._isMethodExpr) {
1070
+ const namedExpr = this._namedMethodExprInfo(expr);
1071
+ if (namedExpr) return `${namedExpr.cls}.$${expr.field}`;
624
1072
  return `((recv, ...args) => recv.${expr.field}(...args))`;
625
1073
  }
1074
+ const namedValue = this._genNamedMethodValue(expr);
1075
+ if (namedValue !== undefined) return namedValue;
626
1076
  if (expr.expr.kind === "Ident") {
627
1077
  const nsConst = NS_CONSTANTS[expr.expr.name]?.[expr.field];
628
1078
  if (nsConst !== undefined) {
@@ -643,19 +1093,104 @@ export const expressionGenMethods = {
643
1093
  this.useHelper("cryptoRand");
644
1094
  } else if (expr.expr.name === "sha256") {
645
1095
  this.useHelper("sha256");
1096
+ } else if (expr.expr.name === "os") {
1097
+ this.useHelper("os");
646
1098
  }
647
1099
  return nsConst;
648
1100
  }
649
1101
  }
650
1102
  const base = this.genExpr(expr.expr);
651
- if (this.bundledPackages.has(base)) return expr.field;
1103
+ // Bundled GoFront package member; browser-global names carry the `$`
1104
+ // suffix of their declaration (see jsBindingName).
1105
+ if (this.bundledPackages.has(base))
1106
+ return BROWSER_GLOBALS.has(expr.field) ? `${expr.field}$` : expr.field;
652
1107
  if (expr.expr._type?.kind === "pointer" && expr.field !== "value")
653
1108
  return this._genPointerSelectorField(expr, base);
654
- const sel = `${base}.${this._jsMemberName(expr)}`;
655
- if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
1109
+ const sel = `${base}${this._promotedPath(expr)}.${this._jsMemberName(expr)}`;
1110
+ if (expr._isMethodValue && !expr._callee) {
1111
+ // A value receiver is copied when the method value is evaluated.
1112
+ const t = expr.expr._type;
1113
+ if (!expr._type?._ptrRecv && this._aggregateBase(t))
1114
+ return `((__r) => __r.${this._jsMemberName(expr)}.bind(__r))(${this._cloneJs(t, base)})`;
1115
+ return `${sel}.bind(${base})`;
1116
+ }
656
1117
  return sel;
657
1118
  },
658
1119
 
1120
+ // `pkg.Fn` used as a value (not called): a stdlib function is inlined at
1121
+ // its call sites, so a value is an arrow function that makes that call.
1122
+ _isStdlibFuncValue(expr) {
1123
+ const t = expr._type;
1124
+ if (expr._callee || t?.kind !== "func" || expr._isMethodExpr) return false;
1125
+ const ns = expr.expr;
1126
+ if (ns?.kind !== "Ident" || ns._type?.kind !== "namespace") return false;
1127
+ return !this.bundledPackages.has(ns.name) && !hasTypeParamType(t);
1128
+ },
1129
+
1130
+ _genStdlibFuncValue(expr) {
1131
+ const t = expr._type;
1132
+ const ps = t.params ?? [];
1133
+ const nFixed = t.variadic && ps.length > 0 ? ps.length - 1 : ps.length;
1134
+ const fixed = ps.slice(0, nFixed).map((p, i) => stdIdent(`__a${i}`, p));
1135
+ const names = fixed.map((a) => a.name);
1136
+ const callJs = (args) =>
1137
+ this.genExpr({
1138
+ kind: "CallExpr",
1139
+ func: { ...expr, _callee: true },
1140
+ args,
1141
+ _type: t.returns?.[0] ?? { kind: "basic", name: "void" },
1142
+ });
1143
+ if (nFixed === ps.length)
1144
+ return `((${names.join(", ")}) => ${callJs(fixed)})`;
1145
+ // Stdlib calls are inlined per operand count, so dispatch on it.
1146
+ const rest = `__a${nFixed}`;
1147
+ let body = `(() => { throw new Error("too many arguments in call to ${expr.expr.name}.${expr.field}"); })()`;
1148
+ for (let k = 8; k >= 0; k--) {
1149
+ const extra = Array.from({ length: k }, (_, i) =>
1150
+ stdIdent(`${rest}[${i}]`, ps[nFixed]),
1151
+ );
1152
+ body = `${rest}.length === ${k} ? ${callJs([...fixed, ...extra])} : ${body}`;
1153
+ }
1154
+ return `((${[...names, `...${rest}`].join(", ")}) => ${body})`;
1155
+ },
1156
+
1157
+ // A promoted field (`o.A` with embedded `In`) lives on the embedded value:
1158
+ // the class also carries a flattened copy, so reads and writes go through
1159
+ // the embedded path (`.In`) to stay in sync. Returns "" for direct fields.
1160
+ _promotedPath(expr) {
1161
+ if (expr._isMethodValue || expr._isMethodExpr) return "";
1162
+ let t = expr.expr._type;
1163
+ if (t?.kind === "pointer") t = t.base;
1164
+ const path = this._embedPath(t, expr.field, 0);
1165
+ return path ? path.map((n) => `.${n}`).join("") : "";
1166
+ },
1167
+
1168
+ _embedPath(t, field, depth) {
1169
+ const st = t?.kind === "named" ? t.underlying : t;
1170
+ if (st?.kind !== "struct" || !st.fields?.has(field) || depth > 8)
1171
+ return null;
1172
+ const direct = (st._structFields ?? []).some(
1173
+ (sf) => !sf.embedded && sf.names?.includes(field),
1174
+ );
1175
+ if (direct) return null;
1176
+ for (const sf of st._structFields ?? []) {
1177
+ if (!sf.embedded) continue;
1178
+ const inner = sf.type?.kind === "pointer" ? sf.type.base : sf.type;
1179
+ if (inner?.kind !== "named" || !this.structNames.has(inner.name))
1180
+ continue;
1181
+ const name = inner.name;
1182
+ if (name === field) return null;
1183
+ const ist = inner.underlying;
1184
+ if (!ist?.fields?.has(field)) continue;
1185
+ const sub = this._embedPath(inner, field, depth + 1);
1186
+ const idirect = (ist._structFields ?? []).some(
1187
+ (x) => !x.embedded && x.names?.includes(field),
1188
+ );
1189
+ return idirect ? [name] : sub ? [name, ...sub] : null;
1190
+ }
1191
+ return null;
1192
+ },
1193
+
659
1194
  // PascalCase aliases of Web API methods (gl.ClearColor) map to the real camelCase member.
660
1195
  _jsMemberName(expr) {
661
1196
  const f = expr.field;
@@ -672,10 +1207,167 @@ export const expressionGenMethods = {
672
1207
  return f;
673
1208
  },
674
1209
 
1210
+ // ── Named non-struct types with methods: raw values, static dispatch ──
1211
+
1212
+ // Boxes a raw named value (or *T) entering an interface so dynamic dispatch works.
1213
+ _genNamedIfaceBox(js, type, isPtr) {
1214
+ const info = this._namedBoxInfo(type);
1215
+ if (!info) return js;
1216
+ return isPtr ? `new ${info.cls}$Ptr(${js})` : `new ${info.cls}(${js})`;
1217
+ },
1218
+
1219
+ // Static target of `x.M` on a named non-struct type: its class and the
1220
+ // receiver argument (the { value } box for pointer receivers).
1221
+ _namedMethodTarget(sel) {
1222
+ const recvType = sel.expr?._type;
1223
+ const viaPtr = recvType?.kind === "pointer";
1224
+ const info = this._namedBoxInfo(viaPtr ? recvType.base : recvType);
1225
+ const method = info?.methods.get(sel.field);
1226
+ if (!method) return null;
1227
+ let recv;
1228
+ if (method._ptrRecv) {
1229
+ recv = viaPtr ? this.genExpr(sel.expr) : this._genAddressOfExpr(sel.expr);
1230
+ } else {
1231
+ recv = viaPtr
1232
+ ? `${this.genExpr(sel.expr)}.value`
1233
+ : this.genExpr(sel.expr);
1234
+ }
1235
+ return {
1236
+ cls: info.cls,
1237
+ recv,
1238
+ byValue: !method._ptrRecv,
1239
+ recvType: viaPtr ? recvType.base : recvType,
1240
+ };
1241
+ },
1242
+
1243
+ // `x.M` used as a value: binds the receiver now, as Go does.
1244
+ _genNamedMethodValue(expr) {
1245
+ if (!expr._isMethodValue || expr._callee) return undefined;
1246
+ const target = this._namedMethodTarget(expr);
1247
+ if (!target) return undefined;
1248
+ // A value receiver (a named array) is copied when the value is evaluated.
1249
+ const recv = target.byValue
1250
+ ? this._cloneJs(target.recvType, target.recv)
1251
+ : target.recv;
1252
+ return `((__r) => (...__a) => ${target.cls}.$${expr.field}(__r, ...__a))(${recv})`;
1253
+ },
1254
+
1255
+ _genAddressOfExpr(operand) {
1256
+ return this._genUnaryExpr({
1257
+ kind: "UnaryExpr",
1258
+ op: "&",
1259
+ operand,
1260
+ _type: { kind: "pointer", base: operand._type },
1261
+ });
1262
+ },
1263
+
1264
+ // `T.M` method expression on a named non-struct type.
1265
+ _namedMethodExprInfo(sel) {
1266
+ const t =
1267
+ sel.expr?._type?.kind === "named"
1268
+ ? sel.expr._type
1269
+ : this.checker?.types.get(sel.expr?.name);
1270
+ const info = this._namedBoxInfo(t);
1271
+ return info?.methods.has(sel.field) ? info : null;
1272
+ },
1273
+
1274
+ _genNamedMethodCall(expr) {
1275
+ const sel = expr.func;
1276
+ if (sel._isMethodExpr) {
1277
+ const info = this._namedMethodExprInfo(sel);
1278
+ if (!info) return undefined;
1279
+ return `${info.cls}.$${sel.field}(${this._genCallArgs(expr)})`;
1280
+ }
1281
+ const target = this._namedMethodTarget(sel);
1282
+ if (!target) return undefined;
1283
+ const args = this._genCallArgs(expr);
1284
+ return `${target.cls}.$${sel.field}(${args ? `${target.recv}, ${args}` : target.recv})`;
1285
+ },
1286
+
1287
+ // `F[int]` as a value is a function that passes F its zero-value factories.
1288
+ _genInstantiationExpr(expr) {
1289
+ const fn = this.genExpr(expr.expr);
1290
+ if (!expr._genericFnType || expr._callee) return fn;
1291
+ const { typeParams } = expr._genericFnType;
1292
+ const zeros = this._typeArgFactories(
1293
+ typeParams,
1294
+ expr._typeArgs,
1295
+ this._genericFnNeedsTypeInfo(expr._genericFnType),
1296
+ );
1297
+ return `((...a) => ${fn}(${zeros.join(", ")}, ...a))`;
1298
+ },
1299
+
1300
+ _genCallArgs(expr) {
1301
+ if (expr._multiForward) return `...${this.genExpr(expr.args[0])}`;
1302
+ return expr.args
1303
+ .map((a, i) =>
1304
+ a._spread
1305
+ ? this._genSpreadArg(expr, a)
1306
+ : this._callerCopiesArg(expr, i)
1307
+ ? this.genValueExpr(a)
1308
+ : this.genExpr(a),
1309
+ )
1310
+ .join(", ");
1311
+ },
1312
+
1313
+ // `f(xs...)`: a Go-compiled variadic callee receives the slice itself in a
1314
+ // marker (its prologue unwraps it); other callees get a JS spread.
1315
+ _genSpreadArg(call, arg) {
1316
+ const js = this.genExpr(arg);
1317
+ return this._calleeIsGoVariadic(call) ? `{ __sp: ${js} }` : `...${js}`;
1318
+ },
1319
+
1320
+ _calleeIsGoVariadic(call) {
1321
+ const f = call.func;
1322
+ if (f.kind === "Ident") {
1323
+ if (this.variadicFns?.has(f.name)) return true;
1324
+ // A local func value (closure or parameter) compiled from Go source.
1325
+ const imported =
1326
+ this.checker?._externalNames?.has(f.name) ||
1327
+ BROWSER_GLOBALS.has(f.name) ||
1328
+ [...(this.jsImports?.values() ?? [])].some((names) =>
1329
+ names.includes(f.name),
1330
+ );
1331
+ return !imported && f._type?.kind === "func" && !!f._type.variadic;
1332
+ }
1333
+ if (f.kind !== "SelectorExpr" || f._isMethodExpr) return false;
1334
+ const recv = this._resolveRecvName(f.expr._type);
1335
+ return !!recv && !!this.variadicFns?.has(`${recv}.${f.field}`);
1336
+ },
1337
+
1338
+ // A callee copies its mutated array and struct parameters on entry, except
1339
+ // when the parameter is a type parameter (generics are erased in JS) or
1340
+ // part of a variadic rest array. For those the caller passes aggregate
1341
+ // (array and struct) arguments as copies.
1342
+ _callerCopiesArg(expr, i) {
1343
+ const argT = expr.args[i]._type;
1344
+ if (!this._aggregateBase(argT) && !this._typeParamClone(argT, ""))
1345
+ return false;
1346
+ let ft = expr.func?._type ?? expr.func?.expr?._type;
1347
+ if (ft?.kind === "generic") ft = ft.underlying;
1348
+ if (ft?.kind !== "func") return false;
1349
+ const params = ft.params ?? [];
1350
+ if (ft.variadic && i >= params.length - 1) return true;
1351
+ return params[i]?.kind === "typeParam";
1352
+ },
1353
+
675
1354
  _genPointerSelectorField(expr, base) {
676
1355
  if (this._isStructPointerType(expr.expr._type)) {
677
- const sel = `${base}.${expr.field}`;
678
- if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
1356
+ // Strict mode: a field of a nil struct pointer panics as in Go.
1357
+ const recv =
1358
+ this.strict && !expr._isMethodValue ? this._genNilChecked(base) : base;
1359
+ const sel = `${recv}${this._promotedPath(expr)}.${expr.field}`;
1360
+ if (expr._isMethodValue && !expr._callee) {
1361
+ // `p.M` with a value receiver binds a copy of `*p` now.
1362
+ if (expr._derefRecv) {
1363
+ const copy = this._cloneJs(
1364
+ expr.expr._type.base,
1365
+ this.strict ? this._genNilChecked(base) : base,
1366
+ );
1367
+ return `((__r) => __r.${expr.field}.bind(__r))(${copy})`;
1368
+ }
1369
+ return `${sel}.bind(${base})`;
1370
+ }
679
1371
  return sel;
680
1372
  }
681
1373
  const sel = `${base}.value.${expr.field}`;
@@ -691,13 +1383,48 @@ export const expressionGenMethods = {
691
1383
  ? `${this.genExpr(expr.expr)}.${wrapField}`
692
1384
  : this.genExpr(expr.expr);
693
1385
  const idx = this.genExpr(expr.index);
694
- if (expr._mapValueType && !expr._lvalue)
695
- return this._genMapIndexExpr(expr, base, idx);
1386
+ if (expr._mapValueType) {
1387
+ if (!expr._lvalue) return this._genMapIndexExpr(expr, base, idx);
1388
+ return `${this._genMapWriteBase(base)}[${idx}]`;
1389
+ }
1390
+ const checked = this._genStrictIndex(expr, base, idx);
1391
+ if (checked) return checked;
696
1392
  if (this._isStringExprType(exprType) && !expr._lvalue)
697
1393
  return `${base}.charCodeAt(${idx})`;
698
1394
  return `${base}[${idx}]`;
699
1395
  },
700
1396
 
1397
+ // `p` for a field access `p.F`, panicking with Go's message when nil.
1398
+ _genNilChecked(base) {
1399
+ this.useHelper("nilCall");
1400
+ return `__nilcall(${base})`;
1401
+ },
1402
+
1403
+ // The map of a write `m[k] = v`: strict mode panics on a nil map.
1404
+ _genMapWriteBase(base) {
1405
+ if (!this.strict) return base;
1406
+ this.useHelper("nilMap");
1407
+ return `__mw(${base})`;
1408
+ },
1409
+
1410
+ // Strict mode: `xs[i]` on a slice, array or string with Go's bounds check
1411
+ // (null when no check applies). Reads of slices and arrays use `__at`;
1412
+ // lvalues and string reads use `base[__ix(base, i)]`, binding `base` to
1413
+ // `__lv` first when evaluating it twice could repeat a call.
1414
+ _genStrictIndex(expr, base, idx) {
1415
+ if (!this.strict || expr._inBounds) return null;
1416
+ const t = expr.expr._type;
1417
+ const u = t?.kind === "named" ? t.underlying : t;
1418
+ const isStr = this._isStringExprType(t);
1419
+ if (!isStr && u?.kind !== "slice" && u?.kind !== "array") return null;
1420
+ this.useHelper("bounds");
1421
+ if (!expr._lvalue && !isStr) return `__at(${base}, ${idx})`;
1422
+ const once = this._hasCallExpr(expr.expr);
1423
+ const obj = once ? `(__lv = ${base})` : base;
1424
+ const at = `__ix(${once ? "__lv" : base}, ${idx})`;
1425
+ return expr._lvalue ? `${obj}[${at}]` : `${obj}.charCodeAt(${at})`;
1426
+ },
1427
+
701
1428
  _genMapIndexExpr(expr, base, idx) {
702
1429
  const zero = this.zeroValueForType(expr._mapValueType);
703
1430
  if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index))
@@ -706,25 +1433,35 @@ export const expressionGenMethods = {
706
1433
  },
707
1434
 
708
1435
  _isStringExprType(exprType) {
1436
+ const t = exprType?.kind === "named" ? exprType.underlying : exprType;
709
1437
  return (
710
- (exprType?.kind === "basic" && exprType.name === "string") ||
711
- (exprType?.kind === "untyped" && exprType.base === "string")
1438
+ (t?.kind === "basic" && t.name === "string") ||
1439
+ (t?.kind === "untyped" && t.base === "string")
712
1440
  );
713
1441
  },
714
1442
 
715
1443
  _genSliceExpr(expr) {
716
1444
  const base = this.genExpr(expr.expr);
717
- const lo = expr.low ? this.genExpr(expr.low) : "";
718
- const hi = expr.high ? this.genExpr(expr.high) : "";
719
- const method = isTypedArraySlice(expr.expr._type) ? "subarray" : "slice";
720
- if (!lo && !hi) return `${base}.${method}()`;
721
- if (!hi) return `${base}.${method}(${lo})`;
722
- if (!lo) return `${base}.${method}(0, ${hi})`;
723
- return `${base}.${method}(${lo}, ${hi})`;
1445
+ const lo = expr.low ? this.genExpr(expr.low) : "0";
1446
+ const hi = expr.high ? this.genExpr(expr.high) : "undefined";
1447
+ if (this._isStringExprType(expr.expr._type))
1448
+ return expr.high ? `${base}.slice(${lo}, ${hi})` : `${base}.slice(${lo})`;
1449
+ // `b[:]`, `b[0:]` and `a[:]` have the operand's length and capacity:
1450
+ // the result is the operand itself. Other bounds make a view sharing
1451
+ // the backing store (`__slice`, which checks the bounds against cap).
1452
+ const zeroLow = !expr.low || Number(expr.low._const?.v) === 0;
1453
+ if (zeroLow && !expr.high && !expr.max) return base;
1454
+ this.useHelper("slice");
1455
+ // `x = x[lo:hi]` (see `_markSelfReslice`) copies a plain array when
1456
+ // the result stays within `len(x)`; typed arrays always share.
1457
+ if (expr._selfReslice && !isTypedArraySlice(expr.expr._type))
1458
+ return `__reslice(${base}, ${lo}, ${hi})`;
1459
+ const max = expr.max ? `, ${this.genExpr(expr.max)}` : "";
1460
+ return `__slice(${base}, ${lo}, ${hi}${max})`;
724
1461
  },
725
1462
 
726
1463
  _genFuncLit(expr) {
727
- const params = expr.params.map((p) => p.name).join(", ");
1464
+ const params = this._paramNames(expr.params).join(", ");
728
1465
  const asyncPrefix = expr.async ? "async " : "";
729
1466
  const saved = this.out;
730
1467
  const prevBoxed = this._boxedVars;
@@ -751,22 +1488,176 @@ export const expressionGenMethods = {
751
1488
  const val = hoist ? "__v" : src;
752
1489
  const check = this._typeCheckExpr(expr.type, val);
753
1490
  const wrap = (body) => (hoist ? `((__v) => ${body})(${src})` : body);
1491
+ // Named non-struct values sit in interfaces as boxes; the asserted value is raw.
1492
+ const out = this._assertedValueJs(expr.type, val);
754
1493
  if (!expr._commaOk) {
755
1494
  // plain assertion: panic if check fails (matches Go behavior)
756
1495
  if (check === "true") return src; // can't check at runtime — pass through
757
1496
  return wrap(
758
- `(${check} ? ${val} : (() => { throw new Error("interface conversion: type assertion failed"); })())`,
1497
+ `(${check} ? ${out} : (() => { throw new Error("interface conversion: type assertion failed"); })())`,
759
1498
  );
760
1499
  }
761
- // comma-ok: emit [value-or-zero, runtimeTypeCheck]
1500
+ // comma-ok: emit [value-or-zero, runtimeTypeCheck]; an asserted array
1501
+ // is a copy of the boxed value.
762
1502
  const zero = this.zeroValueForTypeNode(expr.type);
763
- return wrap(`(${check} ? [${val}, true] : [${zero}, false])`);
1503
+ const arrT = this._typeFromNode(expr.type);
1504
+ const okOut = arrT?.kind === "array" ? this._cloneJs(arrT, out) : out;
1505
+ return wrap(`(${check} ? [${okOut}, true] : [${zero}, false])`);
1506
+ },
1507
+
1508
+ // `csv.ParseError` names the class of the bundled Go-source package type.
1509
+ _qualifiedStructName(name) {
1510
+ if (!name.includes(".") || this.structNames.has(name)) return name;
1511
+ const cls = this.checker?.types.get(name)?.name;
1512
+ return cls && this.structNames.has(cls) ? cls : name;
1513
+ },
1514
+
1515
+ _typeNameCheckExpr(typeName, val) {
1516
+ // A type parameter: the instantiated type's test travels with its factory.
1517
+ const factory = this._typeParamZeroFactory(typeName);
1518
+ if (factory) return `(${factory}?.is?.(${val}) ?? true)`;
1519
+ const name = this._qualifiedStructName(typeName);
1520
+ if (TYPEOF_NUMBER_NAMES.has(name)) return `typeof ${val} === "number"`;
1521
+ if (name === "string") return `typeof ${val} === "string"`;
1522
+ if (name === "bool") return `typeof ${val} === "boolean"`;
1523
+ if (name === "nil") return `${val} === null`;
1524
+ // F32x4 and Mask32x4 are both frozen 4-element arrays; lanes tell them apart.
1525
+ if (name === "simd.F32x4" || name === "F32x4")
1526
+ return `(Array.isArray(${val}) && ${val}.length === 4 && typeof ${val}[0] === "number")`;
1527
+ if (name === "simd.Mask32x4" || name === "Mask32x4")
1528
+ return `(Array.isArray(${val}) && ${val}.length === 4 && typeof ${val}[0] === "boolean")`;
1529
+ if (name === "error")
1530
+ return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
1531
+ if (this.structNames.has(name))
1532
+ return `(${val} instanceof ${name} && ${val}.__p !== true)`;
1533
+ const named = this.checker?.types.get(name);
1534
+ const namedInfo = this._namedBoxInfo(named);
1535
+ if (namedInfo) return `(${val} instanceof ${namedInfo.cls})`;
1536
+ // User-declared interfaces: check the dynamic value has every method.
1537
+ if (!name.includes(".") && named?.underlying?.kind === "interface")
1538
+ return this._ifaceMethodCheck(
1539
+ val,
1540
+ [...named.underlying.methods.keys()],
1541
+ this._unwrapIsMulti(named.underlying.methods.get("Unwrap")),
1542
+ );
1543
+ return "true"; // unknown type — can't check at runtime
1544
+ },
1545
+
1546
+ // Anonymous interfaces and pointers to named non-struct types; null otherwise.
1547
+ _namedOrIfaceCheckExpr(typeNode, val) {
1548
+ if (typeNode.kind === "InterfaceType" && !(typeNode.embeds?.length > 0))
1549
+ return this._ifaceMethodCheck(
1550
+ val,
1551
+ (typeNode.methods ?? []).map((m) => m.name),
1552
+ this._unwrapIsMulti(
1553
+ (typeNode.methods ?? []).find((m) => m.name === "Unwrap"),
1554
+ ),
1555
+ );
1556
+ if (typeNode.kind === "PointerType" && typeNode.base?.kind === "TypeName") {
1557
+ const info = this._namedBoxInfo(
1558
+ this.checker?.types.get(typeNode.base.name),
1559
+ );
1560
+ if (info) return `(${val} instanceof ${info.cls}$Ptr)`;
1561
+ }
1562
+ return null;
1563
+ },
1564
+
1565
+ // `Unwrap() []error` (errors.Join) and `Unwrap() error` share a name: the
1566
+ // JS function of a multi-error `Unwrap` is tagged `__multi`. `method` is
1567
+ // a func type or an interface method AST; null when there is no `Unwrap`.
1568
+ _unwrapIsMulti(method) {
1569
+ if (!method) return null;
1570
+ const ret = method.returnType ?? method.returns?.[0];
1571
+ const u = ret?.kind === "named" ? ret.underlying : ret;
1572
+ return u?.kind === "slice" || u?.kind === "SliceType";
1573
+ },
1574
+
1575
+ _ifaceMethodCheck(val, methodNames, multiUnwrap = null) {
1576
+ const checks = methodNames.map((m) =>
1577
+ m === "Unwrap" && multiUnwrap !== null
1578
+ ? `typeof ${val}.${m} === "function" && (${val}.${m}.__multi === true) === ${multiUnwrap}`
1579
+ : `typeof ${val}.${m} === "function"`,
1580
+ );
1581
+ return `(${[`${val} != null`, ...checks].join(" && ")})`;
1582
+ },
1583
+
1584
+ // The value an assertion to `typeNode` yields: raw, unboxed from the
1585
+ // interface's box for a named non-struct type (known only at run time
1586
+ // for a type parameter).
1587
+ _assertedValueJs(typeNode, val) {
1588
+ if (this._isNamedBoxTypeNode(typeNode)) return `${val}.__gfUnbox()`;
1589
+ const factory =
1590
+ typeNode?.kind === "TypeName"
1591
+ ? this._typeParamZeroFactory(typeNode.name)
1592
+ : null;
1593
+ return factory ? `(${factory}?.u ? ${val}.__gfUnbox() : ${val})` : val;
1594
+ },
1595
+
1596
+ _isNamedBoxTypeNode(typeNode) {
1597
+ if (typeNode?.kind === "TypeName")
1598
+ return Boolean(
1599
+ this._namedBoxInfo(this.checker?.types.get(typeNode.name)),
1600
+ );
1601
+ if (typeNode?.kind === "PointerType" && typeNode.base?.kind === "TypeName")
1602
+ return Boolean(
1603
+ this._namedBoxInfo(this.checker?.types.get(typeNode.base.name)),
1604
+ );
1605
+ return false;
1606
+ },
1607
+
1608
+ // Converting a struct to a different defined struct type builds a copy in
1609
+ // the target's class (so its methods exist); null when not applicable.
1610
+ _genStructClassConversion(targetName, srcType, inner) {
1611
+ if (!targetName || targetName.includes("[")) return null;
1612
+ const target = this.checker?.types.get(targetName);
1613
+ if (target?.kind !== "named" || target.underlying?.kind !== "struct")
1614
+ return null;
1615
+ if (srcType?.name === targetName) return null;
1616
+ const srcU = srcType?.kind === "named" ? srcType.underlying : srcType;
1617
+ if (srcU?.kind !== "struct") return null;
1618
+ const cls = this._dequalify(targetName);
1619
+ return `Object.assign(new ${cls}(), ${this._cloneJs(srcType, inner)})`;
764
1620
  },
765
1621
 
766
1622
  _genTypeConversionCall(expr) {
767
- const name = expr._conversionTargetType?.name;
1623
+ const targetType = expr._conversionTargetType;
768
1624
  const arg = this.genExpr(expr.args[0]);
769
- if (name && this.namedWrapperNames.has(name)) return `new ${name}(${arg})`;
1625
+ const viaParam = this._genTypeParamConversion(targetType?.name, arg);
1626
+ if (targetType?.kind === "typeParam" && viaParam) return viaParam;
1627
+ const structConv = this._genStructClassConversion(
1628
+ targetType?.kind === "named" ? targetType.name : undefined,
1629
+ expr.args[0]?._type,
1630
+ arg,
1631
+ );
1632
+ if (structConv) return structConv;
1633
+ const isRaw =
1634
+ targetType?.name === "json.RawMessage" ||
1635
+ targetType?.name === "encoding/json.RawMessage";
1636
+ const base =
1637
+ targetType?.kind === "named" ? targetType.underlying : targetType;
1638
+ if (
1639
+ targetType?.kind === "named" &&
1640
+ base?.kind === "basic" &&
1641
+ this._namedBoxInfo(targetType)
1642
+ )
1643
+ return this._genPrimitiveConversion(
1644
+ { expr: expr.args[0] },
1645
+ arg,
1646
+ base.name,
1647
+ );
1648
+ if (base?.kind === "slice") {
1649
+ const elem = base.elem?.name;
1650
+ if (
1651
+ (elem === "byte" || elem === "uint8") &&
1652
+ this._isStringExprType(expr.args[0]?._type)
1653
+ ) {
1654
+ const enc = `new TextEncoder().encode(${arg})`;
1655
+ return isRaw ? `((u) => { u.__raw = true; return u; })(${enc})` : enc;
1656
+ }
1657
+ if (isRaw) {
1658
+ return `((u) => { u.__raw = true; return u; })(${arg})`;
1659
+ }
1660
+ }
770
1661
  return arg;
771
1662
  },
772
1663
 
@@ -785,14 +1676,50 @@ export const expressionGenMethods = {
785
1676
  if (result !== undefined) return result;
786
1677
  }
787
1678
 
1679
+ const nilSafe = this._nilSafeMethodClass(expr.func);
1680
+ if (nilSafe) {
1681
+ // The method tests its receiver for nil: call it through the
1682
+ // prototype so a nil `this` reaches it instead of a TypeError.
1683
+ const recv = this.genExpr(expr.func.expr);
1684
+ const rest = this._genCallArgs(expr);
1685
+ return `${nilSafe}.prototype.${expr.func.field}.call(${rest ? `${recv}, ${rest}` : recv})`;
1686
+ }
1687
+
788
1688
  expr.func._callee = true;
789
1689
  const rawFn = this.genExpr(expr.func);
790
1690
  const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
791
- if (expr._multiForward) return `${fn}(...${this.genExpr(expr.args[0])})`;
792
- const args = expr.args
793
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
794
- .join(", ");
795
- return `${fn}(${args})`;
1691
+ const args = this._genCallArgs(expr);
1692
+ const zeros = this._genericCallZeros(expr);
1693
+ if (!zeros) return `${fn}(${args})`;
1694
+ return `${fn}(${args ? `${zeros}, ${args}` : zeros})`;
1695
+ },
1696
+
1697
+ // The class of a pointer-receiver struct method that accepts a nil receiver
1698
+ // when `func` selects it on a pointer of exactly that struct, else null.
1699
+ _nilSafeMethodClass(func) {
1700
+ if (func?.kind !== "SelectorExpr" || func._isMethodExpr) return null;
1701
+ const t = func.expr?._type;
1702
+ if (t?.kind !== "pointer" || t.base?.kind !== "named") return null;
1703
+ const cls = t.base.name;
1704
+ if (t.base.typeArgs?.length) return null;
1705
+ return this.nilSafeMethods?.has(`${cls}.${func.field}`) ? cls : null;
1706
+ },
1707
+
1708
+ // The leading zero-value factories of a call to a generic function
1709
+ // (`F(x)` with inferred, `F[int](x)` with explicit type arguments), or null.
1710
+ _genericCallZeros(expr) {
1711
+ const site = expr._genericFnType
1712
+ ? expr
1713
+ : expr.func.kind === "InstantiationExpr" && expr.func._genericFnType
1714
+ ? expr.func
1715
+ : null;
1716
+ if (!site) return null;
1717
+ const { typeParams } = site._genericFnType;
1718
+ return this._typeArgFactories(
1719
+ typeParams,
1720
+ site._typeArgs,
1721
+ this._genericFnNeedsTypeInfo(site._genericFnType),
1722
+ ).join(", ");
796
1723
  },
797
1724
 
798
1725
  _isErrorInterfaceType(t) {
@@ -814,7 +1741,35 @@ export const expressionGenMethods = {
814
1741
  return recvType?.name ?? recvType?.base?.name;
815
1742
  },
816
1743
 
1744
+ _genJsonStreamCall(recvName, expr) {
1745
+ const isEnc = this._isJsonEncoderName(recvName);
1746
+ if (isEnc && expr.func.field === "Encode") {
1747
+ this.useHelper("json");
1748
+ const enc = this.genExpr(expr.func.expr);
1749
+ this._markJsonAnyEncoders();
1750
+ const argType = expr.args[0]?._type;
1751
+ const codec = this._getJsonEncoder(argType);
1752
+ const arg = this.genExpr(expr.args[0]);
1753
+ return `__json_encoder_encode(${enc}, ${codec}, ${arg})`;
1754
+ }
1755
+ const isDec =
1756
+ recvName === "json.Decoder" ||
1757
+ recvName === "encoding/json.Decoder" ||
1758
+ recvName?.startsWith("Decoder$$");
1759
+ if (isDec && expr.func.field === "Decode") {
1760
+ this.useHelper("json");
1761
+ const dec = this.genExpr(expr.func.expr);
1762
+ const targetType = expr.args[0]?._type;
1763
+ const codec = this._getJsonDecoder(targetType);
1764
+ const arg = this.genExpr(expr.args[0]);
1765
+ return `__json_decoder_decode(${dec}, ${codec}, ${arg}, ${this._jsonTargetArgs(targetType)})`;
1766
+ }
1767
+ return undefined;
1768
+ },
1769
+
817
1770
  _genReceiverTypeCall(recvName, expr) {
1771
+ const jsonStream = this._genJsonStreamCall(recvName, expr);
1772
+ if (jsonStream !== undefined) return jsonStream;
818
1773
  if (recvName === "strings.Builder" || recvName === "bytes.Buffer")
819
1774
  return this._genBuilderCall(recvName, expr.func.field, expr);
820
1775
  if (recvName === "regexp.Regexp")
@@ -823,10 +1778,39 @@ export const expressionGenMethods = {
823
1778
  return this._genTimeMethodCall(expr.func.field, expr);
824
1779
  if (recvName === "time.Duration")
825
1780
  return this._genDurationMethodCall(expr.func.field, expr);
826
- if (recvName === "testing.T")
1781
+ if (recvName === "time.Month") {
1782
+ this.useHelper("duration");
1783
+ return `__monthString(${this.genExpr(expr.func.expr)})`;
1784
+ }
1785
+ if (recvName === "time.Weekday") {
1786
+ this.useHelper("duration");
1787
+ return `__weekdayString(${this.genExpr(expr.func.expr)})`;
1788
+ }
1789
+ if (recvName === "time.Location") {
1790
+ const recv = this.genExpr(expr.func.expr);
1791
+ return `(${recv} ? ${recv}.String() : "UTC")`;
1792
+ }
1793
+ if (recvName === "time.Timer") {
1794
+ const recv = this.genExpr(expr.func.expr);
1795
+ if (expr.func.field === "Stop")
1796
+ return `(${recv} ? ${recv}.Stop() : false)`;
1797
+ if (expr.func.field === "Reset")
1798
+ return `(${recv} ? ${recv}.Reset(${this.genExpr(expr.args[0])}) : false)`;
1799
+ }
1800
+ if (
1801
+ recvName === "testing.T" ||
1802
+ recvName === "testing.B" ||
1803
+ recvName === "testing.M" ||
1804
+ recvName === "testing.TB"
1805
+ )
827
1806
  return this._genTestingMethodCall(expr.func.field, expr);
828
1807
  if (recvName === "strings.Replacer") {
829
1808
  const recv = this.genExpr(expr.func.expr);
1809
+ if (expr.func.field === "WriteString") {
1810
+ const w = this.genExpr(expr.args[0]);
1811
+ const s = this.genExpr(expr.args[1]);
1812
+ return `((r,w,s) => { s = r ? r.Replace(s) : s; const x = w?.value ?? w; if (typeof x.WriteString === "function") return x.WriteString(s); if (typeof x._buf === "string") { x._buf += s; return [s.length, null]; } if (Array.isArray(x._buf)) { const bytes = new TextEncoder().encode(s); for (let i = 0; i < bytes.length; i++) x._buf.push(bytes[i]); return [bytes.length, null]; } if (typeof x.Write === "function") return x.Write(new TextEncoder().encode(s)); return [0, "strings.Replacer.WriteString: unsupported writer"]; })(${recv}, ${w}, ${s})`;
1813
+ }
830
1814
  const arg = this.genExpr(expr.args[0]);
831
1815
  return `(${recv} ? ${recv}.Replace(${arg}) : ${arg})`;
832
1816
  }
@@ -834,9 +1818,13 @@ export const expressionGenMethods = {
834
1818
  const args = expr.args.map((a) => this.genExpr(a)).join(", ");
835
1819
  return `${this.genExpr(expr.func.expr)}.subarray(${args})`;
836
1820
  }
1821
+ if (/^(simd\.)?(F32x4|Mask32x4)$/.test(recvName ?? "")) {
1822
+ return this._genSimdReceiverCall(expr, recvName.endsWith("Mask32x4"));
1823
+ }
837
1824
  if (recvName === "sync.Mutex" || recvName === "sync.RWMutex") {
838
1825
  if (expr.func.field === "TryLock" || expr.func.field === "TryRLock")
839
1826
  return "true";
1827
+ if (expr.func.field === "RLocker") return "({ Lock() {}, Unlock() {} })";
840
1828
  return "(void 0)";
841
1829
  }
842
1830
  if (recvName === "sync.Once" && expr.func.field === "Do") {
@@ -879,6 +1867,17 @@ export const expressionGenMethods = {
879
1867
  return undefined;
880
1868
  },
881
1869
 
1870
+ _genSimdReceiverCall(expr, isMask = false) {
1871
+ this.useHelper("simd");
1872
+ const method = expr.func.field;
1873
+ const recv = this.genExpr(expr.func.expr);
1874
+ const args = expr.args.map((a) => this.genExpr(a));
1875
+ if (method === "Lane") return `${recv}[${args[0]}]`;
1876
+ if (method === "Splat" && !isMask) return `__simd_splat(${args[0]})`;
1877
+ const helper = (isMask ? SIMD_MASK_HELPERS : SIMD_VECTOR_HELPERS)[method];
1878
+ return helper ? `${helper}(${[recv, ...args].join(", ")})` : undefined;
1879
+ },
1880
+
882
1881
  _genSelectorCall(expr) {
883
1882
  if (this._isErrorMethodCall(expr)) {
884
1883
  const recv = this.genExpr(expr.func.expr);
@@ -896,6 +1895,8 @@ export const expressionGenMethods = {
896
1895
  const recvName = this._resolveRecvName(recvType);
897
1896
  const typed = this._genReceiverTypeCall(recvName, expr);
898
1897
  if (typed !== undefined) return typed;
1898
+ const namedCall = this._genNamedMethodCall(expr);
1899
+ if (namedCall !== undefined) return namedCall;
899
1900
  if (this.strict && this._isInterfaceType(recvType)) {
900
1901
  // Go panics with a nil-dereference runtime error; JS would throw a
901
1902
  // TypeError with a different message.
@@ -936,7 +1937,12 @@ export const expressionGenMethods = {
936
1937
  (t?.kind === "named" && t.underlying?.kind === "map")
937
1938
  )
938
1939
  return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
939
- return `(${js}).length = 0`;
1940
+ // A slice: every element goes back to its zero value (the length stays).
1941
+ const st = t?.kind === "named" ? t.underlying : t;
1942
+ const zero = this.zeroValueForType(st?.elem);
1943
+ if (/^(new |\{|\[|Array|__nm\()|\$z_/.test(zero))
1944
+ return `((__s) => { if (__s) for (let __i = 0; __i < __s.length; __i++) __s[__i] = ${zero}; })(${js})`;
1945
+ return `(${js})?.fill(${zero})`;
940
1946
  },
941
1947
 
942
1948
  _genBuiltinIdent(name, expr) {
@@ -944,6 +1950,15 @@ export const expressionGenMethods = {
944
1950
  return gen ? gen(this, expr) : undefined;
945
1951
  },
946
1952
 
1953
+ // The source operand of `append(b, x...)` / `copy(b, x)`: a string
1954
+ // contributes its UTF-8 bytes.
1955
+ _genSliceSource(node) {
1956
+ const js = this.genExpr(node);
1957
+ return this._isStringExprType(node._type)
1958
+ ? `new TextEncoder().encode(${js})`
1959
+ : js;
1960
+ },
1961
+
947
1962
  genAppend(expr) {
948
1963
  const retType = expr._type;
949
1964
  const wrapField = this._namedWrapperField(retType, expr.args[0]);
@@ -953,16 +1968,25 @@ export const expressionGenMethods = {
953
1968
  const elems = expr.args
954
1969
  .slice(1)
955
1970
  .map((a) =>
956
- a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a),
1971
+ a._spread
1972
+ ? `...(${this._genSliceSource(a)} ?? [])`
1973
+ : this.genValueExpr(a),
957
1974
  );
958
1975
  if (elems.length === 0) return this.genExpr(expr.args[0]);
959
1976
  this.useHelper("append");
960
1977
  return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
961
1978
  }
962
1979
  const slice = this.genExpr(expr.args[0]);
1980
+ // Spread elements of array type are values: append copies them.
1981
+ const st = expr._type ?? expr.args[0]._type;
1982
+ const elemT = (st?.kind === "named" ? st.underlying : st)?.elem;
1983
+ const spreadOf = (a) =>
1984
+ this._aggregateBase(elemT)?.kind === "array"
1985
+ ? `...(${this.genExpr(a)} ?? []).map((__e) => ${this._cloneJs(elemT, "__e")})`
1986
+ : `...(${this._genSliceSource(a)} ?? [])`;
963
1987
  const elems = expr.args
964
1988
  .slice(1)
965
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a)));
1989
+ .map((a) => (a._spread ? spreadOf(a) : this.genValueExpr(a)));
966
1990
  if (elems.length === 0) return slice;
967
1991
  this.useHelper("append");
968
1992
  const sliceType = expr._type ?? expr.args[0]._type;
@@ -981,12 +2005,16 @@ export const expressionGenMethods = {
981
2005
  // make(map[K]V) → {}
982
2006
  const typeArg = expr.args[0];
983
2007
  const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
984
- const resolvedKind = typeArg._type?.kind;
2008
+ // A defined type (`type S []int`) makes like its underlying type.
2009
+ const madeType =
2010
+ typeArg._type?.kind === "named"
2011
+ ? typeArg._type.underlying
2012
+ : typeArg._type;
2013
+ const resolvedKind = madeType?.kind;
985
2014
  if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
986
2015
  const n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
987
2016
  const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
988
- const elemResolved =
989
- typeArg._type?.kind === "slice" ? typeArg._type.elem : null;
2017
+ const elemResolved = madeType?.kind === "slice" ? madeType.elem : null;
990
2018
  const typedCtor =
991
2019
  typedArrayConstructorForElem(elemResolved) ??
992
2020
  (elemNode?.name
@@ -995,8 +2023,14 @@ export const expressionGenMethods = {
995
2023
  name: elemNode.name,
996
2024
  })
997
2025
  : null);
2026
+ // make([]T, n, cap) allocates cap elements and returns a view of
2027
+ // the first n (`__mkslice`), so append within cap shares them.
2028
+ const capArg = expr.args[2] ? this.genExpr(expr.args[2]) : null;
2029
+ if (capArg) this.useHelper("slice");
2030
+ const withCap = (alloc) =>
2031
+ capArg ? `__mkslice(${n}, ${alloc(capArg)})` : alloc(n);
998
2032
  if (typedCtor) {
999
- return `new ${typedCtor}(${n})`;
2033
+ return withCap((k) => `new ${typedCtor}(${k})`);
1000
2034
  }
1001
2035
  // Use the proper zero value for the element type (e.g. new Point() not 0)
1002
2036
  let zero = "null";
@@ -1006,10 +2040,10 @@ export const expressionGenMethods = {
1006
2040
  zero = this.zeroValueForTypeNode(elemNode);
1007
2041
  }
1008
2042
  // If zero value is a constructor call, use a factory function so each element is distinct
1009
- if (zero.startsWith("new ")) {
1010
- return `Array.from({length: ${n}}, () => ${zero})`;
2043
+ if (zero.startsWith("new ") || zero.includes("$z_")) {
2044
+ return withCap((k) => `Array.from({length: ${k}}, () => ${zero})`);
1011
2045
  }
1012
- return `new Array(${n}).fill(${zero})`;
2046
+ return withCap((k) => `new Array(${k}).fill(${zero})`);
1013
2047
  }
1014
2048
  // map or fallback
1015
2049
  return "{}";
@@ -1039,22 +2073,42 @@ export const expressionGenMethods = {
1039
2073
  return t?.kind === "MapType";
1040
2074
  },
1041
2075
 
2076
+ // The one place a map literal key becomes a JS property name: a plain
2077
+ // string literal stays as is, any other key is computed so that the key
2078
+ // encoding of `genExpr` (`__ik`, struct keys) applies.
2079
+ _genMapLitKey(key) {
2080
+ return key.litKind === "STRING"
2081
+ ? JSON.stringify(key.value)
2082
+ : `[${this.genExpr(key)}]`;
2083
+ },
2084
+
1042
2085
  _genMapLitEntries(elems) {
1043
2086
  return elems
1044
- .map((e) => {
1045
- if (e.kind === "KeyValueExpr") {
1046
- const k =
1047
- e.key.litKind === "STRING"
1048
- ? JSON.stringify(e.key.value)
1049
- : `[${this.genExpr(e.key)}]`;
1050
- return `${k}: ${this.genValueExpr(e.value)}`;
1051
- }
1052
- return this.genValueExpr(e);
1053
- })
2087
+ .map((e) =>
2088
+ e.kind === "KeyValueExpr"
2089
+ ? `${this._genMapLitKey(e.key)}: ${this.genValueExpr(e.value)}`
2090
+ : this.genValueExpr(e),
2091
+ )
1054
2092
  .join(", ");
1055
2093
  },
1056
2094
 
2095
+ // A literal whose (possibly elided or named) type is a map type.
2096
+ _isMapLit(expr) {
2097
+ let t = expr._type;
2098
+ for (let i = 0; t?.kind === "named" && i < 32; i++) t = t.underlying;
2099
+ return t?.kind === "map";
2100
+ },
2101
+
1057
2102
  _genFallbackCompositeLit(expr) {
2103
+ if (this._isMapLit(expr))
2104
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
2105
+ const at = this._litArrayType(expr);
2106
+ if (at) {
2107
+ // A named array type without methods (`type V [3]int; V{1}`).
2108
+ const elems = this._genArrayLitElems(at, expr);
2109
+ const ctor = typedArrayConstructorForElem(at.elem);
2110
+ return ctor ? `new ${ctor}([${elems}])` : `[${elems}]`;
2111
+ }
1058
2112
  if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
1059
2113
  const fields = expr.elems
1060
2114
  .map(
@@ -1095,10 +2149,12 @@ export const expressionGenMethods = {
1095
2149
  (ce) => (ce._positionalField ?? ce.key?.name) === ef,
1096
2150
  );
1097
2151
  values[idx] = match
1098
- ? this.genValueExpr(match.value ?? match)
2152
+ ? this.genValueExpr(match.kind === "KeyValueExpr" ? match.value : match)
1099
2153
  : fields[idx].zero;
1100
2154
  } else {
1101
- values[idx] = `${this.genExpr(e.value)}.${ef}`;
2155
+ const valExpr = this.genExpr(e.value);
2156
+ values[idx] =
2157
+ `(${valExpr} != null ? ${valExpr}.${ef} : ${fields[idx].zero})`;
1102
2158
  }
1103
2159
  return idx;
1104
2160
  },
@@ -1116,6 +2172,11 @@ export const expressionGenMethods = {
1116
2172
  const idx = this._fillEmbedField(fields, ef, e, values);
1117
2173
  if (idx > maxIdx) maxIdx = idx;
1118
2174
  }
2175
+ const embedIdx = fields.findIndex((f) => f.name === k);
2176
+ if (embedIdx >= 0) {
2177
+ values[embedIdx] = this.genValueExpr(e.value);
2178
+ if (embedIdx > maxIdx) maxIdx = embedIdx;
2179
+ }
1119
2180
  } else {
1120
2181
  const idx = fields.findIndex((f) => f.name === k);
1121
2182
  if (idx >= 0) {
@@ -1129,6 +2190,8 @@ export const expressionGenMethods = {
1129
2190
 
1130
2191
  _genPositionalStructLit(typeName, expr) {
1131
2192
  const fields = this.getStructFields(typeName);
2193
+ const generic = this._genGenericStructLit(fields, expr);
2194
+ if (generic) return generic;
1132
2195
  if (!fields || fields.length === 0 || expr.elems.length === 0) {
1133
2196
  return `new ${typeName}()`;
1134
2197
  }
@@ -1139,17 +2202,102 @@ export const expressionGenMethods = {
1139
2202
  const maxExplicitIdx = hasPositional
1140
2203
  ? this._fillPositionalStructElems(fields, expr, values)
1141
2204
  : this._fillKeyedStructElems(fields, expr, values);
1142
- if (maxExplicitIdx < 0) {
1143
- return `new ${typeName}()`;
1144
- }
1145
- const args = values.slice(0, maxExplicitIdx + 1);
1146
- return `new ${typeName}(${args.join(", ")})`;
2205
+ const inst =
2206
+ maxExplicitIdx < 0
2207
+ ? `new ${typeName}()`
2208
+ : `new ${typeName}(${values.slice(0, maxExplicitIdx + 1).join(", ")})`;
2209
+ return inst;
2210
+ },
2211
+
2212
+ // `Box[int]{…}`: every field is passed (unset ones as undefined, so the
2213
+ // constructor starts them at their zero value) followed by the factories.
2214
+ _genGenericStructLit(fields, expr) {
2215
+ const t = expr._type;
2216
+ if (t?.kind !== "named" || !t.typeArgs?.length) return null;
2217
+ const values = fields.map(() => undefined);
2218
+ if (expr.elems.length > 0) {
2219
+ const hasPositional = expr.elems.some(
2220
+ (e) => e._positionalField || e.kind !== "KeyValueExpr",
2221
+ );
2222
+ if (hasPositional) this._fillPositionalStructElems(fields, expr, values);
2223
+ else this._fillKeyedStructElems(fields, expr, values);
2224
+ }
2225
+ return this._genericStructNew(t.name, t.typeArgs, values);
2226
+ },
2227
+
2228
+ // The array type of a literal (through named types), or null.
2229
+ _litArrayType(expr) {
2230
+ let t = expr._type;
2231
+ while (t?.kind === "named") t = t.underlying;
2232
+ return t?.kind === "array" && typeof t.size === "number" ? t : null;
2233
+ },
2234
+
2235
+ // Elements of a literal of array type `at` (or `{ size, elem }` for a
2236
+ // slice), placed at their (keyed) indices, with every other slot the
2237
+ // element zero value: `[3]int{1}` is `[1, 0, 0]` and `[5]int{2: 7}` is
2238
+ // `[0, 0, 7, 0, 0]`, as in Go. Keys out of ascending order would
2239
+ // evaluate the elements out of source order, so the values are then
2240
+ // computed first and spread into place.
2241
+ _genArrayLitElems(at, expr) {
2242
+ const values = new Array(at.size).fill(null);
2243
+ const indices = compositeLitIndices(expr.elems);
2244
+ const src = expr.elems.map((e) =>
2245
+ this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e),
2246
+ );
2247
+ const ascending = indices.every((v, i) => i === 0 || v > indices[i - 1]);
2248
+ indices.forEach((idx, i) => {
2249
+ values[idx] = ascending ? src[i] : `v$[${i}]`;
2250
+ });
2251
+ for (let i = 0; i < values.length; i++)
2252
+ values[i] ??= this.zeroValueForType(at.elem);
2253
+ if (ascending) return values.join(", ");
2254
+ return `...((v$) => [${values.join(", ")}])([${src.join(", ")}])`;
2255
+ },
2256
+
2257
+ // `{ size, elem }` for a slice or array literal (through named types).
2258
+ _litIndexedShape(expr) {
2259
+ const at = this._litArrayType(expr);
2260
+ if (at) return at;
2261
+ let t = expr._type;
2262
+ while (t?.kind === "named") t = t.underlying;
2263
+ if (t?.kind !== "slice") return null;
2264
+ // A loop, not a spread: huge literals would overflow the call stack.
2265
+ let max = -1;
2266
+ for (const i of compositeLitIndices(expr.elems)) if (i > max) max = i;
2267
+ const size = max + 1;
2268
+ return { size, elem: t.elem };
2269
+ },
2270
+
2271
+ // A large array literal that leaves most slots zero (`[100000]byte{}`,
2272
+ // `[65536]int{1: 2}`) starts from the zero array, as `var a [N]T` does,
2273
+ // and assigns only the given elements instead of spelling out every
2274
+ // slot. Small or mostly-filled literals stay inline (null).
2275
+ _genSparseArrayLit(expr) {
2276
+ const at = this._litArrayType(expr);
2277
+ if (!at || at.size <= SPARSE_ARRAY_MIN) return null;
2278
+ if (expr.elems.length * 2 >= at.size) return null;
2279
+ const zero = this.zeroValueForType(at);
2280
+ if (expr.elems.length === 0) return zero;
2281
+ const indices = compositeLitIndices(expr.elems);
2282
+ const sets = expr.elems.map((e, i) => {
2283
+ const v = this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e);
2284
+ return `a$[${indices[i]}] = (${v})`;
2285
+ });
2286
+ return `((a$) => (${sets.join(", ")}, a$))(${zero})`;
2287
+ },
2288
+
2289
+ _genLitElems(expr) {
2290
+ const at = this._litIndexedShape(expr);
2291
+ if (at) return this._genArrayLitElems(at, expr);
2292
+ return expr.elems
2293
+ .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
2294
+ .join(", ");
1147
2295
  },
1148
2296
 
1149
2297
  _genSliceOrArrayLit(t, expr) {
1150
- const elems = expr.elems
1151
- .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
1152
- .join(", ");
2298
+ const sparse = this._genSparseArrayLit(expr);
2299
+ if (sparse) return sparse;
2300
+ const elems = this._genLitElems(expr);
1153
2301
  const elemNode =
1154
2302
  t.kind === "SliceType" || t.kind === "ArrayType" ? t.elem : null;
1155
2303
  const elemResolved =
@@ -1178,6 +2326,18 @@ export const expressionGenMethods = {
1178
2326
  return "{ _buf: [] }";
1179
2327
  if (typeName === "strings.Builder" || typeName === "Builder")
1180
2328
  return '{ _buf: "" }';
2329
+ if (typeName === "simd.Mask32x4" || typeName === "Mask32x4")
2330
+ return "Object.freeze([false, false, false, false])";
2331
+ if (typeName === "simd.F32x4" || typeName === "F32x4") {
2332
+ this.useHelper("simd");
2333
+ const a = expr.elems[0] ? this.genExpr(expr.elems[0]) : "0";
2334
+ const b = expr.elems[1] ? this.genExpr(expr.elems[1]) : "0";
2335
+ const c = expr.elems[2] ? this.genExpr(expr.elems[2]) : "0";
2336
+ const d = expr.elems[3] ? this.genExpr(expr.elems[3]) : "0";
2337
+ return `__simd_f32x4(${a}, ${b}, ${c}, ${d})`;
2338
+ }
2339
+ const aliasLit = this._genAliasLit(t, typeName, expr);
2340
+ if (aliasLit !== null) return aliasLit;
1181
2341
  if (typeName && this.structNames.has(typeName))
1182
2342
  return this._genPositionalStructLit(typeName, expr);
1183
2343
  if (typeName && this.namedWrapperNames.has(typeName))
@@ -1191,14 +2351,68 @@ export const expressionGenMethods = {
1191
2351
  return this._genFallbackCompositeLit(expr);
1192
2352
  },
1193
2353
 
2354
+ // Literals whose type expression is not the class to construct: a generic
2355
+ // alias `A[X]{…}`, an anonymous `struct{…}{…}`, or `P{…}` for
2356
+ // `type P = struct{…}`. Null for every other literal.
2357
+ _genAliasLit(t, typeName, expr) {
2358
+ if (t.kind === "GenericTypeName" || t.kind === "InstantiationExpr")
2359
+ return this._genGenericAliasLit(typeName, expr);
2360
+ const anon =
2361
+ t.kind === "StructType" ||
2362
+ ((t.kind === "TypeName" || t.kind === "Ident") &&
2363
+ this.checker?.types.get(t.name)?.kind === "struct");
2364
+ return anon ? this._genAnonStructLit(expr) : null;
2365
+ },
2366
+
2367
+ // `A[X]{…}` for a generic alias `type A[P any] = T`: the literal has the
2368
+ // aliased type (expr._type), so dispatch on that instead of on `A`.
2369
+ _genGenericAliasLit(typeName, expr) {
2370
+ if (!this.checker?.types.get(typeName)?._generic?.isAlias) return null;
2371
+ const rt = expr._type;
2372
+ const base = rt?.kind === "named" ? rt.underlying : rt;
2373
+ if (rt?.kind === "named" && this.structNames.has(rt.name))
2374
+ return this._genPositionalStructLit(rt.name, expr);
2375
+ if (base?.kind === "map")
2376
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
2377
+ if (base?.kind === "struct") return this._genAnonStructLit(expr);
2378
+ return this._genImplicitCompositeLit(expr);
2379
+ },
2380
+
2381
+ // A literal of an anonymous struct type: a plain object with every field,
2382
+ // the omitted ones set to their zero value.
2383
+ _genAnonStructLit(expr) {
2384
+ const st =
2385
+ expr._type?.kind === "named" ? expr._type.underlying : expr._type;
2386
+ if (st?.kind !== "struct" || !(st.fields instanceof Map))
2387
+ return this._genFallbackCompositeLit(expr);
2388
+ const values = new Map();
2389
+ for (const e of expr.elems) {
2390
+ if (e._positionalField)
2391
+ values.set(e._positionalField, this.genValueExpr(e));
2392
+ else if (e.kind === "KeyValueExpr")
2393
+ values.set(e.key.name, this.genValueExpr(e.value));
2394
+ }
2395
+ const fields = [...st.fields].map(
2396
+ ([name, ft]) =>
2397
+ `${name}: ${values.get(name) ?? this.zeroValueForType(ft)}`,
2398
+ );
2399
+ return `{ ${fields.join(", ")} }`;
2400
+ },
2401
+
1194
2402
  _genImplicitCompositeLit(expr) {
1195
- const typeName = expr._type?.name ?? expr._type?.underlying?.name;
2403
+ // An elided `&T` (`[]*T{{1}}`) builds the struct; a struct pointer is the object.
2404
+ const lit = expr._type?.kind === "pointer" ? expr._type.base : expr._type;
2405
+ const typeName = lit?.name ?? lit?.underlying?.name;
1196
2406
  if (typeName && this.structNames.has(typeName)) {
1197
2407
  return this._genPositionalStructLit(typeName, expr);
1198
2408
  }
2409
+ if (this._isMapLit(expr))
2410
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
1199
2411
  const hasPositional = expr.elems.some((e) => e._positionalField);
1200
2412
  const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
1201
- if (hasPositional || hasKeyed) {
2413
+ // `{2: 5}` inside `[2][3]int{…}` is an array, not an object.
2414
+ const indexed = this._litIndexedShape(expr) !== null;
2415
+ if (hasPositional || (hasKeyed && !indexed)) {
1202
2416
  const fields = expr.elems
1203
2417
  .map((e) => {
1204
2418
  if (e._positionalField)
@@ -1208,7 +2422,9 @@ export const expressionGenMethods = {
1208
2422
  .join(", ");
1209
2423
  return `{ ${fields} }`;
1210
2424
  }
1211
- const elems = expr.elems.map((e) => this.genValueExpr(e)).join(", ");
2425
+ const sparse = this._genSparseArrayLit(expr);
2426
+ if (sparse) return sparse;
2427
+ const elems = this._genLitElems(expr);
1212
2428
  if (isTypedArraySlice(expr._type)) {
1213
2429
  const typedCtor = typedArrayConstructorForElem(expr._type.elem);
1214
2430
  if (typedCtor) return `new ${typedCtor}([${elems}])`;
@@ -1220,13 +2436,10 @@ export const expressionGenMethods = {
1220
2436
  const namedType = this.checker?.types.get(typeName);
1221
2437
  const u = namedType?.underlying;
1222
2438
  if (u?.kind === "map") {
1223
- return `new ${typeName}({ ${this._genMapLitEntries(expr.elems)} })`;
2439
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
1224
2440
  }
1225
- // Default: slice
1226
- const elems = expr.elems
1227
- .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
1228
- .join(", ");
1229
- return `new ${typeName}([${elems}])`;
2441
+ // Default: slice or array
2442
+ return `[${this._genLitElems(expr)}]`;
1230
2443
  },
1231
2444
 
1232
2445
  // ── Helpers ───────────────────────────────────────────────────
@@ -1238,6 +2451,7 @@ export const expressionGenMethods = {
1238
2451
 
1239
2452
  getTypeName(typeNode) {
1240
2453
  if (!typeNode) return null;
2454
+ if (typeNode._stdName) return typeNode._stdName;
1241
2455
  if (typeNode.kind === "TypeName") return this._dequalify(typeNode.name);
1242
2456
  if (typeNode.kind === "GenericTypeName") return typeNode.name;
1243
2457
  if (typeNode.kind === "Ident") return typeNode.name;
@@ -1268,15 +2482,105 @@ export const expressionGenMethods = {
1268
2482
  },
1269
2483
 
1270
2484
  // Returns true if the AST node contains a function call (side-effect risk).
1271
- _hasCallExpr(node) {
2485
+ _hasCallExpr(node, seen = new Set()) {
1272
2486
  if (!node || typeof node !== "object") return false;
2487
+ if (seen.has(node)) return false;
2488
+ seen.add(node);
1273
2489
  if (node.kind === "CallExpr") return true;
1274
- for (const v of Object.values(node)) {
1275
- if (v && typeof v === "object" && this._hasCallExpr(v)) return true;
2490
+ for (const [k, v] of Object.entries(node)) {
2491
+ if (k.startsWith("_") || k === "scope") continue;
2492
+ if (v && typeof v === "object" && this._hasCallExpr(v, seen)) return true;
1276
2493
  }
1277
2494
  return false;
1278
2495
  },
1279
2496
 
2497
+ // ── Generic zero values (see HELPER_GENERICS) ─────────────────
2498
+
2499
+ // JS condition: type parameter `name` was instantiated with a struct (its
2500
+ // zero value is a struct object, which is its own pointer when erased; a
2501
+ // map's zero is a bare `{}`, so only a class instance or a populated
2502
+ // object counts).
2503
+ _typeParamIsStructJs(name) {
2504
+ const f = this._typeParamZeroFactory(name);
2505
+ return f
2506
+ ? `((__z) => __z !== null && typeof __z === "object" && !Array.isArray(__z) && !ArrayBuffer.isView(__z) && (Object.getPrototypeOf(Object.getPrototypeOf(__z)) !== null || Object.keys(__z).length > 0))(${f}())`
2507
+ : "false";
2508
+ },
2509
+
2510
+ // The zero-value factory bound to type parameter `name` in the code being
2511
+ // emitted (`$z_T`), or null outside a generic body.
2512
+ _typeParamZeroFactory(name) {
2513
+ return this._typeParamZeros?.get(name) ?? null;
2514
+ },
2515
+
2516
+ // Runs `fn` with type parameters `names` bound to the factories `$z_<name>`.
2517
+ _withTypeParamZeros(names, fn) {
2518
+ const prev = this._typeParamZeros;
2519
+ this._typeParamZeros = names
2520
+ ? new Map(names.map((n) => [n, `$z_${n}`]))
2521
+ : null;
2522
+ try {
2523
+ return fn();
2524
+ } finally {
2525
+ this._typeParamZeros = prev;
2526
+ }
2527
+ },
2528
+
2529
+ // A factory `() => zero` for type `t`: what a generic callee receives for
2530
+ // one of its type parameters.
2531
+ zeroFactoryFor(t) {
2532
+ if (t?.kind === "typeParam") {
2533
+ const bound = this._typeParamZeroFactory(t.name);
2534
+ if (bound) return bound;
2535
+ }
2536
+ this.useHelper("generics");
2537
+ const zero = this.zeroValueForType(t);
2538
+ return Object.hasOwn(SHARED_ZERO_FACTORIES, zero)
2539
+ ? SHARED_ZERO_FACTORIES[zero]
2540
+ : `() => (${zero})`;
2541
+ },
2542
+
2543
+ // One factory per type parameter of generic function or type `generic`.
2544
+ // `withInfo`: the callee needs the instantiated types (see type-info.js).
2545
+ _typeArgFactories(typeParams, typeArgs, withInfo = false) {
2546
+ return typeParams.map((_, i) =>
2547
+ withInfo
2548
+ ? this._typeInfoFactory(typeArgs?.[i])
2549
+ : this.zeroFactoryFor(typeArgs?.[i]),
2550
+ );
2551
+ },
2552
+
2553
+ // `new Box(args…, [factories])` for an instance of generic struct `name`
2554
+ // with `typeArgs`; fields without an argument start at their zero value.
2555
+ // Null when `name` is not a generic struct.
2556
+ _genericStructNew(name, typeArgs, args = []) {
2557
+ const generic = this.checker?.types.get(name);
2558
+ const st = generic?.underlying;
2559
+ const cls = this._dequalify(name);
2560
+ if (!generic?._generic || generic._generic.isAlias) return null;
2561
+ if (st?.kind !== "struct") return null;
2562
+ if (!this.structNames.has(cls)) return null;
2563
+ const tps = generic._generic.typeParams;
2564
+ const vals = Array.from(
2565
+ { length: st.fields.size },
2566
+ (_, i) => args[i] ?? "undefined",
2567
+ );
2568
+ const tz = this._typeArgFactories(
2569
+ tps,
2570
+ typeArgs,
2571
+ this._genericTypeNeedsTypeInfo(generic) ||
2572
+ typeArgs?.some((t) => mentionsTypeParam(t)),
2573
+ );
2574
+ const id = instanceId(typeArgs);
2575
+ if (id !== null) tz.push(JSON.stringify(id));
2576
+ else {
2577
+ const runtimeId = this._runtimeInstanceId(typeArgs);
2578
+ if (runtimeId) tz.push(runtimeId);
2579
+ }
2580
+ vals.push(`[${tz.join(", ")}]`);
2581
+ return `new ${cls}(${vals.join(", ")})`;
2582
+ },
2583
+
1280
2584
  // Returns the JS zero-value literal for a basic type name, or null if not a basic type.
1281
2585
  _zeroForBasicName(name) {
1282
2586
  return Object.hasOwn(ZERO_FOR_BASIC, name) ? ZERO_FOR_BASIC[name] : null;
@@ -1288,6 +2592,19 @@ export const expressionGenMethods = {
1288
2592
  if (entry[0]) this.useHelper(entry[0]);
1289
2593
  return entry[1];
1290
2594
  }
2595
+ const named = this.checker?.types.get(name);
2596
+ const u = named?.kind === "named" ? named.underlying : null;
2597
+ if (
2598
+ u &&
2599
+ u.kind !== "struct" &&
2600
+ u.kind !== "interface" &&
2601
+ u.kind !== "named"
2602
+ )
2603
+ return this.zeroValueForType(u);
2604
+ if (named?.kind === "named" && u?.kind === "struct") {
2605
+ const clsName = named.name ?? this._dequalify(name);
2606
+ if (this.structNames.has(clsName)) return `new ${clsName}()`;
2607
+ }
1291
2608
  name = this._dequalify(name);
1292
2609
  if (this.structNames.has(name)) return `new ${name}()`;
1293
2610
  return "null";
@@ -1296,7 +2613,10 @@ export const expressionGenMethods = {
1296
2613
  zeroValueForTypeNode(typeNode) {
1297
2614
  if (!typeNode) return "null";
1298
2615
  switch (typeNode.kind) {
2616
+ case "Ident":
1299
2617
  case "TypeName": {
2618
+ const factory = this._typeParamZeroFactory(typeNode.name);
2619
+ if (factory) return `${factory}()`;
1300
2620
  const basic = this._zeroForBasicName(typeNode.name);
1301
2621
  if (basic !== null) return basic;
1302
2622
  return this._zeroForNamedType(typeNode.name);
@@ -1318,7 +2638,7 @@ export const expressionGenMethods = {
1318
2638
  );
1319
2639
  }
1320
2640
  case "MapType":
1321
- return "{}";
2641
+ return this._nilMapZero();
1322
2642
  case "PointerType":
1323
2643
  return "null";
1324
2644
  case "StructType": {
@@ -1331,16 +2651,31 @@ export const expressionGenMethods = {
1331
2651
  .join(", ");
1332
2652
  return `{ ${fields} }`;
1333
2653
  }
2654
+ case "GenericTypeName":
2655
+ case "InstantiationExpr":
2656
+ return (
2657
+ this._genericStructNew(
2658
+ this.getTypeName(typeNode),
2659
+ typeNode._typeArgs,
2660
+ ) ?? "null"
2661
+ );
1334
2662
  default:
1335
2663
  return "null";
1336
2664
  }
1337
2665
  },
1338
2666
 
2667
+ // A nil map: a fresh tagged `{}` in js packages (`m == nil` holds while it
2668
+ // stays empty); in strict mode the frozen `__nilmap`, which panics on write.
2669
+ _nilMapZero() {
2670
+ this.useHelper(this.strict ? "nilMap" : "nm");
2671
+ return this.strict ? "__nilmap" : "__nm()";
2672
+ },
2673
+
1339
2674
  _arrayZero(n, ctor, elemZero) {
1340
2675
  if (ctor) return `new ${ctor}(${n})`;
1341
2676
  if (n === 0) return "[]";
1342
- if (/^(new |\{|\[|Array)/.test(elemZero))
1343
- return `Array.from({ length: ${n} }, () => ${elemZero})`;
2677
+ if (/^(new |\{|\[|Array|__nm\()|\$z_/.test(elemZero))
2678
+ return `Array.from({ length: ${n} }, () => (${elemZero}))`;
1344
2679
  return `new Array(${n}).fill(${elemZero})`;
1345
2680
  },
1346
2681
 
@@ -1359,7 +2694,7 @@ export const expressionGenMethods = {
1359
2694
  this.zeroValueForType(t.elem),
1360
2695
  );
1361
2696
  case "map":
1362
- return "{}";
2697
+ return this._nilMapZero();
1363
2698
  case "struct": {
1364
2699
  const fields = [...t.fields.entries()]
1365
2700
  .map(([name, ft]) => `${name}: ${this.zeroValueForType(ft)}`)
@@ -1367,7 +2702,14 @@ export const expressionGenMethods = {
1367
2702
  return `{ ${fields} }`;
1368
2703
  }
1369
2704
  case "named":
1370
- return this._zeroForNamedType(t.name);
2705
+ return (
2706
+ this._genericStructNew(t.name, t.typeArgs) ??
2707
+ this._zeroForNamedType(t.name)
2708
+ );
2709
+ case "typeParam": {
2710
+ const factory = this._typeParamZeroFactory(t.name);
2711
+ return factory ? `${factory}()` : "null";
2712
+ }
1371
2713
  default:
1372
2714
  return "null";
1373
2715
  }
@@ -1376,45 +2718,59 @@ export const expressionGenMethods = {
1376
2718
  // Emit a JS boolean expression that checks whether `val` matches type node `t`.
1377
2719
  _typeCheckExpr(typeNode, val) {
1378
2720
  if (!typeNode) return "true";
1379
- if (typeNode.kind === "TypeName") {
1380
- const name = typeNode.name;
1381
- if (TYPEOF_NUMBER_NAMES.has(name)) return `typeof ${val} === "number"`;
1382
- if (name === "string") return `typeof ${val} === "string"`;
1383
- if (name === "bool") return `typeof ${val} === "boolean"`;
1384
- if (name === "nil") return `${val} === null`;
1385
- if (name === "error")
1386
- return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
1387
- if (this.structNames.has(name))
1388
- return `(${val} instanceof ${name} && ${val}.__p !== true)`;
1389
- return "true"; // unknown type — can't check at runtime
1390
- }
2721
+ if (typeNode.kind === "TypeName")
2722
+ return this._typeNameCheckExpr(typeNode.name, val);
2723
+ const generic = this._genericInstanceCheck(typeNode, val);
2724
+ if (generic) return generic;
2725
+ const extended = this._namedOrIfaceCheckExpr(typeNode, val);
2726
+ if (extended) return extended;
1391
2727
  if (
1392
2728
  typeNode.kind === "PointerType" &&
1393
2729
  typeNode.base?.kind === "TypeName" &&
1394
- this.structNames.has(typeNode.base.name)
2730
+ this.structNames.has(this._qualifiedStructName(typeNode.base.name))
1395
2731
  ) {
1396
- const name = typeNode.base.name;
2732
+ const name = this._qualifiedStructName(typeNode.base.name);
1397
2733
  return `(${val} instanceof ${name} && ${val}.__v !== true)`;
1398
2734
  }
1399
2735
  return "true";
1400
2736
  },
1401
2737
 
1402
- zeroValueForExpr(expr) {
1403
- // For new(T) calls
1404
- if (expr.kind === "Ident") {
1405
- switch (expr.name) {
1406
- case "int":
1407
- case "float64":
1408
- return "0";
1409
- case "string":
1410
- return '""';
1411
- case "bool":
1412
- return "false";
1413
- default:
1414
- if (this.structNames.has(expr.name)) return `new ${expr.name}()`;
1415
- }
2738
+ // `Stack[int]` / `*Stack[int]`: the class test plus the instantiation
2739
+ // recorded on the instance (an instance without one matches any).
2740
+ _genericInstanceCheck(typeNode, val) {
2741
+ const isPtr = typeNode.kind === "PointerType";
2742
+ const base = isPtr ? typeNode.base : typeNode;
2743
+ if (base?.kind !== "GenericTypeName" || !base._typeArgs) return null;
2744
+ const cls = this._dequalify(base.name);
2745
+ if (!this.structNames.has(cls)) return null;
2746
+ const generic = this.checker?.types.get(base.name)?._generic;
2747
+ const id = instanceId(base._typeArgs);
2748
+ let check = isPtr
2749
+ ? `${val} instanceof ${cls} && ${val}.__v !== true`
2750
+ : `${val} instanceof ${cls} && ${val}.__p !== true`;
2751
+ if (id !== null && generic) {
2752
+ this.useHelper("generics");
2753
+ const at = generic.typeParams.length;
2754
+ check += ` && (${val}[__tz]?.[${at}] ?? ${JSON.stringify(id)}) === ${JSON.stringify(id)}`;
1416
2755
  }
1417
- return "null";
2756
+ return `(${check})`;
2757
+ },
2758
+
2759
+ zeroValueForExpr(expr) {
2760
+ return this.zeroValueForTypeNode(expr);
2761
+ },
2762
+
2763
+ _genSimdEquality(expr) {
2764
+ if (expr.op !== "==" && expr.op !== "!=") return null;
2765
+ if (!this._isSimdValueType(expr.left._type)) return null;
2766
+ this.useHelper("simd");
2767
+ const cmp = `__simd_equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
2768
+ return expr.op === "==" ? cmp : `!${cmp}`;
2769
+ },
2770
+
2771
+ _isSimdValueType(t) {
2772
+ const base = t?.kind === "named" ? t.underlying : t;
2773
+ return base?.kind === "simd";
1418
2774
  },
1419
2775
 
1420
2776
  // Returns true if the type is a struct or array (uses value comparison with __equal).
@@ -1453,12 +2809,30 @@ export const expressionGenMethods = {
1453
2809
  )
1454
2810
  return this._genComplexBinary(expr);
1455
2811
 
2812
+ const simdEq = this._genSimdEquality(expr);
2813
+ if (simdEq !== null) return simdEq;
2814
+
1456
2815
  if (this._isStructEquality(expr)) {
1457
2816
  this.useHelper("equal");
1458
2817
  const cmp = `__equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
1459
2818
  return expr.op === "==" ? cmp : `!${cmp}`;
1460
2819
  }
1461
2820
 
2821
+ const nilMap = this._genNilMapCompare(expr);
2822
+ if (nilMap) return nilMap;
2823
+
2824
+ const tpEq = this._genTypeParamEquality(expr);
2825
+ if (tpEq) return tpEq;
2826
+
2827
+ if (this._isIfaceEquality(expr)) {
2828
+ this.useHelper("ifeq");
2829
+ const cmp = `__ifeq(${this._genIfaceOperand(expr.left)}, ${this._genIfaceOperand(expr.right)})`;
2830
+ return expr.op === "==" ? cmp : `!${cmp}`;
2831
+ }
2832
+
2833
+ const ptrCmp = this._genPointerEquality(expr);
2834
+ if (ptrCmp !== null) return ptrCmp;
2835
+
1462
2836
  if (this.strict && this._needsStrictOp(expr.op, expr._type)) {
1463
2837
  const l = this.genExpr(expr.left);
1464
2838
  const r = this.genExpr(expr.right);
@@ -1486,6 +2860,43 @@ export const expressionGenMethods = {
1486
2860
  return `${l} ${jsOp} ${r}`;
1487
2861
  },
1488
2862
 
2863
+ // `m == nil` / `m != nil` on a map: true for a nil map (null, the strict
2864
+ // sentinel, or an untouched js-package zero map), false for `map[K]V{}`.
2865
+ _nilMapOperand(expr) {
2866
+ if (expr.op !== "==" && expr.op !== "!=") return null;
2867
+ const other = this._isNilLiteral(expr.right)
2868
+ ? expr.left
2869
+ : this._isNilLiteral(expr.left)
2870
+ ? expr.right
2871
+ : null;
2872
+ if (!other) return null;
2873
+ const t =
2874
+ other._type?.kind === "named" ? other._type.underlying : other._type;
2875
+ return t?.kind === "map" ? other : null;
2876
+ },
2877
+
2878
+ _genNilMapCompare(expr) {
2879
+ const operand = this._nilMapOperand(expr);
2880
+ if (!operand) return null;
2881
+ this.useHelper(this.strict ? "nilMap" : "nm");
2882
+ const cmp = `__isnm(${this.genExpr(operand)})`;
2883
+ return expr.op === "==" ? cmp : `!${cmp}`;
2884
+ },
2885
+
2886
+ // `a == b` on type-parameter operands: __geq compares structs and arrays by value.
2887
+ _genTypeParamEquality(expr) {
2888
+ if (expr.op !== "==" && expr.op !== "!=") return null;
2889
+ const t = expr.left._type;
2890
+ if (t?.kind !== "typeParam" || expr.right._type?.kind !== "typeParam")
2891
+ return null;
2892
+ const factory = this._typeParamZeroFactory(t.name);
2893
+ if (!factory) return null;
2894
+ this.useHelper("equal");
2895
+ this.useHelper("geq");
2896
+ const cmp = `__geq(${factory}, ${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
2897
+ return expr.op === "==" ? cmp : `!${cmp}`;
2898
+ },
2899
+
1489
2900
  _isStructEquality(expr) {
1490
2901
  return (
1491
2902
  (expr.op === "==" || expr.op === "!=") &&
@@ -1493,6 +2904,31 @@ export const expressionGenMethods = {
1493
2904
  );
1494
2905
  },
1495
2906
 
2907
+ _isIfaceType(t) {
2908
+ return isAny(t) || this._isInterfaceType(t);
2909
+ },
2910
+
2911
+ // Interface comparisons go through __ifeq so boxed named values compare by
2912
+ // dynamic type and value, as in Go (`err == ErrNotFound`).
2913
+ _isIfaceEquality(expr) {
2914
+ if (expr.op !== "==" && expr.op !== "!=") return false;
2915
+ if (this._isNilLiteral(expr.left) || this._isNilLiteral(expr.right))
2916
+ return false;
2917
+ return (
2918
+ this._isIfaceType(expr.left._type) || this._isIfaceType(expr.right._type)
2919
+ );
2920
+ },
2921
+
2922
+ // A concrete operand compared with an interface is converted to an interface first.
2923
+ _genIfaceOperand(e) {
2924
+ if (e._ifaceBox || this._isIfaceType(e._type)) return this.genExpr(e);
2925
+ const isPtr = e._type?.kind === "pointer";
2926
+ const named = isPtr ? e._type.base : e._type;
2927
+ if (this._namedBoxInfo(named))
2928
+ return this._genNamedIfaceBox(this.genExpr(e), named, isPtr);
2929
+ return this.genExpr(e);
2930
+ },
2931
+
1496
2932
  _isIntDivision(expr) {
1497
2933
  return (
1498
2934
  expr.op === "/" &&
@@ -1504,7 +2940,7 @@ export const expressionGenMethods = {
1504
2940
  _isNilLiteral(expr) {
1505
2941
  return (
1506
2942
  (expr.kind === "Ident" && expr.name === "nil") ||
1507
- (expr.kind === "BasicLit" && expr.value === "null")
2943
+ (expr.kind === "BasicLit" && expr.litKind === "NIL")
1508
2944
  );
1509
2945
  },
1510
2946
 
@@ -1518,6 +2954,8 @@ export const expressionGenMethods = {
1518
2954
  )
1519
2955
  return null;
1520
2956
  if (this._isStructEquality(expr) || this._isIntDivision(expr)) return null;
2957
+ if (this._nilMapOperand(expr)) return null;
2958
+ if (this._isIfaceEquality(expr)) return null;
1521
2959
  if (expr.op === "&^") return "&";
1522
2960
  if (expr.op === "==" || expr.op === "!=") {
1523
2961
  if (this._isNilLiteral(expr.left) || this._isNilLiteral(expr.right))
@@ -1527,9 +2965,19 @@ export const expressionGenMethods = {
1527
2965
  return expr.op;
1528
2966
  },
1529
2967
 
2968
+ // `T(x)` with T a type parameter: converts by the instantiated type.
2969
+ _genTypeParamConversion(name, inner) {
2970
+ const factory = name ? this._typeParamZeroFactory(name) : null;
2971
+ if (!factory) return null;
2972
+ this.useHelper("generics");
2973
+ return `__tconv(${factory}, ${inner})`;
2974
+ },
2975
+
1530
2976
  _genTypeConversion(expr) {
1531
2977
  const inner = this.genExpr(expr.expr);
1532
2978
  const t = expr.targetType;
2979
+ const viaParam = this._genTypeParamConversion(t?.name, inner);
2980
+ if (viaParam) return viaParam;
1533
2981
  if (t?.name === "complex128" || t?.name === "complex64") {
1534
2982
  return isComplex(expr.expr._type) ? inner : `{ re: ${inner}, im: 0 }`;
1535
2983
  }
@@ -1538,12 +2986,25 @@ export const expressionGenMethods = {
1538
2986
  if (t?.kind === "SliceType")
1539
2987
  return this._genSliceTypeConversion(expr, inner, t);
1540
2988
  const target = t?.name;
2989
+ const structConv = this._genStructClassConversion(
2990
+ target,
2991
+ expr.expr._type,
2992
+ inner,
2993
+ );
2994
+ if (structConv) return structConv;
1541
2995
  if (target === "error") {
2996
+ // A boxed named value that implements error is already an error.
2997
+ if (this._isNamedBoxKind(expr.expr)) return inner;
1542
2998
  this.useHelper("error");
1543
2999
  return `__error(${inner})`;
1544
3000
  }
1545
- if (target && this.namedWrapperNames.has(target))
1546
- return `new ${target}(${inner})`;
3001
+ if (target && this.namedWrapperNames.has(target)) {
3002
+ const named = this.checker?.types.get(target);
3003
+ const u = named?.underlying;
3004
+ return u?.kind === "basic"
3005
+ ? this._genPrimitiveConversion(expr, inner, u.name)
3006
+ : inner;
3007
+ }
1547
3008
  return this._genPrimitiveConversion(expr, inner, target);
1548
3009
  },
1549
3010
 
@@ -1556,14 +3017,19 @@ export const expressionGenMethods = {
1556
3017
  const size = t.size?.value !== undefined ? Number(t.size.value) : t.size;
1557
3018
  return `${inner}.slice(0, ${size})`;
1558
3019
  }
1559
- return inner;
3020
+ // Array-to-array conversion yields a new array value.
3021
+ return this._cloneJs(expr.expr._type, inner);
1560
3022
  },
1561
3023
 
1562
3024
  _genSliceTypeConversion(expr, inner, t) {
3025
+ // `[]T(nil)` is the nil slice, not a copy of anything.
3026
+ if (this._isNilLiteral(expr.expr)) return "null";
1563
3027
  const elem = t.elem?.name;
1564
3028
  if (elem === "byte" || elem === "uint8") {
1565
3029
  if (this._isStringExprType(expr.expr._type))
1566
3030
  return `new TextEncoder().encode(${inner})`;
3031
+ if (isAny(expr.expr._type) || !expr.expr._type)
3032
+ return `(typeof ${inner} === "string" ? new TextEncoder().encode(${inner}) : new Uint8Array(${inner}))`;
1567
3033
  return `new Uint8Array(${inner})`;
1568
3034
  }
1569
3035
  const typedCtor = typedArrayConstructorForElem(
@@ -1581,10 +3047,10 @@ export const expressionGenMethods = {
1581
3047
  },
1582
3048
 
1583
3049
  _genStringConversion(srcType, inner) {
1584
- if (srcType && this.isIntType(srcType))
1585
- return `String.fromCodePoint(${inner})`;
1586
- if (srcType?.kind === "slice") {
1587
- const elem = srcType.elem?.name;
3050
+ const base = srcType?.kind === "named" ? srcType.underlying : srcType;
3051
+ if (base && this.isIntType(base)) return `String.fromCodePoint(${inner})`;
3052
+ if (base?.kind === "slice") {
3053
+ const elem = base.elem?.name;
1588
3054
  if (elem === "byte" || elem === "uint8")
1589
3055
  return `new TextDecoder().decode(new Uint8Array(${inner}))`;
1590
3056
  if (elem === "rune" || elem === "int" || elem === "int32")
@@ -1643,6 +3109,8 @@ export const expressionGenMethods = {
1643
3109
  _needsStrictOp(op, type) {
1644
3110
  if (!STRICT_OPS.has(op)) return false;
1645
3111
  if (this._isStrictAtomicOp(op, this._typeName(type))) return true;
3112
+ if (op === "<<" || op === ">>")
3113
+ return WIDE_INT_NAMES.has(this._typeName(type));
1646
3114
  return this.isIntType(type) && (op === "/" || op === "%");
1647
3115
  },
1648
3116
 
@@ -1692,7 +3160,10 @@ export const expressionGenMethods = {
1692
3160
  if (tName === "int32" || tName === "rune" || tName === "uint32") {
1693
3161
  const signed = tName !== "uint32";
1694
3162
  const cast = signed ? "| 0" : ">>> 0";
1695
- if (signed && op === "*") return `Math.imul(${l}, ${r})`;
3163
+ if (op === "*")
3164
+ return signed
3165
+ ? `Math.imul(${l}, ${r})`
3166
+ : `(Math.imul(${l}, ${r}) >>> 0)`;
1696
3167
  if (op === "/")
1697
3168
  return `((${r}) === 0 ? ${divZero} : (Math.trunc((${l}) / (${r})) ${cast}))`;
1698
3169
  if (op === "%")
@@ -1700,9 +3171,9 @@ export const expressionGenMethods = {
1700
3171
  if (op === "<<" || op === ">>") {
1701
3172
  const ovf = signed && op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0";
1702
3173
  const jsOp = !signed && op === ">>" ? ">>>" : op;
1703
- return `((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast}`;
3174
+ return `(((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast})`;
1704
3175
  }
1705
- if (op === "&^") return `((${l}) & ~(${r})) ${cast}`;
3176
+ if (op === "&^") return `(((${l}) & ~(${r})) ${cast})`;
1706
3177
  return `((${l} ${op} ${r}) ${cast})`;
1707
3178
  }
1708
3179
  if (tName === "int8" || tName === "int16") {
@@ -1732,6 +3203,14 @@ export const expressionGenMethods = {
1732
3203
  if (op === "&^") return `(((${l}) & ~(${r})) & ${mask})`;
1733
3204
  return `((${l} ${op} ${r}) & ${mask})`;
1734
3205
  }
3206
+ if (op === "<<" || op === ">>") {
3207
+ // 64-bit semantics: operand first, then count (Go order); a negative
3208
+ // count panics and the result wraps to 64 bits.
3209
+ this.useHelper("shift");
3210
+ const unsigned = this._typeName(type).startsWith("u");
3211
+ if (op === "<<") return `__shl64(${l}, ${r}, ${unsigned})`;
3212
+ return `__shr64(${l}, ${r}, ${unsigned})`;
3213
+ }
1735
3214
  if (this.isIntType(type)) {
1736
3215
  if (op === "/")
1737
3216
  return `((${r}) === 0 ? ${divZero} : Math.trunc((${l}) / (${r})))`;