gofront 1.7.0 → 1.9.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 (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -2,19 +2,89 @@ import {
2
2
  closureMutates,
3
3
  fnMutates,
4
4
  nodeMutatesVar,
5
- nodeWritesVar,
6
5
  rootIdentName,
6
+ someChild,
7
7
  } from "../../lower/index.js";
8
+ import { hasCallOrReceive } from "../../lower/walk.js";
9
+ import { constIntValue } from "../../typechecker/const-fold.js";
8
10
  import {
11
+ BROWSER_GLOBALS,
12
+ compositeLitIndices,
9
13
  ERROR,
14
+ embeddedFieldName,
15
+ is64BitInt,
16
+ is64BitUnsigned,
10
17
  isAny,
11
18
  isComplex,
12
19
  isTypedArraySlice,
13
20
  typedArrayConstructorForElem,
21
+ typeStr,
14
22
  } from "../../typechecker/types.js";
23
+ import { goTypeName } from "../shared/fmt-types.js";
24
+ import { jsNativeCall, NATIVE_HELPERS } from "./natives.js";
25
+
26
+ // True when `node` compares the identifier `name` with nil (`l == nil`): a
27
+ // pointer-receiver method that does so is meant to run on a nil receiver.
28
+ export function comparesToNil(node, name) {
29
+ if (Array.isArray(node)) return node.some((n) => comparesToNil(n, name));
30
+ if (!node || typeof node !== "object") return false;
31
+ if (node.kind === "BinaryExpr" && (node.op === "==" || node.op === "!=")) {
32
+ const isRecv = (e) => e?.kind === "Ident" && e.name === name;
33
+ const isNil = (e) =>
34
+ (e?.kind === "Ident" && e.name === "nil") ||
35
+ (e?.kind === "BasicLit" && e.litKind === "NIL");
36
+ if (
37
+ (isRecv(node.left) && isNil(node.right)) ||
38
+ (isRecv(node.right) && isNil(node.left))
39
+ )
40
+ return true;
41
+ }
42
+ return someChild(node, (c) => comparesToNil(c, name));
43
+ }
15
44
 
16
45
  /** @typedef {import('./index.js').CodeGen} CodeGen */
17
46
 
47
+ // True when type `t` mentions a type parameter anywhere.
48
+ export function mentionsTypeParam(t, depth = 0) {
49
+ if (!t || depth > 16) return false;
50
+ switch (t.kind) {
51
+ case "typeParam":
52
+ return true;
53
+ case "slice":
54
+ case "array":
55
+ return mentionsTypeParam(t.elem, depth + 1);
56
+ case "pointer":
57
+ return mentionsTypeParam(t.base, depth + 1);
58
+ case "map":
59
+ return (
60
+ mentionsTypeParam(t.key, depth + 1) ||
61
+ mentionsTypeParam(t.value, depth + 1)
62
+ );
63
+ case "func":
64
+ return [...t.params, ...t.returns].some((x) =>
65
+ mentionsTypeParam(x, depth + 1),
66
+ );
67
+ case "named":
68
+ return (t.typeArgs ?? []).some((x) => mentionsTypeParam(x, depth + 1));
69
+ default:
70
+ return false;
71
+ }
72
+ }
73
+
74
+ // How an instance of a generic struct is identified at run time: the type
75
+ // arguments' printed form, kept after the zero-value factories in the
76
+ // instance's `__tz` array. Null when an argument is a type parameter (the
77
+ // instantiation is not known inside erased generic code).
78
+ export function instanceId(typeArgs) {
79
+ if (!typeArgs?.length || typeArgs.some((t) => mentionsTypeParam(t)))
80
+ return null;
81
+ return typeArgs.map(typeStr).join(",");
82
+ }
83
+
84
+ // Array literals longer than this that leave most slots zero are emitted as
85
+ // the zero array plus assignments (_genSparseArrayLit).
86
+ const SPARSE_ARRAY_MIN = 32;
87
+
18
88
  const STRICT_NUMERIC_TYPES = new Set([
19
89
  "float32",
20
90
  "int8",
@@ -27,6 +97,25 @@ const STRICT_NUMERIC_TYPES = new Set([
27
97
  "uint32",
28
98
  ]);
29
99
 
100
+ // Unsigned types narrower than 64 bits: `-x` and `^x` wrap back into range.
101
+ const SMALL_UNSIGNED_WRAP = {
102
+ uint8: "& 0xFF",
103
+ byte: "& 0xFF",
104
+ uint16: "& 0xFFFF",
105
+ uint32: ">>> 0",
106
+ };
107
+
108
+ // Signed types narrower than 64 bits: `-x` wraps back into range.
109
+ const SIGNED_NEG_WRAP = {
110
+ int8: "<< 24 >> 24",
111
+ int16: "<< 16 >> 16",
112
+ int32: "| 0",
113
+ rune: "| 0",
114
+ };
115
+
116
+ // Integer types whose JS shifts must follow Go's 64-bit semantics.
117
+ const WIDE_INT_NAMES = new Set(["int", "int64", "uint", "uint64", "uintptr"]);
118
+
30
119
  const STRICT_OPS = new Set([
31
120
  "+",
32
121
  "-",
@@ -41,23 +130,66 @@ const STRICT_OPS = new Set([
41
130
  ">>",
42
131
  ]);
43
132
 
133
+ function collectFunctionLabels(node, labels = new Set()) {
134
+ if (!node || typeof node !== "object") return labels;
135
+ if (Array.isArray(node)) {
136
+ for (const item of node) collectFunctionLabels(item, labels);
137
+ return labels;
138
+ }
139
+ if (node.kind === "LabeledStmt" && node.label) labels.add(node.label);
140
+ if (node.kind === "ForStmt" && node.label) labels.add(node.label);
141
+ for (const k of Object.keys(node)) {
142
+ if (k.startsWith("_")) continue;
143
+ collectFunctionLabels(node[k], labels);
144
+ }
145
+ return labels;
146
+ }
147
+
44
148
  const NAMED_ZERO_TABLE = {
45
- "time.Time": [null, "{ _d: new Date(0), _isZero: true }"],
46
- "strings.Builder": [null, '{ _buf: "" }'],
47
- "bytes.Buffer": [null, "{ _buf: [] }"],
48
- "strings.Replacer": [null, "{ _p: [], Replace(s) { return s; } }"],
49
- "sync.Mutex": ["sync", "__sync_mutex()"],
50
- "sync.RWMutex": ["sync", "__sync_rwmutex()"],
51
- "sync.Once": ["sync", "__sync_once()"],
52
- "sync.WaitGroup": ["sync", "__sync_waitgroup()"],
53
- "binary.LittleEndian": ["binary", "__binary_le"],
54
- "encoding/binary.LittleEndian": ["binary", "__binary_le"],
55
- "binary.BigEndian": ["binary", "__binary_be"],
56
- "encoding/binary.BigEndian": ["binary", "__binary_be"],
57
- "base64.Encoding": ["base64", "__base64_std"],
58
- "encoding/base64.Encoding": ["base64", "__base64_std"],
59
149
  "simd.F32x4": [null, "Object.freeze([0, 0, 0, 0])"],
60
150
  F32x4: [null, "Object.freeze([0, 0, 0, 0])"],
151
+ "simd.Mask32x4": [null, "Object.freeze([false, false, false, false])"],
152
+ Mask32x4: [null, "Object.freeze([false, false, false, false])"],
153
+ };
154
+
155
+ // gofront/simd receiver methods -> runtime helper; the receiver is passed first.
156
+ const SIMD_VECTOR_HELPERS = {
157
+ Add: "__simd_add",
158
+ Sub: "__simd_sub",
159
+ Mul: "__simd_mul",
160
+ Div: "__simd_div",
161
+ Min: "__simd_min",
162
+ Max: "__simd_max",
163
+ MulAdd: "__simd_muladd",
164
+ Neg: "__simd_neg",
165
+ Abs: "__simd_abs",
166
+ Sqrt: "__simd_sqrt",
167
+ Floor: "__simd_floor",
168
+ Ceil: "__simd_ceil",
169
+ Trunc: "__simd_trunc",
170
+ RoundToEven: "__simd_nearest",
171
+ Dot3: "__simd_dot3",
172
+ Dot4: "__simd_dot4",
173
+ Sum: "__simd_sum",
174
+ WithLane: "__simd_with_lane",
175
+ Swizzle: "__simd_swizzle",
176
+ Equal: "__simd_eq",
177
+ NotEqual: "__simd_ne",
178
+ Less: "__simd_lt",
179
+ LessEqual: "__simd_le",
180
+ Greater: "__simd_gt",
181
+ GreaterEqual: "__simd_ge",
182
+ };
183
+
184
+ const SIMD_MASK_HELPERS = {
185
+ And: "__simd_mask_and",
186
+ Or: "__simd_mask_or",
187
+ Xor: "__simd_mask_xor",
188
+ AndNot: "__simd_mask_andnot",
189
+ Not: "__simd_mask_not",
190
+ Any: "__simd_mask_any",
191
+ All: "__simd_mask_all",
192
+ Bits: "__simd_mask_bits",
61
193
  };
62
194
 
63
195
  // Splits a generated JS lvalue at its outermost trailing access:
@@ -95,83 +227,13 @@ function splitLastAccess(js) {
95
227
 
96
228
  // Namespace constants: pkg.Field → JS literal
97
229
  const NS_CONSTANTS = {
98
- math: {
99
- Pi: "Math.PI",
100
- E: "Math.E",
101
- Phi: "1.618033988749895",
102
- Sqrt2: "Math.SQRT2",
103
- SqrtE: "1.6487212707001282",
104
- SqrtPi: "1.7724538509055159",
105
- SqrtPhi: "1.272019649514069",
106
- Ln2: "Math.LN2",
107
- Log2E: "Math.LOG2E",
108
- Ln10: "Math.LN10",
109
- Log10E: "Math.LOG10E",
110
- MaxFloat32: "3.4028234663852886e38",
111
- SmallestNonzeroFloat32: "1.401298464324817e-45",
112
- MaxFloat64: "Number.MAX_VALUE",
113
- SmallestNonzeroFloat64: "Number.MIN_VALUE",
114
- MaxInt: "Number.MAX_SAFE_INTEGER",
115
- MinInt: "Number.MIN_SAFE_INTEGER",
116
- MaxInt8: "127",
117
- MinInt8: "-128",
118
- MaxInt16: "32767",
119
- MinInt16: "-32768",
120
- MaxInt32: "2147483647",
121
- MinInt32: "-2147483648",
122
- MaxInt64: "9223372036854775807",
123
- MinInt64: "-9223372036854775808",
124
- MaxUint8: "255",
125
- MaxUint16: "65535",
126
- MaxUint32: "4294967295",
127
- MaxUint: "18446744073709551615",
128
- MaxUint64: "18446744073709551615",
129
- },
130
- strconv: {
131
- ErrSyntax: "__strconv_err_syntax",
132
- ErrRange: "__strconv_err_range",
133
- },
134
- path: {
135
- ErrBadPattern: "__path_err_bad_pattern",
136
- },
137
- filepath: {
138
- ErrBadPattern: "__path_err_bad_pattern",
139
- },
140
- io: {
141
- EOF: "__io_eof",
142
- Discard:
143
- "{ WriteString(s) { return [s.length, null]; }, Write(b) { return [b.length, null]; } }",
144
- },
145
- sha256: {
146
- Size: "32",
147
- BlockSize: "64",
148
- },
149
- hex: {
150
- ErrLength: '__error("encoding/hex: odd length hex string")',
151
- },
152
- base64: {
153
- StdPadding: "61",
154
- NoPadding: "-1",
155
- StdEncoding: "__base64_std",
156
- URLEncoding: "__base64_url",
157
- RawStdEncoding: "__base64_raw_std",
158
- RawURLEncoding: "__base64_raw_url",
159
- },
160
- binary: {
161
- LittleEndian: "__binary_le",
162
- BigEndian: "__binary_be",
163
- },
164
- rand: {
165
- Reader: "__crypto_rand_reader",
166
- },
167
- os: { Args: "process.argv" },
168
230
  time: {
169
- Nanosecond: "1",
170
- Microsecond: "1000",
171
- Millisecond: "1000000",
172
- Second: "1000000000",
173
- Minute: "60000000000",
174
- Hour: "3600000000000",
231
+ Nanosecond: "1n",
232
+ Microsecond: "1000n",
233
+ Millisecond: "1000000n",
234
+ Second: "1000000000n",
235
+ Minute: "60000000000n",
236
+ Hour: "3600000000000n",
175
237
  RFC3339: '"2006-01-02T15:04:05Z07:00"',
176
238
  RFC3339Nano: '"2006-01-02T15:04:05.999999999Z07:00"',
177
239
  DateOnly: '"2006-01-02"',
@@ -199,7 +261,6 @@ const NS_CONSTANTS = {
199
261
  Friday: "5",
200
262
  Saturday: "6",
201
263
  },
202
- utf8: { RuneError: "0xFFFD", MaxRune: "0x10FFFF", UTFMax: "4" },
203
264
  };
204
265
 
205
266
  // JavaScript precedence of the operators emitted for Go binary expressions.
@@ -258,6 +319,15 @@ const INT_TYPE_NAMES = new Set([
258
319
  "rune",
259
320
  ]);
260
321
 
322
+ // Zero values with a shared factory in HELPER_GENERICS.
323
+ const SHARED_ZERO_FACTORIES = {
324
+ null: "__zn",
325
+ 0: "__z0",
326
+ '""': "__zs",
327
+ false: "__zf",
328
+ "0n": "__z64",
329
+ };
330
+
261
331
  // Zero-value JS literals for basic Go types.
262
332
  const ZERO_FOR_BASIC = {
263
333
  int: "0",
@@ -265,11 +335,11 @@ const ZERO_FOR_BASIC = {
265
335
  int8: "0",
266
336
  int16: "0",
267
337
  int32: "0",
268
- int64: "0",
338
+ int64: "0n",
269
339
  uint8: "0",
270
340
  uint16: "0",
271
341
  uint32: "0",
272
- uint64: "0",
342
+ uint64: "0n",
273
343
  uintptr: "0",
274
344
  float32: "0",
275
345
  float64: "0",
@@ -282,24 +352,47 @@ const ZERO_FOR_BASIC = {
282
352
  };
283
353
 
284
354
  // Set of all numeric type names (for type-assertion codegen).
285
- const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
355
+ const TYPEOF_NUMBER_NAMES = new Set([
356
+ ...[...INT_TYPE_NAMES].filter((n) => n !== "int64" && n !== "uint64"),
357
+ "float32",
358
+ "float64",
359
+ ]);
286
360
 
287
361
  // Builtin function codegen — keyed by builtin name, value is (self, expr) => string.
288
362
  const BUILTIN_GEN = {
289
363
  append: (s, e) => s.genAppend(e),
290
364
  len: (s, e) => s._genBuiltinLen(e),
291
- cap: (s, e) => `(${s.genExpr(e.args[0])}?.length ?? 0)`,
365
+ cap: (s, e) => {
366
+ if (e._constLen != null) return s._genConstLen(e);
367
+ s.useHelper("slice");
368
+ return `__cap(${s.genExpr(e.args[0])})`;
369
+ },
292
370
  make: (s, e) => s.genMake(e),
293
371
  delete: (s, e) => {
294
372
  const [m, k] = e.args.map((a) => s.genExpr(a));
295
373
  return `(delete ${m}[${k}])`;
296
374
  },
297
375
  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})`;
376
+ const dst = s.genExpr(e.args[0]);
377
+ const src = s._genSliceSource(e.args[1]);
378
+ // Array and struct elements are values: the copied elements must not
379
+ // alias src.
380
+ const t = e.args[1]._type;
381
+ const elem = (t?.kind === "named" ? t.underlying : t)?.elem;
382
+ const clone = s._aggregateBase(elem) ? s._cloneJs(elem, "__e") : "__e";
383
+ const items =
384
+ clone === "__e"
385
+ ? "(__cs.slice ? __cs.slice(0, n) : __cs.subarray(0, n))"
386
+ : `__cs.slice(0, n).map((__e) => ${clone})`;
387
+ 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
388
  },
301
389
  new: (s, e) => {
302
390
  const arg = e.args[0];
391
+ const base = e._type?.kind === "pointer" ? e._type.base : null;
392
+ if (base?.kind === "named" && base.typeArgs?.length) {
393
+ const inst = s._genericStructNew(base.name, base.typeArgs);
394
+ if (inst) return inst;
395
+ }
303
396
  const typeName = arg.name ?? s.getTypeName(arg);
304
397
  if (typeName && s.structNames.has(typeName)) {
305
398
  return `new ${typeName}()`;
@@ -309,25 +402,36 @@ const BUILTIN_GEN = {
309
402
  const name = t.name ?? (t.kind === "named" ? t.name : null);
310
403
  if (name) return `new ${name}()`;
311
404
  }
312
- return `{ value: ${s.zeroValueForExpr(arg)} }`;
405
+ // `new([4]byte)`: the type argument is wrapped in a TypeExpr node.
406
+ const zero = s.zeroValueForExpr(arg.kind === "TypeExpr" ? arg.type : arg);
407
+ // Erased type parameter: a struct instantiation is its own pointer.
408
+ if (base?.kind === "typeParam")
409
+ return `((__v) => (${s._typeParamIsStructJs(base.name)} ? __v : { value: __v }))(${zero})`;
410
+ return `{ value: ${zero} }`;
313
411
  },
314
412
  // `print` writes without a trailing newline (Go semantics); the wasm glue
315
413
  // buffers the same way, so output parity holds across backends.
316
414
  print: (s, e) =>
317
- `((__s) => (typeof process !== "undefined" && process?.stdout?.write ? process.stdout.write(__s) : console.log(__s)))([${e.args.map((a) => s.genExpr(a)).join(", ")}].map(String).join(" "))`,
415
+ `((__s) => (typeof process !== "undefined" && process?.stdout?.write ? process.stdout.write(__s) : console.log(__s)))([${e.args.map((a) => s.genExpr(a)).join(", ")}].map(String).join(""))`,
318
416
  println: (s, e) =>
319
417
  `console.log(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
320
- panic: (s, e) => `(() => { throw new Error(${s.genExpr(e.args[0])}); })()`,
418
+ panic: (s, e) => {
419
+ const arg = s.genExpr(e.args[0]);
420
+ s.useHelper("panicRt");
421
+ return `__gopanicv(${arg})`;
422
+ },
321
423
  recover: (s) => {
322
424
  s.useHelper("panicRt");
323
425
  return `__recover(${s._recoverOkInScope ? "__recoverOk" : "false"})`;
324
426
  },
325
427
  error: (s, e) => {
428
+ // `error(x)` of an error (or nil) is the conversion: x itself.
429
+ if (!s._isStringExprType(e.args[0]?._type)) return s.genExpr(e.args[0]);
326
430
  s.useHelper("error");
327
431
  return `__error(${s.genExpr(e.args[0])})`;
328
432
  },
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(", ")})`,
433
+ min: (s, e) => genMinMax(s, e, "min", "<"),
434
+ max: (s, e) => genMinMax(s, e, "max", ">"),
331
435
  clear: (s, e) => s._genBuiltinClear(e),
332
436
  complex: (s, e) =>
333
437
  `{ re: ${s.genExpr(e.args[0])}, im: ${s.genExpr(e.args[1])} }`,
@@ -335,6 +439,65 @@ const BUILTIN_GEN = {
335
439
  imag: (s, e) => `(${s.genExpr(e.args[0])}).im`,
336
440
  };
337
441
 
442
+ // Math.min/max coerce strings to NaN, so string operands fold with `<`/`>`.
443
+ // A type parameter may be a string or a number: pick the form at run time
444
+ // (Math.min/max keeps NaN propagation and -0 for floats).
445
+ function genMinMax(s, e, fn, cmp) {
446
+ const is64 = s._is64BitInt(e._type);
447
+ const args = e.args.map((a) => {
448
+ const js = s.genExpr(a);
449
+ if (
450
+ is64 &&
451
+ a._type?.kind === "untyped" &&
452
+ !js.endsWith("n") &&
453
+ /^-?\d+$/.test(js)
454
+ ) {
455
+ return `${js}n`;
456
+ }
457
+ return js;
458
+ });
459
+ const fold = (xs) => `${xs}.reduce((a, b) => (b ${cmp} a ? b : a))`;
460
+ if (args.length === 1) return args[0];
461
+ if (e._type?.kind === "typeParam")
462
+ return `((...v) => (typeof v[0] === "string" || typeof v[0] === "bigint" ? ${fold("v")} : Math.${fn}(...v)))(${args.join(", ")})`;
463
+ if (is64 || s._isStringExprType(e._type)) return fold(`[${args.join(", ")}]`);
464
+ return `Math.${fn}(${args.join(", ")})`;
465
+ }
466
+
467
+ // Expression kinds whose folded constant value (`_const`, attached by the
468
+ // checker — see typechecker/const-fold.js) is emitted as a literal. Literals
469
+ // and constant names already emit their value.
470
+ const FOLDED_CONST_KINDS = new Set([
471
+ "UnaryExpr",
472
+ "BinaryExpr",
473
+ "ParenExpr",
474
+ "CallExpr",
475
+ "TypeConversion",
476
+ ]);
477
+
478
+ const JS_SAFE_INT = 2n ** 53n;
479
+
480
+ // `-1.0` already emits its exact value; keep it as written.
481
+ const isSignedLit = (e) =>
482
+ e.kind === "UnaryExpr" &&
483
+ (e.op === "-" || e.op === "+") &&
484
+ e.operand?.kind === "BasicLit";
485
+
486
+ // Negative literals are parenthesised so they splice safely into operators.
487
+ const signed = (n, text) => (n < 0 || Object.is(n, -0) ? `(${text})` : text);
488
+
489
+ // JS literal per folded constant kind; null when JS cannot represent it.
490
+ const CONST_LITS = {
491
+ STRING: (v) => JSON.stringify(v),
492
+ BOOL: (v) => (v ? "true" : "false"),
493
+ FLOAT: (v) =>
494
+ Number.isFinite(v) ? signed(v, Object.is(v, -0) ? "-0" : String(v)) : null,
495
+ INT: (v) => {
496
+ const n = Number(v);
497
+ return Number.isFinite(n) ? signed(n, BigInt(n).toString()) : null;
498
+ },
499
+ };
500
+
338
501
  // Method-name dispatch for genExpr — string values add no static call edges.
339
502
  const EXPR_GEN_DELEGATE = {
340
503
  UnaryExpr: "_genUnaryExpr",
@@ -350,46 +513,159 @@ const EXPR_GEN_DELEGATE = {
350
513
  };
351
514
 
352
515
  /** @type {ThisType<CodeGen>} */
516
+ const stdIdent = (name, type) => ({ kind: "Ident", name, _type: type });
517
+
518
+ // True when a func type mentions a type parameter (a generic stdlib function).
519
+ function hasTypeParamType(t) {
520
+ return JSON.stringify([t.params, t.returns], (k, v) =>
521
+ k.startsWith("_") || k === "underlying" ? undefined : v,
522
+ ).includes('"typeParam"');
523
+ }
524
+
353
525
  export const expressionGenMethods = {
354
526
  genExpr(expr) {
527
+ if (expr._asComplex && !expr._complexWrapping) {
528
+ expr._complexWrapping = true;
529
+ try {
530
+ return `{ re: ${this.genExpr(expr)}, im: 0 }`;
531
+ } finally {
532
+ expr._complexWrapping = false;
533
+ }
534
+ }
535
+ if (expr._ifaceKeyDyn && !expr._ifaceKeying) {
536
+ // A key typed by a type parameter: encode it for the instantiated type.
537
+ const f = this._typeParamZeroFactory(expr._ifaceKeyParam);
538
+ if (f) {
539
+ expr._ifaceKeying = true;
540
+ try {
541
+ this.useHelper("structKey");
542
+ const box = expr._ifaceKeyDyn === "box" ? ", true" : "";
543
+ return `__ikt(${f}, ${this.genExpr(expr)}${box})`;
544
+ } finally {
545
+ expr._ifaceKeying = false;
546
+ }
547
+ }
548
+ }
549
+ if (expr._ifaceKey && !expr._ifaceKeying) {
550
+ // A string key is its own property key; any other dynamic value is
551
+ // encoded with its type (see HELPER_STRUCT_KEY).
552
+ const plain =
553
+ !this._isNamedBoxKind(expr) && this._isStringExprType(expr._type);
554
+ if (!plain) {
555
+ expr._ifaceKeying = true;
556
+ try {
557
+ this.useHelper("structKey");
558
+ const tag = this._ifaceKeyTag(expr._ifaceKeyType);
559
+ return `__ik(${this.genExpr(expr)}${tag ? `, ${JSON.stringify(tag)}` : ""})`;
560
+ } finally {
561
+ expr._ifaceKeying = false;
562
+ }
563
+ }
564
+ }
565
+ if (expr._mapKeyEnc && !expr._mapKeying) {
566
+ // Array, anonymous struct and pointer keys: see HELPER_STRUCT_KEY.
567
+ expr._mapKeying = true;
568
+ try {
569
+ this.useHelper("structKey");
570
+ const enc = expr._mapKeyEnc === "pk" ? "__pk" : "__sk";
571
+ return `${enc}(${this.genExpr(expr)})`;
572
+ } finally {
573
+ expr._mapKeying = false;
574
+ }
575
+ }
576
+ if (
577
+ expr._ifaceSrc?.kind === "pointer" &&
578
+ expr._ifaceSrc.base?.kind === "typeParam" &&
579
+ !expr._pbsWrapping
580
+ ) {
581
+ // `&v` of a type parameter's variable is a box; a struct behind it
582
+ // is its own pointer.
583
+ expr._pbsWrapping = true;
584
+ try {
585
+ this.useHelper("ifaceBox");
586
+ return `__pbs(${this.genExpr(expr)})`;
587
+ } finally {
588
+ expr._pbsWrapping = false;
589
+ }
590
+ }
591
+ if (
592
+ expr._ifaceSrc?.kind === "typeParam" &&
593
+ !expr._tpBoxing &&
594
+ !expr._ifaceKeyDyn &&
595
+ this._typeParamZeroFactory(expr._ifaceSrc.name)
596
+ ) {
597
+ // A type parameter's value entering an interface is boxed and
598
+ // tagged by the instantiated type (see `__ti`).
599
+ expr._tpBoxing = true;
600
+ try {
601
+ const f = this._typeParamZeroFactory(expr._ifaceSrc.name);
602
+ return `(${f}.b ?? ${f}.fb ?? ((__v) => __v))(${this.genExpr(expr)})`;
603
+ } finally {
604
+ expr._tpBoxing = false;
605
+ }
606
+ }
355
607
  if (this._isNamedIfaceBox(expr)) return this._genNamedIfaceBoxExpr(expr);
608
+ if (expr._ifaceSrc && !expr._ityTagging && !this._isNamedBoxKind(expr)) {
609
+ const tag = this._dynTypeTag(expr._ifaceSrc);
610
+ if (tag) {
611
+ this.useHelper("ifaceBox");
612
+ expr._ityTagging = true;
613
+ try {
614
+ const ptr = expr._ifaceSrc.kind === "pointer" ? ", true" : "";
615
+ return `__ity(${this.genExpr(expr)}, ${JSON.stringify(tag)}${ptr})`;
616
+ } finally {
617
+ expr._ityTagging = false;
618
+ }
619
+ }
620
+ }
356
621
  if (expr._ifaceBox && !expr._ifaceBoxing && !this._isNamedBoxKind(expr)) {
357
622
  expr._ifaceBoxing = true;
358
623
  try {
359
624
  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})`;
625
+ const boxed = this._ifaceBoxWrap(
626
+ this.genExpr(expr),
627
+ expr._ifaceBox,
628
+ expr._ifacePtrFields,
629
+ expr.kind === "CompositeLit",
630
+ );
631
+ return this._typedNilWrap(boxed, expr);
376
632
  } finally {
377
633
  expr._ifaceBoxing = false;
378
634
  }
379
635
  }
636
+ const folded = this._genFoldedConst(expr);
637
+ if (folded !== null) return folded;
380
638
  switch (expr.kind) {
381
639
  case "BasicLit":
382
640
  if (expr.litKind === "STRING") return JSON.stringify(expr.value);
641
+ if (
642
+ (this._is64BitInt(expr._type) || expr._is64Bit) &&
643
+ (expr.litKind === "INT" || expr._const?.k === "INT")
644
+ ) {
645
+ return `${expr.value}n`;
646
+ }
647
+ if (expr._const?.k === "INT")
648
+ this._warnConstPrecision(expr._const, expr);
383
649
  return expr.value; // int, float, bool, nil(null)
384
650
  case "ImagLit":
385
651
  return `{ re: 0, im: ${expr.value} }`;
386
- case "Ident":
652
+ case "Ident": {
387
653
  if (this._boxedVars.has(expr.name) && !expr._isAddressOf)
388
654
  return `${expr.name}.value`;
389
- return expr.name;
655
+ // A named untyped constant used as an int64 operand: its
656
+ // declaration holds a Number, so emit the exact BigInt.
657
+ const lit64 =
658
+ expr._is64Bit &&
659
+ expr._const &&
660
+ (expr._type?.kind === "untyped" || this._is64BitInt(expr._type))
661
+ ? this._gen64BitFoldedConst(expr, expr._const)
662
+ : null;
663
+ return lit64 ?? expr.name;
664
+ }
390
665
  case "AwaitExpr":
391
666
  return `await ${this.genExpr(expr.expr)}`;
392
667
  case "InstantiationExpr":
668
+ return this._genInstantiationExpr(expr);
393
669
  case "RangeExpr":
394
670
  return this.genExpr(expr.expr);
395
671
  default: {
@@ -400,6 +676,82 @@ export const expressionGenMethods = {
400
676
  }
401
677
  },
402
678
 
679
+ _gen64BitFoldedConst(expr, val) {
680
+ if (
681
+ (this._is64BitInt(expr._type) || expr._is64Bit) &&
682
+ (val.k === "INT" || expr._intConst)
683
+ ) {
684
+ const n = constIntValue(val);
685
+ if (n !== null) return n < 0n ? `(${n}n)` : `${n}n`;
686
+ }
687
+ return null;
688
+ },
689
+
690
+ // The literal for a folded constant expression (`1 << 40` →
691
+ // `1099511627776`), or null. Integers outside ±2⁵³ emit the nearest
692
+ // Number and a precision warning.
693
+ _genFoldedConst(expr) {
694
+ const val = expr._const;
695
+ if (!val) return null;
696
+ // Constant names emit themselves, but an integer use of one still warns
697
+ // (an integer constant declared in this package warned at its declaration).
698
+ if (expr.kind === "Ident" || expr.kind === "SelectorExpr") {
699
+ const declared = expr.kind === "Ident" && val.k === "INT";
700
+ if (expr._intConst && !declared) this._warnConstPrecision(val, expr);
701
+ return null;
702
+ }
703
+ if (!FOLDED_CONST_KINDS.has(expr.kind) || isSignedLit(expr)) return null;
704
+ const lit64 = this._gen64BitFoldedConst(expr, val);
705
+ if (lit64 !== null) return lit64;
706
+ if (val.k === "INT" || expr._intConst) this._warnConstPrecision(val, expr);
707
+ return CONST_LITS[val.k]?.(val.v) ?? null;
708
+ },
709
+
710
+ // Warns when an integer constant outside ±2⁵³ has no exact Number (once
711
+ // per node per generate run: some forms generate the same node more than
712
+ // once, and cached ASTs are generated again by later compiles).
713
+ _warnConstPrecision(val, expr) {
714
+ if (this._is64BitInt(expr?._type) || expr?._is64Bit || expr?._floatConst)
715
+ return;
716
+ const i = constIntValue(val);
717
+ if (i === null || (i <= JS_SAFE_INT && i >= -JS_SAFE_INT)) return;
718
+ if (BigInt(Number(i)) !== i && !this._precisionWarned.has(expr)) {
719
+ this._precisionWarned.add(expr);
720
+ this._warn(`constant ${i} exceeds JS Number precision`, expr);
721
+ }
722
+ },
723
+
724
+ // `js` boxed as `box` (a checker _ifaceBoxMeta kind) for an interface.
725
+ // `fresh` marks a value nothing else refers to (a composite literal), which
726
+ // a struct box then takes over instead of copying.
727
+ _ifaceBoxWrap(js, box, ptrFields, fresh = false) {
728
+ this.useHelper("ifaceBox");
729
+ const fn =
730
+ {
731
+ ptr: "__ifp",
732
+ ptr_slice: "__ifps",
733
+ ptr_map: "__ifpm",
734
+ map: "__ifm",
735
+ slice: "__ifs",
736
+ }[box] ?? "__ifv";
737
+ const fields = ptrFields?.length ? JSON.stringify(ptrFields) : "";
738
+ if (fn === "__ifv" && fresh)
739
+ return `${fn}(${js}, ${fields || "null"}, true)`;
740
+ return `${fn}(${js}${fields ? `, ${fields}` : ""})`;
741
+ },
742
+
743
+ // A nil `*T` entering an interface is a typed nil: it keeps T's methods
744
+ // (callable with a nil receiver) and differs from the nil interface.
745
+ _typedNilWrap(js, expr) {
746
+ if (expr._ifaceBox !== "ptr") return js;
747
+ const src = expr._ifaceSrc;
748
+ const base = src?.kind === "pointer" ? src.base : null;
749
+ if (base?.kind !== "named" || base.underlying?.kind !== "struct") return js;
750
+ const cls = this._dequalify(base.name).replace(/\[.*$/, "");
751
+ if (!this.structNames.has(cls)) return js;
752
+ return `__ifn(${js}, ${cls})`;
753
+ },
754
+
403
755
  _isNamedBoxKind(expr) {
404
756
  return expr._ifaceBox === "named" || expr._ifaceBox === "named_ptr";
405
757
  },
@@ -444,6 +796,29 @@ export const expressionGenMethods = {
444
796
  return this._isStructType(pt.base);
445
797
  },
446
798
 
799
+ _isScalarPointerType(t) {
800
+ if (!t) return false;
801
+ const pt =
802
+ t.kind === "named"
803
+ ? (t.underlying ?? this.checker?.types.get(t.name)?.underlying)
804
+ : t;
805
+ if (pt?.kind !== "pointer") return false;
806
+ return !this._isStructType(pt.base);
807
+ },
808
+
809
+ _genPointerEquality(expr) {
810
+ if (expr.op !== "==" && expr.op !== "!=") return null;
811
+ if (
812
+ !this._isScalarPointerType(expr.left._type) &&
813
+ !this._isScalarPointerType(expr.right._type)
814
+ ) {
815
+ return null;
816
+ }
817
+ this.useHelper("ptreq");
818
+ const cmp = `__ptreq(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
819
+ return expr.op === "==" ? cmp : `!${cmp}`;
820
+ },
821
+
447
822
  _isStructExpr(expr) {
448
823
  if (!expr) return false;
449
824
  if (expr.kind === "CompositeLit") {
@@ -473,14 +848,31 @@ export const expressionGenMethods = {
473
848
  // Pseudo type object for an AST type node (used where no checker type is available).
474
849
  _typeFromNode(node) {
475
850
  if (!node) return null;
476
- if (node.kind === "TypeName" && this.structNames.has(node.name))
477
- return { kind: "named", name: node.name, underlying: { kind: "struct" } };
851
+ if (node.kind === "TypeName" || node.kind === "SelectorExpr") {
852
+ // `pkg.S` of a bundled package names the class `S`.
853
+ const name = this.getTypeName(node);
854
+ if (name && this.structNames.has(name))
855
+ return { kind: "named", name, underlying: { kind: "struct" } };
856
+ }
478
857
  if (node.kind === "ArrayType")
479
858
  return { kind: "array", elem: this._typeFromNode(node.elem) };
480
859
  return null;
481
860
  },
482
861
 
862
+ // A value of type-parameter type is copied at run time when its type
863
+ // argument is a struct or array (decision: runtime check on the zero-value
864
+ // factory, so one erased function body serves every instantiation).
865
+ _typeParamClone(t, js) {
866
+ if (t?.kind !== "typeParam") return null;
867
+ const factory = this._typeParamZeroFactory(t.name);
868
+ if (!factory) return null;
869
+ this.useHelper("vclone");
870
+ return `__vclone(${factory}, ${js})`;
871
+ },
872
+
483
873
  _cloneJs(t, js) {
874
+ const tp = this._typeParamClone(t, js);
875
+ if (tp) return tp;
484
876
  const base = this._aggregateBase(t);
485
877
  if (!base) return js;
486
878
  if (base.kind === "struct") {
@@ -498,7 +890,9 @@ export const expressionGenMethods = {
498
890
  _isAddressableExpr(e) {
499
891
  switch (e?.kind) {
500
892
  case "Ident":
501
- return e.name !== "_" && !e._isTypeRef;
893
+ return (
894
+ e.name !== "_" && (!e._isTypeRef || e._type?.kind === "typeParam")
895
+ );
502
896
  case "SelectorExpr":
503
897
  return !e._isMethodValue && !e._isMethodExpr;
504
898
  case "IndexExpr":
@@ -516,7 +910,12 @@ export const expressionGenMethods = {
516
910
  genValueExpr(expr) {
517
911
  const js = this.genExpr(expr);
518
912
  if (expr._ifaceBox) return js;
519
- if (!this._aggregateBase(expr._type) || !this._isAddressableExpr(expr))
913
+ if (
914
+ !(
915
+ this._aggregateBase(expr._type) || this._typeParamClone(expr._type, js)
916
+ ) ||
917
+ !this._isAddressableExpr(expr)
918
+ )
520
919
  return js;
521
920
  return this._cloneJs(expr._type, js);
522
921
  },
@@ -525,11 +924,6 @@ export const expressionGenMethods = {
525
924
  return rootIdentName(e);
526
925
  },
527
926
 
528
- // True when `node` itself (not its children) writes to variable `name`.
529
- _nodeWritesVar(node, name) {
530
- return nodeWritesVar(node, name);
531
- },
532
-
533
927
  // True when `node` may modify (or take the address of) the value held by variable `name`.
534
928
  _nodeMutatesVar(node, name, addrOnly = false) {
535
929
  return nodeMutatesVar(node, name, addrOnly);
@@ -541,16 +935,26 @@ export const expressionGenMethods = {
541
935
 
542
936
  _withFnCtx(body, fn) {
543
937
  const prev = this._fnCtx;
938
+ const prevLabels = this._functionLabels;
939
+ const labels = collectFunctionLabels(
940
+ body,
941
+ new Set(this._functionLabels ?? []),
942
+ );
943
+ this._functionLabels = Array.from(labels);
544
944
  this._fnCtx = {
545
945
  body,
546
946
  mut: new Map(),
547
947
  owned: new Set(),
548
948
  borrowed: new Set(),
949
+ // Parameters, receivers and named results, including the enclosing
950
+ // functions' (closures): `x = x[lo:hi]` on them must not copy.
951
+ params: new Set(prev?.params),
549
952
  };
550
953
  try {
551
954
  fn();
552
955
  } finally {
553
956
  this._fnCtx = prev;
957
+ this._functionLabels = prevLabels;
554
958
  }
555
959
  },
556
960
 
@@ -568,6 +972,15 @@ export const expressionGenMethods = {
568
972
  // Copies aggregate params/receivers that the function body mutates.
569
973
  _emitParamCopies(params) {
570
974
  for (const p of params) {
975
+ if (p.name) this._fnCtx?.params.add(p.name);
976
+ if (p.variadic && p.name && p.name !== "_") {
977
+ this.useHelper("va");
978
+ // Only a function that tests the parameter against nil needs an
979
+ // empty call to be nil; others keep an empty slice so stdlib
980
+ // helpers and printing see `[]`.
981
+ const nilTested = comparesToNil(this._fnCtx?.body, p.name);
982
+ this.line(`${p.name} = __va(${p.name}${nilTested ? ", true" : ""});`);
983
+ }
571
984
  if (p.variadic || !p.name || p.name === "_") continue;
572
985
  if (this._boxedVars.has(p.name)) {
573
986
  // Address-taken parameter: rebind it to a { value } box.
@@ -621,12 +1034,26 @@ export const expressionGenMethods = {
621
1034
  ) {
622
1035
  return this.genExpr(expr.operand);
623
1036
  }
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; } }`;
1037
+ if (expr.operand.kind === "IndexExpr") {
1038
+ const obj = this.genExpr(expr.operand.expr);
1039
+ const idx = this.genExpr(expr.operand.index);
1040
+ const check = this._genStrictIndex(
1041
+ { ...expr.operand, _lvalue: false },
1042
+ "__o",
1043
+ "__i",
1044
+ );
1045
+ // Erased generic element: a struct instantiation is its own
1046
+ // pointer, anything else gets the get/set wrapper.
1047
+ if (expr.operand._type?.kind === "typeParam")
1048
+ 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})`;
1049
+ if (check)
1050
+ 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})`;
1051
+ return `((__o, __i) => Object.defineProperties({ get value() { return __o[__i]; }, set value(v) { __o[__i] = v; } }, { __target: { value: __o }, __index: { value: __i } }))(${obj}, ${idx})`;
1052
+ }
1053
+ if (expr.operand.kind === "SelectorExpr") {
1054
+ const obj = this.genExpr(expr.operand.expr);
1055
+ const field = expr.operand.field;
1056
+ return `((__o) => Object.defineProperties({ get value() { return __o.${field}; }, set value(v) { __o.${field} = v; } }, { __target: { value: __o }, __index: { value: "${field}" } }))(${obj})`;
630
1057
  }
631
1058
  // For non-boxed vars (primitives, etc.), wrap in { value: x }
632
1059
  return `{ value: ${this.genExpr(expr.operand)} }`;
@@ -634,7 +1061,13 @@ export const expressionGenMethods = {
634
1061
  // Dereference: *p
635
1062
  if (op === "*") {
636
1063
  if (this._isStructPointerType(expr.operand._type)) {
637
- return this.genExpr(expr.operand);
1064
+ return expr._nonNil
1065
+ ? this.genExpr(expr.operand)
1066
+ : this._genNilChecked(this.genExpr(expr.operand));
1067
+ }
1068
+ if (expr.operand._type?.base?.kind === "typeParam") {
1069
+ if (expr._derefOpTarget) return `${this.genExpr(expr.operand)}.value`;
1070
+ return `((__p) => (${this._typeParamIsStructJs(expr.operand._type.base.name)} ? __p : __p.value))(${this.genExpr(expr.operand)})`;
638
1071
  }
639
1072
  return `${this.genExpr(expr.operand)}.value`;
640
1073
  }
@@ -645,6 +1078,27 @@ export const expressionGenMethods = {
645
1078
  return inner;
646
1079
  }
647
1080
  const code = this.genExpr(expr.operand);
1081
+ if (this._is64BitInt(expr._type) || expr._is64Bit) {
1082
+ let operandCode = code;
1083
+ if (!operandCode.endsWith("n") && /^-?\d+$/.test(operandCode)) {
1084
+ operandCode = `${operandCode}n`;
1085
+ }
1086
+ if (op === "+") return operandCode;
1087
+ const wrap = this._is64BitUnsigned(expr._type)
1088
+ ? "BigInt.asUintN"
1089
+ : "BigInt.asIntN";
1090
+ if (op === "-" || op === "~") return `${wrap}(64, ${op}(${operandCode}))`;
1091
+ }
1092
+ const wrap = this.strict
1093
+ ? SMALL_UNSIGNED_WRAP[this._typeName(expr._type)]
1094
+ : null;
1095
+ if (wrap && (op === "-" || op === "~")) return `(${op}(${code}) ${wrap})`;
1096
+ // -MinInt wraps back to itself for signed narrow integers.
1097
+ const signedWrap =
1098
+ this.strict && op === "-"
1099
+ ? SIGNED_NEG_WRAP[this._typeName(expr._type)]
1100
+ : null;
1101
+ if (signedWrap) return `(-(${code}) ${signedWrap})`;
648
1102
  const needsParens =
649
1103
  this._emittedBinaryOp(expr.operand) !== null ||
650
1104
  ((op === "-" || op === "+") && /^[-+]/.test(code));
@@ -652,9 +1106,24 @@ export const expressionGenMethods = {
652
1106
  },
653
1107
 
654
1108
  _genSelectorExpr(expr) {
1109
+ // Go-source stdlib member: bundled under its mangled top-level name.
1110
+ if (expr._stdName) {
1111
+ // An untyped constant used as an int64 operand: its declaration
1112
+ // holds a Number, so emit the exact BigInt (as for an Ident).
1113
+ const lit64 =
1114
+ expr._is64Bit && expr._type?.kind === "untyped" && expr._const
1115
+ ? this._gen64BitFoldedConst(expr, expr._const)
1116
+ : null;
1117
+ return lit64 ?? expr._stdName;
1118
+ }
1119
+ if (this._isStdlibFuncValue(expr)) return this._genStdlibFuncValue(expr);
655
1120
  if (expr._isMethodExpr) {
656
1121
  const namedExpr = this._namedMethodExprInfo(expr);
657
1122
  if (namedExpr) return `${namedExpr.cls}.$${expr.field}`;
1123
+ const rt = expr._methodExprRecv;
1124
+ const protoCls = this._ptrMethodProtoClass(rt, expr.field);
1125
+ if (protoCls)
1126
+ return `((recv, ...args) => ${protoCls}.prototype.${expr.field}.call(recv, ...args))`;
658
1127
  return `((recv, ...args) => recv.${expr.field}(...args))`;
659
1128
  }
660
1129
  const namedValue = this._genNamedMethodValue(expr);
@@ -662,36 +1131,101 @@ export const expressionGenMethods = {
662
1131
  if (expr.expr.kind === "Ident") {
663
1132
  const nsConst = NS_CONSTANTS[expr.expr.name]?.[expr.field];
664
1133
  if (nsConst !== undefined) {
665
- if (expr.expr.name === "strconv") {
666
- this.useHelper("strconvError");
667
- } else if (expr.expr.name === "path" || expr.expr.name === "filepath") {
668
- if (expr.field === "ErrBadPattern") this.useHelper("pathError");
669
- } else if (expr.expr.name === "io" && expr.field === "EOF") {
670
- this.useHelper("ioEOF");
671
- } else if (expr.expr.name === "binary") {
672
- this.useHelper("binary");
673
- } else if (expr.expr.name === "hex") {
674
- this.useHelper("hex");
675
- this.useHelper("error");
676
- } else if (expr.expr.name === "base64") {
677
- this.useHelper("base64");
678
- } else if (expr.expr.name === "rand" && expr.field === "Reader") {
679
- this.useHelper("cryptoRand");
680
- } else if (expr.expr.name === "sha256") {
681
- this.useHelper("sha256");
682
- }
683
1134
  return nsConst;
684
1135
  }
685
1136
  }
686
1137
  const base = this.genExpr(expr.expr);
687
- if (this.bundledPackages.has(base)) return expr.field;
1138
+ // Bundled GoFront package member; browser-global names carry the `$`
1139
+ // suffix of their declaration (see jsBindingName).
1140
+ if (this.bundledPackages.has(base))
1141
+ return BROWSER_GLOBALS.has(expr.field) ? `${expr.field}$` : expr.field;
688
1142
  if (expr.expr._type?.kind === "pointer" && expr.field !== "value")
689
1143
  return this._genPointerSelectorField(expr, base);
690
- const sel = `${base}.${this._jsMemberName(expr)}`;
691
- if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
1144
+ const sel = `${base}${this._promotedPath(expr)}.${this._jsMemberName(expr)}`;
1145
+ if (expr._isMethodValue && !expr._callee) {
1146
+ // A value receiver is copied when the method value is evaluated.
1147
+ const t = expr.expr._type;
1148
+ if (!expr._type?._ptrRecv && this._aggregateBase(t))
1149
+ return `((__r) => __r.${this._jsMemberName(expr)}.bind(__r))(${this._cloneJs(t, base)})`;
1150
+ return `${sel}.bind(${base})`;
1151
+ }
692
1152
  return sel;
693
1153
  },
694
1154
 
1155
+ // `pkg.Fn` used as a value (not called): a stdlib function is inlined at
1156
+ // its call sites, so a value is an arrow function that makes that call.
1157
+ _isStdlibFuncValue(expr) {
1158
+ const t = expr._type;
1159
+ if (expr._callee || t?.kind !== "func" || expr._isMethodExpr) return false;
1160
+ const ns = expr.expr;
1161
+ if (ns?.kind !== "Ident" || ns._type?.kind !== "namespace") return false;
1162
+ return !this.bundledPackages.has(ns.name) && !hasTypeParamType(t);
1163
+ },
1164
+
1165
+ _genStdlibFuncValue(expr) {
1166
+ const t = expr._type;
1167
+ const ps = t.params ?? [];
1168
+ const nFixed = t.variadic && ps.length > 0 ? ps.length - 1 : ps.length;
1169
+ const fixed = ps.slice(0, nFixed).map((p, i) => stdIdent(`__a${i}`, p));
1170
+ const names = fixed.map((a) => a.name);
1171
+ const callJs = (args) =>
1172
+ this.genExpr({
1173
+ kind: "CallExpr",
1174
+ func: { ...expr, _callee: true },
1175
+ args,
1176
+ _type: t.returns?.[0] ?? { kind: "basic", name: "void" },
1177
+ });
1178
+ if (nFixed === ps.length)
1179
+ return `((${names.join(", ")}) => ${callJs(fixed)})`;
1180
+ // Stdlib calls are inlined per operand count, so dispatch on it.
1181
+ const rest = `__a${nFixed}`;
1182
+ let body = `(() => { throw new Error("too many arguments in call to ${expr.expr.name}.${expr.field}"); })()`;
1183
+ for (let k = 8; k >= 0; k--) {
1184
+ const extra = Array.from({ length: k }, (_, i) =>
1185
+ stdIdent(`${rest}[${i}]`, ps[nFixed]),
1186
+ );
1187
+ body = `${rest}.length === ${k} ? ${callJs([...fixed, ...extra])} : ${body}`;
1188
+ }
1189
+ return `((${[...names, `...${rest}`].join(", ")}) => ${body})`;
1190
+ },
1191
+
1192
+ // A promoted field (`o.A` with embedded `In`) lives on the embedded value:
1193
+ // the class also carries a flattened copy, so reads and writes go through
1194
+ // the embedded path (`.In`) to stay in sync. Returns "" for direct fields.
1195
+ _promotedPath(expr) {
1196
+ if (expr._isMethodValue || expr._isMethodExpr) return "";
1197
+ let t = expr.expr._type;
1198
+ if (t?.kind === "pointer") t = t.base;
1199
+ const path = this._embedPath(t, expr.field, 0);
1200
+ return path ? path.map((n) => `.${n}`).join("") : "";
1201
+ },
1202
+
1203
+ _embedPath(t, field, depth) {
1204
+ const st = t?.kind === "named" ? t.underlying : t;
1205
+ if (st?.kind !== "struct" || !st.fields?.has(field) || depth > 8)
1206
+ return null;
1207
+ const direct = (st._structFields ?? []).some(
1208
+ (sf) => !sf.embedded && sf.names?.includes(field),
1209
+ );
1210
+ if (direct) return null;
1211
+ for (const sf of st._structFields ?? []) {
1212
+ if (!sf.embedded) continue;
1213
+ const inner = sf.type?.kind === "pointer" ? sf.type.base : sf.type;
1214
+ if (inner?.kind !== "named" || !this.structNames.has(inner.name))
1215
+ continue;
1216
+ const name = embeddedFieldName(sf);
1217
+ if (name === field) return null;
1218
+ const ist = inner.underlying;
1219
+ if (!ist?.fields?.has(field)) continue;
1220
+ const sub = this._embedPath(inner, field, depth + 1);
1221
+ const idirect = (ist._structFields ?? []).some(
1222
+ (x) => !x.embedded && x.names?.includes(field),
1223
+ );
1224
+ return idirect ? [name] : sub ? [name, ...sub] : null;
1225
+ }
1226
+ return null;
1227
+ },
1228
+
695
1229
  // PascalCase aliases of Web API methods (gl.ClearColor) map to the real camelCase member.
696
1230
  _jsMemberName(expr) {
697
1231
  const f = expr.field;
@@ -733,7 +1267,12 @@ export const expressionGenMethods = {
733
1267
  ? `${this.genExpr(sel.expr)}.value`
734
1268
  : this.genExpr(sel.expr);
735
1269
  }
736
- return { cls: info.cls, recv };
1270
+ return {
1271
+ cls: info.cls,
1272
+ recv,
1273
+ byValue: !method._ptrRecv,
1274
+ recvType: viaPtr ? recvType.base : recvType,
1275
+ };
737
1276
  },
738
1277
 
739
1278
  // `x.M` used as a value: binds the receiver now, as Go does.
@@ -741,7 +1280,11 @@ export const expressionGenMethods = {
741
1280
  if (!expr._isMethodValue || expr._callee) return undefined;
742
1281
  const target = this._namedMethodTarget(expr);
743
1282
  if (!target) return undefined;
744
- return `((__r) => (...__a) => ${target.cls}.$${expr.field}(__r, ...__a))(${target.recv})`;
1283
+ // A value receiver (a named array) is copied when the value is evaluated.
1284
+ const recv = target.byValue
1285
+ ? this._cloneJs(target.recvType, target.recv)
1286
+ : target.recv;
1287
+ return `((__r) => (...__a) => ${target.cls}.$${expr.field}(__r, ...__a))(${recv})`;
745
1288
  },
746
1289
 
747
1290
  _genAddressOfExpr(operand) {
@@ -776,17 +1319,134 @@ export const expressionGenMethods = {
776
1319
  return `${target.cls}.$${sel.field}(${args ? `${target.recv}, ${args}` : target.recv})`;
777
1320
  },
778
1321
 
1322
+ // `F[int]` as a value is a function that passes F its zero-value factories.
1323
+ _genInstantiationExpr(expr) {
1324
+ const fn = this.genExpr(expr.expr);
1325
+ if (!expr._genericFnType || expr._callee) return fn;
1326
+ const { typeParams } = expr._genericFnType;
1327
+ const zeros = this._typeArgFactories(
1328
+ typeParams,
1329
+ expr._typeArgs,
1330
+ this._genericFnNeedsTypeInfo(expr._genericFnType),
1331
+ );
1332
+ return `((...a) => ${fn}(${zeros.join(", ")}, ...a))`;
1333
+ },
1334
+
779
1335
  _genCallArgs(expr) {
780
1336
  if (expr._multiForward) return `...${this.genExpr(expr.args[0])}`;
781
- return expr.args
782
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
783
- .join(", ");
1337
+ return this._genCallArgList(expr).join(", ");
1338
+ },
1339
+
1340
+ _genCallArgList(expr) {
1341
+ return expr.args.map((a, i) =>
1342
+ a._spread
1343
+ ? this._genSpreadArg(expr, a)
1344
+ : a._boxTypeParam
1345
+ ? this._genTypeParamBox(a)
1346
+ : this._callerCopiesArg(expr, i)
1347
+ ? this.genValueExpr(a)
1348
+ : this.genExpr(a),
1349
+ );
1350
+ },
1351
+
1352
+ // A type parameter operand of a Print-family fmt call (see
1353
+ // src/lower/fmt-formatters.js): a JS value does not know its Go type, so
1354
+ // the type argument boxes it as converting it to an `any` does.
1355
+ _genTypeParamBox(a) {
1356
+ // Boxed below: skip the boxing of a value entering an interface.
1357
+ const wasBoxing = a._tpBoxing;
1358
+ a._tpBoxing = true;
1359
+ const js = this.genExpr(a);
1360
+ a._tpBoxing = wasBoxing;
1361
+ const tpf = this._typeParamZeroFactory(a._type.name);
1362
+ return tpf ? `(${tpf}.b ?? ${tpf}.fb ?? ((__v) => __v))(${js})` : js;
1363
+ },
1364
+
1365
+ // A direct call of a `//gofront:native` function: host code from the
1366
+ // registry, or null (declined, natives off, spread arguments) to call the
1367
+ // Go body.
1368
+ _genNativeCall(expr) {
1369
+ if (!this.natives || !expr._native) return null;
1370
+ if (expr._multiForward || expr.args.some((a) => a._spread)) return null;
1371
+ const typeArgs = expr._typeArgs ?? expr.func._typeArgs ?? [];
1372
+ const js = jsNativeCall(expr._native, this._genCallArgList(expr), typeArgs);
1373
+ if (js !== null && NATIVE_HELPERS.has(expr._native))
1374
+ this.useHelper(NATIVE_HELPERS.get(expr._native));
1375
+ return js;
1376
+ },
1377
+
1378
+ // `f(xs...)`: a Go-compiled variadic callee receives the slice itself in a
1379
+ // marker (its prologue unwraps it); other callees get a JS spread.
1380
+ _genSpreadArg(call, arg) {
1381
+ const js = this.genExpr(arg);
1382
+ // A variadic function of a Go-source package (`fmt.Println`): a nil
1383
+ // slice spreads to no operands.
1384
+ if (call.func._stdName) return `...(${js} ?? [])`;
1385
+ return this._calleeIsGoVariadic(call) ? `{ __sp: ${js} }` : `...${js}`;
1386
+ },
1387
+
1388
+ _calleeIsGoVariadic(call) {
1389
+ const f = call.func;
1390
+ if (f.kind === "Ident") {
1391
+ if (this.variadicFns?.has(f.name)) return true;
1392
+ // A local func value (closure or parameter) compiled from Go source.
1393
+ const imported =
1394
+ this.checker?._externalNames?.has(f.name) ||
1395
+ BROWSER_GLOBALS.has(f.name) ||
1396
+ [...(this.jsImports?.values() ?? [])].some((names) =>
1397
+ names.includes(f.name),
1398
+ );
1399
+ return !imported && f._type?.kind === "func" && !!f._type.variadic;
1400
+ }
1401
+ if (f.kind !== "SelectorExpr" || f._isMethodExpr) return false;
1402
+ const recv = this._resolveRecvName(f.expr._type);
1403
+ return !!recv && !!this.variadicFns?.has(`${recv}.${f.field}`);
1404
+ },
1405
+
1406
+ // A callee copies its mutated array and struct parameters on entry, except
1407
+ // when the parameter is a type parameter (generics are erased in JS) or
1408
+ // part of a variadic rest array. For those the caller passes aggregate
1409
+ // (array and struct) arguments as copies.
1410
+ _callerCopiesArg(expr, i) {
1411
+ const argT = expr.args[i]._type;
1412
+ if (!this._aggregateBase(argT) && !this._typeParamClone(argT, ""))
1413
+ return false;
1414
+ let ft = expr.func?._type ?? expr.func?.expr?._type;
1415
+ if (ft?.kind === "generic") ft = ft.underlying;
1416
+ if (ft?.kind !== "func") return false;
1417
+ const params = ft.params ?? [];
1418
+ if (ft.variadic && i >= params.length - 1) return true;
1419
+ return params[i]?.kind === "typeParam";
784
1420
  },
785
1421
 
786
1422
  _genPointerSelectorField(expr, base) {
787
1423
  if (this._isStructPointerType(expr.expr._type)) {
788
- const sel = `${base}.${expr.field}`;
789
- if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
1424
+ // Strict mode: a field of a nil struct pointer panics as in Go.
1425
+ const recv =
1426
+ this.strict && !expr._isMethodValue && !expr._nonNil
1427
+ ? this._genNilChecked(base)
1428
+ : base;
1429
+ const sel = `${recv}${this._promotedPath(expr)}.${expr.field}`;
1430
+ if (expr._isMethodValue && !expr._callee) {
1431
+ // `p.M` with a value receiver binds a copy of `*p` now.
1432
+ if (expr._derefRecv) {
1433
+ const copy = this._cloneJs(
1434
+ expr.expr._type.base,
1435
+ this.strict ? this._genNilChecked(base) : base,
1436
+ );
1437
+ return `((__r) => __r.${expr.field}.bind(__r))(${copy})`;
1438
+ }
1439
+ // A pointer receiver may be nil: bind through the prototype so
1440
+ // `s.M` on a nil pointer does not read `.M` off null.
1441
+ const protoCls = this._ptrMethodProtoClass(
1442
+ expr.expr._type,
1443
+ expr.field,
1444
+ expr,
1445
+ );
1446
+ if (protoCls)
1447
+ return `${protoCls}.prototype.${expr.field}.bind(${base})`;
1448
+ return `${sel}.bind(${base})`;
1449
+ }
790
1450
  return sel;
791
1451
  }
792
1452
  const sel = `${base}.value.${expr.field}`;
@@ -801,19 +1461,55 @@ export const expressionGenMethods = {
801
1461
  const base = wrapField
802
1462
  ? `${this.genExpr(expr.expr)}.${wrapField}`
803
1463
  : this.genExpr(expr.expr);
804
- const idx = this.genExpr(expr.index);
805
- if (expr._mapValueType && !expr._lvalue)
806
- return this._genMapIndexExpr(expr, base, idx);
1464
+ let idx = this.genExpr(expr.index);
1465
+ if (expr._mapValueType) {
1466
+ if (!expr._lvalue) return this._genMapIndexExpr(expr, base, idx);
1467
+ return `${this._genMapWriteBase(base)}[${idx}]`;
1468
+ }
1469
+ if (this._is64BitInt(expr.index?._type)) idx = `Number(${idx})`;
1470
+ const checked = this._genStrictIndex(expr, base, idx);
1471
+ if (checked) return checked;
807
1472
  if (this._isStringExprType(exprType) && !expr._lvalue)
808
1473
  return `${base}.charCodeAt(${idx})`;
809
1474
  return `${base}[${idx}]`;
810
1475
  },
811
1476
 
1477
+ // `p` for a field access `p.F`, panicking with Go's message when nil.
1478
+ _genNilChecked(base) {
1479
+ this.useHelper("nilCall");
1480
+ return `__nilcall(${base})`;
1481
+ },
1482
+
1483
+ // The map of a write `m[k] = v`: strict mode panics on a nil map.
1484
+ _genMapWriteBase(base) {
1485
+ if (!this.strict) return base;
1486
+ this.useHelper("nilMap");
1487
+ return `__mw(${base})`;
1488
+ },
1489
+
1490
+ // Strict mode: `xs[i]` on a slice, array or string with Go's bounds check
1491
+ // (null when no check applies). Reads of slices and arrays use `__at`;
1492
+ // lvalues and string reads use `base[__ix(base, i)]`, binding `base` to
1493
+ // `__lv` first when evaluating it twice could repeat a call.
1494
+ _genStrictIndex(expr, base, idx) {
1495
+ if (!this.strict || expr._inBounds) return null;
1496
+ const t = expr.expr._type;
1497
+ const u = t?.kind === "named" ? t.underlying : t;
1498
+ const isStr = this._isStringExprType(t);
1499
+ if (!isStr && u?.kind !== "slice" && u?.kind !== "array") return null;
1500
+ this.useHelper("bounds");
1501
+ if (!expr._lvalue && !isStr) return `__at(${base}, ${idx})`;
1502
+ const once = this._hasCallExpr(expr.expr);
1503
+ const obj = once ? `(__lv = ${base})` : base;
1504
+ const at = `__ix(${once ? "__lv" : base}, ${idx})`;
1505
+ return expr._lvalue ? `${obj}[${at}]` : `${obj}.charCodeAt(${at})`;
1506
+ },
1507
+
812
1508
  _genMapIndexExpr(expr, base, idx) {
813
1509
  const zero = this.zeroValueForType(expr._mapValueType);
814
1510
  if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index))
815
- return `((__m, __k) => __m[__k] ?? ${zero})(${base}, ${idx})`;
816
- return `(${base}[${idx}] ?? ${zero})`;
1511
+ return `((__m, __k) => __m?.[__k] ?? ${zero})(${base}, ${idx})`;
1512
+ return `(${base}?.[${idx}] ?? ${zero})`;
817
1513
  },
818
1514
 
819
1515
  _isStringExprType(exprType) {
@@ -826,17 +1522,36 @@ export const expressionGenMethods = {
826
1522
 
827
1523
  _genSliceExpr(expr) {
828
1524
  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})`;
1525
+ const lo = expr.low
1526
+ ? this._is64BitInt(expr.low._type)
1527
+ ? `Number(${this.genExpr(expr.low)})`
1528
+ : this.genExpr(expr.low)
1529
+ : "0";
1530
+ const hi = expr.high
1531
+ ? this._is64BitInt(expr.high._type)
1532
+ ? `Number(${this.genExpr(expr.high)})`
1533
+ : this.genExpr(expr.high)
1534
+ : "undefined";
1535
+ if (this._isStringExprType(expr.expr._type))
1536
+ return expr.high ? `${base}.slice(${lo}, ${hi})` : `${base}.slice(${lo})`;
1537
+ // `b[:]`, `b[0:]` and `a[:]` have the operand's length and capacity:
1538
+ // the result is the operand itself. Other bounds make a view sharing
1539
+ // the backing store (`__slice`, which checks the bounds against cap).
1540
+ const zeroLow = !expr.low || Number(expr.low._const?.v) === 0;
1541
+ if (zeroLow && !expr.high && !expr.max) return base;
1542
+ this.useHelper("slice");
1543
+ // `x = x[lo:hi]` (see `_markSelfReslice`) copies a plain array when
1544
+ // the result stays within `len(x)`; typed arrays always share.
1545
+ if (expr._selfReslice && !isTypedArraySlice(expr.expr._type))
1546
+ return `__reslice(${base}, ${lo}, ${hi})`;
1547
+ const max = expr.max
1548
+ ? `, ${this._is64BitInt(expr.max._type) ? `Number(${this.genExpr(expr.max)})` : this.genExpr(expr.max)}`
1549
+ : "";
1550
+ return `__slice(${base}, ${lo}, ${hi}${max})`;
836
1551
  },
837
1552
 
838
1553
  _genFuncLit(expr) {
839
- const params = expr.params.map((p) => p.name).join(", ");
1554
+ const params = this._paramNames(expr.params).join(", ");
840
1555
  const asyncPrefix = expr.async ? "async " : "";
841
1556
  const saved = this.out;
842
1557
  const prevBoxed = this._boxedVars;
@@ -846,7 +1561,7 @@ export const expressionGenMethods = {
846
1561
  this._withFnCtx(expr.body, () =>
847
1562
  this.indented(() => {
848
1563
  this._emitParamCopies(expr.params);
849
- this._genBody(expr.body, expr);
1564
+ this._withNamedReturns(expr, () => this._genBody(expr.body, expr));
850
1565
  }),
851
1566
  );
852
1567
  const body = this.out.join("\n");
@@ -864,26 +1579,80 @@ export const expressionGenMethods = {
864
1579
  const check = this._typeCheckExpr(expr.type, val);
865
1580
  const wrap = (body) => (hoist ? `((__v) => ${body})(${src})` : body);
866
1581
  // 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;
1582
+ const out = this._assertedValueJs(expr.type, val);
870
1583
  if (!expr._commaOk) {
871
1584
  // plain assertion: panic if check fails (matches Go behavior)
872
1585
  if (check === "true") return src; // can't check at runtime — pass through
1586
+ this.useHelper("ifaceBox");
873
1587
  return wrap(
874
- `(${check} ? ${out} : (() => { throw new Error("interface conversion: type assertion failed"); })())`,
1588
+ `(${check} ? ${out} : (() => { throw ${this._typeAssertFailJs(expr, val)}; })())`,
875
1589
  );
876
1590
  }
877
- // comma-ok: emit [value-or-zero, runtimeTypeCheck]
1591
+ // comma-ok: emit [value-or-zero, runtimeTypeCheck]; an asserted struct
1592
+ // or array is a copy of the boxed value.
878
1593
  const zero = this.zeroValueForTypeNode(expr.type);
879
- return wrap(`(${check} ? [${out}, true] : [${zero}, false])`);
1594
+ const valT = this._typeFromNode(expr.type);
1595
+ const okOut = this._aggregateBase(valT) ? this._cloneJs(valT, out) : out;
1596
+ return wrap(`(${check} ? [${okOut}, true] : [${zero}, false])`);
1597
+ },
1598
+
1599
+ // The error a failed `x.(T)` throws: Go's `interface conversion: ...` text
1600
+ // naming the static interface type, the dynamic type and the target.
1601
+ _typeAssertFailJs(expr, val) {
1602
+ const target = expr._type;
1603
+ const u = this._underlyingOf(target);
1604
+ const methods =
1605
+ u?.kind === "interface" ? [...(u.methods?.keys() ?? [])] : [];
1606
+ const tName = this._assertTypeName(target) ?? goTypeName(expr.type);
1607
+ const src = this._assertTypeName(expr.expr?._type) ?? "interface {}";
1608
+ return `__tafail(${JSON.stringify(src)}, ${val}, ${JSON.stringify(tName)}, ${methods.length ? JSON.stringify(methods.sort()) : "null"})`;
1609
+ },
1610
+
1611
+ // Go's spelling of the static type in a failed assertion message; null for
1612
+ // `any` (`interface {}`).
1613
+ _assertTypeName(t) {
1614
+ if (!t || isAny(t)) return null;
1615
+ return t.name === "error" ? "error" : goTypeName(t);
1616
+ },
1617
+
1618
+ // `csv.ParseError` names the class of the bundled Go-source package type.
1619
+ _qualifiedStructName(name) {
1620
+ if (!name.includes(".") || this.structNames.has(name)) return name;
1621
+ const cls = this.checker?.types.get(name)?.name;
1622
+ return cls && this.structNames.has(cls) ? cls : name;
1623
+ },
1624
+
1625
+ _simdCheckExpr(name, val) {
1626
+ if (name === "simd.F32x4" || name === "F32x4")
1627
+ return `(Array.isArray(${val}) && ${val}.length === 4 && typeof ${val}[0] === "number")`;
1628
+ if (name === "simd.Mask32x4" || name === "Mask32x4")
1629
+ return `(Array.isArray(${val}) && ${val}.length === 4 && typeof ${val}[0] === "boolean")`;
1630
+ return null;
880
1631
  },
881
1632
 
882
- _typeNameCheckExpr(name, val) {
1633
+ _typeNameCheckExpr(typeName, val) {
1634
+ // A type parameter: the instantiated type's test travels with its factory.
1635
+ const factory = this._typeParamZeroFactory(typeName);
1636
+ if (factory) return `(${factory}?.is?.(${val}) ?? true)`;
1637
+ const name = this._qualifiedStructName(typeName);
1638
+ // int64 and uint64 share the BigInt: a value out of int64's range can
1639
+ // only be a uint64.
1640
+ if (name === "int64")
1641
+ return `(typeof ${val} === "bigint" && ${val} >= -9223372036854775808n && ${val} <= 9223372036854775807n)`;
1642
+ if (name === "uint64")
1643
+ return `(typeof ${val} === "bigint" && ${val} >= 0n)`;
1644
+ // Integer types share the JS number with the floats: only a whole
1645
+ // number is one, so a fraction or -0 in an `any` falls through to float64.
1646
+ if (INT_TYPE_NAMES.has(name))
1647
+ return `(Number.isSafeInteger(${val}) && !Object.is(${val}, -0))`;
883
1648
  if (TYPEOF_NUMBER_NAMES.has(name)) return `typeof ${val} === "number"`;
1649
+ if (name === "complex128" || name === "complex64")
1650
+ return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.re === "number" && typeof ${val}.im === "number")`;
884
1651
  if (name === "string") return `typeof ${val} === "string"`;
885
1652
  if (name === "bool") return `typeof ${val} === "boolean"`;
886
1653
  if (name === "nil") return `${val} === null`;
1654
+ const simd = this._simdCheckExpr(name, val);
1655
+ if (simd) return simd;
887
1656
  if (name === "error")
888
1657
  return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
889
1658
  if (this.structNames.has(name))
@@ -892,8 +1661,16 @@ export const expressionGenMethods = {
892
1661
  const namedInfo = this._namedBoxInfo(named);
893
1662
  if (namedInfo) return `(${val} instanceof ${namedInfo.cls})`;
894
1663
  // User-declared interfaces: check the dynamic value has every method.
895
- if (!name.includes(".") && named?.underlying?.kind === "interface")
896
- return this._ifaceMethodCheck(val, [...named.underlying.methods.keys()]);
1664
+ if (named?.underlying?.kind === "interface")
1665
+ return this._ifaceMethodCheck(
1666
+ val,
1667
+ [...named.underlying.methods.keys()],
1668
+ this._unwrapIsMulti(named.underlying.methods.get("Unwrap")),
1669
+ );
1670
+ if (this._isNamedComposite(named)) {
1671
+ this.useHelper("ifaceBox");
1672
+ return `__isty(${val}, ${JSON.stringify(name)}, "named")`;
1673
+ }
897
1674
  return "true"; // unknown type — can't check at runtime
898
1675
  },
899
1676
 
@@ -903,6 +1680,9 @@ export const expressionGenMethods = {
903
1680
  return this._ifaceMethodCheck(
904
1681
  val,
905
1682
  (typeNode.methods ?? []).map((m) => m.name),
1683
+ this._unwrapIsMulti(
1684
+ (typeNode.methods ?? []).find((m) => m.name === "Unwrap"),
1685
+ ),
906
1686
  );
907
1687
  if (typeNode.kind === "PointerType" && typeNode.base?.kind === "TypeName") {
908
1688
  const info = this._namedBoxInfo(
@@ -913,11 +1693,65 @@ export const expressionGenMethods = {
913
1693
  return null;
914
1694
  },
915
1695
 
916
- _ifaceMethodCheck(val, methodNames) {
917
- const checks = methodNames.map((m) => `typeof ${val}.${m} === "function"`);
1696
+ // `Unwrap() []error` (errors.Join) and `Unwrap() error` share a name: the
1697
+ // JS function of a multi-error `Unwrap` is tagged `__multi`. `method` is
1698
+ // a func type or an interface method AST; null when there is no `Unwrap`.
1699
+ _unwrapIsMulti(method) {
1700
+ if (!method) return null;
1701
+ const ret = method.returnType ?? method.returns?.[0];
1702
+ const u = ret?.kind === "named" ? ret.underlying : ret;
1703
+ return u?.kind === "slice" || u?.kind === "SliceType";
1704
+ },
1705
+
1706
+ _ifaceMethodCheck(val, methodNames, multiUnwrap = null) {
1707
+ const checks = methodNames.map((m) => {
1708
+ const base =
1709
+ m === "Unwrap" && multiUnwrap !== null
1710
+ ? `typeof ${val}.${m} === "function" && (${val}.${m}.__multi === true) === ${multiUnwrap}`
1711
+ : `typeof ${val}.${m} === "function"`;
1712
+ // A struct value's method set lacks the pointer-receiver methods.
1713
+ return `${base} && !(${val}.__v === true && ${val}.constructor.__pm?.includes(${JSON.stringify(m)}))`;
1714
+ });
918
1715
  return `(${[`${val} != null`, ...checks].join(" && ")})`;
919
1716
  },
920
1717
 
1718
+ // The value an assertion to `typeNode` yields: raw, unboxed from the
1719
+ // interface's box for a named non-struct type (known only at run time
1720
+ // for a type parameter).
1721
+ _assertedValueJs(typeNode, val) {
1722
+ if (this._isNamedBoxTypeNode(typeNode)) return `${val}.__gfUnbox()`;
1723
+ if (
1724
+ this._isStructPtrTypeNode(typeNode) ||
1725
+ (typeNode?.kind === "PointerType" && typeNode._assertType)
1726
+ ) {
1727
+ this.useHelper("ifaceBox");
1728
+ return `__unnil(${val})`;
1729
+ }
1730
+ if (typeNode?._assertType?.kind === "slice") {
1731
+ // A nil slice in an interface is a tagged empty array.
1732
+ this.useHelper("ifaceBox");
1733
+ return `__unnil(${val})`;
1734
+ }
1735
+ const factory =
1736
+ typeNode?.kind === "TypeName"
1737
+ ? this._typeParamZeroFactory(typeNode.name)
1738
+ : null;
1739
+ return factory ? `(${factory}?.u ? ${val}.__gfUnbox() : ${val})` : val;
1740
+ },
1741
+
1742
+ _isStructPtrTypeNode(typeNode) {
1743
+ const base = typeNode?.kind === "PointerType" ? typeNode.base : null;
1744
+ const name =
1745
+ base?.kind === "TypeName"
1746
+ ? base.name
1747
+ : base?.kind === "GenericTypeName"
1748
+ ? this._dequalify(base.name)
1749
+ : null;
1750
+ return (
1751
+ name !== null && this.structNames.has(this._qualifiedStructName(name))
1752
+ );
1753
+ },
1754
+
921
1755
  _isNamedBoxTypeNode(typeNode) {
922
1756
  if (typeNode?.kind === "TypeName")
923
1757
  return Boolean(
@@ -930,14 +1764,35 @@ export const expressionGenMethods = {
930
1764
  return false;
931
1765
  },
932
1766
 
1767
+ // Converting a struct to a different defined struct type builds a copy in
1768
+ // the target's class (so its methods exist); null when not applicable.
1769
+ _genStructClassConversion(targetName, srcType, inner) {
1770
+ if (!targetName || targetName.includes("[")) return null;
1771
+ const target = this.checker?.types.get(targetName);
1772
+ if (target?.kind !== "named" || target.underlying?.kind !== "struct")
1773
+ return null;
1774
+ if (srcType?.name === targetName) return null;
1775
+ const srcU = srcType?.kind === "named" ? srcType.underlying : srcType;
1776
+ if (srcU?.kind !== "struct") return null;
1777
+ const cls = this._dequalify(targetName);
1778
+ return `Object.assign(new ${cls}(), ${this._cloneJs(srcType, inner)})`;
1779
+ },
1780
+
933
1781
  _genTypeConversionCall(expr) {
934
1782
  const targetType = expr._conversionTargetType;
935
1783
  const arg = this.genExpr(expr.args[0]);
936
- const isRaw =
937
- targetType?.name === "json.RawMessage" ||
938
- targetType?.name === "encoding/json.RawMessage";
1784
+ const viaParam = this._genTypeParamConversion(targetType?.name, arg);
1785
+ if (targetType?.kind === "typeParam" && viaParam) return viaParam;
1786
+ const structConv = this._genStructClassConversion(
1787
+ targetType?.kind === "named" ? targetType.name : undefined,
1788
+ expr.args[0]?._type,
1789
+ arg,
1790
+ );
1791
+ if (structConv) return structConv;
939
1792
  const base =
940
1793
  targetType?.kind === "named" ? targetType.underlying : targetType;
1794
+ if (base?.kind === "map" && this._isNilLiteral(expr.args[0]))
1795
+ return this._nilMapZero();
941
1796
  if (
942
1797
  targetType?.kind === "named" &&
943
1798
  base?.kind === "basic" &&
@@ -954,19 +1809,29 @@ export const expressionGenMethods = {
954
1809
  (elem === "byte" || elem === "uint8") &&
955
1810
  this._isStringExprType(expr.args[0]?._type)
956
1811
  ) {
957
- const enc = `new TextEncoder().encode(${arg})`;
958
- return isRaw ? `((u) => { u.__raw = true; return u; })(${enc})` : enc;
959
- }
960
- if (isRaw) {
961
- return `((u) => { u.__raw = true; return u; })(${arg})`;
1812
+ this.useHelper("u8enc");
1813
+ return `__u8enc(${arg})`;
962
1814
  }
963
1815
  }
964
1816
  return arg;
965
1817
  },
966
1818
 
1819
+ // `json.Marshal(v)` / `enc.Encode(v)` call the generated codec entry
1820
+ // (see src/lower/json-codecs.js); a stream method takes its receiver first.
1821
+ _jsonEntryCall(expr) {
1822
+ const sel = expr.func;
1823
+ const func = { kind: "Ident", name: sel._genEntry, _type: sel._type };
1824
+ const args = sel._jsonStream ? [sel.expr, ...expr.args] : expr.args;
1825
+ return { ...expr, func, args };
1826
+ },
1827
+
967
1828
  genCall(expr) {
1829
+ if (expr.func._genEntry) return this.genCall(this._jsonEntryCall(expr));
968
1830
  if (expr._isTypeConversion) return this._genTypeConversionCall(expr);
969
1831
 
1832
+ const native = this._genNativeCall(expr);
1833
+ if (native !== null) return native;
1834
+
970
1835
  const funcExpr =
971
1836
  expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
972
1837
  if (funcExpr.kind === "Ident") {
@@ -974,19 +1839,113 @@ export const expressionGenMethods = {
974
1839
  if (builtin !== undefined) return builtin;
975
1840
  }
976
1841
 
1842
+ const viaBox = this._genTypeParamMethodCall(expr);
1843
+ if (viaBox !== null) return viaBox;
1844
+
977
1845
  if (expr.func.kind === "SelectorExpr") {
978
1846
  const result = this._genSelectorCall(expr);
979
1847
  if (result !== undefined) return result;
980
1848
  }
981
1849
 
1850
+ const converted = this._convertedPointerMethodCall(expr);
1851
+ if (converted) return converted;
1852
+
1853
+ const nilSafe = this._nilSafeMethodClass(expr.func);
1854
+ if (nilSafe) {
1855
+ // The method tests its receiver for nil: call it through the
1856
+ // prototype so a nil `this` reaches it instead of a TypeError.
1857
+ const recv = this.genExpr(expr.func.expr);
1858
+ const rest = this._genCallArgs(expr);
1859
+ return `${nilSafe}.prototype.${expr.func.field}.call(${rest ? `${recv}, ${rest}` : recv})`;
1860
+ }
1861
+
982
1862
  expr.func._callee = true;
983
1863
  const rawFn = this.genExpr(expr.func);
984
1864
  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})`;
1865
+ const args = this._genCallArgs(expr);
1866
+ const zeros = this._genericCallZeros(expr);
1867
+ if (!zeros) return `${fn}(${args})`;
1868
+ return `${fn}(${args ? `${zeros}, ${args}` : zeros})`;
1869
+ },
1870
+
1871
+ // `x.M(...)` on a type-parameter value: a raw value of a named non-struct
1872
+ // type argument is boxed (by the type's factory) to reach its methods.
1873
+ _genTypeParamMethodCall(expr) {
1874
+ if (!this._isTypeParamMethodCall(expr)) return null;
1875
+ const factory = this._typeParamZeroFactory(expr.func.expr._type.name);
1876
+ if (!factory) return null;
1877
+ this.useHelper("generics");
1878
+ const recv = `__tbox(${factory}, ${this.genExpr(expr.func.expr)})`;
1879
+ const args = this._genCallArgs(expr);
1880
+ return `${recv}.${expr.func.field}(${args})`;
1881
+ },
1882
+
1883
+ // `(*S)(p).M()` where `p` points at another struct type: the JS object keeps
1884
+ // its own class, so call S's prototype method on it.
1885
+ _convertedPointerMethodCall(expr) {
1886
+ const sel = expr.func;
1887
+ if (sel?.kind !== "SelectorExpr" || sel._isMethodExpr) return null;
1888
+ let recv = sel.expr;
1889
+ while (recv?.kind === "ParenExpr") recv = recv.expr;
1890
+ if (recv?._isTypeConversion !== true) return null;
1891
+ const target = recv._conversionTargetType;
1892
+ const from = recv.args?.[0]?._type;
1893
+ if (target?.kind !== "pointer" || target.base?.kind !== "named")
1894
+ return null;
1895
+ if (from?.kind !== "pointer" || from.base === target.base) return null;
1896
+ if (target.base.underlying?.kind !== "struct") return null;
1897
+ const cls = target.base.name;
1898
+ if (!target.base.methods?.has(sel.field)) return null;
1899
+ const rest = this._genCallArgs(expr);
1900
+ const obj = this.genExpr(recv.args[0]);
1901
+ return `${cls}.prototype.${sel.field}.call(${rest ? `${obj}, ${rest}` : obj})`;
1902
+ },
1903
+
1904
+ // The class whose prototype holds pointer-receiver method `field`, for a
1905
+ // pointer to a struct type that declares it directly (not promoted), so a
1906
+ // method value or expression can reach it with a nil receiver; else null.
1907
+ _ptrMethodProtoClass(ptr, field, sel) {
1908
+ if (ptr?.kind !== "pointer" || ptr.base?.kind !== "named") return null;
1909
+ if (ptr.base.underlying?.kind !== "struct") return null;
1910
+ if (sel && this._promotedPath(sel)) return null;
1911
+ if (!ptr.base.methods?.has(field)) return null;
1912
+ return ptr.base.name;
1913
+ },
1914
+
1915
+ // The class of a pointer-receiver struct method that accepts a nil receiver
1916
+ // when `func` selects it on a pointer of exactly that struct, else null.
1917
+ _nilSafeMethodClass(func) {
1918
+ if (func?.kind !== "SelectorExpr" || func._isMethodExpr) return null;
1919
+ const t = func.expr?._type;
1920
+ if (t?.kind !== "pointer" || t.base?.kind !== "named") return null;
1921
+ const cls = t.base.name;
1922
+ if (t.base.typeArgs?.length) return null;
1923
+ if (this.nilSafeMethods?.has(`${cls}.${func.field}`)) return cls;
1924
+ // A Go-source stdlib type (`Buffer$$bytes`) is declared outside this
1925
+ // package: its method decl tells whether it tests the receiver for nil.
1926
+ const decl = cls.includes("$$")
1927
+ ? t.base.methods?.get(func.field)?._methodDecl
1928
+ : null;
1929
+ return decl?.recvPointer && comparesToNil(decl.body, decl.recvName)
1930
+ ? cls
1931
+ : null;
1932
+ },
1933
+
1934
+ // The leading zero-value factories of a call to a generic function
1935
+ // (`F(x)` with inferred, `F[int](x)` with explicit type arguments), or null.
1936
+ _genericCallZeros(expr) {
1937
+ const site = expr._genericFnType
1938
+ ? expr
1939
+ : expr.func.kind === "InstantiationExpr" && expr.func._genericFnType
1940
+ ? expr.func
1941
+ : null;
1942
+ if (!site) return null;
1943
+ const { typeParams } = site._genericFnType;
1944
+ return this._typeArgFactories(
1945
+ typeParams,
1946
+ site._typeArgs,
1947
+ this._genericFnNeedsTypeInfo(site._genericFnType),
1948
+ ).join(", ");
990
1949
  },
991
1950
 
992
1951
  _isErrorInterfaceType(t) {
@@ -1009,31 +1968,17 @@ export const expressionGenMethods = {
1009
1968
  },
1010
1969
 
1011
1970
  _genReceiverTypeCall(recvName, expr) {
1012
- if (recvName === "strings.Builder" || recvName === "bytes.Buffer")
1013
- return this._genBuilderCall(recvName, expr.func.field, expr);
1014
- if (recvName === "regexp.Regexp")
1015
- return this._genRegexpMethodCall(expr.func.field, expr);
1016
- if (recvName === "time.Time")
1017
- return this._genTimeMethodCall(expr.func.field, expr);
1018
- if (recvName === "time.Duration")
1019
- return this._genDurationMethodCall(expr.func.field, expr);
1020
- if (recvName === "testing.T")
1021
- return this._genTestingMethodCall(expr.func.field, expr);
1022
- if (recvName === "strings.Replacer") {
1023
- const recv = this.genExpr(expr.func.expr);
1024
- const arg = this.genExpr(expr.args[0]);
1025
- return `(${recv} ? ${recv}.Replace(${arg}) : ${arg})`;
1026
- }
1027
1971
  if (recvName?.startsWith("shared.") && expr.func.field === "Subarray") {
1028
1972
  const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1029
1973
  return `${this.genExpr(expr.func.expr)}.subarray(${args})`;
1030
1974
  }
1031
- if (recvName === "simd.F32x4" || recvName === "F32x4") {
1032
- return this._genSimdReceiverCall(expr);
1975
+ if (/^(simd\.)?(F32x4|Mask32x4)$/.test(recvName ?? "")) {
1976
+ return this._genSimdReceiverCall(expr, recvName.endsWith("Mask32x4"));
1033
1977
  }
1034
1978
  if (recvName === "sync.Mutex" || recvName === "sync.RWMutex") {
1035
1979
  if (expr.func.field === "TryLock" || expr.func.field === "TryRLock")
1036
1980
  return "true";
1981
+ if (expr.func.field === "RLocker") return "({ Lock() {}, Unlock() {} })";
1037
1982
  return "(void 0)";
1038
1983
  }
1039
1984
  if (recvName === "sync.Once" && expr.func.field === "Do") {
@@ -1051,58 +1996,18 @@ export const expressionGenMethods = {
1051
1996
  }
1052
1997
  if (expr.func.field === "Wait") return "(void 0)";
1053
1998
  }
1054
- if (
1055
- recvName === "binary.LittleEndian" ||
1056
- recvName === "binary.BigEndian" ||
1057
- recvName === "encoding/binary.LittleEndian" ||
1058
- recvName === "encoding/binary.BigEndian" ||
1059
- recvName === "binary.ByteOrder" ||
1060
- recvName === "encoding/binary.ByteOrder"
1061
- ) {
1062
- this.useHelper("binary");
1063
- const recv = this.genExpr(expr.func.expr);
1064
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1065
- return `${recv}.${expr.func.field}(${args})`;
1066
- }
1067
- if (
1068
- recvName === "base64.Encoding" ||
1069
- recvName === "encoding/base64.Encoding"
1070
- ) {
1071
- this.useHelper("base64");
1072
- const recv = this.genExpr(expr.func.expr);
1073
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1074
- return `${recv}.${expr.func.field}(${args})`;
1075
- }
1076
1999
  return undefined;
1077
2000
  },
1078
2001
 
1079
- _genSimdReceiverCall(expr) {
2002
+ _genSimdReceiverCall(expr, isMask = false) {
1080
2003
  this.useHelper("simd");
1081
2004
  const method = expr.func.field;
1082
2005
  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;
2006
+ const args = expr.args.map((a) => this.genExpr(a));
2007
+ if (method === "Lane") return `${recv}[${args[0]}]`;
2008
+ if (method === "Splat" && !isMask) return `__simd_splat(${args[0]})`;
2009
+ const helper = (isMask ? SIMD_MASK_HELPERS : SIMD_VECTOR_HELPERS)[method];
2010
+ return helper ? `${helper}(${[recv, ...args].join(", ")})` : undefined;
1106
2011
  },
1107
2012
 
1108
2013
  _genSelectorCall(expr) {
@@ -1116,12 +2021,44 @@ export const expressionGenMethods = {
1116
2021
  const ns = expr.func.expr.name;
1117
2022
  const fn = expr.func.field;
1118
2023
  const result = this._genStdlibCall(ns, fn, expr);
1119
- if (result !== undefined) return result;
2024
+ if (result !== undefined) {
2025
+ if (this._is64BitInt(expr._type)) {
2026
+ return `((__r) => typeof __r === "number" ? BigInt(__r) : __r)(${result})`;
2027
+ }
2028
+ if (
2029
+ expr._type?.kind === "tuple" &&
2030
+ expr._type.types.some((t) => this._is64BitInt(t))
2031
+ ) {
2032
+ const casts = expr._type.types.map((t, i) =>
2033
+ this._is64BitInt(t)
2034
+ ? `typeof __r[${i}] === "number" ? BigInt(__r[${i}]) : __r[${i}]`
2035
+ : `__r[${i}]`,
2036
+ );
2037
+ return `((__r) => [${casts.join(", ")}])(${result})`;
2038
+ }
2039
+ return result;
2040
+ }
1120
2041
  }
1121
2042
  const recvType = expr.func.expr._type;
1122
2043
  const recvName = this._resolveRecvName(recvType);
1123
2044
  const typed = this._genReceiverTypeCall(recvName, expr);
1124
- if (typed !== undefined) return typed;
2045
+ if (typed !== undefined) {
2046
+ if (this._is64BitInt(expr._type)) {
2047
+ return `((__r) => typeof __r === "number" ? BigInt(__r) : __r)(${typed})`;
2048
+ }
2049
+ if (
2050
+ expr._type?.kind === "tuple" &&
2051
+ expr._type.types.some((t) => this._is64BitInt(t))
2052
+ ) {
2053
+ const casts = expr._type.types.map((t, i) =>
2054
+ this._is64BitInt(t)
2055
+ ? `typeof __r[${i}] === "number" ? BigInt(__r[${i}]) : __r[${i}]`
2056
+ : `__r[${i}]`,
2057
+ );
2058
+ return `((__r) => [${casts.join(", ")}])(${typed})`;
2059
+ }
2060
+ return typed;
2061
+ }
1125
2062
  const namedCall = this._genNamedMethodCall(expr);
1126
2063
  if (namedCall !== undefined) return namedCall;
1127
2064
  if (this.strict && this._isInterfaceType(recvType)) {
@@ -1137,6 +2074,16 @@ export const expressionGenMethods = {
1137
2074
  return undefined;
1138
2075
  },
1139
2076
 
2077
+ // `len`/`cap` of an array or array pointer is a constant, but an operand
2078
+ // containing a call or receive is still evaluated (and a nil pointer is
2079
+ // not dereferenced).
2080
+ _genConstLen(expr) {
2081
+ const arg = expr.args[0];
2082
+ if (arg && hasCallOrReceive(arg))
2083
+ return `(${this.genExpr(arg)}, ${expr._constLen})`;
2084
+ return String(expr._constLen);
2085
+ },
2086
+
1140
2087
  _isInterfaceType(t) {
1141
2088
  if (!t) return false;
1142
2089
  if (t.kind === "interface") return true;
@@ -1144,13 +2091,13 @@ export const expressionGenMethods = {
1144
2091
  },
1145
2092
 
1146
2093
  _genBuiltinLen(expr) {
1147
- if (expr._constLen != null) return String(expr._constLen);
2094
+ if (expr._constLen != null) return this._genConstLen(expr);
1148
2095
  const arg = expr.args[0];
1149
2096
  const t = arg?._type;
1150
2097
  const wrapField = this._namedWrapperField(t, arg);
1151
2098
  if (wrapField) return `${this.genExpr(arg)}.${wrapField}.length`;
1152
2099
  const js = this.genExpr(arg);
1153
- if (t?.kind === "map") return `Object.keys(${js}).length`;
2100
+ if (t?.kind === "map") return `Object.keys(${js} ?? {}).length`;
1154
2101
  this.useHelper("len");
1155
2102
  return `__len(${js})`;
1156
2103
  },
@@ -1164,7 +2111,12 @@ export const expressionGenMethods = {
1164
2111
  (t?.kind === "named" && t.underlying?.kind === "map")
1165
2112
  )
1166
2113
  return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
1167
- return `(${js}).length = 0`;
2114
+ // A slice: every element goes back to its zero value (the length stays).
2115
+ const st = t?.kind === "named" ? t.underlying : t;
2116
+ const zero = this.zeroValueForType(st?.elem);
2117
+ if (/^(new |\{|\[|Array|__nm\()|\$z_/.test(zero))
2118
+ return `((__s) => { if (__s) for (let __i = 0; __i < __s.length; __i++) __s[__i] = ${zero}; })(${js})`;
2119
+ return `(${js})?.fill(${zero})`;
1168
2120
  },
1169
2121
 
1170
2122
  _genBuiltinIdent(name, expr) {
@@ -1172,6 +2124,15 @@ export const expressionGenMethods = {
1172
2124
  return gen ? gen(this, expr) : undefined;
1173
2125
  },
1174
2126
 
2127
+ // The source operand of `append(b, x...)` / `copy(b, x)`: a string
2128
+ // contributes its UTF-8 bytes.
2129
+ _genSliceSource(node) {
2130
+ const js = this.genExpr(node);
2131
+ if (!this._isStringExprType(node._type)) return js;
2132
+ this.useHelper("u8enc");
2133
+ return `__u8enc(${js})`;
2134
+ },
2135
+
1175
2136
  genAppend(expr) {
1176
2137
  const retType = expr._type;
1177
2138
  const wrapField = this._namedWrapperField(retType, expr.args[0]);
@@ -1181,16 +2142,36 @@ export const expressionGenMethods = {
1181
2142
  const elems = expr.args
1182
2143
  .slice(1)
1183
2144
  .map((a) =>
1184
- a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a),
2145
+ a._spread
2146
+ ? `...(${this._genSliceSource(a)} ?? [])`
2147
+ : this.genValueExpr(a),
1185
2148
  );
1186
2149
  if (elems.length === 0) return this.genExpr(expr.args[0]);
1187
2150
  this.useHelper("append");
1188
2151
  return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
1189
2152
  }
1190
2153
  const slice = this.genExpr(expr.args[0]);
1191
- const elems = expr.args
1192
- .slice(1)
1193
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a)));
2154
+ // Spread elements of array type are values: append copies them.
2155
+ const st = expr._type ?? expr.args[0]._type;
2156
+ const elemT = (st?.kind === "named" ? st.underlying : st)?.elem;
2157
+ const spreadOf = (a) =>
2158
+ this._aggregateBase(elemT)
2159
+ ? `...(${this.genExpr(a)} ?? []).map((__e) => ${this._cloneJs(elemT, "__e")})`
2160
+ : `...(${this._genSliceSource(a)} ?? [])`;
2161
+ const is64Elem = this._is64BitInt(elemT);
2162
+ const elems = expr.args.slice(1).map((a) => {
2163
+ if (a._spread) return spreadOf(a);
2164
+ const val = this.genValueExpr(a);
2165
+ if (
2166
+ is64Elem &&
2167
+ a._type?.kind === "untyped" &&
2168
+ !val.endsWith("n") &&
2169
+ /^-?\d+$/.test(val)
2170
+ ) {
2171
+ return `${val}n`;
2172
+ }
2173
+ return val;
2174
+ });
1194
2175
  if (elems.length === 0) return slice;
1195
2176
  this.useHelper("append");
1196
2177
  const sliceType = expr._type ?? expr.args[0]._type;
@@ -1199,22 +2180,55 @@ export const expressionGenMethods = {
1199
2180
  (sliceType.kind === "named" ? sliceType.underlying : sliceType).elem,
1200
2181
  )
1201
2182
  : null;
1202
- if (typedCtor)
1203
- return `__append((${slice}) ?? new ${typedCtor}(0), ${elems.join(", ")})`;
2183
+ if (typedCtor) {
2184
+ const single = expr.args.length === 2 && expr.args[1]._spread;
2185
+ // One source slice or string: append it as it is, not spread.
2186
+ if (single)
2187
+ return `__appendv((${slice}) ?? __tn(${typedCtor}), ${this._genSliceSource(expr.args[1])} ?? [])`;
2188
+ return `__append((${slice}) ?? __tn(${typedCtor}), ${elems.join(", ")})`;
2189
+ }
2190
+ // A json codec grows a slice it has just made and no one else holds:
2191
+ // pushing in place is O(1), where __append copies the whole slice.
2192
+ if (
2193
+ this._inJsonCodecFile() &&
2194
+ expr.args.length === 2 &&
2195
+ expr.args[0].kind === "Ident" &&
2196
+ !expr.args[1]._spread
2197
+ ) {
2198
+ this.useHelper("pushs");
2199
+ return `__pushs(${slice}, ${elems[0]})`;
2200
+ }
1204
2201
  return `__append(${slice}, ${elems.join(", ")})`;
1205
2202
  },
1206
2203
 
2204
+ // A `make` length or capacity expression wrapped in its negative-value
2205
+ // check (a literal needs none).
2206
+ _guardMakeSize(code, guard) {
2207
+ if (/^\d+$/.test(code)) return code;
2208
+ this.useHelper("mklen");
2209
+ return `${guard}(${code})`;
2210
+ },
2211
+
1207
2212
  genMake(expr) {
1208
2213
  // make([]T, n) or make([]T, n, cap) → new Array(n).fill(zero)
1209
2214
  // make(map[K]V) → {}
1210
2215
  const typeArg = expr.args[0];
1211
2216
  const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
1212
- const resolvedKind = typeArg._type?.kind;
2217
+ // A defined type (`type S []int`) makes like its underlying type.
2218
+ const madeType =
2219
+ typeArg._coreType ??
2220
+ (typeArg._type?.kind === "named"
2221
+ ? typeArg._type.underlying
2222
+ : typeArg._type);
2223
+ const resolvedKind = madeType?.kind;
1213
2224
  if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
1214
- const n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
2225
+ let n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
2226
+ if (expr.args[1] && this._is64BitInt(expr.args[1]._type))
2227
+ n = `Number(${n})`;
2228
+ // A negative length or capacity panics with Go's message.
2229
+ n = this._guardMakeSize(n, "__mkn");
1215
2230
  const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
1216
- const elemResolved =
1217
- typeArg._type?.kind === "slice" ? typeArg._type.elem : null;
2231
+ const elemResolved = madeType?.kind === "slice" ? madeType.elem : null;
1218
2232
  const typedCtor =
1219
2233
  typedArrayConstructorForElem(elemResolved) ??
1220
2234
  (elemNode?.name
@@ -1223,8 +2237,19 @@ export const expressionGenMethods = {
1223
2237
  name: elemNode.name,
1224
2238
  })
1225
2239
  : null);
2240
+ // make([]T, n, cap) allocates cap elements and returns a view of
2241
+ // the first n (`__mkslice`), so append within cap shares them.
2242
+ let capArg = expr.args[2] ? this.genExpr(expr.args[2]) : null;
2243
+ if (capArg && expr.args[2] && this._is64BitInt(expr.args[2]._type))
2244
+ capArg = `Number(${capArg})`;
2245
+ if (capArg) {
2246
+ this.useHelper("slice");
2247
+ capArg = this._guardMakeSize(capArg, "__mkc");
2248
+ }
2249
+ const withCap = (alloc) =>
2250
+ capArg ? `__mkslice(${n}, ${alloc(capArg)})` : alloc(n);
1226
2251
  if (typedCtor) {
1227
- return `new ${typedCtor}(${n})`;
2252
+ return withCap((k) => `new ${typedCtor}(${k})`);
1228
2253
  }
1229
2254
  // Use the proper zero value for the element type (e.g. new Point() not 0)
1230
2255
  let zero = "null";
@@ -1234,29 +2259,23 @@ export const expressionGenMethods = {
1234
2259
  zero = this.zeroValueForTypeNode(elemNode);
1235
2260
  }
1236
2261
  // 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})`;
2262
+ if (zero.startsWith("new ") || zero.includes("$z_")) {
2263
+ return withCap((k) => `Array.from({length: ${k}}, () => ${zero})`);
1239
2264
  }
1240
- return `new Array(${n}).fill(${zero})`;
2265
+ return withCap((k) => `new Array(${k}).fill(${zero})`);
1241
2266
  }
1242
- // map or fallback
2267
+ // map or fallback. The json codecs' maps hold decoded keys, among them
2268
+ // "__proto__": without a prototype that is an ordinary key.
2269
+ if (
2270
+ this._inJsonCodecFile() &&
2271
+ (resolvedKind === "map" || typeNode.kind === "MapType")
2272
+ )
2273
+ return "Object.create(null)";
1243
2274
  return "{}";
1244
2275
  },
1245
2276
 
1246
- // Render struct elements as a JS field list string.
1247
- // Handles positional (_positionalField), embedded-spread (_isEmbedInit), and keyed (KeyValueExpr).
1248
- _genStructFields(elems) {
1249
- return elems
1250
- .map((e) => {
1251
- if (e._positionalField) {
1252
- return `${e._positionalField}: ${this.genExpr(e)}`;
1253
- }
1254
- if (e.kind !== "KeyValueExpr") return null;
1255
- if (e._isEmbedInit) return `...${this.genExpr(e.value)}`;
1256
- return `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`;
1257
- })
1258
- .filter((s) => s !== null)
1259
- .join(", ");
2277
+ _inJsonCodecFile() {
2278
+ return this._jsonCodecFileIdx === (this._currentSrcFileIdx ?? 0);
1260
2279
  },
1261
2280
 
1262
2281
  _isSliceOrArrayType(t) {
@@ -1267,22 +2286,42 @@ export const expressionGenMethods = {
1267
2286
  return t?.kind === "MapType";
1268
2287
  },
1269
2288
 
2289
+ // The one place a map literal key becomes a JS property name: a plain
2290
+ // string literal stays as is, any other key is computed so that the key
2291
+ // encoding of `genExpr` (`__ik`, struct keys) applies.
2292
+ _genMapLitKey(key) {
2293
+ return key.litKind === "STRING"
2294
+ ? JSON.stringify(key.value)
2295
+ : `[${this.genExpr(key)}]`;
2296
+ },
2297
+
1270
2298
  _genMapLitEntries(elems) {
1271
2299
  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
- })
2300
+ .map((e) =>
2301
+ e.kind === "KeyValueExpr"
2302
+ ? `${this._genMapLitKey(e.key)}: ${this.genValueExpr(e.value)}`
2303
+ : this.genValueExpr(e),
2304
+ )
1282
2305
  .join(", ");
1283
2306
  },
1284
2307
 
2308
+ // A literal whose (possibly elided or named) type is a map type.
2309
+ _isMapLit(expr) {
2310
+ let t = expr._type;
2311
+ for (let i = 0; t?.kind === "named" && i < 32; i++) t = t.underlying;
2312
+ return t?.kind === "map";
2313
+ },
2314
+
1285
2315
  _genFallbackCompositeLit(expr) {
2316
+ if (this._isMapLit(expr))
2317
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
2318
+ const at = this._litArrayType(expr);
2319
+ if (at) {
2320
+ // A named array type without methods (`type V [3]int; V{1}`).
2321
+ const elems = this._genArrayLitElems(at, expr);
2322
+ const ctor = typedArrayConstructorForElem(at.elem);
2323
+ return ctor ? `new ${ctor}([${elems}])` : `[${elems}]`;
2324
+ }
1286
2325
  if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
1287
2326
  const fields = expr.elems
1288
2327
  .map(
@@ -1323,7 +2362,7 @@ export const expressionGenMethods = {
1323
2362
  (ce) => (ce._positionalField ?? ce.key?.name) === ef,
1324
2363
  );
1325
2364
  values[idx] = match
1326
- ? this.genValueExpr(match.value ?? match)
2365
+ ? this.genValueExpr(match.kind === "KeyValueExpr" ? match.value : match)
1327
2366
  : fields[idx].zero;
1328
2367
  } else {
1329
2368
  const valExpr = this.genExpr(e.value);
@@ -1364,6 +2403,8 @@ export const expressionGenMethods = {
1364
2403
 
1365
2404
  _genPositionalStructLit(typeName, expr) {
1366
2405
  const fields = this.getStructFields(typeName);
2406
+ const generic = this._genGenericStructLit(fields, expr);
2407
+ if (generic) return generic;
1367
2408
  if (!fields || fields.length === 0 || expr.elems.length === 0) {
1368
2409
  return `new ${typeName}()`;
1369
2410
  }
@@ -1381,10 +2422,95 @@ export const expressionGenMethods = {
1381
2422
  return inst;
1382
2423
  },
1383
2424
 
1384
- _genSliceOrArrayLit(t, expr) {
1385
- const elems = expr.elems
2425
+ // `Box[int]{…}`: every field is passed (unset ones as undefined, so the
2426
+ // constructor starts them at their zero value) followed by the factories.
2427
+ _genGenericStructLit(fields, expr) {
2428
+ const t = expr._type;
2429
+ if (t?.kind !== "named" || !t.typeArgs?.length) return null;
2430
+ const values = fields.map(() => undefined);
2431
+ if (expr.elems.length > 0) {
2432
+ const hasPositional = expr.elems.some(
2433
+ (e) => e._positionalField || e.kind !== "KeyValueExpr",
2434
+ );
2435
+ if (hasPositional) this._fillPositionalStructElems(fields, expr, values);
2436
+ else this._fillKeyedStructElems(fields, expr, values);
2437
+ }
2438
+ return this._genericStructNew(t.name, t.typeArgs, values);
2439
+ },
2440
+
2441
+ // The array type of a literal (through named types), or null.
2442
+ _litArrayType(expr) {
2443
+ let t = expr._type;
2444
+ while (t?.kind === "named") t = t.underlying;
2445
+ return t?.kind === "array" && typeof t.size === "number" ? t : null;
2446
+ },
2447
+
2448
+ // Elements of a literal of array type `at` (or `{ size, elem }` for a
2449
+ // slice), placed at their (keyed) indices, with every other slot the
2450
+ // element zero value: `[3]int{1}` is `[1, 0, 0]` and `[5]int{2: 7}` is
2451
+ // `[0, 0, 7, 0, 0]`, as in Go. Keys out of ascending order would
2452
+ // evaluate the elements out of source order, so the values are then
2453
+ // computed first and spread into place.
2454
+ _genArrayLitElems(at, expr) {
2455
+ const values = new Array(at.size).fill(null);
2456
+ const indices = compositeLitIndices(expr.elems);
2457
+ const src = expr.elems.map((e) =>
2458
+ this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e),
2459
+ );
2460
+ const ascending = indices.every((v, i) => i === 0 || v > indices[i - 1]);
2461
+ indices.forEach((idx, i) => {
2462
+ values[idx] = ascending ? src[i] : `v$[${i}]`;
2463
+ });
2464
+ for (let i = 0; i < values.length; i++)
2465
+ values[i] ??= this.zeroValueForType(at.elem);
2466
+ if (ascending) return values.join(", ");
2467
+ return `...((v$) => [${values.join(", ")}])([${src.join(", ")}])`;
2468
+ },
2469
+
2470
+ // `{ size, elem }` for a slice or array literal (through named types).
2471
+ _litIndexedShape(expr) {
2472
+ const at = this._litArrayType(expr);
2473
+ if (at) return at;
2474
+ let t = expr._type;
2475
+ while (t?.kind === "named") t = t.underlying;
2476
+ if (t?.kind !== "slice") return null;
2477
+ // A loop, not a spread: huge literals would overflow the call stack.
2478
+ let max = -1;
2479
+ for (const i of compositeLitIndices(expr.elems)) if (i > max) max = i;
2480
+ const size = max + 1;
2481
+ return { size, elem: t.elem };
2482
+ },
2483
+
2484
+ // A large array literal that leaves most slots zero (`[100000]byte{}`,
2485
+ // `[65536]int{1: 2}`) starts from the zero array, as `var a [N]T` does,
2486
+ // and assigns only the given elements instead of spelling out every
2487
+ // slot. Small or mostly-filled literals stay inline (null).
2488
+ _genSparseArrayLit(expr) {
2489
+ const at = this._litArrayType(expr);
2490
+ if (!at || at.size <= SPARSE_ARRAY_MIN) return null;
2491
+ if (expr.elems.length * 2 >= at.size) return null;
2492
+ const zero = this.zeroValueForType(at);
2493
+ if (expr.elems.length === 0) return zero;
2494
+ const indices = compositeLitIndices(expr.elems);
2495
+ const sets = expr.elems.map((e, i) => {
2496
+ const v = this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e);
2497
+ return `a$[${indices[i]}] = (${v})`;
2498
+ });
2499
+ return `((a$) => (${sets.join(", ")}, a$))(${zero})`;
2500
+ },
2501
+
2502
+ _genLitElems(expr) {
2503
+ const at = this._litIndexedShape(expr);
2504
+ if (at) return this._genArrayLitElems(at, expr);
2505
+ return expr.elems
1386
2506
  .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
1387
2507
  .join(", ");
2508
+ },
2509
+
2510
+ _genSliceOrArrayLit(t, expr) {
2511
+ const sparse = this._genSparseArrayLit(expr);
2512
+ if (sparse) return sparse;
2513
+ const elems = this._genLitElems(expr);
1388
2514
  const elemNode =
1389
2515
  t.kind === "SliceType" || t.kind === "ArrayType" ? t.elem : null;
1390
2516
  const elemResolved =
@@ -1409,10 +2535,8 @@ export const expressionGenMethods = {
1409
2535
  const t = expr.typeExpr;
1410
2536
  if (t === null) return this._genImplicitCompositeLit(expr);
1411
2537
  const typeName = this.getTypeName(t);
1412
- if (typeName === "bytes.Buffer" || typeName === "Buffer")
1413
- return "{ _buf: [] }";
1414
- if (typeName === "strings.Builder" || typeName === "Builder")
1415
- return '{ _buf: "" }';
2538
+ if (typeName === "simd.Mask32x4" || typeName === "Mask32x4")
2539
+ return "Object.freeze([false, false, false, false])";
1416
2540
  if (typeName === "simd.F32x4" || typeName === "F32x4") {
1417
2541
  this.useHelper("simd");
1418
2542
  const a = expr.elems[0] ? this.genExpr(expr.elems[0]) : "0";
@@ -1421,6 +2545,8 @@ export const expressionGenMethods = {
1421
2545
  const d = expr.elems[3] ? this.genExpr(expr.elems[3]) : "0";
1422
2546
  return `__simd_f32x4(${a}, ${b}, ${c}, ${d})`;
1423
2547
  }
2548
+ const aliasLit = this._genAliasLit(t, typeName, expr);
2549
+ if (aliasLit !== null) return aliasLit;
1424
2550
  if (typeName && this.structNames.has(typeName))
1425
2551
  return this._genPositionalStructLit(typeName, expr);
1426
2552
  if (typeName && this.namedWrapperNames.has(typeName))
@@ -1434,14 +2560,68 @@ export const expressionGenMethods = {
1434
2560
  return this._genFallbackCompositeLit(expr);
1435
2561
  },
1436
2562
 
2563
+ // Literals whose type expression is not the class to construct: a generic
2564
+ // alias `A[X]{…}`, an anonymous `struct{…}{…}`, or `P{…}` for
2565
+ // `type P = struct{…}`. Null for every other literal.
2566
+ _genAliasLit(t, typeName, expr) {
2567
+ if (t.kind === "GenericTypeName" || t.kind === "InstantiationExpr")
2568
+ return this._genGenericAliasLit(typeName, expr);
2569
+ const anon =
2570
+ t.kind === "StructType" ||
2571
+ ((t.kind === "TypeName" || t.kind === "Ident") &&
2572
+ this.checker?.types.get(t.name)?.kind === "struct");
2573
+ return anon ? this._genAnonStructLit(expr) : null;
2574
+ },
2575
+
2576
+ // `A[X]{…}` for a generic alias `type A[P any] = T`: the literal has the
2577
+ // aliased type (expr._type), so dispatch on that instead of on `A`.
2578
+ _genGenericAliasLit(typeName, expr) {
2579
+ if (!this.checker?.types.get(typeName)?._generic?.isAlias) return null;
2580
+ const rt = expr._type;
2581
+ const base = rt?.kind === "named" ? rt.underlying : rt;
2582
+ if (rt?.kind === "named" && this.structNames.has(rt.name))
2583
+ return this._genPositionalStructLit(rt.name, expr);
2584
+ if (base?.kind === "map")
2585
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
2586
+ if (base?.kind === "struct") return this._genAnonStructLit(expr);
2587
+ return this._genImplicitCompositeLit(expr);
2588
+ },
2589
+
2590
+ // A literal of an anonymous struct type: a plain object with every field,
2591
+ // the omitted ones set to their zero value.
2592
+ _genAnonStructLit(expr) {
2593
+ const st =
2594
+ expr._type?.kind === "named" ? expr._type.underlying : expr._type;
2595
+ if (st?.kind !== "struct" || !(st.fields instanceof Map))
2596
+ return this._genFallbackCompositeLit(expr);
2597
+ const values = new Map();
2598
+ for (const e of expr.elems) {
2599
+ if (e._positionalField)
2600
+ values.set(e._positionalField, this.genValueExpr(e));
2601
+ else if (e.kind === "KeyValueExpr")
2602
+ values.set(e.key.name, this.genValueExpr(e.value));
2603
+ }
2604
+ const fields = [...st.fields].map(
2605
+ ([name, ft]) =>
2606
+ `${name}: ${values.get(name) ?? this.zeroValueForType(ft)}`,
2607
+ );
2608
+ return `{ ${fields.join(", ")} }`;
2609
+ },
2610
+
1437
2611
  _genImplicitCompositeLit(expr) {
1438
- const typeName = expr._type?.name ?? expr._type?.underlying?.name;
2612
+ // An elided `&T` (`[]*T{{1}}`) builds the struct; a struct pointer is the object.
2613
+ const lit = expr._type?.kind === "pointer" ? expr._type.base : expr._type;
2614
+ const typeName = lit?.name ?? lit?.underlying?.name;
1439
2615
  if (typeName && this.structNames.has(typeName)) {
1440
2616
  return this._genPositionalStructLit(typeName, expr);
1441
2617
  }
2618
+ if (this._isMapLit(expr))
2619
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
1442
2620
  const hasPositional = expr.elems.some((e) => e._positionalField);
1443
2621
  const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
1444
- if (hasPositional || hasKeyed) {
2622
+ // `{2: 5}` inside `[2][3]int{…}` is an array, not an object.
2623
+ const indexed = this._litIndexedShape(expr) !== null;
2624
+ if (hasPositional || (hasKeyed && !indexed)) {
1445
2625
  const fields = expr.elems
1446
2626
  .map((e) => {
1447
2627
  if (e._positionalField)
@@ -1451,7 +2631,9 @@ export const expressionGenMethods = {
1451
2631
  .join(", ");
1452
2632
  return `{ ${fields} }`;
1453
2633
  }
1454
- const elems = expr.elems.map((e) => this.genValueExpr(e)).join(", ");
2634
+ const sparse = this._genSparseArrayLit(expr);
2635
+ if (sparse) return sparse;
2636
+ const elems = this._genLitElems(expr);
1455
2637
  if (isTypedArraySlice(expr._type)) {
1456
2638
  const typedCtor = typedArrayConstructorForElem(expr._type.elem);
1457
2639
  if (typedCtor) return `new ${typedCtor}([${elems}])`;
@@ -1465,35 +2647,51 @@ export const expressionGenMethods = {
1465
2647
  if (u?.kind === "map") {
1466
2648
  return `{ ${this._genMapLitEntries(expr.elems)} }`;
1467
2649
  }
1468
- // Default: slice
1469
- const elems = expr.elems
1470
- .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
1471
- .join(", ");
1472
- return `[${elems}]`;
2650
+ // Default: slice or array
2651
+ return `[${this._genLitElems(expr)}]`;
1473
2652
  },
1474
2653
 
1475
2654
  // ── Helpers ───────────────────────────────────────────────────
1476
2655
 
1477
2656
  _genComplexOperand(expr) {
1478
- if (isComplex(expr._type)) return this.genExpr(expr);
2657
+ if (isComplex(expr._type) || expr._asComplex) return this.genExpr(expr);
1479
2658
  return `{ re: ${this.genExpr(expr)}, im: 0 }`;
1480
2659
  },
1481
2660
 
1482
2661
  getTypeName(typeNode) {
1483
2662
  if (!typeNode) return null;
1484
- if (typeNode.kind === "TypeName") return this._dequalify(typeNode.name);
2663
+ if (typeNode._stdName) return typeNode._stdName;
2664
+ if (typeNode.kind === "TypeName")
2665
+ return this._aliasTarget(this._dequalify(typeNode.name));
1485
2666
  if (typeNode.kind === "GenericTypeName") return typeNode.name;
1486
- if (typeNode.kind === "Ident") return typeNode.name;
2667
+ if (typeNode.kind === "Ident") return this._aliasTarget(typeNode.name);
1487
2668
  if (typeNode.kind === "InstantiationExpr")
1488
2669
  return this.getTypeName(typeNode.expr);
1489
2670
  if (typeNode.kind === "SelectorExpr") {
1490
2671
  const base = this.getTypeName(typeNode.expr);
1491
- if (this.bundledPackages.has(base)) return typeNode.field;
2672
+ if (this.bundledPackages.has(base)) {
2673
+ // `p.A` for `type A = Base` in p: no class is emitted for A.
2674
+ const t = this.checker?.types.get(`${base}.${typeNode.field}`);
2675
+ return t?.kind === "named" && t.name && !t.name.includes("$$")
2676
+ ? t.name
2677
+ : typeNode.field;
2678
+ }
1492
2679
  return `${base}.${typeNode.field}`;
1493
2680
  }
1494
2681
  return null;
1495
2682
  },
1496
2683
 
2684
+ // `type A = Base`: no class is emitted for A, so it names Base's.
2685
+ _aliasTarget(name) {
2686
+ const t = this.checker?.types.get(name);
2687
+ return t?.kind === "named" &&
2688
+ t.name &&
2689
+ t.name !== name &&
2690
+ !name.includes(".")
2691
+ ? t.name
2692
+ : name;
2693
+ },
2694
+
1497
2695
  // Bundled GoFront packages are inlined, so `pkg.T` names the class `T`.
1498
2696
  _dequalify(name) {
1499
2697
  const dot = name.indexOf(".");
@@ -1523,6 +2721,104 @@ export const expressionGenMethods = {
1523
2721
  return false;
1524
2722
  },
1525
2723
 
2724
+ // ── Generic zero values (see HELPER_GENERICS) ─────────────────
2725
+
2726
+ // JS condition: type parameter `name` was instantiated with a struct (its
2727
+ // zero value is a struct object, which is its own pointer when erased; a
2728
+ // map's zero is a bare `{}`, so only a class instance or a populated
2729
+ // object counts).
2730
+ _typeParamIsStructJs(name) {
2731
+ const f = this._typeParamZeroFactory(name);
2732
+ return f
2733
+ ? `((__z) => __z !== null && typeof __z === "object" && !Array.isArray(__z) && !ArrayBuffer.isView(__z) && (Object.getPrototypeOf(Object.getPrototypeOf(__z)) !== null || Object.keys(__z).length > 0))(${f}())`
2734
+ : "false";
2735
+ },
2736
+
2737
+ // The zero-value factory bound to type parameter `name` in the code being
2738
+ // emitted (`$z_T`), or null outside a generic body.
2739
+ _typeParamZeroFactory(name) {
2740
+ return this._typeParamZeros?.get(name) ?? null;
2741
+ },
2742
+
2743
+ // Runs `fn` with type parameters `names` bound to the factories `$z_<name>`.
2744
+ _withoutNatives(fn) {
2745
+ const saved = this.natives;
2746
+ this.natives = false;
2747
+ try {
2748
+ return fn();
2749
+ } finally {
2750
+ this.natives = saved;
2751
+ }
2752
+ },
2753
+
2754
+ _withTypeParamZeros(names, fn) {
2755
+ const prev = this._typeParamZeros;
2756
+ this._typeParamZeros = names
2757
+ ? new Map(names.map((n) => [n, `$z_${n}`]))
2758
+ : null;
2759
+ try {
2760
+ return fn();
2761
+ } finally {
2762
+ this._typeParamZeros = prev;
2763
+ }
2764
+ },
2765
+
2766
+ // A factory `() => zero` for type `t`: what a generic callee receives for
2767
+ // one of its type parameters.
2768
+ zeroFactoryFor(t) {
2769
+ if (t?.kind === "typeParam") {
2770
+ const bound = this._typeParamZeroFactory(t.name);
2771
+ if (bound) return bound;
2772
+ }
2773
+ this.useHelper("generics");
2774
+ if (this._is64BitUnsigned(t)) return "__zu64";
2775
+ const zero = this.zeroValueForType(t);
2776
+ return Object.hasOwn(SHARED_ZERO_FACTORIES, zero)
2777
+ ? SHARED_ZERO_FACTORIES[zero]
2778
+ : `() => (${zero})`;
2779
+ },
2780
+
2781
+ // One factory per type parameter of generic function or type `generic`.
2782
+ // `withInfo`: the callee needs the instantiated types (see type-info.js).
2783
+ _typeArgFactories(typeParams, typeArgs, withInfo = false) {
2784
+ return typeParams.map((_, i) =>
2785
+ withInfo
2786
+ ? this._typeInfoFactory(typeArgs?.[i])
2787
+ : this.zeroFactoryFor(typeArgs?.[i]),
2788
+ );
2789
+ },
2790
+
2791
+ // `new Box(args…, [factories])` for an instance of generic struct `name`
2792
+ // with `typeArgs`; fields without an argument start at their zero value.
2793
+ // Null when `name` is not a generic struct.
2794
+ _genericStructNew(name, typeArgs, args = []) {
2795
+ const generic = this.checker?.types.get(name);
2796
+ const st = generic?.underlying;
2797
+ const cls = this._dequalify(name);
2798
+ if (!generic?._generic || generic._generic.isAlias) return null;
2799
+ if (st?.kind !== "struct") return null;
2800
+ if (!this.structNames.has(cls)) return null;
2801
+ const tps = generic._generic.typeParams;
2802
+ const vals = Array.from(
2803
+ { length: st.fields.size },
2804
+ (_, i) => args[i] ?? "undefined",
2805
+ );
2806
+ const tz = this._typeArgFactories(
2807
+ tps,
2808
+ typeArgs,
2809
+ this._genericTypeNeedsTypeInfo(generic) ||
2810
+ typeArgs?.some((t) => mentionsTypeParam(t)),
2811
+ );
2812
+ const id = instanceId(typeArgs);
2813
+ if (id !== null) tz.push(JSON.stringify(id));
2814
+ else {
2815
+ const runtimeId = this._runtimeInstanceId(typeArgs);
2816
+ if (runtimeId) tz.push(runtimeId);
2817
+ }
2818
+ vals.push(`[${tz.join(", ")}]`);
2819
+ return `new ${cls}(${vals.join(", ")})`;
2820
+ },
2821
+
1526
2822
  // Returns the JS zero-value literal for a basic type name, or null if not a basic type.
1527
2823
  _zeroForBasicName(name) {
1528
2824
  return Object.hasOwn(ZERO_FOR_BASIC, name) ? ZERO_FOR_BASIC[name] : null;
@@ -1543,6 +2839,10 @@ export const expressionGenMethods = {
1543
2839
  u.kind !== "named"
1544
2840
  )
1545
2841
  return this.zeroValueForType(u);
2842
+ if (named?.kind === "named" && u?.kind === "struct") {
2843
+ const clsName = named.name ?? this._dequalify(name);
2844
+ if (this.structNames.has(clsName)) return `new ${clsName}()`;
2845
+ }
1546
2846
  name = this._dequalify(name);
1547
2847
  if (this.structNames.has(name)) return `new ${name}()`;
1548
2848
  return "null";
@@ -1551,7 +2851,10 @@ export const expressionGenMethods = {
1551
2851
  zeroValueForTypeNode(typeNode) {
1552
2852
  if (!typeNode) return "null";
1553
2853
  switch (typeNode.kind) {
2854
+ case "Ident":
1554
2855
  case "TypeName": {
2856
+ const factory = this._typeParamZeroFactory(typeNode.name);
2857
+ if (factory) return `${factory}()`;
1555
2858
  const basic = this._zeroForBasicName(typeNode.name);
1556
2859
  if (basic !== null) return basic;
1557
2860
  return this._zeroForNamedType(typeNode.name);
@@ -1573,7 +2876,7 @@ export const expressionGenMethods = {
1573
2876
  );
1574
2877
  }
1575
2878
  case "MapType":
1576
- return "{}";
2879
+ return this._nilMapZero();
1577
2880
  case "PointerType":
1578
2881
  return "null";
1579
2882
  case "StructType": {
@@ -1586,16 +2889,31 @@ export const expressionGenMethods = {
1586
2889
  .join(", ");
1587
2890
  return `{ ${fields} }`;
1588
2891
  }
2892
+ case "GenericTypeName":
2893
+ case "InstantiationExpr":
2894
+ return (
2895
+ this._genericStructNew(
2896
+ this.getTypeName(typeNode),
2897
+ typeNode._typeArgs,
2898
+ ) ?? "null"
2899
+ );
1589
2900
  default:
1590
2901
  return "null";
1591
2902
  }
1592
2903
  },
1593
2904
 
2905
+ // A nil map: a fresh tagged `{}` in js packages (`m == nil` holds while it
2906
+ // stays empty); in strict mode the frozen `__nilmap`, which panics on write.
2907
+ _nilMapZero() {
2908
+ this.useHelper(this.strict ? "nilMap" : "nm");
2909
+ return this.strict ? "__nilmap" : "__nm()";
2910
+ },
2911
+
1594
2912
  _arrayZero(n, ctor, elemZero) {
1595
2913
  if (ctor) return `new ${ctor}(${n})`;
1596
2914
  if (n === 0) return "[]";
1597
- if (/^(new |\{|\[|Array)/.test(elemZero))
1598
- return `Array.from({ length: ${n} }, () => ${elemZero})`;
2915
+ if (/^(new |\{|\[|Array|__nm\()|\$z_/.test(elemZero))
2916
+ return `Array.from({ length: ${n} }, () => (${elemZero}))`;
1599
2917
  return `new Array(${n}).fill(${elemZero})`;
1600
2918
  },
1601
2919
 
@@ -1614,7 +2932,7 @@ export const expressionGenMethods = {
1614
2932
  this.zeroValueForType(t.elem),
1615
2933
  );
1616
2934
  case "map":
1617
- return "{}";
2935
+ return this._nilMapZero();
1618
2936
  case "struct": {
1619
2937
  const fields = [...t.fields.entries()]
1620
2938
  .map(([name, ft]) => `${name}: ${this.zeroValueForType(ft)}`)
@@ -1622,7 +2940,14 @@ export const expressionGenMethods = {
1622
2940
  return `{ ${fields} }`;
1623
2941
  }
1624
2942
  case "named":
1625
- return this._zeroForNamedType(t.name);
2943
+ return (
2944
+ this._genericStructNew(t.name, t.typeArgs) ??
2945
+ this._zeroForNamedType(t.name)
2946
+ );
2947
+ case "typeParam": {
2948
+ const factory = this._typeParamZeroFactory(t.name);
2949
+ return factory ? `${factory}()` : "null";
2950
+ }
1626
2951
  default:
1627
2952
  return "null";
1628
2953
  }
@@ -1633,35 +2958,59 @@ export const expressionGenMethods = {
1633
2958
  if (!typeNode) return "true";
1634
2959
  if (typeNode.kind === "TypeName")
1635
2960
  return this._typeNameCheckExpr(typeNode.name, val);
2961
+ const generic = this._genericInstanceCheck(typeNode, val);
2962
+ if (generic) return generic;
2963
+ const dynamic = this._dynTypeCheck(typeNode, val);
2964
+ if (dynamic) return dynamic;
1636
2965
  const extended = this._namedOrIfaceCheckExpr(typeNode, val);
1637
2966
  if (extended) return extended;
1638
2967
  if (
1639
2968
  typeNode.kind === "PointerType" &&
1640
2969
  typeNode.base?.kind === "TypeName" &&
1641
- this.structNames.has(typeNode.base.name)
2970
+ this.structNames.has(this._qualifiedStructName(typeNode.base.name))
1642
2971
  ) {
1643
- const name = typeNode.base.name;
2972
+ const name = this._qualifiedStructName(typeNode.base.name);
1644
2973
  return `(${val} instanceof ${name} && ${val}.__v !== true)`;
1645
2974
  }
1646
2975
  return "true";
1647
2976
  },
1648
2977
 
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
- }
2978
+ // `Stack[int]` / `*Stack[int]`: the class test plus the instantiation
2979
+ // recorded on the instance (an instance without one matches any).
2980
+ _genericInstanceCheck(typeNode, val) {
2981
+ const isPtr = typeNode.kind === "PointerType";
2982
+ const base = isPtr ? typeNode.base : typeNode;
2983
+ if (base?.kind !== "GenericTypeName" || !base._typeArgs) return null;
2984
+ const cls = this._dequalify(base.name);
2985
+ if (!this.structNames.has(cls)) return null;
2986
+ const generic = this.checker?.types.get(base.name)?._generic;
2987
+ const id = instanceId(base._typeArgs);
2988
+ let check = isPtr
2989
+ ? `${val} instanceof ${cls} && ${val}.__v !== true`
2990
+ : `${val} instanceof ${cls} && ${val}.__p !== true`;
2991
+ if (id !== null && generic) {
2992
+ this.useHelper("generics");
2993
+ const at = generic.typeParams.length;
2994
+ check += ` && (${val}[__tz]?.[${at}] ?? ${JSON.stringify(id)}) === ${JSON.stringify(id)}`;
1663
2995
  }
1664
- return "null";
2996
+ return `(${check})`;
2997
+ },
2998
+
2999
+ zeroValueForExpr(expr) {
3000
+ return this.zeroValueForTypeNode(expr);
3001
+ },
3002
+
3003
+ _genSimdEquality(expr) {
3004
+ if (expr.op !== "==" && expr.op !== "!=") return null;
3005
+ if (!this._isSimdValueType(expr.left._type)) return null;
3006
+ this.useHelper("simd");
3007
+ const cmp = `__simd_equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
3008
+ return expr.op === "==" ? cmp : `!${cmp}`;
3009
+ },
3010
+
3011
+ _isSimdValueType(t) {
3012
+ const base = t?.kind === "named" ? t.underlying : t;
3013
+ return base?.kind === "simd";
1665
3014
  },
1666
3015
 
1667
3016
  // Returns true if the type is a struct or array (uses value comparison with __equal).
@@ -1700,24 +3049,112 @@ export const expressionGenMethods = {
1700
3049
  )
1701
3050
  return this._genComplexBinary(expr);
1702
3051
 
3052
+ const simdEq = this._genSimdEquality(expr);
3053
+ if (simdEq !== null) return simdEq;
3054
+
1703
3055
  if (this._isStructEquality(expr)) {
1704
3056
  this.useHelper("equal");
1705
3057
  const cmp = `__equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
1706
3058
  return expr.op === "==" ? cmp : `!${cmp}`;
1707
3059
  }
1708
3060
 
3061
+ const nilMap = this._genNilMapCompare(expr);
3062
+ if (nilMap) return nilMap;
3063
+
3064
+ const anyNil = this._genAnyNilCompare(expr);
3065
+ if (anyNil) return anyNil;
3066
+
3067
+ const tpEq = this._genTypeParamEquality(expr);
3068
+ if (tpEq) return tpEq;
3069
+
1709
3070
  if (this._isIfaceEquality(expr)) {
1710
3071
  this.useHelper("ifeq");
1711
3072
  const cmp = `__ifeq(${this._genIfaceOperand(expr.left)}, ${this._genIfaceOperand(expr.right)})`;
1712
3073
  return expr.op === "==" ? cmp : `!${cmp}`;
1713
3074
  }
1714
3075
 
1715
- if (this.strict && this._needsStrictOp(expr.op, expr._type)) {
3076
+ const ptrCmp = this._genPointerEquality(expr);
3077
+ if (ptrCmp !== null) return ptrCmp;
3078
+
3079
+ if (this._is64BitInt(expr._type)) return this._gen64BitBinaryExpr(expr);
3080
+
3081
+ if (
3082
+ (this.strict || this._isFloat32(expr._type)) &&
3083
+ this._needsStrictOp(expr.op, expr._type)
3084
+ ) {
1716
3085
  const l = this.genExpr(expr.left);
1717
3086
  const r = this.genExpr(expr.right);
1718
- return this._genStrictBinary(expr.op, l, r, expr._type);
3087
+ return this._genStrictBinary(expr.op, l, r, expr._type, expr.right._type);
3088
+ }
3089
+
3090
+ return this._genStandardBinaryExpr(expr);
3091
+ },
3092
+
3093
+ _gen64BitBinaryExpr(expr) {
3094
+ let l = this.genExpr(expr.left);
3095
+ let r = this.genExpr(expr.right);
3096
+ // An untyped operand is a literal (`3` -> `3n`) or a named constant,
3097
+ // which holds a Number.
3098
+ const lit = (js) => (/^-?\d+$/.test(js) ? `${js}n` : `BigInt(${js})`);
3099
+ if (expr.left._type?.kind === "untyped" && !l.endsWith("n")) l = lit(l);
3100
+ else if (!this._is64BitInt(expr.left?._type)) l = `BigInt(${l})`;
3101
+ if (expr.right._type?.kind === "untyped" && !r.endsWith("n")) r = lit(r);
3102
+ else if (!this._is64BitInt(expr.right?._type)) r = `BigInt(${r})`;
3103
+ return this._gen64BitBinary(expr.op, l, r, expr._type);
3104
+ },
3105
+
3106
+ _tconvBinaryOperands(expr, l, r) {
3107
+ if (
3108
+ expr.left._type?.kind === "typeParam" &&
3109
+ expr.right._type?.kind === "untyped"
3110
+ ) {
3111
+ const factory = this._typeParamZeroFactory(expr.left._type.name);
3112
+ if (factory) {
3113
+ this.useHelper("generics");
3114
+ r = `__tconv(${factory}, ${r})`;
3115
+ }
3116
+ } else if (
3117
+ expr.right._type?.kind === "typeParam" &&
3118
+ expr.left._type?.kind === "untyped"
3119
+ ) {
3120
+ const factory = this._typeParamZeroFactory(expr.right._type.name);
3121
+ if (factory) {
3122
+ this.useHelper("generics");
3123
+ l = `__tconv(${factory}, ${l})`;
3124
+ }
1719
3125
  }
3126
+ return [l, r];
3127
+ },
3128
+
3129
+ _coerceUntyped64(expr, l, r, isLeft64, isRight64) {
3130
+ if (isLeft64 && expr.right?._type?.kind === "untyped") {
3131
+ if (!r.endsWith("n") && /^-?\d+$/.test(r)) return [l, `${r}n`];
3132
+ } else if (isRight64 && expr.left?._type?.kind === "untyped") {
3133
+ if (!l.endsWith("n") && /^-?\d+$/.test(l)) return [`${l}n`, r];
3134
+ }
3135
+ return [l, r];
3136
+ },
3137
+
3138
+ _coerceMixed64(expr, l, r, isLeft64, isRight64) {
3139
+ const isShift = expr.op === "<<" || expr.op === ">>";
3140
+ if (isLeft64 && !isRight64 && !isShift) r = `BigInt(${r})`;
3141
+ else if (isRight64 && !isLeft64 && !isShift) l = `BigInt(${l})`;
3142
+ if (isShift && isRight64) r = `Number(${r})`;
3143
+ return [l, r];
3144
+ },
3145
+
3146
+ _wrapBinaryResult(expr, res, isLeft64, isRight64) {
3147
+ const isCmp =
3148
+ expr.op === "==" ||
3149
+ expr.op === "!=" ||
3150
+ expr.op === "<" ||
3151
+ expr.op === "<=" ||
3152
+ expr.op === ">" ||
3153
+ expr.op === ">=";
3154
+ return (isLeft64 || isRight64) && !isCmp ? `Number(${res})` : res;
3155
+ },
1720
3156
 
3157
+ _genStandardBinaryExpr(expr) {
1721
3158
  const jsOp = this._emittedBinaryOp(expr) ?? "/";
1722
3159
  const operand = (child, isRight) =>
1723
3160
  wrapForJsOp(
@@ -1726,17 +3163,66 @@ export const expressionGenMethods = {
1726
3163
  jsOp,
1727
3164
  isRight,
1728
3165
  );
1729
- const l = operand(expr.left, false);
3166
+ let [l, r] = this._tconvBinaryOperands(
3167
+ expr,
3168
+ operand(expr.left, false),
3169
+ operand(expr.right, true),
3170
+ );
3171
+ const isLeft64 = this._is64BitInt(expr.left?._type);
3172
+ const isRight64 = this._is64BitInt(expr.right?._type);
3173
+ [l, r] = this._coerceUntyped64(expr, l, r, isLeft64, isRight64);
3174
+ [l, r] = this._coerceMixed64(expr, l, r, isLeft64, isRight64);
1730
3175
 
1731
3176
  if (expr.op === "&^") {
1732
3177
  const rCode = this.genExpr(expr.right);
1733
- const r =
3178
+ const rP =
1734
3179
  this._emittedBinaryOp(expr.right) !== null ? `(${rCode})` : rCode;
1735
- return `${l} & ~${r}`;
3180
+ return this._wrapBinaryResult(expr, `${l} & ~${rP}`, isLeft64, isRight64);
1736
3181
  }
1737
- const r = operand(expr.right, true);
1738
3182
  if (this._isIntDivision(expr)) return `Math.trunc(${l} / ${r})`;
1739
- return `${l} ${jsOp} ${r}`;
3183
+ return this._wrapBinaryResult(
3184
+ expr,
3185
+ `${l} ${jsOp} ${r}`,
3186
+ isLeft64,
3187
+ isRight64,
3188
+ );
3189
+ },
3190
+
3191
+ // `m == nil` / `m != nil` on a map: true for a nil map (null, the strict
3192
+ // sentinel, or an untouched js-package zero map), false for `map[K]V{}`.
3193
+ _nilMapOperand(expr) {
3194
+ if (expr.op !== "==" && expr.op !== "!=") return null;
3195
+ const other = this._isNilLiteral(expr.right)
3196
+ ? expr.left
3197
+ : this._isNilLiteral(expr.left)
3198
+ ? expr.right
3199
+ : null;
3200
+ if (!other) return null;
3201
+ const t =
3202
+ other._type?.kind === "named" ? other._type.underlying : other._type;
3203
+ return t?.kind === "map" || other._coreType?.kind === "map" ? other : null;
3204
+ },
3205
+
3206
+ _genNilMapCompare(expr) {
3207
+ const operand = this._nilMapOperand(expr);
3208
+ if (!operand) return null;
3209
+ this.useHelper(this.strict ? "nilMap" : "nm");
3210
+ const cmp = `__isnm(${this.genExpr(operand)})`;
3211
+ return expr.op === "==" ? cmp : `!${cmp}`;
3212
+ },
3213
+
3214
+ // `a == b` on type-parameter operands: __geq compares structs and arrays by value.
3215
+ _genTypeParamEquality(expr) {
3216
+ if (expr.op !== "==" && expr.op !== "!=") return null;
3217
+ const t = expr.left._type;
3218
+ if (t?.kind !== "typeParam" || expr.right._type?.kind !== "typeParam")
3219
+ return null;
3220
+ const factory = this._typeParamZeroFactory(t.name);
3221
+ if (!factory) return null;
3222
+ this.useHelper("equal");
3223
+ this.useHelper("geq");
3224
+ const cmp = `__geq(${factory}, ${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
3225
+ return expr.op === "==" ? cmp : `!${cmp}`;
1740
3226
  },
1741
3227
 
1742
3228
  _isStructEquality(expr) {
@@ -1779,10 +3265,29 @@ export const expressionGenMethods = {
1779
3265
  );
1780
3266
  },
1781
3267
 
3268
+ // The `any` operand of `x == nil` / `x != nil` (JS documents a nil slice
3269
+ // stored in an `any` as equal to nil, though it keeps a type tag), or null.
3270
+ _anyNilOperand(expr) {
3271
+ if (expr.op !== "==" && expr.op !== "!=") return null;
3272
+ const operand = this._isNilLiteral(expr.right)
3273
+ ? expr.left
3274
+ : this._isNilLiteral(expr.left)
3275
+ ? expr.right
3276
+ : null;
3277
+ return operand && this._isIfaceType(operand._type) ? operand : null;
3278
+ },
3279
+
3280
+ _genAnyNilCompare(expr) {
3281
+ const operand = this._anyNilOperand(expr);
3282
+ if (!operand) return null;
3283
+ const test = `((__a) => __a == null || __a.__nils === true)(${this.genExpr(operand)})`;
3284
+ return expr.op === "==" ? test : `!${test}`;
3285
+ },
3286
+
1782
3287
  _isNilLiteral(expr) {
1783
3288
  return (
1784
3289
  (expr.kind === "Ident" && expr.name === "nil") ||
1785
- (expr.kind === "BasicLit" && expr.value === "null")
3290
+ (expr.kind === "BasicLit" && expr.litKind === "NIL")
1786
3291
  );
1787
3292
  },
1788
3293
 
@@ -1796,6 +3301,8 @@ export const expressionGenMethods = {
1796
3301
  )
1797
3302
  return null;
1798
3303
  if (this._isStructEquality(expr) || this._isIntDivision(expr)) return null;
3304
+ if (this._nilMapOperand(expr)) return null;
3305
+ if (this._anyNilOperand(expr)) return null;
1799
3306
  if (this._isIfaceEquality(expr)) return null;
1800
3307
  if (expr.op === "&^") return "&";
1801
3308
  if (expr.op === "==" || expr.op === "!=") {
@@ -1806,9 +3313,19 @@ export const expressionGenMethods = {
1806
3313
  return expr.op;
1807
3314
  },
1808
3315
 
3316
+ // `T(x)` with T a type parameter: converts by the instantiated type.
3317
+ _genTypeParamConversion(name, inner) {
3318
+ const factory = name ? this._typeParamZeroFactory(name) : null;
3319
+ if (!factory) return null;
3320
+ this.useHelper("generics");
3321
+ return `__tconv(${factory}, ${inner})`;
3322
+ },
3323
+
1809
3324
  _genTypeConversion(expr) {
1810
3325
  const inner = this.genExpr(expr.expr);
1811
3326
  const t = expr.targetType;
3327
+ const viaParam = this._genTypeParamConversion(t?.name, inner);
3328
+ if (viaParam) return viaParam;
1812
3329
  if (t?.name === "complex128" || t?.name === "complex64") {
1813
3330
  return isComplex(expr.expr._type) ? inner : `{ re: ${inner}, im: 0 }`;
1814
3331
  }
@@ -1816,8 +3333,21 @@ export const expressionGenMethods = {
1816
3333
  return this._genArrayTypeConversion(expr, inner, t);
1817
3334
  if (t?.kind === "SliceType")
1818
3335
  return this._genSliceTypeConversion(expr, inner, t);
3336
+ // `map[K]V(nil)` is a nil map value, which stays distinct from the nil
3337
+ // interface once boxed in an `any`.
3338
+ if (t?.kind === "MapType" && this._isNilLiteral(expr.expr))
3339
+ return this._nilMapZero();
1819
3340
  const target = t?.name;
3341
+ const structConv = this._genStructClassConversion(
3342
+ target,
3343
+ expr.expr._type,
3344
+ inner,
3345
+ );
3346
+ if (structConv) return structConv;
1820
3347
  if (target === "error") {
3348
+ // A boxed named value that implements error is already an error.
3349
+ if (this._isNamedBoxKind(expr.expr)) return inner;
3350
+ if (!this._isStringExprType(expr.expr._type)) return inner;
1821
3351
  this.useHelper("error");
1822
3352
  return `__error(${inner})`;
1823
3353
  }
@@ -1840,16 +3370,33 @@ export const expressionGenMethods = {
1840
3370
  const size = t.size?.value !== undefined ? Number(t.size.value) : t.size;
1841
3371
  return `${inner}.slice(0, ${size})`;
1842
3372
  }
1843
- return inner;
3373
+ // Array-to-array conversion yields a new array value.
3374
+ return this._cloneJs(expr.expr._type, inner);
1844
3375
  },
1845
3376
 
1846
3377
  _genSliceTypeConversion(expr, inner, t) {
3378
+ // `[]T(nil)` is the nil slice, not a copy of anything.
3379
+ if (this._isNilLiteral(expr.expr)) return "null";
1847
3380
  const elem = t.elem?.name;
3381
+ // Converting a nil slice yields a nil slice.
3382
+ const src = this._underlyingOf(expr.expr._type);
3383
+ if (src?.kind === "slice" && !this._sliceConvNilSafe) {
3384
+ this._sliceConvNilSafe = true;
3385
+ try {
3386
+ return `((__s) => __s == null ? null : ${this._genSliceTypeConversion(expr, "__s", t)})(${inner})`;
3387
+ } finally {
3388
+ this._sliceConvNilSafe = false;
3389
+ }
3390
+ }
1848
3391
  if (elem === "byte" || elem === "uint8") {
1849
- if (this._isStringExprType(expr.expr._type))
1850
- return `new TextEncoder().encode(${inner})`;
1851
- if (isAny(expr.expr._type) || !expr.expr._type)
1852
- return `(typeof ${inner} === "string" ? new TextEncoder().encode(${inner}) : new Uint8Array(${inner}))`;
3392
+ if (this._isStringExprType(expr.expr._type)) {
3393
+ this.useHelper("u8enc");
3394
+ return `__u8enc(${inner})`;
3395
+ }
3396
+ if (isAny(expr.expr._type) || !expr.expr._type) {
3397
+ this.useHelper("u8enc");
3398
+ return `(typeof ${inner} === "string" ? __u8enc(${inner}) : new Uint8Array(${inner}))`;
3399
+ }
1853
3400
  return `new Uint8Array(${inner})`;
1854
3401
  }
1855
3402
  const typedCtor = typedArrayConstructorForElem(
@@ -1868,41 +3415,71 @@ export const expressionGenMethods = {
1868
3415
 
1869
3416
  _genStringConversion(srcType, inner) {
1870
3417
  const base = srcType?.kind === "named" ? srcType.underlying : srcType;
1871
- if (base && this.isIntType(base)) return `String.fromCodePoint(${inner})`;
3418
+ if (base && this.isIntType(base)) {
3419
+ this.useHelper("runestr");
3420
+ return `__rstr(${inner})`;
3421
+ }
1872
3422
  if (base?.kind === "slice") {
1873
3423
  const elem = base.elem?.name;
1874
- if (elem === "byte" || elem === "uint8")
1875
- return `new TextDecoder().decode(new Uint8Array(${inner}))`;
1876
- if (elem === "rune" || elem === "int" || elem === "int32")
1877
- return `Array.from(${inner}, c => String.fromCodePoint(c)).join("")`;
3424
+ if (elem === "byte" || elem === "uint8") {
3425
+ this.useHelper("u8dec");
3426
+ return `__u8dec(${inner})`;
3427
+ }
3428
+ if (elem === "rune" || elem === "int" || elem === "int32") {
3429
+ this.useHelper("runestr");
3430
+ return `Array.from(${inner}, c => __rstr(c)).join("")`;
3431
+ }
1878
3432
  }
1879
3433
  return `String(${inner})`;
1880
3434
  },
1881
3435
 
1882
3436
  _genPrimitiveConversion(expr, inner, target) {
3437
+ const srcType = expr.expr?._type;
3438
+ const isSrc64 = this._is64BitInt(srcType);
1883
3439
  switch (target) {
1884
3440
  case "string":
1885
- return this._genStringConversion(expr.expr._type, inner);
3441
+ return this._genStringConversion(
3442
+ srcType,
3443
+ isSrc64 ? `Number(${inner})` : inner,
3444
+ );
3445
+ case "int64": {
3446
+ this.useHelper("to64");
3447
+ return `__to_i64(${inner})`;
3448
+ }
3449
+ case "uint64": {
3450
+ this.useHelper("to64");
3451
+ return `__to_u64(${inner})`;
3452
+ }
1886
3453
  case "int":
1887
- case "int64":
1888
3454
  case "uint":
1889
- case "uint64":
1890
3455
  case "uintptr":
1891
- return `Math.trunc(Number(${inner}))`;
3456
+ return isSrc64 ? `Number(${inner})` : `Math.trunc(Number(${inner}))`;
1892
3457
  case "int8":
1893
- return `(Number(${inner}) << 24 >> 24)`;
3458
+ return isSrc64
3459
+ ? `Number(BigInt.asIntN(8, ${inner}))`
3460
+ : `(Number(${inner}) << 24 >> 24)`;
1894
3461
  case "int16":
1895
- return `(Number(${inner}) << 16 >> 16)`;
3462
+ return isSrc64
3463
+ ? `Number(BigInt.asIntN(16, ${inner}))`
3464
+ : `(Number(${inner}) << 16 >> 16)`;
1896
3465
  case "int32":
1897
3466
  case "rune":
1898
- return `(Number(${inner}) | 0)`;
3467
+ return isSrc64
3468
+ ? `Number(BigInt.asIntN(32, ${inner}))`
3469
+ : `(Number(${inner}) | 0)`;
1899
3470
  case "uint8":
1900
3471
  case "byte":
1901
- return `(Number(${inner}) & 0xFF)`;
3472
+ return isSrc64
3473
+ ? `Number(BigInt.asUintN(8, ${inner}))`
3474
+ : `(Number(${inner}) & 0xFF)`;
1902
3475
  case "uint16":
1903
- return `(Number(${inner}) & 0xFFFF)`;
3476
+ return isSrc64
3477
+ ? `Number(BigInt.asUintN(16, ${inner}))`
3478
+ : `(Number(${inner}) & 0xFFFF)`;
1904
3479
  case "uint32":
1905
- return `(Number(${inner}) >>> 0)`;
3480
+ return isSrc64
3481
+ ? `Number(BigInt.asUintN(32, ${inner}))`
3482
+ : `(Number(${inner}) >>> 0)`;
1906
3483
  case "float32":
1907
3484
  return `Math.fround(Number(${inner}))`;
1908
3485
  case "float64":
@@ -1926,15 +3503,77 @@ export const expressionGenMethods = {
1926
3503
  return STRICT_NUMERIC_TYPES.has(typeName);
1927
3504
  },
1928
3505
 
3506
+ _is64BitInt(t) {
3507
+ return is64BitInt(t);
3508
+ },
3509
+
3510
+ // float32 arithmetic rounds through Math.fround and arithmetic on a type
3511
+ // parameter wraps for the instantiated type (`__tar`) in every package kind.
3512
+ _isFloat32(t) {
3513
+ return (
3514
+ this._typeName(t) === "float32" ||
3515
+ (t?.kind === "typeParam" && this._typeParamZeroFactory(t.name) != null)
3516
+ );
3517
+ },
3518
+
3519
+ _is64BitUnsigned(t) {
3520
+ return is64BitUnsigned(t);
3521
+ },
3522
+
3523
+ _gen64BitBinary(op, l, r, type) {
3524
+ const unsigned = this._is64BitUnsigned(type);
3525
+ const wrap = unsigned ? "BigInt.asUintN" : "BigInt.asIntN";
3526
+ const divZero =
3527
+ '(() => { throw new Error("runtime error: integer divide by zero"); })()';
3528
+ if (op === "/" || op === "%") {
3529
+ // The divisor is read twice (zero check, division): bind both
3530
+ // operands once, left first, unless they are plain names or literals.
3531
+ const simple = /^[A-Za-z_$][\w$.]*$|^-?\d+n$/;
3532
+ if ((simple.test(l) && simple.test(r)) || /\b(await|yield)\b/.test(l + r))
3533
+ return `((${r}) === 0n ? ${divZero} : ${wrap}(64, (${l}) ${op} (${r})))`;
3534
+ return `((__a, __b) => __b === 0n ? ${divZero} : ${wrap}(64, __a ${op} __b))(${l}, ${r})`;
3535
+ }
3536
+ if (op === "<<" || op === ">>") {
3537
+ this.useHelper("shift");
3538
+ return op === "<<"
3539
+ ? `__shl64(${l}, ${r}, ${unsigned})`
3540
+ : `__shr64(${l}, ${r}, ${unsigned})`;
3541
+ }
3542
+ if (op === "&^") return `${wrap}(64, (${l}) & ~(${r}))`;
3543
+ return `${wrap}(64, (${l}) ${op} (${r}))`;
3544
+ },
3545
+
3546
+ // `uint` / `uintptr`: 64-bit unsigned values held as JS numbers.
3547
+ _isUintWord(type) {
3548
+ const name = this._typeName(type);
3549
+ return name === "uint" || name === "uintptr";
3550
+ },
3551
+
1929
3552
  _needsStrictOp(op, type) {
3553
+ if (type?.kind === "typeParam")
3554
+ return (
3555
+ STRICT_OPS.has(op) && this._typeParamZeroFactory(type.name) != null
3556
+ );
3557
+ if (this._is64BitInt(type))
3558
+ return STRICT_OPS.has(op) || op === "<<" || op === ">>" || op === "&^";
1930
3559
  if (!STRICT_OPS.has(op)) return false;
1931
3560
  if (this._isStrictAtomicOp(op, this._typeName(type))) return true;
3561
+ if (op === "-" && this._isUintWord(type)) return true;
3562
+ if (op === "<<" || op === ">>")
3563
+ return WIDE_INT_NAMES.has(this._typeName(type));
1932
3564
  return this.isIntType(type) && (op === "/" || op === "%");
1933
3565
  },
1934
3566
 
1935
3567
  // Emits `lhs = strict(lhs op rhs)` for any lvalue shape; returns false when not applicable.
1936
3568
  // Indexed / nested targets are split into object + key temps so sub-expressions run once.
1937
- _genStrictCompound(lhsNode, op, rhsJs, srcLine = null, lhsJs = null) {
3569
+ _genStrictCompound(
3570
+ lhsNode,
3571
+ op,
3572
+ rhsJs,
3573
+ srcLine = null,
3574
+ lhsJs = null,
3575
+ countType = null,
3576
+ ) {
1938
3577
  const type = lhsNode._type;
1939
3578
  if (!this._needsStrictOp(op, type)) return false;
1940
3579
  if (lhsNode.kind === "IndexExpr" && lhsNode.expr?._type?.kind === "map")
@@ -1942,7 +3581,7 @@ export const expressionGenMethods = {
1942
3581
  const l = lhsJs ?? this._genAssignLhsExpr(lhsNode);
1943
3582
  if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(l)) {
1944
3583
  this.line(
1945
- `${l} = ${this._genStrictBinary(op, l, rhsJs, type)};`,
3584
+ `${l} = ${this._genStrictBinary(op, l, rhsJs, type, countType)};`,
1946
3585
  srcLine,
1947
3586
  );
1948
3587
  return true;
@@ -1955,21 +3594,40 @@ export const expressionGenMethods = {
1955
3594
  const k = `__k${this._tmpCounter}`;
1956
3595
  const target = `${o}[${k}]`;
1957
3596
  this.line(
1958
- `{ const ${o} = ${split.obj}, ${k} = ${split.key}; ${target} = ${this._genStrictBinary(op, target, rhsJs, type)}; }`,
3597
+ `{ const ${o} = ${split.obj}, ${k} = ${split.key}; ${target} = ${this._genStrictBinary(op, target, rhsJs, type, countType)}; }`,
1959
3598
  srcLine,
1960
3599
  );
1961
3600
  } else {
1962
3601
  const target = `${o}.${split.prop}`;
1963
3602
  this.line(
1964
- `{ const ${o} = ${split.obj}; ${target} = ${this._genStrictBinary(op, target, rhsJs, type)}; }`,
3603
+ `{ const ${o} = ${split.obj}; ${target} = ${this._genStrictBinary(op, target, rhsJs, type, countType)}; }`,
1965
3604
  srcLine,
1966
3605
  );
1967
3606
  }
1968
3607
  return true;
1969
3608
  },
1970
3609
 
1971
- _genStrictBinary(op, l, r, type) {
3610
+ // A non-constant signed shift count on a narrow integer is checked for a
3611
+ // negative value (Go panics); 64-bit counts arrive as BigInt.
3612
+ _guardShiftCount(r, countType) {
3613
+ if (!countType || countType.kind === "untyped") return r;
3614
+ const name = this._typeName(countType);
3615
+ if (!name || name.startsWith("u")) return r;
3616
+ this.useHelper("shift");
3617
+ return `__shc(${this._is64BitInt(countType) ? `Number(${r})` : r})`;
3618
+ },
3619
+
3620
+ _genStrictBinary(op, l, r, type, countType = null) {
3621
+ if (type?.kind === "typeParam") {
3622
+ this.useHelper("generics");
3623
+ return `__tar(${this._typeParamZeroFactory(type.name)}, "${op}", ${l}, ${r})`;
3624
+ }
3625
+ if (this._is64BitInt(type)) {
3626
+ return this._gen64BitBinary(op, l, r, type);
3627
+ }
1972
3628
  const tName = this._typeName(type);
3629
+ const isShift = op === "<<" || op === ">>";
3630
+ const rc = isShift ? this._guardShiftCount(r, countType) : r;
1973
3631
  if (tName === "float32") {
1974
3632
  return `Math.fround(${l} ${op} ${r})`;
1975
3633
  }
@@ -1978,7 +3636,10 @@ export const expressionGenMethods = {
1978
3636
  if (tName === "int32" || tName === "rune" || tName === "uint32") {
1979
3637
  const signed = tName !== "uint32";
1980
3638
  const cast = signed ? "| 0" : ">>> 0";
1981
- if (signed && op === "*") return `Math.imul(${l}, ${r})`;
3639
+ if (op === "*")
3640
+ return signed
3641
+ ? `Math.imul(${l}, ${r})`
3642
+ : `(Math.imul(${l}, ${r}) >>> 0)`;
1982
3643
  if (op === "/")
1983
3644
  return `((${r}) === 0 ? ${divZero} : (Math.trunc((${l}) / (${r})) ${cast}))`;
1984
3645
  if (op === "%")
@@ -1986,9 +3647,9 @@ export const expressionGenMethods = {
1986
3647
  if (op === "<<" || op === ">>") {
1987
3648
  const ovf = signed && op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0";
1988
3649
  const jsOp = !signed && op === ">>" ? ">>>" : op;
1989
- return `((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast}`;
3650
+ return `(((${rc}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast})`;
1990
3651
  }
1991
- if (op === "&^") return `((${l}) & ~(${r})) ${cast}`;
3652
+ if (op === "&^") return `(((${l}) & ~(${r})) ${cast})`;
1992
3653
  return `((${l} ${op} ${r}) ${cast})`;
1993
3654
  }
1994
3655
  if (tName === "int8" || tName === "int16") {
@@ -1999,7 +3660,7 @@ export const expressionGenMethods = {
1999
3660
  if (op === "%")
2000
3661
  return `((${r}) === 0 ? ${divZero} : (((((${l}) % (${r})) << ${sh}) >> ${sh})))`;
2001
3662
  if (op === "<<" || op === ">>") {
2002
- return `((${r}) >= ${bits} ? (${op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0"}) : (((${l}) ${op} (${r})) << ${sh} >> ${sh}))`;
3663
+ return `((${rc}) >= ${bits} ? (${op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0"}) : (((${l}) ${op} (${r})) << ${sh} >> ${sh}))`;
2003
3664
  }
2004
3665
  if (op === "&^") return `((((${l}) & ~(${r})) << ${sh}) >> ${sh})`;
2005
3666
  return `((((${l} ${op} ${r})) << ${sh}) >> ${sh})`;
@@ -2013,11 +3674,21 @@ export const expressionGenMethods = {
2013
3674
  return `((${r}) === 0 ? ${divZero} : (((${l}) % (${r})) & ${mask}))`;
2014
3675
  if (op === "<<" || op === ">>") {
2015
3676
  const jsOp = op === ">>" ? ">>>" : "<<";
2016
- return `((${r}) >= ${bits} ? 0 : (((${l}) ${jsOp} (${r})) & ${mask}))`;
3677
+ return `((${rc}) >= ${bits} ? 0 : (((${l}) ${jsOp} (${r})) & ${mask}))`;
2017
3678
  }
2018
3679
  if (op === "&^") return `(((${l}) & ~(${r})) & ${mask})`;
2019
3680
  return `((${l} ${op} ${r}) & ${mask})`;
2020
3681
  }
3682
+ if (op === "-" && this._isUintWord(type))
3683
+ return `((__d) => __d < 0 ? __d + 2 ** 64 : __d)((${l}) - (${r}))`;
3684
+ if (op === "<<" || op === ">>") {
3685
+ // 64-bit semantics: operand first, then count (Go order); a negative
3686
+ // count panics and the result wraps to 64 bits.
3687
+ this.useHelper("shift");
3688
+ const unsigned = this._typeName(type).startsWith("u");
3689
+ if (op === "<<") return `__shl64(${l}, ${r}, ${unsigned})`;
3690
+ return `__shr64(${l}, ${r}, ${unsigned})`;
3691
+ }
2021
3692
  if (this.isIntType(type)) {
2022
3693
  if (op === "/")
2023
3694
  return `((${r}) === 0 ? ${divZero} : Math.trunc((${l}) / (${r})))`;