gofront 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -20,67 +20,63 @@
20
20
  import {
21
21
  computeEmbeddedStubs,
22
22
  hasDirectRecover,
23
- isReferenceType,
24
23
  markInBoundsIndexes,
24
+ markRedundantNilChecks,
25
25
  normalizeDefers,
26
26
  scanAddressTaken,
27
+ splitForwardedTuples,
28
+ takesElementAddress,
27
29
  } from "../../lower/index.js";
28
30
  import { orderPackageInits } from "../../typechecker/init-order.js";
29
31
  import {
30
32
  BROWSER_GLOBALS,
31
33
  isComplex,
34
+ isMethodPromotedOverField,
32
35
  isNumeric,
33
36
  } from "../../typechecker/types.js";
34
- import { comparesToNil, expressionGenMethods } from "./expressions.js";
37
+ import { goTypeName } from "../shared/fmt-types.js";
38
+ import {
39
+ unicodeIsJs,
40
+ unicodeToLowerJs,
41
+ unicodeToUpperJs,
42
+ } from "../shared/host-unicode.js";
43
+ import {
44
+ comparesToNil,
45
+ derefsReceiverFirst,
46
+ expressionGenMethods,
47
+ } from "./expressions.js";
35
48
  import {
36
49
  HELPER_APPEND,
37
- HELPER_BASE64,
38
- HELPER_BINARY,
39
50
  HELPER_BOUNDS,
40
51
  HELPER_CDIV,
41
52
  HELPER_CMUL,
42
- HELPER_CRYPTO_RAND,
43
- HELPER_DURATION,
44
53
  HELPER_EQUAL,
45
54
  HELPER_ERROR,
46
- HELPER_ERROR_AS,
47
- HELPER_ERROR_IS,
48
- HELPER_ERROR_JOIN,
49
55
  HELPER_GENERICS,
50
56
  HELPER_GEQ,
51
- HELPER_HEX,
52
57
  HELPER_IFACE_BOX,
53
- HELPER_IFD,
54
58
  HELPER_IFEQ,
55
59
  HELPER_INJECT_STYLES,
56
- HELPER_IO_EOF,
57
- HELPER_JSON,
60
+ HELPER_JSONRT,
58
61
  HELPER_LEN,
62
+ HELPER_MKLEN,
59
63
  HELPER_NIL_CALL,
60
64
  HELPER_NIL_MAP,
61
65
  HELPER_NM,
62
- HELPER_OS,
63
66
  HELPER_PANIC,
64
- HELPER_PATH_CLEAN,
65
- HELPER_PATH_ERROR,
66
67
  HELPER_PTREQ,
68
+ HELPER_PUSHS,
69
+ HELPER_RUNESTR,
67
70
  HELPER_S,
68
71
  HELPER_SCLONE,
69
- HELPER_SHA256,
70
72
  HELPER_SHIFT,
71
73
  HELPER_SIMD,
72
74
  HELPER_SLICE,
73
- HELPER_SORT_SLICE,
74
- HELPER_SPRINT,
75
- HELPER_SPRINTF,
76
- HELPER_STRCONV,
77
- HELPER_STRCONV_ERROR,
78
75
  HELPER_STRUCT_KEY,
79
76
  HELPER_SV,
80
- HELPER_SYNC,
81
- HELPER_TESTING,
82
- HELPER_TIME_FMT,
83
- HELPER_TIME_PARSE,
77
+ HELPER_TO64,
78
+ HELPER_U8DEC,
79
+ HELPER_U8ENC,
84
80
  HELPER_VA,
85
81
  HELPER_VCLONE,
86
82
  } from "./runtime.js";
@@ -142,15 +138,19 @@ const jsBindingName = (n) =>
142
138
  // `this.useHelper(name)`; `_prependHelpers` emits every marked entry.
143
139
  const HELPER_MAP = [
144
140
  ["len", HELPER_LEN],
141
+ ["u8enc", HELPER_U8ENC],
142
+ ["u8dec", HELPER_U8DEC],
145
143
  ["sv", HELPER_SV],
146
144
  ["slice", HELPER_SLICE],
145
+ ["mklen", HELPER_MKLEN],
146
+ ["runestr", HELPER_RUNESTR],
147
147
  ["append", HELPER_APPEND],
148
148
  ["sliceGuard", HELPER_S],
149
+ ["pushs", HELPER_PUSHS],
149
150
  ["sclone", HELPER_SCLONE],
150
151
  ["va", HELPER_VA],
151
152
  ["vclone", HELPER_VCLONE],
152
153
  ["ifaceBox", HELPER_IFACE_BOX],
153
- ["ifd", HELPER_IFD],
154
154
  ["equal", HELPER_EQUAL],
155
155
  ["geq", HELPER_GEQ],
156
156
  ["ptreq", HELPER_PTREQ],
@@ -159,42 +159,87 @@ const HELPER_MAP = [
159
159
  ["cdiv", HELPER_CDIV],
160
160
  ["panicRt", HELPER_PANIC],
161
161
  ["shift", HELPER_SHIFT],
162
+ ["to64", HELPER_TO64],
162
163
  ["nilCall", HELPER_NIL_CALL],
163
164
  ["bounds", HELPER_BOUNDS],
164
165
  ["nilMap", HELPER_NIL_MAP],
165
166
  ["nm", HELPER_NM],
166
167
  ["structKey", HELPER_STRUCT_KEY],
167
168
  ["generics", HELPER_GENERICS],
168
- ["sprintf", HELPER_SPRINTF],
169
- ["sprint", HELPER_SPRINT],
170
169
  ["error", HELPER_ERROR],
171
- ["errorIs", HELPER_ERROR_IS],
172
- ["errorAs", HELPER_ERROR_AS],
173
- ["errorJoin", HELPER_ERROR_JOIN],
174
- ["strconvError", HELPER_STRCONV_ERROR],
175
- ["pathError", HELPER_PATH_ERROR],
176
- ["ioEOF", HELPER_IO_EOF],
177
- ["os", HELPER_OS],
178
- ["strconv", HELPER_STRCONV],
179
- ["pathClean", HELPER_PATH_CLEAN],
180
- ["sortSlice", HELPER_SORT_SLICE],
181
- ["timeFmt", HELPER_TIME_FMT],
182
- ["timeParse", HELPER_TIME_PARSE],
183
- ["duration", HELPER_DURATION],
184
- ["testing", HELPER_TESTING],
185
- ["sync", HELPER_SYNC],
186
- ["cryptoRand", HELPER_CRYPTO_RAND],
187
- ["sha256", HELPER_SHA256],
188
- ["binary", HELPER_BINARY],
189
- ["hex", HELPER_HEX],
190
- ["base64", HELPER_BASE64],
191
- ["json", HELPER_JSON],
170
+ ["jsonrt", HELPER_JSONRT],
192
171
  ["simd", HELPER_SIMD],
193
172
  ];
194
173
 
195
174
  // JS bodies of `//gofront:intrinsic` functions (names: INTRINSICS in
196
175
  // typechecker/types.js). The WASM backend keeps their empty body.
197
- const JS_INTRINSICS = {};
176
+ const JS_INTRINSICS = {
177
+ rand_source: () => "return Math.random();",
178
+ time_now: () =>
179
+ 'return BigInt(Math.floor(typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now())) * 1000000n;',
180
+ time_sleep: ([ns]) =>
181
+ `await new Promise((r) => setTimeout(r, Number(${ns}) / 1000000));`,
182
+ time_zone_offset: ([unix]) =>
183
+ `return (-new Date(Number(${unix}) * 1000).getTimezoneOffset() * 60) | 0;`,
184
+ // os: node writes straight to the stream; other hosts print whole lines
185
+ // (console.log adds the newline), buffering the partial line.
186
+ os_write: ([fd, s]) =>
187
+ `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;`,
188
+ os_exit: ([code]) =>
189
+ `if (typeof process !== "undefined" && process?.exit) process.exit(${code}); throw new Error("os.Exit(" + ${code} + ")");`,
190
+ os_getenv: ([key]) =>
191
+ `return (typeof process !== "undefined" && process?.env ? process.env : (globalThis.__os_env ??= {}))[${key}] ?? "";`,
192
+ os_setenv: ([key, value]) =>
193
+ `(typeof process !== "undefined" && process?.env ? process.env : (globalThis.__os_env ??= {}))[${key}] = ${value};`,
194
+ unicode_is: ([r, class_]) => unicodeIsJs(r, class_),
195
+ unicode_to_upper: ([r]) => unicodeToUpperJs(r),
196
+ unicode_to_lower: ([r]) => unicodeToLowerJs(r),
197
+ crypto_rand_u32: () =>
198
+ 'const c = typeof crypto !== "undefined" ? crypto : globalThis.crypto; if (c?.getRandomValues) return c.getRandomValues(new Uint32Array(1))[0]; return Math.floor(Math.random() * 4294967296);',
199
+ // A number per object for the whole run, from 1 (pointers print as 0x<id>).
200
+ testing_nanotime: () => "return BigInt(Math.round(performance.now() * 1e6));",
201
+ testing_match: ([pattern, s]) =>
202
+ `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});`,
203
+ testing_call: ([f, t]) =>
204
+ `const r = ${f}(${t}); return r && typeof r.then === "function" ? r : null;`,
205
+ fmt_ptr_id: ([v]) =>
206
+ `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);`,
207
+ math_float64bits: ([f]) =>
208
+ `const $b = new ArrayBuffer(8); new Float64Array($b)[0] = ${f}; return new BigUint64Array($b)[0];`,
209
+ math_float64frombits: ([u]) =>
210
+ `const $b = new ArrayBuffer(8); new BigUint64Array($b)[0] = BigInt(${u}); return new Float64Array($b)[0];`,
211
+ math_float32bits: ([f]) =>
212
+ `const $b = new ArrayBuffer(4); new Float32Array($b)[0] = ${f}; return new Uint32Array($b)[0];`,
213
+ math_float32frombits: ([u]) =>
214
+ `const $b = new ArrayBuffer(4); new Uint32Array($b)[0] = ${u} >>> 0; return new Float32Array($b)[0];`,
215
+ math_mod: ([x, y]) => `return ${x} % ${y};`,
216
+ ...Object.fromEntries(
217
+ [
218
+ "sin",
219
+ "cos",
220
+ "tan",
221
+ "asin",
222
+ "acos",
223
+ "atan",
224
+ "sinh",
225
+ "cosh",
226
+ "tanh",
227
+ "exp",
228
+ "expm1",
229
+ "log",
230
+ "log1p",
231
+ "log2",
232
+ "log10",
233
+ "cbrt",
234
+ ].map((f) => [`math_${f}`, ([x]) => `return Math.${f}(${x});`]),
235
+ ),
236
+ ...Object.fromEntries(
237
+ ["atan2", "pow", "hypot"].map((f) => [
238
+ `math_${f}`,
239
+ ([x, y]) => `return Math.${f}(${x}, ${y});`,
240
+ ]),
241
+ ),
242
+ };
198
243
 
199
244
  export class CodeGen {
200
245
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
@@ -209,6 +254,7 @@ export class CodeGen {
209
254
  this.checker = checker;
210
255
  this.target = options.target ?? "js";
211
256
  this.strict = Boolean(options.strict || this.target === "both");
257
+ this.natives = options.natives !== false; // replace native calls with host code
212
258
  this.out = [];
213
259
  this.indent = 0;
214
260
  this.structNames = new Set();
@@ -224,9 +270,9 @@ export class CodeGen {
224
270
  this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
225
271
  this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
226
272
  this._boxedVars = new Set(); // address-taken scalar variables that need boxing
227
- this._jsonCodecs = new Map(); // memoized JSON encoders and decoders
228
- this._localTypeNames = new Set(); // function-local type declarations
229
- this._jsonAnyUsed = false; // program calls json.Marshal / Encoder.Encode
273
+ this._hoistedLocals = new Set(); // function-local classes the json codecs name
274
+ this._hasJsonCodecs = false;
275
+ this._jsonCodecFileIdx = -1;
230
276
  // Runtime helper usage tracking — only emit helpers that are actually used
231
277
  this._helpers = new Set();
232
278
  // Per-function context for Go value semantics (see _withFnCtx)
@@ -328,10 +374,15 @@ export class CodeGen {
328
374
  this._precisionWarned = new Set();
329
375
  // Defer lowering (argument/receiver hoisting, recover-target marking) is
330
376
  // idempotent and owned by the backends: the wasm path runs it via
331
- // `lower()`, the JS path here.
377
+ // `lower()`, the JS path here. So is splitting multi-value calls
378
+ // forwarded into interfaces into temps.
379
+ splitForwardedTuples(program);
332
380
  normalizeDefers(program);
333
381
  // Strict mode: index checks the loop shape proves unnecessary are elided.
334
- if (this.strict) markInBoundsIndexes(program);
382
+ if (this.strict) {
383
+ markInBoundsIndexes(program);
384
+ markRedundantNilChecks(program, (d) => comparesToNil(d.body, d.recvName));
385
+ }
335
386
  if (program._filename) this._srcFileNames = [program._filename];
336
387
  // Ensure _pkgName is set on all decls (generateAll sets it for
337
388
  // multi-file; for single-file compiles we derive it from the AST).
@@ -343,16 +394,22 @@ export class CodeGen {
343
394
  }
344
395
  }
345
396
  this._renameReservedIdents(program.decls);
397
+ this._elemAddrTaken =
398
+ takesElementAddress(program.decls) ||
399
+ this.checker?.depElemAddrTaken === true;
346
400
  this._boxedGlobals = this._collectBoxedGlobals(program);
347
401
  this._boxedVars = new Set(this._boxedGlobals);
348
402
  this._collectResliceNames(program);
349
- this._jsonAnyUsed = this._usesJsonAnyMarshal(program.decls);
350
403
  const methods = this._collectDecls(program);
351
404
  this._emitJsImports();
352
405
  this._emitTypeDecls(program, methods);
406
+ // Package variable initialisers may call functions that declare local
407
+ // structs, so the hoisted classes are declared before them.
408
+ const hoistedAt = this.out.length;
409
+ if (this._hasJsonCodecs) this.line("");
353
410
  this._emitVarConstDecls(program);
354
411
  const initNames = this._emitFuncDecls(program);
355
- this._emitJsonCodecs();
412
+ this._emitHoistedLocals(hoistedAt);
356
413
  this._callInitAndMain(initNames, program, isTest);
357
414
  const prependedLines = this._prependHelpers(isTest);
358
415
  return this._finalizeOutput(prependedLines);
@@ -467,6 +524,7 @@ export class CodeGen {
467
524
  FuncLit: "_renameSignature",
468
525
  TemplDecl: "_renameSignature",
469
526
  TemplExpr: "_renameTemplExprNode",
527
+ TemplChildren: "_renameTemplExprNode",
470
528
  TemplComponent: "_renameTemplExprNode",
471
529
  TemplIf: "_renameTemplExprNode",
472
530
  TemplSwitch: "_renameTemplExprNode",
@@ -535,13 +593,35 @@ export class CodeGen {
535
593
  const name = d.recvType.name;
536
594
  if (!methods.has(name)) methods.set(name, []);
537
595
  methods.get(name).push(d);
538
- if (d.recvPointer && comparesToNil(d.body, d.recvName))
539
- this.nilSafeMethods.add(`${name}.${d.name}`);
540
596
  }
541
597
  }
598
+ this._markNilSafeMethods(methods);
542
599
  return methods;
543
600
  }
544
601
 
602
+ // Pointer-receiver methods that can run with a nil receiver: they compare
603
+ // it with nil, or do not read through it before anything could skip the
604
+ // read (a method that does panics on nil either way, so it keeps the
605
+ // faster direct call). Calls to these go through the prototype (see
606
+ // _nilSafeMethodClass).
607
+ _markNilSafeMethods(methods) {
608
+ for (const [name, decls] of methods) {
609
+ const safe = new Set();
610
+ const ptr = decls.filter((d) => d.recvPointer);
611
+ for (const d of ptr) safe.add(d.name);
612
+ for (let changed = true; changed; ) {
613
+ changed = false;
614
+ for (const d of ptr) {
615
+ if (!safe.has(d.name) || comparesToNil(d.body, d.recvName)) continue;
616
+ if (!derefsReceiverFirst(d.body?.stmts, d.recvName, safe)) continue;
617
+ safe.delete(d.name);
618
+ changed = true;
619
+ }
620
+ }
621
+ for (const m of safe) this.nilSafeMethods.add(`${name}.${m}`);
622
+ }
623
+ }
624
+
545
625
  _collectNamedWrappers(program, methods) {
546
626
  for (const d of program.decls) {
547
627
  if (
@@ -567,37 +647,15 @@ export class CodeGen {
567
647
  this._helpers.add(name);
568
648
  if (name === "ifeq" || name === "geq") this._helpers.add("equal");
569
649
  if (name === "geq") this._helpers.add("ifeq");
570
- if (
571
- name === "strconvError" ||
572
- name === "pathError" ||
573
- name === "ioEOF" ||
574
- name === "base64" ||
575
- name === "hex" ||
576
- name === "json"
577
- ) {
578
- this._helpers.add("error");
579
- }
580
650
  if (name === "json") {
581
- this.useHelper("base64");
582
- }
583
- if (name === "os") {
584
- this.useHelper("ioEOF");
585
651
  this._helpers.add("error");
586
652
  }
653
+ if (name === "panicRt") this._helpers.add("error");
587
654
  if (name === "append") this.useHelper("slice");
588
- if (name === "sprint") this._helpers.add("sprintf");
589
655
  if (name === "slice") this._helpers.add("sv");
590
- if (name === "binary") {
591
- this.useHelper("ioEOF");
592
- this._helpers.add("error");
593
- }
594
656
  }
595
657
 
596
- _prependHelpers(isTest = false) {
597
- if (isTest || this._helpers.has("testing")) {
598
- this.useHelper("sprintf");
599
- this.useHelper("testing");
600
- }
658
+ _prependHelpers(_isTest = false) {
601
659
  const helpers = [];
602
660
  if (this.collectedCss.length > 0) helpers.push(HELPER_INJECT_STYLES);
603
661
  for (const [name, src] of HELPER_MAP) {
@@ -616,6 +674,8 @@ export class CodeGen {
616
674
  // Annotates each decl with its source file index before merging.
617
675
  generateAll(programs, options = {}) {
618
676
  this._srcFileNames = programs.map((p) => p._filename ?? null);
677
+ this._jsonCodecFileIdx = programs.findIndex((p) => p._generated);
678
+ this._hasJsonCodecs = this._jsonCodecFileIdx >= 0;
619
679
  for (let i = 0; i < programs.length; i++) {
620
680
  for (const decl of programs[i].decls) {
621
681
  decl._srcFileIdx = i;
@@ -632,8 +692,13 @@ export class CodeGen {
632
692
  // Records a non-fatal diagnostic at `node` ("main.go at line 3:5: msg").
633
693
  _warn(msg, node) {
634
694
  const file = this._srcFileNames[this._currentSrcFileIdx ?? 0];
635
- const line = node?.line ?? node?._line;
636
- const at = line ? `at line ${line}:${node.col ?? node._col ?? 1}` : "";
695
+ // Binary and unary expressions carry no position of their own: use the
696
+ // leftmost operand that does.
697
+ let pos = node;
698
+ while (pos && !(pos.line ?? pos._line))
699
+ pos = pos.left ?? pos.operand ?? pos.expr;
700
+ const line = pos?.line ?? pos?._line;
701
+ const at = line ? `at line ${line}:${pos.col ?? pos._col ?? 1}` : "";
637
702
  const loc = [file, at].filter(Boolean).join(" ");
638
703
  this.warnings.push(loc ? `${loc}: ${msg}` : msg);
639
704
  }
@@ -646,14 +711,6 @@ export class CodeGen {
646
711
  return this._srcMappings;
647
712
  }
648
713
 
649
- // Returns a source map JSON string for the last generate() call.
650
- // sources: string[] of source filenames (relative to the output file).
651
- // sourcesContent: string[] of original file contents (embedded for DevTools breakpoints).
652
- getSourceMap(sources, sourcesContent) {
653
- const srcArray = Array.isArray(sources) ? sources : [sources];
654
- return buildSourceMap(srcArray, this._srcMappings, sourcesContent);
655
- }
656
-
657
714
  // ── Type declarations ────────────────────────────────────────
658
715
 
659
716
  // `type S Base` with Base a struct declared in this package: the struct
@@ -665,15 +722,42 @@ export class CodeGen {
665
722
  let t = decl.type;
666
723
  for (let depth = 0; depth < 16 && t; depth++) {
667
724
  if (t.kind === "StructType") return t;
725
+ // `type S Base[int]`: the fields come from the checked type.
726
+ if (t.kind === "GenericTypeName" || t.kind === "InstantiationExpr")
727
+ return { kind: "StructType", fields: [] };
668
728
  if (t.kind !== "TypeName" && t.kind !== "Ident") return null;
669
729
  const next = this._typeDeclsByName?.get(t.name);
670
- if (!next || next.isAlias) return null;
730
+ if (!next) return null;
671
731
  t = next.type;
672
732
  }
673
733
  return null;
674
734
  }
675
735
 
676
736
  genTypeDeclWithMethods(decl, methodDecls) {
737
+ this._genTypeDeclBody(decl, methodDecls);
738
+ this._genUnhashableMark(decl.name);
739
+ }
740
+
741
+ // Marks a defined type Go cannot hash so `__ik` can name it in the panic
742
+ // of an interface-keyed map insert: a struct class carries `__uh`, a
743
+ // defined slice or map type registers its interface tag.
744
+ _genUnhashableMark(name) {
745
+ const t = this.checker?.types.get(name);
746
+ if (t?.kind !== "named" || t.typeParams?.length || t.typeArgs?.length)
747
+ return;
748
+ if (!this._isUnhashableType(t)) return;
749
+ const goName = JSON.stringify(goTypeName(t));
750
+ if (this.structNames.has(name)) {
751
+ this.line(
752
+ `Object.defineProperty(${name}, "__uh", { value: ${goName} });`,
753
+ );
754
+ } else if (this._isNamedComposite(t)) {
755
+ this.useHelper("structKey");
756
+ this.line(`__uht.set(${JSON.stringify(name)}, ${goName});`);
757
+ }
758
+ }
759
+
760
+ _genTypeDeclBody(decl, methodDecls) {
677
761
  const definedStruct =
678
762
  decl.type.kind === "StructType" ? null : this._definedStructAst(decl);
679
763
  if (decl.type.kind === "StructType") {
@@ -715,6 +799,7 @@ export class CodeGen {
715
799
  );
716
800
  }
717
801
  }
802
+ this._tagMultiUnwrap(methodDecls);
718
803
  });
719
804
  this.line("}");
720
805
  // Box for *T in an interface. It also proxies `.value` to the pointer, so
@@ -732,10 +817,20 @@ export class CodeGen {
732
817
  `${m.name}(...__a) { return ${name}.$${m.name}(${recv}, ...__a); }`,
733
818
  );
734
819
  }
820
+ this._tagMultiUnwrap(methodDecls);
735
821
  });
736
822
  this.line("}");
737
823
  }
738
824
 
825
+ // `Unwrap() []error` on a box class is told apart from `Unwrap() error`
826
+ // in type tests, as on struct classes.
827
+ _tagMultiUnwrap(methodDecls) {
828
+ const unwrap = methodDecls.find(
829
+ (m) => m.name === "Unwrap" && m.returnType?.kind === "SliceType",
830
+ );
831
+ if (unwrap) this.line("static { this.prototype.Unwrap.__multi = true; }");
832
+ }
833
+
739
834
  _namedBoxField(underlying) {
740
835
  if (underlying?.kind === "func") return "_fn";
741
836
  if (underlying?.kind === "map") return "_map";
@@ -776,8 +871,11 @@ export class CodeGen {
776
871
  if (this.checker) {
777
872
  const resolved = this.checker.types.get(name)?.underlying;
778
873
  if (resolved?.kind === "struct") {
779
- for (const [fName, fType] of resolved.fields.entries())
874
+ for (const [fName, fType] of resolved.fields.entries()) {
875
+ // A method promoted shallower shadows the deeper field.
876
+ if (isMethodPromotedOverField(resolved, fName)) continue;
780
877
  fields.push({ name: fName, zero: this.zeroValueForType(fType) });
878
+ }
781
879
  }
782
880
  } else {
783
881
  for (const f of structTypeAst.fields) {
@@ -797,8 +895,10 @@ export class CodeGen {
797
895
  const fields = [];
798
896
  // Cached per type name, so never bound to the current generic body.
799
897
  this._withTypeParamZeros(null, () => {
800
- for (const [name, type] of resolved.fields.entries())
898
+ for (const [name, type] of resolved.fields.entries()) {
899
+ if (isMethodPromotedOverField(resolved, name)) continue;
801
900
  fields.push({ name, zero: this.zeroValueForType(type) });
901
+ }
802
902
  });
803
903
  this.structFields.set(typeName, fields);
804
904
  return fields;
@@ -948,46 +1048,102 @@ export class CodeGen {
948
1048
  `Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
949
1049
  );
950
1050
  }
951
- this._genFmtClassMeta(name, methodDecls);
1051
+ this._genValueMethodMeta(name, methodDecls);
952
1052
  }
953
1053
 
954
- _emitJsonCodecs() {
955
- this._emitJsonAnyHooks();
956
- if (!this._jsonCodecs || this._jsonCodecs.size === 0) return;
957
- for (const [, code] of this._jsonCodecs) {
958
- this.line(code);
959
- this.blank();
960
- }
1054
+ // Names of the value-receiver methods: a typed nil pointer in an interface
1055
+ // panics when one is called (see __mknil).
1056
+ _genValueMethodMeta(name, methodDecls) {
1057
+ const vm = methodDecls.filter((m) => !m.recvPointer).map((m) => m.name);
1058
+ if (vm.length > 0)
1059
+ this.line(
1060
+ `Object.defineProperty(${name}, "__vm", { value: ${JSON.stringify(vm)} });`,
1061
+ );
1062
+ // Pointer-receiver methods are outside the method set of a struct value
1063
+ // held in an interface (see _ifaceMethodCheck).
1064
+ const pm = methodDecls.filter((m) => m.recvPointer).map((m) => m.name);
1065
+ // ...and so are those promoted from an embedded struct value.
1066
+ for (const stub of computeEmbeddedStubs(name, methodDecls, this.checker))
1067
+ if (stub.ptrOnly) pm.push(stub.methodName);
1068
+ if (pm.length > 0)
1069
+ this.line(
1070
+ `Object.defineProperty(${name}, "__pm", { value: ${JSON.stringify(pm)} });`,
1071
+ );
1072
+ }
1073
+
1074
+ // The generated json codecs live at package level and name function-local
1075
+ // struct classes: those are declared once at module level and assigned
1076
+ // where the Go source declares them.
1077
+ _hoistLocalClass(stmt, start) {
1078
+ if (!this._hasJsonCodecs || stmt.type.kind !== "StructType") return;
1079
+ const open = this.out.findIndex(
1080
+ (l, i) => i >= start && l.trimStart().startsWith(`class ${stmt.name} `),
1081
+ );
1082
+ if (open < 0) return;
1083
+ const indent = this.out[open].match(/^\s*/)[0];
1084
+ const close = this.out.findIndex((l, i) => i > open && l === `${indent}}`);
1085
+ if (close < 0) return;
1086
+ this.out[open] =
1087
+ `${indent}${stmt.name} = ${stmt.name}.__ph ? ${this.out[open].trimStart()}`;
1088
+ this.out[close] = `${indent}} : ${stmt.name};`;
1089
+ this._hoistedLocals.add(stmt.name);
1090
+ }
1091
+
1092
+ _emitHoistedLocals(at) {
1093
+ if (this._hoistedLocals.size > 0 && this._hasJsonCodecs)
1094
+ this.out[at] =
1095
+ `var ${[...this._hoistedLocals].map((n) => `${n} = class { static __ph = true; }`).join(", ")};`;
961
1096
  }
962
1097
 
963
1098
  // A promoted method runs on the embedded value (`this.In`), so it and the
964
1099
  // outer struct's promoted-field accesses see the same storage. Embedded
965
1100
  // types that are not compiled Go structs keep the borrowed-`this` form.
966
- _embedStub(method, embedName) {
967
- const call = this.structNames.has(embedName)
968
- ? `this.${embedName}.${method}(...__a)`
969
- : `${embedName}.prototype.${method}.call(this, ...__a)`;
1101
+ _embedStub(
1102
+ method,
1103
+ embedName,
1104
+ valueEmbed = false,
1105
+ fieldName = null,
1106
+ nilPtrRecv = false,
1107
+ ifaceEmbed = false,
1108
+ ) {
1109
+ // An embedded interface forwards to the interface value in its field.
1110
+ if (ifaceEmbed)
1111
+ return `${method}(...__a) { return this.${fieldName}.${method}(...__a); }`;
1112
+ // The field is named after the type without its package suffix
1113
+ // (`Mutex$$sync` is the field `Mutex`), or after the alias it embeds.
1114
+ const field = fieldName || embedName.replace(/\$\$.*$/, "");
1115
+ // A named non-struct type is boxed: its methods are static, taking
1116
+ // the value as the receiver.
1117
+ if (valueEmbed)
1118
+ return `${method}(...__a) { return ${embedName}.$${method}(this.${field}, ...__a); }`;
1119
+ const call = this._embedStubCall(method, embedName, field, nilPtrRecv);
970
1120
  return `${method}(...__a) { return ${call}; }`;
971
1121
  }
972
1122
 
973
- _genSingleEmbedStubs(embed, declared) {
974
- const embedName = embed.kind === "named" ? embed.name : null;
975
- if (!embedName) return;
976
- const embedBase = embed.kind === "named" ? embed.underlying : embed;
977
- if (embedBase?.kind !== "struct" || !embedBase.methods) return;
978
- for (const [mName] of embedBase.methods.entries()) {
979
- if (!declared.has(mName)) {
980
- this.blank();
981
- this.line(this._embedStub(mName, embedName));
982
- }
983
- }
1123
+ _embedStubCall(method, embedName, field, nilPtrRecv) {
1124
+ if (!this.structNames.has(embedName))
1125
+ return `${embedName}.prototype.${method}.call(this, ...__a)`;
1126
+ // A nil embedded pointer still reaches a pointer-receiver method, as in
1127
+ // Go: call it through the prototype instead of reading it off null.
1128
+ return nilPtrRecv
1129
+ ? `${embedName}.prototype.${method}.call(this.${field}, ...__a)`
1130
+ : `this.${field}.${method}(...__a)`;
984
1131
  }
985
1132
 
986
1133
  _genEmbeddedMethodStubs(name, methodDecls) {
987
1134
  const stubs = computeEmbeddedStubs(name, methodDecls, this.checker);
988
1135
  for (const stub of stubs) {
989
1136
  this.blank();
990
- this.line(this._embedStub(stub.methodName, stub.embedName));
1137
+ this.line(
1138
+ this._embedStub(
1139
+ stub.methodName,
1140
+ stub.embedName,
1141
+ stub.valueEmbed,
1142
+ stub.fieldName,
1143
+ stub.nilPtrRecv,
1144
+ stub.ifaceEmbed,
1145
+ ),
1146
+ );
991
1147
  }
992
1148
  }
993
1149
 
@@ -1252,47 +1408,65 @@ export class CodeGen {
1252
1408
  return Boolean(info?.methods.get(method)?._ptrRecv);
1253
1409
  }
1254
1410
 
1255
- _isReferenceType(t) {
1256
- return isReferenceType(t);
1257
- }
1258
-
1259
1411
  // ── Variable / const declarations ────────────────────────────
1260
1412
 
1261
1413
  genVarDecl(decl) {
1262
1414
  for (const spec of decl.decls) {
1263
- if (spec.value) {
1264
- const paired = spec.value.length === spec.names.length;
1265
- const vals = spec.value.map((v, i) => {
1266
- const js = paired
1267
- ? this._genDeclValue(spec.names[i], v)
1268
- : this.genExpr(v);
1269
- // Wrap numeric values assigned to complex-typed vars
1270
- if (
1271
- spec.type?.name === "complex128" ||
1272
- spec.type?.name === "complex64"
1273
- ) {
1274
- if (
1275
- !isComplex(v._type) &&
1276
- (isNumeric(v._type) || v._type?.kind === "untyped")
1277
- ) {
1278
- return `{ re: ${js}, im: 0 }`;
1279
- }
1280
- }
1281
- return js;
1282
- });
1283
- this._emitVarBindings(spec.names, vals, paired);
1284
- } else {
1285
- const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
1286
- for (const name of spec.names) {
1287
- if (name === "_") continue;
1288
- this._markOwnership(name, true);
1289
- const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
1290
- this.line(`let ${name} = ${val};`);
1415
+ if (spec._commaOk) this._genDefineStmt(this._varSpecAsDefine(spec));
1416
+ else if (spec.value) this._genVarSpecValues(spec);
1417
+ else this._genVarSpecZero(spec);
1418
+ }
1419
+ }
1420
+
1421
+ _genVarSpecValues(spec) {
1422
+ const paired = spec.value.length === spec.names.length;
1423
+ const vals = spec.value.map((v, i) => {
1424
+ const js = paired
1425
+ ? this._genDeclValue(spec.names[i], v)
1426
+ : this.genExpr(v);
1427
+ // Wrap numeric values assigned to complex-typed vars
1428
+ if (spec.type?.name === "complex128" || spec.type?.name === "complex64") {
1429
+ if (
1430
+ !isComplex(v._type) &&
1431
+ !v._asComplex &&
1432
+ (isNumeric(v._type) || v._type?.kind === "untyped")
1433
+ ) {
1434
+ return `{ re: ${js}, im: 0 }`;
1435
+ }
1436
+ }
1437
+ if (spec.type?.name === "int64" || spec.type?.name === "uint64") {
1438
+ if (
1439
+ v._type?.kind === "untyped" &&
1440
+ !js.endsWith("n") &&
1441
+ /^-?\d+$/.test(js)
1442
+ ) {
1443
+ return `${js}n`;
1291
1444
  }
1292
1445
  }
1446
+ return js;
1447
+ });
1448
+ this._emitVarBindings(spec.names, vals, paired);
1449
+ }
1450
+
1451
+ _genVarSpecZero(spec) {
1452
+ const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
1453
+ for (const name of spec.names) {
1454
+ if (name === "_") continue;
1455
+ this._markOwnership(name, true);
1456
+ const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
1457
+ this.line(`let ${name} = ${val};`);
1293
1458
  }
1294
1459
  }
1295
1460
 
1461
+ // `var v, ok = m[k]` generates like `v, ok := m[k]`.
1462
+ _varSpecAsDefine(spec) {
1463
+ return {
1464
+ kind: "DefineStmt",
1465
+ lhs: spec.names.map((name) => ({ kind: "Ident", name })),
1466
+ rhs: spec.value,
1467
+ };
1468
+ }
1469
+
1296
1470
  // `var a = v` / `var a, b = v1, v2` / `var a, b = f()`, boxing address-taken names.
1297
1471
  _emitVarBindings(names, vals, paired) {
1298
1472
  if (names.length === 1 && names[0] === "_") {