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
@@ -20,53 +20,64 @@
20
20
  import {
21
21
  computeEmbeddedStubs,
22
22
  hasDirectRecover,
23
- isReferenceType,
23
+ markInBoundsIndexes,
24
+ markRedundantNilChecks,
24
25
  normalizeDefers,
25
26
  scanAddressTaken,
27
+ takesElementAddress,
26
28
  } from "../../lower/index.js";
27
- import { isComplex, isNumeric } from "../../typechecker/types.js";
28
- import { expressionGenMethods } from "./expressions.js";
29
+ import { orderPackageInits } from "../../typechecker/init-order.js";
30
+ import {
31
+ BROWSER_GLOBALS,
32
+ isComplex,
33
+ isNumeric,
34
+ } from "../../typechecker/types.js";
35
+ import {
36
+ unicodeIsJs,
37
+ unicodeToLowerJs,
38
+ unicodeToUpperJs,
39
+ } from "../shared/host-unicode.js";
40
+ import { comparesToNil, expressionGenMethods } from "./expressions.js";
29
41
  import {
30
42
  HELPER_APPEND,
31
- HELPER_BASE64,
32
- HELPER_BINARY,
43
+ HELPER_BOUNDS,
33
44
  HELPER_CDIV,
34
45
  HELPER_CMUL,
35
- HELPER_CRYPTO_RAND,
36
- HELPER_DURATION,
37
46
  HELPER_EQUAL,
38
47
  HELPER_ERROR,
39
- HELPER_ERROR_AS,
40
- HELPER_ERROR_IS,
41
- HELPER_ERROR_JOIN,
42
- HELPER_HEX,
48
+ HELPER_GENERICS,
49
+ HELPER_GEQ,
43
50
  HELPER_IFACE_BOX,
44
51
  HELPER_IFEQ,
45
52
  HELPER_INJECT_STYLES,
46
- HELPER_IO_EOF,
47
- HELPER_JSON,
53
+ HELPER_JSONRT,
48
54
  HELPER_LEN,
55
+ HELPER_MKLEN,
49
56
  HELPER_NIL_CALL,
57
+ HELPER_NIL_MAP,
58
+ HELPER_NM,
50
59
  HELPER_PANIC,
51
- HELPER_PATH_CLEAN,
52
- HELPER_PATH_ERROR,
60
+ HELPER_PTREQ,
61
+ HELPER_PUSHS,
62
+ HELPER_RUNESTR,
53
63
  HELPER_S,
54
64
  HELPER_SCLONE,
55
- HELPER_SHA256,
65
+ HELPER_SHIFT,
56
66
  HELPER_SIMD,
57
- HELPER_SORT_SLICE,
58
- HELPER_SPRINTF,
59
- HELPER_STRCONV,
60
- HELPER_STRCONV_ERROR,
61
- HELPER_SYNC,
62
- HELPER_TESTING,
63
- HELPER_TIME_FMT,
64
- HELPER_TIME_PARSE,
67
+ HELPER_SLICE,
68
+ HELPER_STRUCT_KEY,
69
+ HELPER_SV,
70
+ HELPER_TO64,
71
+ HELPER_U8DEC,
72
+ HELPER_U8ENC,
73
+ HELPER_VA,
74
+ HELPER_VCLONE,
65
75
  } from "./runtime.js";
66
76
  import { buildSourceMap, buildWasmSourceMap } from "./source-map.js";
67
77
  import { statementGenMethods } from "./statements.js";
68
78
  import { stdlibGenMethods } from "./stdlib/index.js";
69
79
  import { templGenMethods } from "./templ.js";
80
+ import { typeInfoGenMethods } from "./type-info.js";
70
81
 
71
82
  export { buildSourceMap, buildWasmSourceMap };
72
83
 
@@ -110,45 +121,119 @@ const JS_RESERVED = new Set([
110
121
  const jsSafeName = (n) =>
111
122
  typeof n === "string" && JS_RESERVED.has(n) ? `${n}$` : n;
112
123
 
124
+ // Go declarations may shadow browser globals (Task 0.9). The JS bundle is one
125
+ // flat module scope, so such a binding gets a `$` suffix: generated code and
126
+ // other packages keep reaching the real global (`Array.from`, `Object.keys`).
127
+ const jsBindingName = (n) =>
128
+ typeof n === "string" && BROWSER_GLOBALS.has(n) ? `${n}$` : jsSafeName(n);
129
+
113
130
  // Runtime helpers by registry name, in output order. Mark one as needed with
114
131
  // `this.useHelper(name)`; `_prependHelpers` emits every marked entry.
115
132
  const HELPER_MAP = [
116
133
  ["len", HELPER_LEN],
134
+ ["u8enc", HELPER_U8ENC],
135
+ ["u8dec", HELPER_U8DEC],
136
+ ["sv", HELPER_SV],
137
+ ["slice", HELPER_SLICE],
138
+ ["mklen", HELPER_MKLEN],
139
+ ["runestr", HELPER_RUNESTR],
117
140
  ["append", HELPER_APPEND],
118
141
  ["sliceGuard", HELPER_S],
142
+ ["pushs", HELPER_PUSHS],
119
143
  ["sclone", HELPER_SCLONE],
144
+ ["va", HELPER_VA],
145
+ ["vclone", HELPER_VCLONE],
120
146
  ["ifaceBox", HELPER_IFACE_BOX],
121
147
  ["equal", HELPER_EQUAL],
148
+ ["geq", HELPER_GEQ],
149
+ ["ptreq", HELPER_PTREQ],
122
150
  ["ifeq", HELPER_IFEQ],
123
151
  ["cmul", HELPER_CMUL],
124
152
  ["cdiv", HELPER_CDIV],
125
153
  ["panicRt", HELPER_PANIC],
154
+ ["shift", HELPER_SHIFT],
155
+ ["to64", HELPER_TO64],
126
156
  ["nilCall", HELPER_NIL_CALL],
127
- ["sprintf", HELPER_SPRINTF],
157
+ ["bounds", HELPER_BOUNDS],
158
+ ["nilMap", HELPER_NIL_MAP],
159
+ ["nm", HELPER_NM],
160
+ ["structKey", HELPER_STRUCT_KEY],
161
+ ["generics", HELPER_GENERICS],
128
162
  ["error", HELPER_ERROR],
129
- ["errorIs", HELPER_ERROR_IS],
130
- ["errorAs", HELPER_ERROR_AS],
131
- ["errorJoin", HELPER_ERROR_JOIN],
132
- ["strconvError", HELPER_STRCONV_ERROR],
133
- ["pathError", HELPER_PATH_ERROR],
134
- ["ioEOF", HELPER_IO_EOF],
135
- ["strconv", HELPER_STRCONV],
136
- ["pathClean", HELPER_PATH_CLEAN],
137
- ["sortSlice", HELPER_SORT_SLICE],
138
- ["timeFmt", HELPER_TIME_FMT],
139
- ["timeParse", HELPER_TIME_PARSE],
140
- ["duration", HELPER_DURATION],
141
- ["testing", HELPER_TESTING],
142
- ["sync", HELPER_SYNC],
143
- ["cryptoRand", HELPER_CRYPTO_RAND],
144
- ["sha256", HELPER_SHA256],
145
- ["binary", HELPER_BINARY],
146
- ["hex", HELPER_HEX],
147
- ["base64", HELPER_BASE64],
148
- ["json", HELPER_JSON],
163
+ ["jsonrt", HELPER_JSONRT],
149
164
  ["simd", HELPER_SIMD],
150
165
  ];
151
166
 
167
+ // JS bodies of `//gofront:intrinsic` functions (names: INTRINSICS in
168
+ // typechecker/types.js). The WASM backend keeps their empty body.
169
+ const JS_INTRINSICS = {
170
+ rand_source: () => "return Math.random();",
171
+ time_now: () =>
172
+ 'return BigInt(Math.floor(typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now())) * 1000000n;',
173
+ time_sleep: ([ns]) =>
174
+ `await new Promise((r) => setTimeout(r, Number(${ns}) / 1000000));`,
175
+ time_zone_offset: ([unix]) =>
176
+ `return (-new Date(Number(${unix}) * 1000).getTimezoneOffset() * 60) | 0;`,
177
+ // os: node writes straight to the stream; other hosts print whole lines
178
+ // (console.log adds the newline), buffering the partial line.
179
+ os_write: ([fd, s]) =>
180
+ `if (typeof process !== "undefined" && process?.stdout?.write) { (${fd} === 2 && process.stderr?.write ? process.stderr : process.stdout).write(${s}); return; } let b = (globalThis.__os_buf ?? "") + ${s}; const nl = b.lastIndexOf("\\n"); if (nl >= 0) { (${fd} === 2 && typeof console.error === "function" ? console.error : console.log)(b.slice(0, nl)); b = b.slice(nl + 1); } globalThis.__os_buf = b;`,
181
+ os_exit: ([code]) =>
182
+ `if (typeof process !== "undefined" && process?.exit) process.exit(${code}); throw new Error("os.Exit(" + ${code} + ")");`,
183
+ os_getenv: ([key]) =>
184
+ `return (typeof process !== "undefined" && process?.env ? process.env : (globalThis.__os_env ??= {}))[${key}] ?? "";`,
185
+ os_setenv: ([key, value]) =>
186
+ `(typeof process !== "undefined" && process?.env ? process.env : (globalThis.__os_env ??= {}))[${key}] = ${value};`,
187
+ unicode_is: ([r, class_]) => unicodeIsJs(r, class_),
188
+ unicode_to_upper: ([r]) => unicodeToUpperJs(r),
189
+ unicode_to_lower: ([r]) => unicodeToLowerJs(r),
190
+ crypto_rand_u32: () =>
191
+ 'const c = typeof crypto !== "undefined" ? crypto : globalThis.crypto; if (c?.getRandomValues) return c.getRandomValues(new Uint32Array(1))[0]; return Math.floor(Math.random() * 4294967296);',
192
+ // A number per object for the whole run, from 1 (pointers print as 0x<id>).
193
+ testing_nanotime: () => "return BigInt(Math.round(performance.now() * 1e6));",
194
+ testing_match: ([pattern, s]) =>
195
+ `const c = (globalThis.__testRe ??= new Map()); let re = c.get(${pattern}); if (!re) { re = new RegExp(${pattern}); c.set(${pattern}, re); } return re.test(${s});`,
196
+ testing_call: ([f, t]) =>
197
+ `const r = ${f}(${t}); return r && typeof r.then === "function" ? r : null;`,
198
+ fmt_ptr_id: ([v]) =>
199
+ `if (${v} === null || ${v} === undefined || (typeof ${v} !== "object" && typeof ${v} !== "function")) return 0n; const t = (globalThis.__fmtptr ??= { m: new WeakMap(), n: 1 }); let id = t.m.get(${v}); if (!id) { id = t.n++; t.m.set(${v}, id); } return BigInt(id);`,
200
+ math_float64bits: ([f]) =>
201
+ `const $b = new ArrayBuffer(8); new Float64Array($b)[0] = ${f}; return new BigUint64Array($b)[0];`,
202
+ math_float64frombits: ([u]) =>
203
+ `const $b = new ArrayBuffer(8); new BigUint64Array($b)[0] = BigInt(${u}); return new Float64Array($b)[0];`,
204
+ math_float32bits: ([f]) =>
205
+ `const $b = new ArrayBuffer(4); new Float32Array($b)[0] = ${f}; return new Uint32Array($b)[0];`,
206
+ math_float32frombits: ([u]) =>
207
+ `const $b = new ArrayBuffer(4); new Uint32Array($b)[0] = ${u} >>> 0; return new Float32Array($b)[0];`,
208
+ math_mod: ([x, y]) => `return ${x} % ${y};`,
209
+ ...Object.fromEntries(
210
+ [
211
+ "sin",
212
+ "cos",
213
+ "tan",
214
+ "asin",
215
+ "acos",
216
+ "atan",
217
+ "sinh",
218
+ "cosh",
219
+ "tanh",
220
+ "exp",
221
+ "expm1",
222
+ "log",
223
+ "log1p",
224
+ "log2",
225
+ "log10",
226
+ "cbrt",
227
+ ].map((f) => [`math_${f}`, ([x]) => `return Math.${f}(${x});`]),
228
+ ),
229
+ ...Object.fromEntries(
230
+ ["atan2", "pow", "hypot"].map((f) => [
231
+ `math_${f}`,
232
+ ([x, y]) => `return Math.${f}(${x}, ${y});`,
233
+ ]),
234
+ ),
235
+ };
236
+
152
237
  export class CodeGen {
153
238
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
154
239
  // bundledPackages: Set<string> — GoFront package names bundled inline;
@@ -162,19 +247,25 @@ export class CodeGen {
162
247
  this.checker = checker;
163
248
  this.target = options.target ?? "js";
164
249
  this.strict = Boolean(options.strict || this.target === "both");
250
+ this.natives = options.natives !== false; // replace native calls with host code
165
251
  this.out = [];
166
252
  this.indent = 0;
167
253
  this.structNames = new Set();
168
254
  this.structFields = new Map();
169
255
  this.namedWrapperNames = new Set();
170
256
  this.collectedCss = [];
257
+ this.warnings = []; // non-fatal diagnostics; the compiler prints them
258
+ this._precisionWarned = new Set(); // nodes already given a precision warning
259
+ this._srcFileNames = []; // source file name per _srcFileIdx
171
260
  this.jsImports = jsImports;
172
261
  this.bundledPackages = bundledPackages;
173
262
  this.namedReturnVars = null; // names of current function's named return vars
174
263
  this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
175
264
  this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
176
265
  this._boxedVars = new Set(); // address-taken scalar variables that need boxing
177
- this._jsonCodecs = new Map(); // memoized JSON encoders and decoders
266
+ this._hoistedLocals = new Set(); // function-local classes the json codecs name
267
+ this._hasJsonCodecs = false;
268
+ this._jsonCodecFileIdx = -1;
178
269
  // Runtime helper usage tracking — only emit helpers that are actually used
179
270
  this._helpers = new Set();
180
271
  // Per-function context for Go value semantics (see _withFnCtx)
@@ -221,6 +312,9 @@ export class CodeGen {
221
312
  }
222
313
 
223
314
  _emitTypeDecls(program, methods) {
315
+ this._typeDeclsByName = new Map();
316
+ for (const d of program.decls)
317
+ if (d.kind === "TypeDecl") this._typeDeclsByName.set(d.name, d);
224
318
  for (const d of program.decls) {
225
319
  if (d.kind === "TypeDecl") {
226
320
  this._currentSrcFileIdx = d._srcFileIdx ?? 0;
@@ -231,20 +325,28 @@ export class CodeGen {
231
325
  }
232
326
 
233
327
  _emitVarConstDecls(program) {
234
- for (const d of program.decls) {
235
- if (d.kind === "ConstDecl") {
236
- this._currentSrcFileIdx = d._srcFileIdx ?? 0;
237
- this.genConstDecl(d);
238
- this.blank();
328
+ const { consts, vars } = orderPackageInits([program]);
329
+ const emit = (decls, gen, ordered) => {
330
+ const natural = decls.flatMap((d) => d.decls ?? []);
331
+ const inOrder = ordered.every((e, i) => e.spec === natural[i]);
332
+ if (inOrder) {
333
+ for (const d of decls) {
334
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
335
+ gen.call(this, d);
336
+ this.blank();
337
+ }
338
+ return;
239
339
  }
240
- }
241
- for (const d of program.decls) {
242
- if (d.kind === "VarDecl") {
243
- this._currentSrcFileIdx = d._srcFileIdx ?? 0;
244
- this.genVarDecl(d);
340
+ // Initialisers depend on declarations that come later (Go allows it).
341
+ for (const e of ordered) {
342
+ this._currentSrcFileIdx = e.decl._srcFileIdx ?? 0;
343
+ gen.call(this, { ...e.decl, decls: [e.spec] });
245
344
  this.blank();
246
345
  }
247
- }
346
+ };
347
+ const declsOf = (kind) => program.decls.filter((d) => d.kind === kind);
348
+ emit(declsOf("ConstDecl"), this.genConstDecl, consts);
349
+ emit(declsOf("VarDecl"), this.genVarDecl, vars);
248
350
  }
249
351
 
250
352
  _callInitAndMain(initNames, program, isTest = false) {
@@ -262,25 +364,43 @@ export class CodeGen {
262
364
 
263
365
  generate(program, options = {}) {
264
366
  const isTest = options.isTest ?? false;
367
+ this._precisionWarned = new Set();
265
368
  // Defer lowering (argument/receiver hoisting, recover-target marking) is
266
369
  // idempotent and owned by the backends: the wasm path runs it via
267
370
  // `lower()`, the JS path here.
268
371
  normalizeDefers(program);
372
+ // Strict mode: index checks the loop shape proves unnecessary are elided.
373
+ if (this.strict) {
374
+ markInBoundsIndexes(program);
375
+ markRedundantNilChecks(program, (d) => comparesToNil(d.body, d.recvName));
376
+ }
377
+ if (program._filename) this._srcFileNames = [program._filename];
269
378
  // Ensure _pkgName is set on all decls (generateAll sets it for
270
379
  // multi-file; for single-file compiles we derive it from the AST).
271
380
  const pkgName = program.pkg?.name;
272
381
  if (pkgName) {
382
+ this.pkgName = pkgName;
273
383
  for (const d of program.decls) {
274
384
  if (!d._pkgName) d._pkgName = pkgName;
275
385
  }
276
386
  }
277
387
  this._renameReservedIdents(program.decls);
388
+ this._elemAddrTaken =
389
+ takesElementAddress(program.decls) ||
390
+ this.checker?.depElemAddrTaken === true;
391
+ this._boxedGlobals = this._collectBoxedGlobals(program);
392
+ this._boxedVars = new Set(this._boxedGlobals);
393
+ this._collectResliceNames(program);
278
394
  const methods = this._collectDecls(program);
279
395
  this._emitJsImports();
280
396
  this._emitTypeDecls(program, methods);
397
+ // Package variable initialisers may call functions that declare local
398
+ // structs, so the hoisted classes are declared before them.
399
+ const hoistedAt = this.out.length;
400
+ if (this._hasJsonCodecs) this.line("");
281
401
  this._emitVarConstDecls(program);
282
402
  const initNames = this._emitFuncDecls(program);
283
- this._emitJsonCodecs();
403
+ this._emitHoistedLocals(hoistedAt);
284
404
  this._callInitAndMain(initNames, program, isTest);
285
405
  const prependedLines = this._prependHelpers(isTest);
286
406
  return this._finalizeOutput(prependedLines);
@@ -331,12 +451,12 @@ export class CodeGen {
331
451
  }
332
452
 
333
453
  _renameParams(params) {
334
- for (const p of params ?? []) p.name = jsSafeName(p.name);
454
+ for (const p of params ?? []) p.name = jsBindingName(p.name);
335
455
  }
336
456
 
337
457
  _renameNamedReturns(node) {
338
458
  for (const r of node.returnType?._namedReturns ?? [])
339
- r.name = jsSafeName(r.name);
459
+ r.name = jsBindingName(r.name);
340
460
  }
341
461
 
342
462
  _renameSignature(node) {
@@ -345,27 +465,37 @@ export class CodeGen {
345
465
  }
346
466
 
347
467
  _renameIdentNode(node) {
348
- if (!node._isStructKey && node._type?.kind !== "builtin")
349
- node.name = jsSafeName(node.name);
468
+ if (node._isStructKey || node._type?.kind === "builtin") return;
469
+ node.name = node._shadowsGlobal
470
+ ? jsBindingName(node.name)
471
+ : jsSafeName(node.name);
350
472
  }
351
473
 
352
474
  _renameFuncDeclNode(node) {
353
- node.name = jsSafeName(node.name);
475
+ node.name = jsBindingName(node.name);
354
476
  this._renameSignature(node);
355
477
  }
356
478
 
357
479
  _renameMethodDeclNode(node) {
358
- node.recvName = jsSafeName(node.recvName);
480
+ node.recvName = jsBindingName(node.recvName);
359
481
  this._renameSignature(node);
360
482
  }
361
483
 
362
484
  _renameDeclSpecs(node) {
363
485
  for (const spec of node.decls ?? [])
364
- spec.names = spec.names.map(jsSafeName);
486
+ spec.names = spec.names.map(jsBindingName);
487
+ }
488
+
489
+ // `document := 1` declares a binding that shadows the browser global. A
490
+ // `:=` redeclaration (`x, document := 2, 3`) always targets a local of the
491
+ // same scope, which carries the same suffix.
492
+ _renameDefineNode(node) {
493
+ for (const l of node.lhs ?? [])
494
+ if (l.kind === "Ident") l.name = jsBindingName(l.name);
365
495
  }
366
496
 
367
497
  _renameTypeSwitchNode(node) {
368
- node.assign = jsSafeName(node.assign);
498
+ node.assign = jsBindingName(node.assign);
369
499
  }
370
500
 
371
501
  _markStructKeys(node) {
@@ -384,7 +514,16 @@ export class CodeGen {
384
514
  MethodDecl: "_renameMethodDeclNode",
385
515
  FuncLit: "_renameSignature",
386
516
  TemplDecl: "_renameSignature",
517
+ TemplExpr: "_renameTemplExprNode",
518
+ TemplChildren: "_renameTemplExprNode",
519
+ TemplComponent: "_renameTemplExprNode",
520
+ TemplIf: "_renameTemplExprNode",
521
+ TemplSwitch: "_renameTemplExprNode",
522
+ TemplCase: "_renameTemplExprNode",
523
+ TemplFor: "_renameTemplForNode",
524
+ TemplElement: "_renameTemplElementNode",
387
525
  CssDecl: "_renameSignature",
526
+ DefineStmt: "_renameDefineNode",
388
527
  VarDecl: "_renameDeclSpecs",
389
528
  ConstDecl: "_renameDeclSpecs",
390
529
  TypeSwitchStmt: "_renameTypeSwitchNode",
@@ -410,6 +549,18 @@ export class CodeGen {
410
549
  // Collects struct names, method map, and named wrapper names from program decls.
411
550
  // Returns the method map (typeName → MethodDecl[]).
412
551
  _collectStructNames(program) {
552
+ // Go-compiled variadic functions, methods and templ components: a
553
+ // `f(xs...)` call to one passes the slice itself (see `_genCallArgs`,
554
+ // `_emitParamCopies`).
555
+ this.variadicFns = new Set();
556
+ for (const d of program.decls) {
557
+ const last = d.params?.[d.params.length - 1];
558
+ if (!last?.variadic) continue;
559
+ if (d.kind === "FuncDecl" || d.kind === "TemplDecl")
560
+ this.variadicFns.add(d.name);
561
+ else if (d.kind === "MethodDecl")
562
+ this.variadicFns.add(`${d.recvType.name}.${d.name}`);
563
+ }
413
564
  for (const d of program.decls) {
414
565
  if (d.kind === "TypeDecl" && d.type.kind === "StructType")
415
566
  this.structNames.add(d.name);
@@ -427,11 +578,14 @@ export class CodeGen {
427
578
 
428
579
  _collectMethodMap(program) {
429
580
  const methods = new Map();
581
+ this.nilSafeMethods = new Set();
430
582
  for (const d of program.decls) {
431
583
  if (d.kind === "MethodDecl") {
432
584
  const name = d.recvType.name;
433
585
  if (!methods.has(name)) methods.set(name, []);
434
586
  methods.get(name).push(d);
587
+ if (d.recvPointer && comparesToNil(d.body, d.recvName))
588
+ this.nilSafeMethods.add(`${name}.${d.name}`);
435
589
  }
436
590
  }
437
591
  return methods;
@@ -460,30 +614,17 @@ export class CodeGen {
460
614
  /** Marks a runtime helper (key of HELPER_MAP) as needed by the output. */
461
615
  useHelper(name) {
462
616
  this._helpers.add(name);
463
- if (
464
- name === "strconvError" ||
465
- name === "pathError" ||
466
- name === "ioEOF" ||
467
- name === "base64" ||
468
- name === "hex" ||
469
- name === "json"
470
- ) {
471
- this._helpers.add("error");
472
- }
617
+ if (name === "ifeq" || name === "geq") this._helpers.add("equal");
618
+ if (name === "geq") this._helpers.add("ifeq");
473
619
  if (name === "json") {
474
- this.useHelper("base64");
475
- }
476
- if (name === "binary") {
477
- this.useHelper("ioEOF");
478
620
  this._helpers.add("error");
479
621
  }
622
+ if (name === "panicRt") this._helpers.add("error");
623
+ if (name === "append") this.useHelper("slice");
624
+ if (name === "slice") this._helpers.add("sv");
480
625
  }
481
626
 
482
- _prependHelpers(isTest = false) {
483
- if (isTest || this._helpers.has("testing")) {
484
- this.useHelper("sprintf");
485
- this.useHelper("testing");
486
- }
627
+ _prependHelpers(_isTest = false) {
487
628
  const helpers = [];
488
629
  if (this.collectedCss.length > 0) helpers.push(HELPER_INJECT_STYLES);
489
630
  for (const [name, src] of HELPER_MAP) {
@@ -501,16 +642,36 @@ export class CodeGen {
501
642
  // Generate a single bundle from multiple programs (same-package multi-file).
502
643
  // Annotates each decl with its source file index before merging.
503
644
  generateAll(programs, options = {}) {
645
+ this._srcFileNames = programs.map((p) => p._filename ?? null);
646
+ this._jsonCodecFileIdx = programs.findIndex((p) => p._generated);
647
+ this._hasJsonCodecs = this._jsonCodecFileIdx >= 0;
504
648
  for (let i = 0; i < programs.length; i++) {
505
649
  for (const decl of programs[i].decls) {
506
650
  decl._srcFileIdx = i;
507
651
  decl._pkgName = programs[i].pkg?.name;
508
652
  }
509
653
  }
510
- const merged = { decls: programs.flatMap((p) => p.decls) };
654
+ const merged = {
655
+ pkg: programs[0]?.pkg,
656
+ decls: programs.flatMap((p) => p.decls),
657
+ };
511
658
  return this.generate(merged, options);
512
659
  }
513
660
 
661
+ // Records a non-fatal diagnostic at `node` ("main.go at line 3:5: msg").
662
+ _warn(msg, node) {
663
+ const file = this._srcFileNames[this._currentSrcFileIdx ?? 0];
664
+ // Binary and unary expressions carry no position of their own: use the
665
+ // leftmost operand that does.
666
+ let pos = node;
667
+ while (pos && !(pos.line ?? pos._line))
668
+ pos = pos.left ?? pos.operand ?? pos.expr;
669
+ const line = pos?.line ?? pos?._line;
670
+ const at = line ? `at line ${line}:${pos.col ?? pos._col ?? 1}` : "";
671
+ const loc = [file, at].filter(Boolean).join(" ");
672
+ this.warnings.push(loc ? `${loc}: ${msg}` : msg);
673
+ }
674
+
514
675
  getCss() {
515
676
  return [...new Set(this.collectedCss)].join("\n\n");
516
677
  }
@@ -519,24 +680,45 @@ export class CodeGen {
519
680
  return this._srcMappings;
520
681
  }
521
682
 
522
- // Returns a source map JSON string for the last generate() call.
523
- // sources: string[] of source filenames (relative to the output file).
524
- // sourcesContent: string[] of original file contents (embedded for DevTools breakpoints).
525
- getSourceMap(sources, sourcesContent) {
526
- const srcArray = Array.isArray(sources) ? sources : [sources];
527
- return buildSourceMap(srcArray, this._srcMappings, sourcesContent);
528
- }
529
-
530
683
  // ── Type declarations ────────────────────────────────────────
531
684
 
685
+ // `type S Base` with Base a struct declared in this package: the struct
686
+ // AST S inherits its fields from, or null.
687
+ _definedStructAst(decl) {
688
+ if (decl.isAlias || decl.typeParams) return null;
689
+ if (this.checker?.types.get(decl.name)?.underlying?.kind !== "struct")
690
+ return null;
691
+ let t = decl.type;
692
+ for (let depth = 0; depth < 16 && t; depth++) {
693
+ if (t.kind === "StructType") return t;
694
+ // `type S Base[int]`: the fields come from the checked type.
695
+ if (t.kind === "GenericTypeName" || t.kind === "InstantiationExpr")
696
+ return { kind: "StructType", fields: [] };
697
+ if (t.kind !== "TypeName" && t.kind !== "Ident") return null;
698
+ const next = this._typeDeclsByName?.get(t.name);
699
+ if (!next) return null;
700
+ t = next.type;
701
+ }
702
+ return null;
703
+ }
704
+
532
705
  genTypeDeclWithMethods(decl, methodDecls) {
706
+ const definedStruct =
707
+ decl.type.kind === "StructType" ? null : this._definedStructAst(decl);
533
708
  if (decl.type.kind === "StructType") {
534
709
  this.genStruct(decl.name, decl.type, methodDecls);
710
+ } else if (definedStruct) {
711
+ this.genStruct(decl.name, definedStruct, methodDecls);
535
712
  } else if (decl.type.kind === "InterfaceType") {
536
713
  // Interfaces are compile-time only — no JS output needed.
537
714
  this.line(`// interface ${decl.name} (compile-time only)`);
538
- } else if (methodDecls.length > 0) {
715
+ } else if (
716
+ methodDecls.length > 0 ||
717
+ this._isPlainNamedBox(this.checker?.types.get(decl.name))
718
+ ) {
539
719
  // Named non-struct type with methods — emit an ES6 wrapper class.
720
+ if (methodDecls.length === 0)
721
+ this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
540
722
  this.genNamedTypeClass(decl.name, methodDecls);
541
723
  } else {
542
724
  this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
@@ -562,6 +744,7 @@ export class CodeGen {
562
744
  );
563
745
  }
564
746
  }
747
+ this._tagMultiUnwrap(methodDecls);
565
748
  });
566
749
  this.line("}");
567
750
  // Box for *T in an interface. It also proxies `.value` to the pointer, so
@@ -579,22 +762,48 @@ export class CodeGen {
579
762
  `${m.name}(...__a) { return ${name}.$${m.name}(${recv}, ...__a); }`,
580
763
  );
581
764
  }
765
+ this._tagMultiUnwrap(methodDecls);
582
766
  });
583
767
  this.line("}");
584
768
  }
585
769
 
770
+ // `Unwrap() []error` on a box class is told apart from `Unwrap() error`
771
+ // in type tests, as on struct classes.
772
+ _tagMultiUnwrap(methodDecls) {
773
+ const unwrap = methodDecls.find(
774
+ (m) => m.name === "Unwrap" && m.returnType?.kind === "SliceType",
775
+ );
776
+ if (unwrap) this.line("static { this.prototype.Unwrap.__multi = true; }");
777
+ }
778
+
586
779
  _namedBoxField(underlying) {
587
780
  if (underlying?.kind === "func") return "_fn";
588
781
  if (underlying?.kind === "map") return "_map";
589
782
  return "_items";
590
783
  }
591
784
 
785
+ // A user-declared named basic type without methods: still boxed in an
786
+ // interface so `any(L("x")).(string)` fails like Go.
787
+ _isPlainNamedBox(type) {
788
+ if (type?.kind !== "named" || type._generic) return false;
789
+ if (type._target === undefined || type._target === "wasm") return false;
790
+ if (type.underlying?.kind !== "basic" || type.methods?.size > 0)
791
+ return false;
792
+ return type.methods instanceof Map;
793
+ }
794
+
592
795
  // Box/dispatch info for a named non-struct type with methods, or null.
593
796
  _namedBoxInfo(type) {
594
797
  if (type?.kind !== "named") return null;
595
798
  const u = type.underlying;
596
799
  if (!u || u.kind === "struct" || u.kind === "interface") return null;
597
800
  const methods = type.methods ?? type._generic?.methods;
801
+ if (this._isPlainNamedBox(type))
802
+ return {
803
+ cls: this._dequalify(type.name),
804
+ field: this._namedBoxField(u),
805
+ methods: new Map(),
806
+ };
598
807
  if (!methods || methods.size === 0) return null;
599
808
  if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
600
809
  return null;
@@ -626,9 +835,11 @@ export class CodeGen {
626
835
  const resolved = this.checker.types.get(typeName)?.underlying;
627
836
  if (resolved?.kind === "struct") {
628
837
  const fields = [];
629
- for (const [name, type] of resolved.fields.entries()) {
630
- fields.push({ name, zero: this.zeroValueForType(type) });
631
- }
838
+ // Cached per type name, so never bound to the current generic body.
839
+ this._withTypeParamZeros(null, () => {
840
+ for (const [name, type] of resolved.fields.entries())
841
+ fields.push({ name, zero: this.zeroValueForType(type) });
842
+ });
632
843
  this.structFields.set(typeName, fields);
633
844
  return fields;
634
845
  }
@@ -661,17 +872,69 @@ export class CodeGen {
661
872
  this.line("}");
662
873
  }
663
874
 
875
+ // A generic struct's constructor takes the instance's zero-value factories
876
+ // after the fields (see HELPER_GENERICS). A field whose zero value depends
877
+ // on a type parameter starts at it when its argument is undefined.
878
+ _genTypeParamBindings(tpNames, tz) {
879
+ tpNames.forEach((n, i) => {
880
+ this.line(`const $z_${n} = ${tz}?.[${i}] ?? __zn;`);
881
+ });
882
+ }
883
+
884
+ _genGenericStructConstructor(fields, tpNames) {
885
+ const usesTz = (f) => f.zero.includes("$z_");
886
+ const params = fields.map((f) =>
887
+ usesTz(f) ? `${f.name}$` : `${f.name}$ = ${f.zero}`,
888
+ );
889
+ this.line(`constructor(${[...params, "$tz"].join(", ")}) {`);
890
+ this.indented(() => {
891
+ this.line("this[__tz] = $tz;");
892
+ if (fields.some(usesTz)) this._genTypeParamBindings(tpNames, "$tz");
893
+ for (const f of fields) {
894
+ const v = usesTz(f)
895
+ ? `${f.name}$ === undefined ? ${f.zero} : ${f.name}$`
896
+ : `${f.name}$`;
897
+ this.line(`this.${f.name} = ${v};`);
898
+ }
899
+ });
900
+ this.line("}");
901
+ }
902
+
903
+ // JS object keys are strings: a struct used as a map key becomes an
904
+ // injective encoding of its fields (see HELPER_STRUCT_KEY), so equal values
905
+ // share a key and distinct values never collide.
906
+ _genStructKeyString(_name, fields) {
907
+ if (fields.some((f) => f.name === "toString")) return;
908
+ if (fields.length === 0) {
909
+ this.line('toString() { return "{}"; }');
910
+ return;
911
+ }
912
+ this.useHelper("structKey");
913
+ this.line("toString() { return __sk(this); }");
914
+ }
915
+
664
916
  _genStructClone(name, structTypeAst, fields) {
665
917
  const resolved = this.checker?.types.get(name)?.underlying;
666
918
  const astTypes = new Map();
667
919
  for (const f of structTypeAst?.fields ?? [])
668
920
  for (const n of f.names ?? []) astTypes.set(n, f.type);
669
- const args = fields.map((f) => {
670
- const t =
671
- resolved?.fields?.get(f.name) ??
672
- this._typeFromNode(astTypes.get(f.name));
673
- return this._cloneJs(t, `this.${f.name}`);
674
- });
921
+ const tpNames = this._genericTypeParamNames(name);
922
+ const prevZeros = this._typeParamZeros;
923
+ this._typeParamZeros = tpNames
924
+ ? new Map(tpNames.map((n, i) => [n, `this[__tz]?.[${i}]`]))
925
+ : null;
926
+ let args;
927
+ try {
928
+ args = fields.map((f) => {
929
+ const t =
930
+ resolved?.fields?.get(f.name) ??
931
+ this._typeFromNode(astTypes.get(f.name));
932
+ return this._cloneJs(t, `this.${f.name}`);
933
+ });
934
+ } finally {
935
+ this._typeParamZeros = prevZeros;
936
+ }
937
+ if (tpNames) args.push("this[__tz]");
675
938
  this.blank();
676
939
  this.line(`__clone() { return new ${name}(${args.join(", ")}); }`);
677
940
  }
@@ -685,18 +948,38 @@ export class CodeGen {
685
948
  );
686
949
  }
687
950
 
951
+ // Type parameter names of generic type `name`, or null.
952
+ _genericTypeParamNames(name) {
953
+ const generic = this.checker?.types.get(name)?._generic;
954
+ if (!generic?.typeParams?.length || generic.isAlias) return null;
955
+ return generic.typeParams.map((tp) => tp.name);
956
+ }
957
+
688
958
  genStruct(name, structTypeAst, methodDecls) {
689
- const fields = this._collectStructFields(name, structTypeAst);
690
- this.structFields.set(name, fields);
959
+ const tpNames = this._genericTypeParamNames(name);
960
+ if (tpNames) this.useHelper("generics");
961
+ const fields = this._withTypeParamZeros(tpNames, () =>
962
+ this._collectStructFields(name, structTypeAst),
963
+ );
964
+ this.structFields.set(
965
+ name,
966
+ tpNames
967
+ ? this._withTypeParamZeros(null, () =>
968
+ this._collectStructFields(name, structTypeAst),
969
+ )
970
+ : fields,
971
+ );
691
972
  this.line(`class ${name} {`);
692
973
  this.indented(() => {
693
- this._genStructConstructor(fields);
974
+ if (tpNames) this._genGenericStructConstructor(fields, tpNames);
975
+ else this._genStructConstructor(fields);
694
976
  for (const m of methodDecls) {
695
977
  this.blank();
696
978
  this.genMethod(m);
697
979
  }
698
980
  this._genEmbeddedMethodStubs(name, methodDecls);
699
981
  this._genStructClone(name, structTypeAst, fields);
982
+ this._genStructKeyString(name, fields);
700
983
  });
701
984
  this.line("}");
702
985
  // Lets generic `p.value` dereferences resolve to the struct itself.
@@ -705,29 +988,68 @@ export class CodeGen {
705
988
  `Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
706
989
  );
707
990
  }
991
+ this._genValueMethodMeta(name, methodDecls);
708
992
  }
709
993
 
710
- _emitJsonCodecs() {
711
- if (!this._jsonCodecs || this._jsonCodecs.size === 0) return;
712
- for (const [, code] of this._jsonCodecs) {
713
- this.line(code);
714
- this.blank();
715
- }
994
+ // Names of the value-receiver methods: a typed nil pointer in an interface
995
+ // panics when one is called (see __mknil).
996
+ _genValueMethodMeta(name, methodDecls) {
997
+ const vm = methodDecls.filter((m) => !m.recvPointer).map((m) => m.name);
998
+ if (vm.length > 0)
999
+ this.line(
1000
+ `Object.defineProperty(${name}, "__vm", { value: ${JSON.stringify(vm)} });`,
1001
+ );
1002
+ // Pointer-receiver methods are outside the method set of a struct value
1003
+ // held in an interface (see _ifaceMethodCheck).
1004
+ const pm = methodDecls.filter((m) => m.recvPointer).map((m) => m.name);
1005
+ // ...and so are those promoted from an embedded struct value.
1006
+ for (const stub of computeEmbeddedStubs(name, methodDecls, this.checker))
1007
+ if (stub.ptrOnly) pm.push(stub.methodName);
1008
+ if (pm.length > 0)
1009
+ this.line(
1010
+ `Object.defineProperty(${name}, "__pm", { value: ${JSON.stringify(pm)} });`,
1011
+ );
716
1012
  }
717
1013
 
718
- _genSingleEmbedStubs(embed, declared) {
719
- const embedName = embed.kind === "named" ? embed.name : null;
720
- if (!embedName) return;
721
- const embedBase = embed.kind === "named" ? embed.underlying : embed;
722
- if (embedBase?.kind !== "struct" || !embedBase.methods) return;
723
- for (const [mName] of embedBase.methods.entries()) {
724
- if (!declared.has(mName)) {
725
- this.blank();
726
- this.line(
727
- `${mName}(...__a) { return ${embedName}.prototype.${mName}.call(this, ...__a); }`,
728
- );
729
- }
730
- }
1014
+ // The generated json codecs live at package level and name function-local
1015
+ // struct classes: those are declared once at module level and assigned
1016
+ // where the Go source declares them.
1017
+ _hoistLocalClass(stmt, start) {
1018
+ if (!this._hasJsonCodecs || stmt.type.kind !== "StructType") return;
1019
+ const open = this.out.findIndex(
1020
+ (l, i) => i >= start && l.trimStart().startsWith(`class ${stmt.name} `),
1021
+ );
1022
+ if (open < 0) return;
1023
+ const indent = this.out[open].match(/^\s*/)[0];
1024
+ const close = this.out.findIndex((l, i) => i > open && l === `${indent}}`);
1025
+ if (close < 0) return;
1026
+ this.out[open] =
1027
+ `${indent}${stmt.name} = ${stmt.name}.__ph ? ${this.out[open].trimStart()}`;
1028
+ this.out[close] = `${indent}} : ${stmt.name};`;
1029
+ this._hoistedLocals.add(stmt.name);
1030
+ }
1031
+
1032
+ _emitHoistedLocals(at) {
1033
+ if (this._hoistedLocals.size > 0 && this._hasJsonCodecs)
1034
+ this.out[at] =
1035
+ `var ${[...this._hoistedLocals].map((n) => `${n} = class { static __ph = true; }`).join(", ")};`;
1036
+ }
1037
+
1038
+ // A promoted method runs on the embedded value (`this.In`), so it and the
1039
+ // outer struct's promoted-field accesses see the same storage. Embedded
1040
+ // types that are not compiled Go structs keep the borrowed-`this` form.
1041
+ _embedStub(method, embedName, valueEmbed = false, fieldName = null) {
1042
+ // The field is named after the type without its package suffix
1043
+ // (`Mutex$$sync` is the field `Mutex`), or after the alias it embeds.
1044
+ const field = fieldName || embedName.replace(/\$\$.*$/, "");
1045
+ // A named non-struct type is boxed: its methods are static, taking
1046
+ // the value as the receiver.
1047
+ if (valueEmbed)
1048
+ return `${method}(...__a) { return ${embedName}.$${method}(this.${field}, ...__a); }`;
1049
+ const call = this.structNames.has(embedName)
1050
+ ? `this.${field}.${method}(...__a)`
1051
+ : `${embedName}.prototype.${method}.call(this, ...__a)`;
1052
+ return `${method}(...__a) { return ${call}; }`;
731
1053
  }
732
1054
 
733
1055
  _genEmbeddedMethodStubs(name, methodDecls) {
@@ -735,7 +1057,12 @@ export class CodeGen {
735
1057
  for (const stub of stubs) {
736
1058
  this.blank();
737
1059
  this.line(
738
- `${stub.methodName}(...__a) { return ${stub.embedName}.prototype.${stub.methodName}.call(this, ...__a); }`,
1060
+ this._embedStub(
1061
+ stub.methodName,
1062
+ stub.embedName,
1063
+ stub.valueEmbed,
1064
+ stub.fieldName,
1065
+ ),
739
1066
  );
740
1067
  }
741
1068
  }
@@ -751,12 +1078,35 @@ export class CodeGen {
751
1078
  decl._line ?? null,
752
1079
  );
753
1080
  const prevBoxed = this._boxedVars;
754
- this._boxedVars = new Set();
1081
+ this._boxedVars = new Set(this._boxedGlobals);
755
1082
  this._scanAddressTaken(decl.body);
1083
+ // Methods of a generic struct read its type arguments from the
1084
+ // instance; named non-struct types have no instance to carry them.
1085
+ const tpNames = staticNamed
1086
+ ? null
1087
+ : this._genericTypeParamNames(decl.recvType?.name);
1088
+ this._withTypeParamZeros(tpNames, () =>
1089
+ this._genMethodBody(decl, staticNamed, recvParam, tpNames),
1090
+ );
1091
+ this._boxedVars = prevBoxed;
1092
+ this.line("}");
1093
+ // `Unwrap() []error` is told apart from `Unwrap() error` in type tests.
1094
+ if (
1095
+ !staticNamed &&
1096
+ decl.name === "Unwrap" &&
1097
+ decl.returnType?.kind === "SliceType"
1098
+ )
1099
+ this.line("static { this.prototype.Unwrap.__multi = true; }");
1100
+ this._currentSrcFileIdx = prevFileIdx;
1101
+ }
1102
+
1103
+ _genMethodBody(decl, staticNamed, recvParam, tpNames) {
756
1104
  this._withFnCtx(decl.body, () =>
757
1105
  this.indented(() => {
1106
+ if (tpNames) this._genTypeParamBindings(tpNames, "this?.[__tz]");
1107
+ if (decl.recvName) this._fnCtx.params.add(decl.recvName);
758
1108
  if (!staticNamed && decl.recvName && decl.recvName !== "_") {
759
- this.line(`const ${decl.recvName} = ${this._receiverValue(decl)};`);
1109
+ this.line(`let ${decl.recvName} = ${this._receiverValue(decl)};`);
760
1110
  }
761
1111
  if (staticNamed && this._boxedVars.has(recvParam)) {
762
1112
  this.line(`${recvParam} = { value: ${recvParam} };`);
@@ -765,14 +1115,18 @@ export class CodeGen {
765
1115
  this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
766
1116
  }),
767
1117
  );
768
- this._boxedVars = prevBoxed;
769
- this.line("}");
770
- this._currentSrcFileIdx = prevFileIdx;
1118
+ }
1119
+
1120
+ // JS parameter names; a variadic Go parameter is a rest parameter.
1121
+ _paramNames(params) {
1122
+ return params.map((p, i) =>
1123
+ p.variadic && i === params.length - 1 ? `...${p.name}` : p.name,
1124
+ );
771
1125
  }
772
1126
 
773
1127
  // Static methods of named non-struct types take the receiver as first parameter.
774
1128
  _methodSignature(decl, staticNamed, recvParam) {
775
- const params = decl.params.map((p) => p.name);
1129
+ const params = this._paramNames(decl.params);
776
1130
  const asyncPrefix = decl.async ? "async " : "";
777
1131
  if (!staticNamed)
778
1132
  return `${asyncPrefix}${decl.name}(${params.join(", ")}) {`;
@@ -803,17 +1157,33 @@ export class CodeGen {
803
1157
  // ── Function declarations ────────────────────────────────────
804
1158
 
805
1159
  genFuncDecl(decl, nameOverride) {
1160
+ const tpNames = decl.typeParams?.length
1161
+ ? decl.typeParams.map((tp) => tp.name)
1162
+ : null;
1163
+ this._withTypeParamZeros(tpNames, () =>
1164
+ this._genFuncDecl(decl, nameOverride, tpNames),
1165
+ );
1166
+ }
1167
+
1168
+ // A generic function takes one zero-value factory per type parameter
1169
+ // before its parameters (see HELPER_GENERICS).
1170
+ _genFuncDecl(decl, nameOverride, tpNames) {
806
1171
  const name = nameOverride ?? decl.name;
807
- const params = decl.params
808
- .map((p, i) =>
809
- p.variadic && i === decl.params.length - 1 ? `...${p.name}` : p.name,
810
- )
811
- .join(", ");
1172
+ const params = [
1173
+ ...(tpNames ?? []).map((n) => `$z_${n}`),
1174
+ ...this._paramNames(decl.params),
1175
+ ].join(", ");
812
1176
  const asyncPrefix = decl.async ? "async " : "";
813
1177
  const srcLine = decl._line ?? null;
814
1178
  this.line(`${asyncPrefix}function ${name}(${params}) {`, srcLine);
1179
+ if (decl.intrinsic) {
1180
+ const args = decl.params.map((p) => p.name);
1181
+ this.indented(() => this.line(JS_INTRINSICS[decl.intrinsic](args)));
1182
+ this.line("}");
1183
+ return;
1184
+ }
815
1185
  const prevBoxed = this._boxedVars;
816
- this._boxedVars = new Set();
1186
+ this._boxedVars = new Set(this._boxedGlobals);
817
1187
  this._scanAddressTaken(decl.body);
818
1188
  this._withFnCtx(decl.body, () =>
819
1189
  this.indented(() => {
@@ -836,6 +1206,7 @@ export class CodeGen {
836
1206
  const { name, type } = named[i];
837
1207
  const varName = !name || name === "_" ? `__ret_blank$${i}` : name;
838
1208
  this._markOwnership(varName, true);
1209
+ this._fnCtx?.params.add(varName);
839
1210
  this.line(`let ${varName} = ${this.zeroValueForTypeNode(type)};`);
840
1211
  }
841
1212
  this.namedReturnVars = named.map((r, i) =>
@@ -910,11 +1281,25 @@ export class CodeGen {
910
1281
  this._recoverOkInScope = prevRecoverOk;
911
1282
  }
912
1283
 
1284
+ // Package-level variables whose address is taken anywhere (`&g`, `g.Inc()`
1285
+ // on a pointer-receiver method) are boxed like address-taken locals.
1286
+ _collectBoxedGlobals(program) {
1287
+ const names = new Set();
1288
+ for (const d of program.decls)
1289
+ if (d.kind === "VarDecl")
1290
+ for (const spec of d.decls) for (const n of spec.names) names.add(n);
1291
+ const boxed = new Set();
1292
+ if (names.size === 0) return boxed;
1293
+ for (const n of this._scanAddressTaken(program.decls, new Set()))
1294
+ if (names.has(n)) boxed.add(n);
1295
+ return boxed;
1296
+ }
1297
+
913
1298
  // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
914
1299
  // A pointer-receiver method call on a named non-struct local (`l.Set(5)`)
915
1300
  // takes the variable's address too, so the variable must be boxed.
916
- _scanAddressTaken(node) {
917
- scanAddressTaken(node, this._boxedVars);
1301
+ _scanAddressTaken(node, into = this._boxedVars) {
1302
+ scanAddressTaken(node, into);
918
1303
  const visit = (n) => {
919
1304
  if (!n || typeof n !== "object") return;
920
1305
  if (Array.isArray(n)) {
@@ -927,7 +1312,7 @@ export class CodeGen {
927
1312
  recv?.kind === "Ident" &&
928
1313
  this._namedPtrMethodOn(recv._type, n.func.field)
929
1314
  )
930
- this._boxedVars.add(recv.name);
1315
+ into.add(recv.name);
931
1316
  }
932
1317
  for (const key of Object.keys(n)) {
933
1318
  if (key.startsWith("_") || key === "kind") continue;
@@ -935,6 +1320,7 @@ export class CodeGen {
935
1320
  }
936
1321
  };
937
1322
  visit(node);
1323
+ return into;
938
1324
  }
939
1325
 
940
1326
  _namedPtrMethodOn(type, method) {
@@ -942,48 +1328,72 @@ export class CodeGen {
942
1328
  return Boolean(info?.methods.get(method)?._ptrRecv);
943
1329
  }
944
1330
 
945
- _isReferenceType(t) {
946
- return isReferenceType(t);
947
- }
948
-
949
1331
  // ── Variable / const declarations ────────────────────────────
950
1332
 
951
1333
  genVarDecl(decl) {
952
1334
  for (const spec of decl.decls) {
953
- if (spec.value) {
954
- const paired = spec.value.length === spec.names.length;
955
- const vals = spec.value.map((v, i) => {
956
- const js = paired
957
- ? this._genDeclValue(spec.names[i], v)
958
- : this.genExpr(v);
959
- // Wrap numeric values assigned to complex-typed vars
960
- if (
961
- spec.type?.name === "complex128" ||
962
- spec.type?.name === "complex64"
963
- ) {
964
- if (
965
- !isComplex(v._type) &&
966
- (isNumeric(v._type) || v._type?.kind === "untyped")
967
- ) {
968
- return `{ re: ${js}, im: 0 }`;
969
- }
970
- }
971
- return js;
972
- });
973
- this._emitVarBindings(spec.names, vals, paired);
974
- } else {
975
- const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
976
- for (const name of spec.names) {
977
- this._markOwnership(name, true);
978
- const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
979
- this.line(`let ${name} = ${val};`);
1335
+ if (spec._commaOk) this._genDefineStmt(this._varSpecAsDefine(spec));
1336
+ else if (spec.value) this._genVarSpecValues(spec);
1337
+ else this._genVarSpecZero(spec);
1338
+ }
1339
+ }
1340
+
1341
+ _genVarSpecValues(spec) {
1342
+ const paired = spec.value.length === spec.names.length;
1343
+ const vals = spec.value.map((v, i) => {
1344
+ const js = paired
1345
+ ? this._genDeclValue(spec.names[i], v)
1346
+ : this.genExpr(v);
1347
+ // Wrap numeric values assigned to complex-typed vars
1348
+ if (spec.type?.name === "complex128" || spec.type?.name === "complex64") {
1349
+ if (
1350
+ !isComplex(v._type) &&
1351
+ !v._asComplex &&
1352
+ (isNumeric(v._type) || v._type?.kind === "untyped")
1353
+ ) {
1354
+ return `{ re: ${js}, im: 0 }`;
1355
+ }
1356
+ }
1357
+ if (spec.type?.name === "int64" || spec.type?.name === "uint64") {
1358
+ if (
1359
+ v._type?.kind === "untyped" &&
1360
+ !js.endsWith("n") &&
1361
+ /^-?\d+$/.test(js)
1362
+ ) {
1363
+ return `${js}n`;
980
1364
  }
981
1365
  }
1366
+ return js;
1367
+ });
1368
+ this._emitVarBindings(spec.names, vals, paired);
1369
+ }
1370
+
1371
+ _genVarSpecZero(spec) {
1372
+ const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
1373
+ for (const name of spec.names) {
1374
+ if (name === "_") continue;
1375
+ this._markOwnership(name, true);
1376
+ const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
1377
+ this.line(`let ${name} = ${val};`);
982
1378
  }
983
1379
  }
984
1380
 
1381
+ // `var v, ok = m[k]` generates like `v, ok := m[k]`.
1382
+ _varSpecAsDefine(spec) {
1383
+ return {
1384
+ kind: "DefineStmt",
1385
+ lhs: spec.names.map((name) => ({ kind: "Ident", name })),
1386
+ rhs: spec.value,
1387
+ };
1388
+ }
1389
+
985
1390
  // `var a = v` / `var a, b = v1, v2` / `var a, b = f()`, boxing address-taken names.
986
1391
  _emitVarBindings(names, vals, paired) {
1392
+ if (names.length === 1 && names[0] === "_") {
1393
+ // A blank binding only runs its initialiser (`var _ = f()` may repeat).
1394
+ this.line(`void (${vals[0]});`);
1395
+ return;
1396
+ }
987
1397
  if (names.length === 1) {
988
1398
  const val = this._boxedVars.has(names[0])
989
1399
  ? `{ value: ${vals[0]} }`
@@ -992,7 +1402,8 @@ export class CodeGen {
992
1402
  return;
993
1403
  }
994
1404
  const rhs = paired ? `[${vals.join(", ")}]` : vals[0];
995
- this.line(`let [${names.join(", ")}] = ${rhs};`);
1405
+ const targets = names.map((n) => (n === "_" ? "" : n));
1406
+ this.line(`let [${targets.join(", ")}] = ${rhs};`);
996
1407
  this._boxDeclaredNames(names);
997
1408
  }
998
1409
 
@@ -1012,5 +1423,6 @@ export class CodeGen {
1012
1423
 
1013
1424
  Object.assign(CodeGen.prototype, statementGenMethods);
1014
1425
  Object.assign(CodeGen.prototype, expressionGenMethods);
1426
+ Object.assign(CodeGen.prototype, typeInfoGenMethods);
1015
1427
  Object.assign(CodeGen.prototype, stdlibGenMethods);
1016
1428
  Object.assign(CodeGen.prototype, templGenMethods);