gofront 1.7.0 → 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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  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 +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  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 +10 -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 +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  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 +512 -77
  34. package/src/backend/wasm/encode.js +43 -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/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -4,17 +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,
10
14
  isAny,
11
15
  isComplex,
12
16
  isTypedArraySlice,
13
17
  typedArrayConstructorForElem,
18
+ typeStr,
14
19
  } from "../../typechecker/types.js";
15
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
+
16
40
  /** @typedef {import('./index.js').CodeGen} CodeGen */
17
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
+
18
83
  const STRICT_NUMERIC_TYPES = new Set([
19
84
  "float32",
20
85
  "int8",
@@ -27,6 +92,25 @@ const STRICT_NUMERIC_TYPES = new Set([
27
92
  "uint32",
28
93
  ]);
29
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
+
30
114
  const STRICT_OPS = new Set([
31
115
  "+",
32
116
  "-",
@@ -41,6 +125,21 @@ const STRICT_OPS = new Set([
41
125
  ">>",
42
126
  ]);
43
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
+
44
143
  const NAMED_ZERO_TABLE = {
45
144
  "time.Time": [null, "{ _d: new Date(0), _isZero: true }"],
46
145
  "strings.Builder": [null, '{ _buf: "" }'],
@@ -58,6 +157,48 @@ const NAMED_ZERO_TABLE = {
58
157
  "encoding/base64.Encoding": ["base64", "__base64_std"],
59
158
  "simd.F32x4": [null, "Object.freeze([0, 0, 0, 0])"],
60
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",
61
202
  };
62
203
 
63
204
  // Splits a generated JS lvalue at its outermost trailing access:
@@ -131,6 +272,9 @@ const NS_CONSTANTS = {
131
272
  ErrSyntax: "__strconv_err_syntax",
132
273
  ErrRange: "__strconv_err_range",
133
274
  },
275
+ bits: {
276
+ UintSize: "64",
277
+ },
134
278
  path: {
135
279
  ErrBadPattern: "__path_err_bad_pattern",
136
280
  },
@@ -164,7 +308,12 @@ const NS_CONSTANTS = {
164
308
  rand: {
165
309
  Reader: "__crypto_rand_reader",
166
310
  },
167
- os: { Args: "process.argv" },
311
+ os: {
312
+ Args: "process.argv",
313
+ Stdout: "__os_stdout",
314
+ Stderr: "__os_stderr",
315
+ Stdin: "__os_stdin",
316
+ },
168
317
  time: {
169
318
  Nanosecond: "1",
170
319
  Microsecond: "1000",
@@ -258,6 +407,14 @@ const INT_TYPE_NAMES = new Set([
258
407
  "rune",
259
408
  ]);
260
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
+
261
418
  // Zero-value JS literals for basic Go types.
262
419
  const ZERO_FOR_BASIC = {
263
420
  int: "0",
@@ -288,18 +445,38 @@ const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
288
445
  const BUILTIN_GEN = {
289
446
  append: (s, e) => s.genAppend(e),
290
447
  len: (s, e) => s._genBuiltinLen(e),
291
- 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
+ },
292
452
  make: (s, e) => s.genMake(e),
293
453
  delete: (s, e) => {
294
454
  const [m, k] = e.args.map((a) => s.genExpr(a));
295
455
  return `(delete ${m}[${k}])`;
296
456
  },
297
457
  copy: (s, e) => {
298
- const [dst, src] = e.args.map((a) => s.genExpr(a));
299
- 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})`;
300
472
  },
301
473
  new: (s, e) => {
302
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
+ }
303
480
  const typeName = arg.name ?? s.getTypeName(arg);
304
481
  if (typeName && s.structNames.has(typeName)) {
305
482
  return `new ${typeName}()`;
@@ -309,7 +486,12 @@ const BUILTIN_GEN = {
309
486
  const name = t.name ?? (t.kind === "named" ? t.name : null);
310
487
  if (name) return `new ${name}()`;
311
488
  }
312
- 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} }`;
313
495
  },
314
496
  // `print` writes without a trailing newline (Go semantics); the wasm glue
315
497
  // buffers the same way, so output parity holds across backends.
@@ -326,8 +508,8 @@ const BUILTIN_GEN = {
326
508
  s.useHelper("error");
327
509
  return `__error(${s.genExpr(e.args[0])})`;
328
510
  },
329
- min: (s, e) => `Math.min(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
330
- 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", ">"),
331
513
  clear: (s, e) => s._genBuiltinClear(e),
332
514
  complex: (s, e) =>
333
515
  `{ re: ${s.genExpr(e.args[0])}, im: ${s.genExpr(e.args[1])} }`,
@@ -335,6 +517,53 @@ const BUILTIN_GEN = {
335
517
  imag: (s, e) => `(${s.genExpr(e.args[0])}).im`,
336
518
  };
337
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
+
338
567
  // Method-name dispatch for genExpr — string values add no static call edges.
339
568
  const EXPR_GEN_DELEGATE = {
340
569
  UnaryExpr: "_genUnaryExpr",
@@ -350,36 +579,78 @@ const EXPR_GEN_DELEGATE = {
350
579
  };
351
580
 
352
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
+
353
591
  export const expressionGenMethods = {
354
592
  genExpr(expr) {
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
+ }
355
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
+ }
356
634
  if (expr._ifaceBox && !expr._ifaceBoxing && !this._isNamedBoxKind(expr)) {
357
635
  expr._ifaceBoxing = true;
358
636
  try {
359
637
  this.useHelper("ifaceBox");
360
- const fn =
361
- expr._ifaceBox === "ptr"
362
- ? "__ifp"
363
- : expr._ifaceBox === "ptr_slice"
364
- ? "__ifps"
365
- : expr._ifaceBox === "ptr_map"
366
- ? "__ifpm"
367
- : expr._ifaceBox === "map"
368
- ? "__ifm"
369
- : expr._ifaceBox === "slice"
370
- ? "__ifs"
371
- : "__ifv";
372
- const extra = expr._ifacePtrFields?.length
373
- ? `, ${JSON.stringify(expr._ifacePtrFields)}`
374
- : "";
375
- return `${fn}(${this.genExpr(expr)}${extra})`;
638
+ return this._ifaceBoxWrap(
639
+ this.genExpr(expr),
640
+ expr._ifaceBox,
641
+ expr._ifacePtrFields,
642
+ );
376
643
  } finally {
377
644
  expr._ifaceBoxing = false;
378
645
  }
379
646
  }
647
+ const folded = this._genFoldedConst(expr);
648
+ if (folded !== null) return folded;
380
649
  switch (expr.kind) {
381
650
  case "BasicLit":
382
651
  if (expr.litKind === "STRING") return JSON.stringify(expr.value);
652
+ if (expr._const?.k === "INT")
653
+ this._warnConstPrecision(expr._const, expr);
383
654
  return expr.value; // int, float, bool, nil(null)
384
655
  case "ImagLit":
385
656
  return `{ re: 0, im: ${expr.value} }`;
@@ -390,6 +661,7 @@ export const expressionGenMethods = {
390
661
  case "AwaitExpr":
391
662
  return `await ${this.genExpr(expr.expr)}`;
392
663
  case "InstantiationExpr":
664
+ return this._genInstantiationExpr(expr);
393
665
  case "RangeExpr":
394
666
  return this.genExpr(expr.expr);
395
667
  default: {
@@ -400,6 +672,57 @@ export const expressionGenMethods = {
400
672
  }
401
673
  },
402
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
+
403
726
  _isNamedBoxKind(expr) {
404
727
  return expr._ifaceBox === "named" || expr._ifaceBox === "named_ptr";
405
728
  },
@@ -444,6 +767,29 @@ export const expressionGenMethods = {
444
767
  return this._isStructType(pt.base);
445
768
  },
446
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
+
447
793
  _isStructExpr(expr) {
448
794
  if (!expr) return false;
449
795
  if (expr.kind === "CompositeLit") {
@@ -480,7 +826,20 @@ export const expressionGenMethods = {
480
826
  return null;
481
827
  },
482
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
+
483
840
  _cloneJs(t, js) {
841
+ const tp = this._typeParamClone(t, js);
842
+ if (tp) return tp;
484
843
  const base = this._aggregateBase(t);
485
844
  if (!base) return js;
486
845
  if (base.kind === "struct") {
@@ -516,7 +875,12 @@ export const expressionGenMethods = {
516
875
  genValueExpr(expr) {
517
876
  const js = this.genExpr(expr);
518
877
  if (expr._ifaceBox) return js;
519
- 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
+ )
520
884
  return js;
521
885
  return this._cloneJs(expr._type, js);
522
886
  },
@@ -541,16 +905,26 @@ export const expressionGenMethods = {
541
905
 
542
906
  _withFnCtx(body, fn) {
543
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);
544
914
  this._fnCtx = {
545
915
  body,
546
916
  mut: new Map(),
547
917
  owned: new Set(),
548
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),
549
922
  };
550
923
  try {
551
924
  fn();
552
925
  } finally {
553
926
  this._fnCtx = prev;
927
+ this._functionLabels = prevLabels;
554
928
  }
555
929
  },
556
930
 
@@ -568,6 +942,15 @@ export const expressionGenMethods = {
568
942
  // Copies aggregate params/receivers that the function body mutates.
569
943
  _emitParamCopies(params) {
570
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
+ }
571
954
  if (p.variadic || !p.name || p.name === "_") continue;
572
955
  if (this._boxedVars.has(p.name)) {
573
956
  // Address-taken parameter: rebind it to a { value } box.
@@ -621,12 +1004,26 @@ export const expressionGenMethods = {
621
1004
  ) {
622
1005
  return this.genExpr(expr.operand);
623
1006
  }
624
- if (
625
- expr.operand.kind === "SelectorExpr" ||
626
- expr.operand.kind === "IndexExpr"
627
- ) {
628
- const inner = this.genExpr(expr.operand);
629
- 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})`;
630
1027
  }
631
1028
  // For non-boxed vars (primitives, etc.), wrap in { value: x }
632
1029
  return `{ value: ${this.genExpr(expr.operand)} }`;
@@ -634,7 +1031,11 @@ export const expressionGenMethods = {
634
1031
  // Dereference: *p
635
1032
  if (op === "*") {
636
1033
  if (this._isStructPointerType(expr.operand._type)) {
637
- 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)})`;
638
1039
  }
639
1040
  return `${this.genExpr(expr.operand)}.value`;
640
1041
  }
@@ -645,6 +1046,16 @@ export const expressionGenMethods = {
645
1046
  return inner;
646
1047
  }
647
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})`;
648
1059
  const needsParens =
649
1060
  this._emittedBinaryOp(expr.operand) !== null ||
650
1061
  ((op === "-" || op === "+") && /^[-+]/.test(code));
@@ -652,6 +1063,9 @@ export const expressionGenMethods = {
652
1063
  },
653
1064
 
654
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);
655
1069
  if (expr._isMethodExpr) {
656
1070
  const namedExpr = this._namedMethodExprInfo(expr);
657
1071
  if (namedExpr) return `${namedExpr.cls}.$${expr.field}`;
@@ -679,19 +1093,104 @@ export const expressionGenMethods = {
679
1093
  this.useHelper("cryptoRand");
680
1094
  } else if (expr.expr.name === "sha256") {
681
1095
  this.useHelper("sha256");
1096
+ } else if (expr.expr.name === "os") {
1097
+ this.useHelper("os");
682
1098
  }
683
1099
  return nsConst;
684
1100
  }
685
1101
  }
686
1102
  const base = this.genExpr(expr.expr);
687
- 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;
688
1107
  if (expr.expr._type?.kind === "pointer" && expr.field !== "value")
689
1108
  return this._genPointerSelectorField(expr, base);
690
- const sel = `${base}.${this._jsMemberName(expr)}`;
691
- 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
+ }
692
1117
  return sel;
693
1118
  },
694
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
+
695
1194
  // PascalCase aliases of Web API methods (gl.ClearColor) map to the real camelCase member.
696
1195
  _jsMemberName(expr) {
697
1196
  const f = expr.field;
@@ -733,7 +1232,12 @@ export const expressionGenMethods = {
733
1232
  ? `${this.genExpr(sel.expr)}.value`
734
1233
  : this.genExpr(sel.expr);
735
1234
  }
736
- return { cls: info.cls, recv };
1235
+ return {
1236
+ cls: info.cls,
1237
+ recv,
1238
+ byValue: !method._ptrRecv,
1239
+ recvType: viaPtr ? recvType.base : recvType,
1240
+ };
737
1241
  },
738
1242
 
739
1243
  // `x.M` used as a value: binds the receiver now, as Go does.
@@ -741,7 +1245,11 @@ export const expressionGenMethods = {
741
1245
  if (!expr._isMethodValue || expr._callee) return undefined;
742
1246
  const target = this._namedMethodTarget(expr);
743
1247
  if (!target) return undefined;
744
- return `((__r) => (...__a) => ${target.cls}.$${expr.field}(__r, ...__a))(${target.recv})`;
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})`;
745
1253
  },
746
1254
 
747
1255
  _genAddressOfExpr(operand) {
@@ -776,17 +1284,90 @@ export const expressionGenMethods = {
776
1284
  return `${target.cls}.$${sel.field}(${args ? `${target.recv}, ${args}` : target.recv})`;
777
1285
  },
778
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
+
779
1300
  _genCallArgs(expr) {
780
1301
  if (expr._multiForward) return `...${this.genExpr(expr.args[0])}`;
781
1302
  return expr.args
782
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
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
+ )
783
1310
  .join(", ");
784
1311
  },
785
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
+
786
1354
  _genPointerSelectorField(expr, base) {
787
1355
  if (this._isStructPointerType(expr.expr._type)) {
788
- const sel = `${base}.${expr.field}`;
789
- 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
+ }
790
1371
  return sel;
791
1372
  }
792
1373
  const sel = `${base}.value.${expr.field}`;
@@ -802,13 +1383,48 @@ export const expressionGenMethods = {
802
1383
  ? `${this.genExpr(expr.expr)}.${wrapField}`
803
1384
  : this.genExpr(expr.expr);
804
1385
  const idx = this.genExpr(expr.index);
805
- if (expr._mapValueType && !expr._lvalue)
806
- 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;
807
1392
  if (this._isStringExprType(exprType) && !expr._lvalue)
808
1393
  return `${base}.charCodeAt(${idx})`;
809
1394
  return `${base}[${idx}]`;
810
1395
  },
811
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
+
812
1428
  _genMapIndexExpr(expr, base, idx) {
813
1429
  const zero = this.zeroValueForType(expr._mapValueType);
814
1430
  if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index))
@@ -826,17 +1442,26 @@ export const expressionGenMethods = {
826
1442
 
827
1443
  _genSliceExpr(expr) {
828
1444
  const base = this.genExpr(expr.expr);
829
- const lo = expr.low ? this.genExpr(expr.low) : "";
830
- const hi = expr.high ? this.genExpr(expr.high) : "";
831
- const method = isTypedArraySlice(expr.expr._type) ? "subarray" : "slice";
832
- if (!lo && !hi) return `${base}.${method}()`;
833
- if (!hi) return `${base}.${method}(${lo})`;
834
- if (!lo) return `${base}.${method}(0, ${hi})`;
835
- 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})`;
836
1461
  },
837
1462
 
838
1463
  _genFuncLit(expr) {
839
- const params = expr.params.map((p) => p.name).join(", ");
1464
+ const params = this._paramNames(expr.params).join(", ");
840
1465
  const asyncPrefix = expr.async ? "async " : "";
841
1466
  const saved = this.out;
842
1467
  const prevBoxed = this._boxedVars;
@@ -864,9 +1489,7 @@ export const expressionGenMethods = {
864
1489
  const check = this._typeCheckExpr(expr.type, val);
865
1490
  const wrap = (body) => (hoist ? `((__v) => ${body})(${src})` : body);
866
1491
  // Named non-struct values sit in interfaces as boxes; the asserted value is raw.
867
- const out = this._isNamedBoxTypeNode(expr.type)
868
- ? `${val}.__gfUnbox()`
869
- : val;
1492
+ const out = this._assertedValueJs(expr.type, val);
870
1493
  if (!expr._commaOk) {
871
1494
  // plain assertion: panic if check fails (matches Go behavior)
872
1495
  if (check === "true") return src; // can't check at runtime — pass through
@@ -874,16 +1497,35 @@ export const expressionGenMethods = {
874
1497
  `(${check} ? ${out} : (() => { throw new Error("interface conversion: type assertion failed"); })())`,
875
1498
  );
876
1499
  }
877
- // 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.
878
1502
  const zero = this.zeroValueForTypeNode(expr.type);
879
- return wrap(`(${check} ? [${out}, 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])`);
880
1506
  },
881
1507
 
882
- _typeNameCheckExpr(name, val) {
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);
883
1520
  if (TYPEOF_NUMBER_NAMES.has(name)) return `typeof ${val} === "number"`;
884
1521
  if (name === "string") return `typeof ${val} === "string"`;
885
1522
  if (name === "bool") return `typeof ${val} === "boolean"`;
886
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")`;
887
1529
  if (name === "error")
888
1530
  return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
889
1531
  if (this.structNames.has(name))
@@ -893,7 +1535,11 @@ export const expressionGenMethods = {
893
1535
  if (namedInfo) return `(${val} instanceof ${namedInfo.cls})`;
894
1536
  // User-declared interfaces: check the dynamic value has every method.
895
1537
  if (!name.includes(".") && named?.underlying?.kind === "interface")
896
- return this._ifaceMethodCheck(val, [...named.underlying.methods.keys()]);
1538
+ return this._ifaceMethodCheck(
1539
+ val,
1540
+ [...named.underlying.methods.keys()],
1541
+ this._unwrapIsMulti(named.underlying.methods.get("Unwrap")),
1542
+ );
897
1543
  return "true"; // unknown type — can't check at runtime
898
1544
  },
899
1545
 
@@ -903,6 +1549,9 @@ export const expressionGenMethods = {
903
1549
  return this._ifaceMethodCheck(
904
1550
  val,
905
1551
  (typeNode.methods ?? []).map((m) => m.name),
1552
+ this._unwrapIsMulti(
1553
+ (typeNode.methods ?? []).find((m) => m.name === "Unwrap"),
1554
+ ),
906
1555
  );
907
1556
  if (typeNode.kind === "PointerType" && typeNode.base?.kind === "TypeName") {
908
1557
  const info = this._namedBoxInfo(
@@ -913,11 +1562,37 @@ export const expressionGenMethods = {
913
1562
  return null;
914
1563
  },
915
1564
 
916
- _ifaceMethodCheck(val, methodNames) {
917
- const checks = methodNames.map((m) => `typeof ${val}.${m} === "function"`);
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
+ );
918
1581
  return `(${[`${val} != null`, ...checks].join(" && ")})`;
919
1582
  },
920
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
+
921
1596
  _isNamedBoxTypeNode(typeNode) {
922
1597
  if (typeNode?.kind === "TypeName")
923
1598
  return Boolean(
@@ -930,9 +1605,31 @@ export const expressionGenMethods = {
930
1605
  return false;
931
1606
  },
932
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)})`;
1620
+ },
1621
+
933
1622
  _genTypeConversionCall(expr) {
934
1623
  const targetType = expr._conversionTargetType;
935
1624
  const arg = this.genExpr(expr.args[0]);
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;
936
1633
  const isRaw =
937
1634
  targetType?.name === "json.RawMessage" ||
938
1635
  targetType?.name === "encoding/json.RawMessage";
@@ -979,14 +1676,50 @@ export const expressionGenMethods = {
979
1676
  if (result !== undefined) return result;
980
1677
  }
981
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
+
982
1688
  expr.func._callee = true;
983
1689
  const rawFn = this.genExpr(expr.func);
984
1690
  const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
985
- if (expr._multiForward) return `${fn}(...${this.genExpr(expr.args[0])})`;
986
- const args = expr.args
987
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
988
- .join(", ");
989
- 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(", ");
990
1723
  },
991
1724
 
992
1725
  _isErrorInterfaceType(t) {
@@ -1008,7 +1741,35 @@ export const expressionGenMethods = {
1008
1741
  return recvType?.name ?? recvType?.base?.name;
1009
1742
  },
1010
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
+
1011
1770
  _genReceiverTypeCall(recvName, expr) {
1771
+ const jsonStream = this._genJsonStreamCall(recvName, expr);
1772
+ if (jsonStream !== undefined) return jsonStream;
1012
1773
  if (recvName === "strings.Builder" || recvName === "bytes.Buffer")
1013
1774
  return this._genBuilderCall(recvName, expr.func.field, expr);
1014
1775
  if (recvName === "regexp.Regexp")
@@ -1017,10 +1778,39 @@ export const expressionGenMethods = {
1017
1778
  return this._genTimeMethodCall(expr.func.field, expr);
1018
1779
  if (recvName === "time.Duration")
1019
1780
  return this._genDurationMethodCall(expr.func.field, expr);
1020
- 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
+ )
1021
1806
  return this._genTestingMethodCall(expr.func.field, expr);
1022
1807
  if (recvName === "strings.Replacer") {
1023
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
+ }
1024
1814
  const arg = this.genExpr(expr.args[0]);
1025
1815
  return `(${recv} ? ${recv}.Replace(${arg}) : ${arg})`;
1026
1816
  }
@@ -1028,12 +1818,13 @@ export const expressionGenMethods = {
1028
1818
  const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1029
1819
  return `${this.genExpr(expr.func.expr)}.subarray(${args})`;
1030
1820
  }
1031
- if (recvName === "simd.F32x4" || recvName === "F32x4") {
1032
- return this._genSimdReceiverCall(expr);
1821
+ if (/^(simd\.)?(F32x4|Mask32x4)$/.test(recvName ?? "")) {
1822
+ return this._genSimdReceiverCall(expr, recvName.endsWith("Mask32x4"));
1033
1823
  }
1034
1824
  if (recvName === "sync.Mutex" || recvName === "sync.RWMutex") {
1035
1825
  if (expr.func.field === "TryLock" || expr.func.field === "TryRLock")
1036
1826
  return "true";
1827
+ if (expr.func.field === "RLocker") return "({ Lock() {}, Unlock() {} })";
1037
1828
  return "(void 0)";
1038
1829
  }
1039
1830
  if (recvName === "sync.Once" && expr.func.field === "Do") {
@@ -1076,33 +1867,15 @@ export const expressionGenMethods = {
1076
1867
  return undefined;
1077
1868
  },
1078
1869
 
1079
- _genSimdReceiverCall(expr) {
1870
+ _genSimdReceiverCall(expr, isMask = false) {
1080
1871
  this.useHelper("simd");
1081
1872
  const method = expr.func.field;
1082
1873
  const recv = this.genExpr(expr.func.expr);
1083
- const helpers = {
1084
- Add: "__simd_add",
1085
- Sub: "__simd_sub",
1086
- Mul: "__simd_mul",
1087
- Div: "__simd_div",
1088
- Min: "__simd_min",
1089
- Max: "__simd_max",
1090
- Dot3: "__simd_dot3",
1091
- Dot4: "__simd_dot4",
1092
- };
1093
- if (helpers[method]) {
1094
- return `${helpers[method]}(${recv}, ${this.genExpr(expr.args[0])})`;
1095
- }
1096
- if (method === "Lane") {
1097
- return `${recv}[${this.genExpr(expr.args[0])}]`;
1098
- }
1099
- if (method === "WithLane") {
1100
- return `__simd_with_lane(${recv}, ${this.genExpr(expr.args[0])}, ${this.genExpr(expr.args[1])})`;
1101
- }
1102
- if (method === "Splat") {
1103
- return `__simd_splat(${this.genExpr(expr.args[0])})`;
1104
- }
1105
- return undefined;
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;
1106
1879
  },
1107
1880
 
1108
1881
  _genSelectorCall(expr) {
@@ -1164,7 +1937,12 @@ export const expressionGenMethods = {
1164
1937
  (t?.kind === "named" && t.underlying?.kind === "map")
1165
1938
  )
1166
1939
  return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
1167
- 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})`;
1168
1946
  },
1169
1947
 
1170
1948
  _genBuiltinIdent(name, expr) {
@@ -1172,6 +1950,15 @@ export const expressionGenMethods = {
1172
1950
  return gen ? gen(this, expr) : undefined;
1173
1951
  },
1174
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
+
1175
1962
  genAppend(expr) {
1176
1963
  const retType = expr._type;
1177
1964
  const wrapField = this._namedWrapperField(retType, expr.args[0]);
@@ -1181,16 +1968,25 @@ export const expressionGenMethods = {
1181
1968
  const elems = expr.args
1182
1969
  .slice(1)
1183
1970
  .map((a) =>
1184
- a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a),
1971
+ a._spread
1972
+ ? `...(${this._genSliceSource(a)} ?? [])`
1973
+ : this.genValueExpr(a),
1185
1974
  );
1186
1975
  if (elems.length === 0) return this.genExpr(expr.args[0]);
1187
1976
  this.useHelper("append");
1188
1977
  return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
1189
1978
  }
1190
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)} ?? [])`;
1191
1987
  const elems = expr.args
1192
1988
  .slice(1)
1193
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a)));
1989
+ .map((a) => (a._spread ? spreadOf(a) : this.genValueExpr(a)));
1194
1990
  if (elems.length === 0) return slice;
1195
1991
  this.useHelper("append");
1196
1992
  const sliceType = expr._type ?? expr.args[0]._type;
@@ -1209,12 +2005,16 @@ export const expressionGenMethods = {
1209
2005
  // make(map[K]V) → {}
1210
2006
  const typeArg = expr.args[0];
1211
2007
  const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
1212
- 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;
1213
2014
  if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
1214
2015
  const n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
1215
2016
  const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
1216
- const elemResolved =
1217
- typeArg._type?.kind === "slice" ? typeArg._type.elem : null;
2017
+ const elemResolved = madeType?.kind === "slice" ? madeType.elem : null;
1218
2018
  const typedCtor =
1219
2019
  typedArrayConstructorForElem(elemResolved) ??
1220
2020
  (elemNode?.name
@@ -1223,8 +2023,14 @@ export const expressionGenMethods = {
1223
2023
  name: elemNode.name,
1224
2024
  })
1225
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);
1226
2032
  if (typedCtor) {
1227
- return `new ${typedCtor}(${n})`;
2033
+ return withCap((k) => `new ${typedCtor}(${k})`);
1228
2034
  }
1229
2035
  // Use the proper zero value for the element type (e.g. new Point() not 0)
1230
2036
  let zero = "null";
@@ -1234,10 +2040,10 @@ export const expressionGenMethods = {
1234
2040
  zero = this.zeroValueForTypeNode(elemNode);
1235
2041
  }
1236
2042
  // If zero value is a constructor call, use a factory function so each element is distinct
1237
- if (zero.startsWith("new ")) {
1238
- return `Array.from({length: ${n}}, () => ${zero})`;
2043
+ if (zero.startsWith("new ") || zero.includes("$z_")) {
2044
+ return withCap((k) => `Array.from({length: ${k}}, () => ${zero})`);
1239
2045
  }
1240
- return `new Array(${n}).fill(${zero})`;
2046
+ return withCap((k) => `new Array(${k}).fill(${zero})`);
1241
2047
  }
1242
2048
  // map or fallback
1243
2049
  return "{}";
@@ -1267,22 +2073,42 @@ export const expressionGenMethods = {
1267
2073
  return t?.kind === "MapType";
1268
2074
  },
1269
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
+
1270
2085
  _genMapLitEntries(elems) {
1271
2086
  return elems
1272
- .map((e) => {
1273
- if (e.kind === "KeyValueExpr") {
1274
- const k =
1275
- e.key.litKind === "STRING"
1276
- ? JSON.stringify(e.key.value)
1277
- : `[${this.genExpr(e.key)}]`;
1278
- return `${k}: ${this.genValueExpr(e.value)}`;
1279
- }
1280
- return this.genValueExpr(e);
1281
- })
2087
+ .map((e) =>
2088
+ e.kind === "KeyValueExpr"
2089
+ ? `${this._genMapLitKey(e.key)}: ${this.genValueExpr(e.value)}`
2090
+ : this.genValueExpr(e),
2091
+ )
1282
2092
  .join(", ");
1283
2093
  },
1284
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
+
1285
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
+ }
1286
2112
  if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
1287
2113
  const fields = expr.elems
1288
2114
  .map(
@@ -1323,7 +2149,7 @@ export const expressionGenMethods = {
1323
2149
  (ce) => (ce._positionalField ?? ce.key?.name) === ef,
1324
2150
  );
1325
2151
  values[idx] = match
1326
- ? this.genValueExpr(match.value ?? match)
2152
+ ? this.genValueExpr(match.kind === "KeyValueExpr" ? match.value : match)
1327
2153
  : fields[idx].zero;
1328
2154
  } else {
1329
2155
  const valExpr = this.genExpr(e.value);
@@ -1364,6 +2190,8 @@ export const expressionGenMethods = {
1364
2190
 
1365
2191
  _genPositionalStructLit(typeName, expr) {
1366
2192
  const fields = this.getStructFields(typeName);
2193
+ const generic = this._genGenericStructLit(fields, expr);
2194
+ if (generic) return generic;
1367
2195
  if (!fields || fields.length === 0 || expr.elems.length === 0) {
1368
2196
  return `new ${typeName}()`;
1369
2197
  }
@@ -1381,10 +2209,95 @@ export const expressionGenMethods = {
1381
2209
  return inst;
1382
2210
  },
1383
2211
 
1384
- _genSliceOrArrayLit(t, expr) {
1385
- const elems = expr.elems
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
1386
2293
  .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
1387
2294
  .join(", ");
2295
+ },
2296
+
2297
+ _genSliceOrArrayLit(t, expr) {
2298
+ const sparse = this._genSparseArrayLit(expr);
2299
+ if (sparse) return sparse;
2300
+ const elems = this._genLitElems(expr);
1388
2301
  const elemNode =
1389
2302
  t.kind === "SliceType" || t.kind === "ArrayType" ? t.elem : null;
1390
2303
  const elemResolved =
@@ -1413,6 +2326,8 @@ export const expressionGenMethods = {
1413
2326
  return "{ _buf: [] }";
1414
2327
  if (typeName === "strings.Builder" || typeName === "Builder")
1415
2328
  return '{ _buf: "" }';
2329
+ if (typeName === "simd.Mask32x4" || typeName === "Mask32x4")
2330
+ return "Object.freeze([false, false, false, false])";
1416
2331
  if (typeName === "simd.F32x4" || typeName === "F32x4") {
1417
2332
  this.useHelper("simd");
1418
2333
  const a = expr.elems[0] ? this.genExpr(expr.elems[0]) : "0";
@@ -1421,6 +2336,8 @@ export const expressionGenMethods = {
1421
2336
  const d = expr.elems[3] ? this.genExpr(expr.elems[3]) : "0";
1422
2337
  return `__simd_f32x4(${a}, ${b}, ${c}, ${d})`;
1423
2338
  }
2339
+ const aliasLit = this._genAliasLit(t, typeName, expr);
2340
+ if (aliasLit !== null) return aliasLit;
1424
2341
  if (typeName && this.structNames.has(typeName))
1425
2342
  return this._genPositionalStructLit(typeName, expr);
1426
2343
  if (typeName && this.namedWrapperNames.has(typeName))
@@ -1434,14 +2351,68 @@ export const expressionGenMethods = {
1434
2351
  return this._genFallbackCompositeLit(expr);
1435
2352
  },
1436
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
+
1437
2402
  _genImplicitCompositeLit(expr) {
1438
- 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;
1439
2406
  if (typeName && this.structNames.has(typeName)) {
1440
2407
  return this._genPositionalStructLit(typeName, expr);
1441
2408
  }
2409
+ if (this._isMapLit(expr))
2410
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
1442
2411
  const hasPositional = expr.elems.some((e) => e._positionalField);
1443
2412
  const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
1444
- 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)) {
1445
2416
  const fields = expr.elems
1446
2417
  .map((e) => {
1447
2418
  if (e._positionalField)
@@ -1451,7 +2422,9 @@ export const expressionGenMethods = {
1451
2422
  .join(", ");
1452
2423
  return `{ ${fields} }`;
1453
2424
  }
1454
- 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);
1455
2428
  if (isTypedArraySlice(expr._type)) {
1456
2429
  const typedCtor = typedArrayConstructorForElem(expr._type.elem);
1457
2430
  if (typedCtor) return `new ${typedCtor}([${elems}])`;
@@ -1465,11 +2438,8 @@ export const expressionGenMethods = {
1465
2438
  if (u?.kind === "map") {
1466
2439
  return `{ ${this._genMapLitEntries(expr.elems)} }`;
1467
2440
  }
1468
- // Default: slice
1469
- const elems = expr.elems
1470
- .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
1471
- .join(", ");
1472
- return `[${elems}]`;
2441
+ // Default: slice or array
2442
+ return `[${this._genLitElems(expr)}]`;
1473
2443
  },
1474
2444
 
1475
2445
  // ── Helpers ───────────────────────────────────────────────────
@@ -1481,6 +2451,7 @@ export const expressionGenMethods = {
1481
2451
 
1482
2452
  getTypeName(typeNode) {
1483
2453
  if (!typeNode) return null;
2454
+ if (typeNode._stdName) return typeNode._stdName;
1484
2455
  if (typeNode.kind === "TypeName") return this._dequalify(typeNode.name);
1485
2456
  if (typeNode.kind === "GenericTypeName") return typeNode.name;
1486
2457
  if (typeNode.kind === "Ident") return typeNode.name;
@@ -1523,6 +2494,93 @@ export const expressionGenMethods = {
1523
2494
  return false;
1524
2495
  },
1525
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
+
1526
2584
  // Returns the JS zero-value literal for a basic type name, or null if not a basic type.
1527
2585
  _zeroForBasicName(name) {
1528
2586
  return Object.hasOwn(ZERO_FOR_BASIC, name) ? ZERO_FOR_BASIC[name] : null;
@@ -1543,6 +2601,10 @@ export const expressionGenMethods = {
1543
2601
  u.kind !== "named"
1544
2602
  )
1545
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
+ }
1546
2608
  name = this._dequalify(name);
1547
2609
  if (this.structNames.has(name)) return `new ${name}()`;
1548
2610
  return "null";
@@ -1551,7 +2613,10 @@ export const expressionGenMethods = {
1551
2613
  zeroValueForTypeNode(typeNode) {
1552
2614
  if (!typeNode) return "null";
1553
2615
  switch (typeNode.kind) {
2616
+ case "Ident":
1554
2617
  case "TypeName": {
2618
+ const factory = this._typeParamZeroFactory(typeNode.name);
2619
+ if (factory) return `${factory}()`;
1555
2620
  const basic = this._zeroForBasicName(typeNode.name);
1556
2621
  if (basic !== null) return basic;
1557
2622
  return this._zeroForNamedType(typeNode.name);
@@ -1573,7 +2638,7 @@ export const expressionGenMethods = {
1573
2638
  );
1574
2639
  }
1575
2640
  case "MapType":
1576
- return "{}";
2641
+ return this._nilMapZero();
1577
2642
  case "PointerType":
1578
2643
  return "null";
1579
2644
  case "StructType": {
@@ -1586,16 +2651,31 @@ export const expressionGenMethods = {
1586
2651
  .join(", ");
1587
2652
  return `{ ${fields} }`;
1588
2653
  }
2654
+ case "GenericTypeName":
2655
+ case "InstantiationExpr":
2656
+ return (
2657
+ this._genericStructNew(
2658
+ this.getTypeName(typeNode),
2659
+ typeNode._typeArgs,
2660
+ ) ?? "null"
2661
+ );
1589
2662
  default:
1590
2663
  return "null";
1591
2664
  }
1592
2665
  },
1593
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
+
1594
2674
  _arrayZero(n, ctor, elemZero) {
1595
2675
  if (ctor) return `new ${ctor}(${n})`;
1596
2676
  if (n === 0) return "[]";
1597
- if (/^(new |\{|\[|Array)/.test(elemZero))
1598
- return `Array.from({ length: ${n} }, () => ${elemZero})`;
2677
+ if (/^(new |\{|\[|Array|__nm\()|\$z_/.test(elemZero))
2678
+ return `Array.from({ length: ${n} }, () => (${elemZero}))`;
1599
2679
  return `new Array(${n}).fill(${elemZero})`;
1600
2680
  },
1601
2681
 
@@ -1614,7 +2694,7 @@ export const expressionGenMethods = {
1614
2694
  this.zeroValueForType(t.elem),
1615
2695
  );
1616
2696
  case "map":
1617
- return "{}";
2697
+ return this._nilMapZero();
1618
2698
  case "struct": {
1619
2699
  const fields = [...t.fields.entries()]
1620
2700
  .map(([name, ft]) => `${name}: ${this.zeroValueForType(ft)}`)
@@ -1622,7 +2702,14 @@ export const expressionGenMethods = {
1622
2702
  return `{ ${fields} }`;
1623
2703
  }
1624
2704
  case "named":
1625
- 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
+ }
1626
2713
  default:
1627
2714
  return "null";
1628
2715
  }
@@ -1633,35 +2720,57 @@ export const expressionGenMethods = {
1633
2720
  if (!typeNode) return "true";
1634
2721
  if (typeNode.kind === "TypeName")
1635
2722
  return this._typeNameCheckExpr(typeNode.name, val);
2723
+ const generic = this._genericInstanceCheck(typeNode, val);
2724
+ if (generic) return generic;
1636
2725
  const extended = this._namedOrIfaceCheckExpr(typeNode, val);
1637
2726
  if (extended) return extended;
1638
2727
  if (
1639
2728
  typeNode.kind === "PointerType" &&
1640
2729
  typeNode.base?.kind === "TypeName" &&
1641
- this.structNames.has(typeNode.base.name)
2730
+ this.structNames.has(this._qualifiedStructName(typeNode.base.name))
1642
2731
  ) {
1643
- const name = typeNode.base.name;
2732
+ const name = this._qualifiedStructName(typeNode.base.name);
1644
2733
  return `(${val} instanceof ${name} && ${val}.__v !== true)`;
1645
2734
  }
1646
2735
  return "true";
1647
2736
  },
1648
2737
 
1649
- zeroValueForExpr(expr) {
1650
- // For new(T) calls
1651
- if (expr.kind === "Ident") {
1652
- switch (expr.name) {
1653
- case "int":
1654
- case "float64":
1655
- return "0";
1656
- case "string":
1657
- return '""';
1658
- case "bool":
1659
- return "false";
1660
- default:
1661
- if (this.structNames.has(expr.name)) return `new ${expr.name}()`;
1662
- }
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)}`;
1663
2755
  }
1664
- 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";
1665
2774
  },
1666
2775
 
1667
2776
  // Returns true if the type is a struct or array (uses value comparison with __equal).
@@ -1700,18 +2809,30 @@ export const expressionGenMethods = {
1700
2809
  )
1701
2810
  return this._genComplexBinary(expr);
1702
2811
 
2812
+ const simdEq = this._genSimdEquality(expr);
2813
+ if (simdEq !== null) return simdEq;
2814
+
1703
2815
  if (this._isStructEquality(expr)) {
1704
2816
  this.useHelper("equal");
1705
2817
  const cmp = `__equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
1706
2818
  return expr.op === "==" ? cmp : `!${cmp}`;
1707
2819
  }
1708
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
+
1709
2827
  if (this._isIfaceEquality(expr)) {
1710
2828
  this.useHelper("ifeq");
1711
2829
  const cmp = `__ifeq(${this._genIfaceOperand(expr.left)}, ${this._genIfaceOperand(expr.right)})`;
1712
2830
  return expr.op === "==" ? cmp : `!${cmp}`;
1713
2831
  }
1714
2832
 
2833
+ const ptrCmp = this._genPointerEquality(expr);
2834
+ if (ptrCmp !== null) return ptrCmp;
2835
+
1715
2836
  if (this.strict && this._needsStrictOp(expr.op, expr._type)) {
1716
2837
  const l = this.genExpr(expr.left);
1717
2838
  const r = this.genExpr(expr.right);
@@ -1739,6 +2860,43 @@ export const expressionGenMethods = {
1739
2860
  return `${l} ${jsOp} ${r}`;
1740
2861
  },
1741
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
+
1742
2900
  _isStructEquality(expr) {
1743
2901
  return (
1744
2902
  (expr.op === "==" || expr.op === "!=") &&
@@ -1782,7 +2940,7 @@ export const expressionGenMethods = {
1782
2940
  _isNilLiteral(expr) {
1783
2941
  return (
1784
2942
  (expr.kind === "Ident" && expr.name === "nil") ||
1785
- (expr.kind === "BasicLit" && expr.value === "null")
2943
+ (expr.kind === "BasicLit" && expr.litKind === "NIL")
1786
2944
  );
1787
2945
  },
1788
2946
 
@@ -1796,6 +2954,7 @@ export const expressionGenMethods = {
1796
2954
  )
1797
2955
  return null;
1798
2956
  if (this._isStructEquality(expr) || this._isIntDivision(expr)) return null;
2957
+ if (this._nilMapOperand(expr)) return null;
1799
2958
  if (this._isIfaceEquality(expr)) return null;
1800
2959
  if (expr.op === "&^") return "&";
1801
2960
  if (expr.op === "==" || expr.op === "!=") {
@@ -1806,9 +2965,19 @@ export const expressionGenMethods = {
1806
2965
  return expr.op;
1807
2966
  },
1808
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
+
1809
2976
  _genTypeConversion(expr) {
1810
2977
  const inner = this.genExpr(expr.expr);
1811
2978
  const t = expr.targetType;
2979
+ const viaParam = this._genTypeParamConversion(t?.name, inner);
2980
+ if (viaParam) return viaParam;
1812
2981
  if (t?.name === "complex128" || t?.name === "complex64") {
1813
2982
  return isComplex(expr.expr._type) ? inner : `{ re: ${inner}, im: 0 }`;
1814
2983
  }
@@ -1817,7 +2986,15 @@ export const expressionGenMethods = {
1817
2986
  if (t?.kind === "SliceType")
1818
2987
  return this._genSliceTypeConversion(expr, inner, t);
1819
2988
  const target = t?.name;
2989
+ const structConv = this._genStructClassConversion(
2990
+ target,
2991
+ expr.expr._type,
2992
+ inner,
2993
+ );
2994
+ if (structConv) return structConv;
1820
2995
  if (target === "error") {
2996
+ // A boxed named value that implements error is already an error.
2997
+ if (this._isNamedBoxKind(expr.expr)) return inner;
1821
2998
  this.useHelper("error");
1822
2999
  return `__error(${inner})`;
1823
3000
  }
@@ -1840,10 +3017,13 @@ export const expressionGenMethods = {
1840
3017
  const size = t.size?.value !== undefined ? Number(t.size.value) : t.size;
1841
3018
  return `${inner}.slice(0, ${size})`;
1842
3019
  }
1843
- return inner;
3020
+ // Array-to-array conversion yields a new array value.
3021
+ return this._cloneJs(expr.expr._type, inner);
1844
3022
  },
1845
3023
 
1846
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";
1847
3027
  const elem = t.elem?.name;
1848
3028
  if (elem === "byte" || elem === "uint8") {
1849
3029
  if (this._isStringExprType(expr.expr._type))
@@ -1929,6 +3109,8 @@ export const expressionGenMethods = {
1929
3109
  _needsStrictOp(op, type) {
1930
3110
  if (!STRICT_OPS.has(op)) return false;
1931
3111
  if (this._isStrictAtomicOp(op, this._typeName(type))) return true;
3112
+ if (op === "<<" || op === ">>")
3113
+ return WIDE_INT_NAMES.has(this._typeName(type));
1932
3114
  return this.isIntType(type) && (op === "/" || op === "%");
1933
3115
  },
1934
3116
 
@@ -1978,7 +3160,10 @@ export const expressionGenMethods = {
1978
3160
  if (tName === "int32" || tName === "rune" || tName === "uint32") {
1979
3161
  const signed = tName !== "uint32";
1980
3162
  const cast = signed ? "| 0" : ">>> 0";
1981
- 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)`;
1982
3167
  if (op === "/")
1983
3168
  return `((${r}) === 0 ? ${divZero} : (Math.trunc((${l}) / (${r})) ${cast}))`;
1984
3169
  if (op === "%")
@@ -1986,9 +3171,9 @@ export const expressionGenMethods = {
1986
3171
  if (op === "<<" || op === ">>") {
1987
3172
  const ovf = signed && op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0";
1988
3173
  const jsOp = !signed && op === ">>" ? ">>>" : op;
1989
- return `((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast}`;
3174
+ return `(((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast})`;
1990
3175
  }
1991
- if (op === "&^") return `((${l}) & ~(${r})) ${cast}`;
3176
+ if (op === "&^") return `(((${l}) & ~(${r})) ${cast})`;
1992
3177
  return `((${l} ${op} ${r}) ${cast})`;
1993
3178
  }
1994
3179
  if (tName === "int8" || tName === "int16") {
@@ -2018,6 +3203,14 @@ export const expressionGenMethods = {
2018
3203
  if (op === "&^") return `(((${l}) & ~(${r})) & ${mask})`;
2019
3204
  return `((${l} ${op} ${r}) & ${mask})`;
2020
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
+ }
2021
3214
  if (this.isIntType(type)) {
2022
3215
  if (op === "/")
2023
3216
  return `((${r}) === 0 ? ${divZero} : Math.trunc((${l}) / (${r})))`;