gofront 1.8.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 (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  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 +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  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 +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -20,10 +20,11 @@
20
20
  import {
21
21
  computeEmbeddedStubs,
22
22
  hasDirectRecover,
23
- isReferenceType,
24
23
  markInBoundsIndexes,
24
+ markRedundantNilChecks,
25
25
  normalizeDefers,
26
26
  scanAddressTaken,
27
+ takesElementAddress,
27
28
  } from "../../lower/index.js";
28
29
  import { orderPackageInits } from "../../typechecker/init-order.js";
29
30
  import {
@@ -31,56 +32,44 @@ import {
31
32
  isComplex,
32
33
  isNumeric,
33
34
  } from "../../typechecker/types.js";
35
+ import {
36
+ unicodeIsJs,
37
+ unicodeToLowerJs,
38
+ unicodeToUpperJs,
39
+ } from "../shared/host-unicode.js";
34
40
  import { comparesToNil, expressionGenMethods } from "./expressions.js";
35
41
  import {
36
42
  HELPER_APPEND,
37
- HELPER_BASE64,
38
- HELPER_BINARY,
39
43
  HELPER_BOUNDS,
40
44
  HELPER_CDIV,
41
45
  HELPER_CMUL,
42
- HELPER_CRYPTO_RAND,
43
- HELPER_DURATION,
44
46
  HELPER_EQUAL,
45
47
  HELPER_ERROR,
46
- HELPER_ERROR_AS,
47
- HELPER_ERROR_IS,
48
- HELPER_ERROR_JOIN,
49
48
  HELPER_GENERICS,
50
49
  HELPER_GEQ,
51
- HELPER_HEX,
52
50
  HELPER_IFACE_BOX,
53
- HELPER_IFD,
54
51
  HELPER_IFEQ,
55
52
  HELPER_INJECT_STYLES,
56
- HELPER_IO_EOF,
57
- HELPER_JSON,
53
+ HELPER_JSONRT,
58
54
  HELPER_LEN,
55
+ HELPER_MKLEN,
59
56
  HELPER_NIL_CALL,
60
57
  HELPER_NIL_MAP,
61
58
  HELPER_NM,
62
- HELPER_OS,
63
59
  HELPER_PANIC,
64
- HELPER_PATH_CLEAN,
65
- HELPER_PATH_ERROR,
66
60
  HELPER_PTREQ,
61
+ HELPER_PUSHS,
62
+ HELPER_RUNESTR,
67
63
  HELPER_S,
68
64
  HELPER_SCLONE,
69
- HELPER_SHA256,
70
65
  HELPER_SHIFT,
71
66
  HELPER_SIMD,
72
67
  HELPER_SLICE,
73
- HELPER_SORT_SLICE,
74
- HELPER_SPRINT,
75
- HELPER_SPRINTF,
76
- HELPER_STRCONV,
77
- HELPER_STRCONV_ERROR,
78
68
  HELPER_STRUCT_KEY,
79
69
  HELPER_SV,
80
- HELPER_SYNC,
81
- HELPER_TESTING,
82
- HELPER_TIME_FMT,
83
- HELPER_TIME_PARSE,
70
+ HELPER_TO64,
71
+ HELPER_U8DEC,
72
+ HELPER_U8ENC,
84
73
  HELPER_VA,
85
74
  HELPER_VCLONE,
86
75
  } from "./runtime.js";
@@ -142,15 +131,19 @@ const jsBindingName = (n) =>
142
131
  // `this.useHelper(name)`; `_prependHelpers` emits every marked entry.
143
132
  const HELPER_MAP = [
144
133
  ["len", HELPER_LEN],
134
+ ["u8enc", HELPER_U8ENC],
135
+ ["u8dec", HELPER_U8DEC],
145
136
  ["sv", HELPER_SV],
146
137
  ["slice", HELPER_SLICE],
138
+ ["mklen", HELPER_MKLEN],
139
+ ["runestr", HELPER_RUNESTR],
147
140
  ["append", HELPER_APPEND],
148
141
  ["sliceGuard", HELPER_S],
142
+ ["pushs", HELPER_PUSHS],
149
143
  ["sclone", HELPER_SCLONE],
150
144
  ["va", HELPER_VA],
151
145
  ["vclone", HELPER_VCLONE],
152
146
  ["ifaceBox", HELPER_IFACE_BOX],
153
- ["ifd", HELPER_IFD],
154
147
  ["equal", HELPER_EQUAL],
155
148
  ["geq", HELPER_GEQ],
156
149
  ["ptreq", HELPER_PTREQ],
@@ -159,42 +152,87 @@ const HELPER_MAP = [
159
152
  ["cdiv", HELPER_CDIV],
160
153
  ["panicRt", HELPER_PANIC],
161
154
  ["shift", HELPER_SHIFT],
155
+ ["to64", HELPER_TO64],
162
156
  ["nilCall", HELPER_NIL_CALL],
163
157
  ["bounds", HELPER_BOUNDS],
164
158
  ["nilMap", HELPER_NIL_MAP],
165
159
  ["nm", HELPER_NM],
166
160
  ["structKey", HELPER_STRUCT_KEY],
167
161
  ["generics", HELPER_GENERICS],
168
- ["sprintf", HELPER_SPRINTF],
169
- ["sprint", HELPER_SPRINT],
170
162
  ["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],
163
+ ["jsonrt", HELPER_JSONRT],
192
164
  ["simd", HELPER_SIMD],
193
165
  ];
194
166
 
195
167
  // JS bodies of `//gofront:intrinsic` functions (names: INTRINSICS in
196
168
  // typechecker/types.js). The WASM backend keeps their empty body.
197
- const JS_INTRINSICS = {};
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
+ };
198
236
 
199
237
  export class CodeGen {
200
238
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
@@ -209,6 +247,7 @@ export class CodeGen {
209
247
  this.checker = checker;
210
248
  this.target = options.target ?? "js";
211
249
  this.strict = Boolean(options.strict || this.target === "both");
250
+ this.natives = options.natives !== false; // replace native calls with host code
212
251
  this.out = [];
213
252
  this.indent = 0;
214
253
  this.structNames = new Set();
@@ -224,9 +263,9 @@ export class CodeGen {
224
263
  this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
225
264
  this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
226
265
  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
266
+ this._hoistedLocals = new Set(); // function-local classes the json codecs name
267
+ this._hasJsonCodecs = false;
268
+ this._jsonCodecFileIdx = -1;
230
269
  // Runtime helper usage tracking — only emit helpers that are actually used
231
270
  this._helpers = new Set();
232
271
  // Per-function context for Go value semantics (see _withFnCtx)
@@ -331,7 +370,10 @@ export class CodeGen {
331
370
  // `lower()`, the JS path here.
332
371
  normalizeDefers(program);
333
372
  // Strict mode: index checks the loop shape proves unnecessary are elided.
334
- if (this.strict) markInBoundsIndexes(program);
373
+ if (this.strict) {
374
+ markInBoundsIndexes(program);
375
+ markRedundantNilChecks(program, (d) => comparesToNil(d.body, d.recvName));
376
+ }
335
377
  if (program._filename) this._srcFileNames = [program._filename];
336
378
  // Ensure _pkgName is set on all decls (generateAll sets it for
337
379
  // multi-file; for single-file compiles we derive it from the AST).
@@ -343,16 +385,22 @@ export class CodeGen {
343
385
  }
344
386
  }
345
387
  this._renameReservedIdents(program.decls);
388
+ this._elemAddrTaken =
389
+ takesElementAddress(program.decls) ||
390
+ this.checker?.depElemAddrTaken === true;
346
391
  this._boxedGlobals = this._collectBoxedGlobals(program);
347
392
  this._boxedVars = new Set(this._boxedGlobals);
348
393
  this._collectResliceNames(program);
349
- this._jsonAnyUsed = this._usesJsonAnyMarshal(program.decls);
350
394
  const methods = this._collectDecls(program);
351
395
  this._emitJsImports();
352
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("");
353
401
  this._emitVarConstDecls(program);
354
402
  const initNames = this._emitFuncDecls(program);
355
- this._emitJsonCodecs();
403
+ this._emitHoistedLocals(hoistedAt);
356
404
  this._callInitAndMain(initNames, program, isTest);
357
405
  const prependedLines = this._prependHelpers(isTest);
358
406
  return this._finalizeOutput(prependedLines);
@@ -467,6 +515,7 @@ export class CodeGen {
467
515
  FuncLit: "_renameSignature",
468
516
  TemplDecl: "_renameSignature",
469
517
  TemplExpr: "_renameTemplExprNode",
518
+ TemplChildren: "_renameTemplExprNode",
470
519
  TemplComponent: "_renameTemplExprNode",
471
520
  TemplIf: "_renameTemplExprNode",
472
521
  TemplSwitch: "_renameTemplExprNode",
@@ -567,37 +616,15 @@ export class CodeGen {
567
616
  this._helpers.add(name);
568
617
  if (name === "ifeq" || name === "geq") this._helpers.add("equal");
569
618
  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
619
  if (name === "json") {
581
- this.useHelper("base64");
582
- }
583
- if (name === "os") {
584
- this.useHelper("ioEOF");
585
620
  this._helpers.add("error");
586
621
  }
622
+ if (name === "panicRt") this._helpers.add("error");
587
623
  if (name === "append") this.useHelper("slice");
588
- if (name === "sprint") this._helpers.add("sprintf");
589
624
  if (name === "slice") this._helpers.add("sv");
590
- if (name === "binary") {
591
- this.useHelper("ioEOF");
592
- this._helpers.add("error");
593
- }
594
625
  }
595
626
 
596
- _prependHelpers(isTest = false) {
597
- if (isTest || this._helpers.has("testing")) {
598
- this.useHelper("sprintf");
599
- this.useHelper("testing");
600
- }
627
+ _prependHelpers(_isTest = false) {
601
628
  const helpers = [];
602
629
  if (this.collectedCss.length > 0) helpers.push(HELPER_INJECT_STYLES);
603
630
  for (const [name, src] of HELPER_MAP) {
@@ -616,6 +643,8 @@ export class CodeGen {
616
643
  // Annotates each decl with its source file index before merging.
617
644
  generateAll(programs, options = {}) {
618
645
  this._srcFileNames = programs.map((p) => p._filename ?? null);
646
+ this._jsonCodecFileIdx = programs.findIndex((p) => p._generated);
647
+ this._hasJsonCodecs = this._jsonCodecFileIdx >= 0;
619
648
  for (let i = 0; i < programs.length; i++) {
620
649
  for (const decl of programs[i].decls) {
621
650
  decl._srcFileIdx = i;
@@ -632,8 +661,13 @@ export class CodeGen {
632
661
  // Records a non-fatal diagnostic at `node` ("main.go at line 3:5: msg").
633
662
  _warn(msg, node) {
634
663
  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}` : "";
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}` : "";
637
671
  const loc = [file, at].filter(Boolean).join(" ");
638
672
  this.warnings.push(loc ? `${loc}: ${msg}` : msg);
639
673
  }
@@ -646,14 +680,6 @@ export class CodeGen {
646
680
  return this._srcMappings;
647
681
  }
648
682
 
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
683
  // ── Type declarations ────────────────────────────────────────
658
684
 
659
685
  // `type S Base` with Base a struct declared in this package: the struct
@@ -665,9 +691,12 @@ export class CodeGen {
665
691
  let t = decl.type;
666
692
  for (let depth = 0; depth < 16 && t; depth++) {
667
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: [] };
668
697
  if (t.kind !== "TypeName" && t.kind !== "Ident") return null;
669
698
  const next = this._typeDeclsByName?.get(t.name);
670
- if (!next || next.isAlias) return null;
699
+ if (!next) return null;
671
700
  t = next.type;
672
701
  }
673
702
  return null;
@@ -715,6 +744,7 @@ export class CodeGen {
715
744
  );
716
745
  }
717
746
  }
747
+ this._tagMultiUnwrap(methodDecls);
718
748
  });
719
749
  this.line("}");
720
750
  // Box for *T in an interface. It also proxies `.value` to the pointer, so
@@ -732,10 +762,20 @@ export class CodeGen {
732
762
  `${m.name}(...__a) { return ${name}.$${m.name}(${recv}, ...__a); }`,
733
763
  );
734
764
  }
765
+ this._tagMultiUnwrap(methodDecls);
735
766
  });
736
767
  this.line("}");
737
768
  }
738
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
+
739
779
  _namedBoxField(underlying) {
740
780
  if (underlying?.kind === "func") return "_fn";
741
781
  if (underlying?.kind === "map") return "_map";
@@ -948,46 +988,82 @@ export class CodeGen {
948
988
  `Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
949
989
  );
950
990
  }
951
- this._genFmtClassMeta(name, methodDecls);
991
+ this._genValueMethodMeta(name, methodDecls);
952
992
  }
953
993
 
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
- }
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
+ );
1012
+ }
1013
+
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(", ")};`;
961
1036
  }
962
1037
 
963
1038
  // A promoted method runs on the embedded value (`this.In`), so it and the
964
1039
  // outer struct's promoted-field accesses see the same storage. Embedded
965
1040
  // types that are not compiled Go structs keep the borrowed-`this` form.
966
- _embedStub(method, embedName) {
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); }`;
967
1049
  const call = this.structNames.has(embedName)
968
- ? `this.${embedName}.${method}(...__a)`
1050
+ ? `this.${field}.${method}(...__a)`
969
1051
  : `${embedName}.prototype.${method}.call(this, ...__a)`;
970
1052
  return `${method}(...__a) { return ${call}; }`;
971
1053
  }
972
1054
 
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
- }
984
- }
985
-
986
1055
  _genEmbeddedMethodStubs(name, methodDecls) {
987
1056
  const stubs = computeEmbeddedStubs(name, methodDecls, this.checker);
988
1057
  for (const stub of stubs) {
989
1058
  this.blank();
990
- this.line(this._embedStub(stub.methodName, stub.embedName));
1059
+ this.line(
1060
+ this._embedStub(
1061
+ stub.methodName,
1062
+ stub.embedName,
1063
+ stub.valueEmbed,
1064
+ stub.fieldName,
1065
+ ),
1066
+ );
991
1067
  }
992
1068
  }
993
1069
 
@@ -1252,47 +1328,65 @@ export class CodeGen {
1252
1328
  return Boolean(info?.methods.get(method)?._ptrRecv);
1253
1329
  }
1254
1330
 
1255
- _isReferenceType(t) {
1256
- return isReferenceType(t);
1257
- }
1258
-
1259
1331
  // ── Variable / const declarations ────────────────────────────
1260
1332
 
1261
1333
  genVarDecl(decl) {
1262
1334
  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};`);
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`;
1291
1364
  }
1292
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};`);
1293
1378
  }
1294
1379
  }
1295
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
+
1296
1390
  // `var a = v` / `var a, b = v1, v2` / `var a, b = f()`, boxing address-taken names.
1297
1391
  _emitVarBindings(names, vals, paired) {
1298
1392
  if (names.length === 1 && names[0] === "_") {