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
@@ -1,74 +1,56 @@
1
1
  // CodeGen stdlib call generation — installed as a mixin on CodeGen.prototype.
2
- // One file per Go package under ./stdlib/. This index merges them.
2
+ // Dispatches `gofront/shared` and `gofront/simd` calls to inline JS.
3
3
 
4
- import { base64Methods } from "./base64.js";
5
- import { binaryMethods } from "./binary.js";
6
- import { bitsMethods } from "./bits.js";
7
- import { builderMethods } from "./builder.js";
8
- import { bytesMethods } from "./bytes.js";
9
- import { errorsMethods } from "./errors.js";
10
- import { fmtMethods } from "./fmt.js";
11
- import { gomMethods } from "./gom.js";
12
- import { hexMethods } from "./hex.js";
13
- import { htmlMethods } from "./html.js";
14
- import { ioMethods } from "./io.js";
15
- import { jsonMethods } from "./json.js";
16
- import { mapsMethods } from "./maps.js";
17
- import { mathMethods } from "./math.js";
18
- import { osMethods } from "./os.js";
19
- import { pathMethods } from "./path.js";
20
- import { randMethods } from "./rand.js";
21
- import { regexpMethods } from "./regexp.js";
22
- import { sha256Methods } from "./sha256.js";
23
- import { sharedMethods } from "./shared.js";
24
- import { simdMethods } from "./simd.js";
25
- import { slicesMethods } from "./slices.js";
26
- import { sortMethods } from "./sort.js";
27
- import { strconvMethods } from "./strconv.js";
28
- import { stringsMethods } from "./strings.js";
29
- import { syncMethods } from "./sync.js";
30
- import { testingMethods } from "./testing.js";
31
- import { timeMethods } from "./time.js";
32
- import { unicodeMethods } from "./unicode.js";
33
- import { utf8Methods } from "./utf8.js";
4
+ import { SHARED_KINDS } from "../../../typechecker/stdlib/shared.js";
34
5
 
35
6
  /** @typedef {import('../index.js').CodeGen} CodeGen */
36
7
 
37
8
  // Stdlib namespace → handler method name. Used by _genStdlibCall to dispatch
38
9
  // dynamically without creating static call edges.
39
10
  const STDLIB_METHOD_MAP = {
40
- fmt: "_genFmt",
41
- strings: "_genStrings",
42
- bytes: "_genBytes",
43
- strconv: "_genStrconv",
44
- sort: "_genSort",
45
- math: "_genMath",
46
- bits: "_genBits",
47
- unicode: "_genUnicode",
48
- os: "_genOs",
49
- errors: "_genErrors",
50
- time: "_genTime",
51
- regexp: "_genRegexp",
52
- slices: "_genSlices",
53
- maps: "_genMaps",
54
- html: "_genHtml",
55
- io: "_genIo",
56
- rand: "_genRand",
57
- utf8: "_genUtf8",
58
- path: "_genPath",
59
- filepath: "_genPath",
60
- testing: "_genTesting",
61
11
  shared: "_genShared",
62
- sync: "_genSync",
63
- sha256: "_genSha256",
64
- binary: "_genBinary",
65
- hex: "_genHex",
66
- base64: "_genBase64",
67
- json: "_genJson",
68
12
  simd: "_genSimd",
69
13
  };
70
14
 
71
15
  const dispatchMethods = {
16
+ // `gofront/shared`: in js packages a shared buffer is just a TypedArray (no
17
+ // linear memory is involved); in wasm packages the facade hands out
18
+ // zero-copy views onto the module memory.
19
+ _genShared(fn, a) {
20
+ if (!fn.startsWith("New")) return undefined;
21
+ const info = SHARED_KINDS[fn.slice(3)];
22
+ if (!info) return undefined;
23
+ return `new ${info.ctor}(${a()[0]})`;
24
+ },
25
+
26
+ // `gofront/simd` in JS packages.
27
+ _genSimd(fn, a) {
28
+ this.useHelper("simd");
29
+ const args = a();
30
+ switch (fn) {
31
+ case "Splat":
32
+ return `__simd_splat(${args[0]})`;
33
+ case "NewF32x4":
34
+ return `__simd_f32x4(${args[0]}, ${args[1]}, ${args[2]}, ${args[3]})`;
35
+ case "Select":
36
+ return `__simd_select(${args[0]}, ${args[1]}, ${args[2]})`;
37
+ case "Shuffle":
38
+ return `__simd_shuffle(${args.join(", ")})`;
39
+ case "LoadF32x4":
40
+ return `__simd_load(${args[0]}, ${args[1]})`;
41
+ case "StoreF32x4":
42
+ return `__simd_store(${args[0]}, ${args[1]}, ${args[2]})`;
43
+ case "LoadF32x4Slice":
44
+ return `__simd_load_slice(${args[0]}, ${args[1]})`;
45
+ case "StoreF32x4Slice":
46
+ return `__simd_store_slice(${args[0]}, ${args[1]}, ${args[2]})`;
47
+ case "Pad4":
48
+ return `(Math.floor((${args[0]} + 3) / 4) * 4)`;
49
+ default:
50
+ return undefined;
51
+ }
52
+ },
53
+
72
54
  typeComment(typeNode) {
73
55
  if (!typeNode) return "unknown";
74
56
  switch (typeNode.kind) {
@@ -86,18 +68,18 @@ const dispatchMethods = {
86
68
  // Dispatch table for stdlib namespace calls (pkg.Func → inline JS).
87
69
  // Returns the generated JS string, or undefined if not handled.
88
70
  _genStdlibCall(ns, fn, expr) {
89
- if (ns === "gom") return this._genGom(fn, expr);
90
71
  const method = STDLIB_METHOD_MAP[ns];
91
72
  if (!method) return undefined;
92
- // Stdlib shims take raw values; only fmt and errors use the interface box
93
- // of a named value (Error() dispatch, error comparisons).
73
+ // A member written in Go is an ordinary call of its mangled name.
74
+ if (expr.func?._stdName) return undefined;
75
+ // Stdlib shims take raw values, not the interface box of a named value.
94
76
  const isNamespace = expr.func?.expr?._type?.kind === "namespace";
95
- if (isNamespace && ns !== "fmt" && ns !== "errors") {
77
+ if (isNamespace) {
96
78
  for (const e of expr.args) if (e) e._noNamedBox = true;
97
79
  }
98
80
  const a = () => expr.args.map((e) => this.genExpr(e));
99
81
  const res = this[method](fn, a, expr);
100
- if (res === undefined && isNamespace && ns !== "fmt" && ns !== "errors") {
82
+ if (res === undefined && isNamespace) {
101
83
  for (const e of expr.args) if (e) delete e._noNamedBox;
102
84
  }
103
85
  return res;
@@ -106,35 +88,5 @@ const dispatchMethods = {
106
88
 
107
89
  /** @type {ThisType<CodeGen>} */
108
90
  export const stdlibGenMethods = {
109
- ...bitsMethods,
110
- ...builderMethods,
111
- ...bytesMethods,
112
- ...errorsMethods,
113
- ...fmtMethods,
114
- ...gomMethods,
115
- ...htmlMethods,
116
- ...ioMethods,
117
- ...mapsMethods,
118
- ...mathMethods,
119
- ...osMethods,
120
- ...pathMethods,
121
- ...randMethods,
122
- ...regexpMethods,
123
- ...sharedMethods,
124
- ...simdMethods,
125
- ...slicesMethods,
126
- ...sortMethods,
127
- ...strconvMethods,
128
- ...stringsMethods,
129
- ...timeMethods,
130
- ...unicodeMethods,
131
- ...utf8Methods,
132
- ...testingMethods,
133
- ...syncMethods,
134
- ...sha256Methods,
135
- ...binaryMethods,
136
- ...hexMethods,
137
- ...base64Methods,
138
- ...jsonMethods,
139
91
  ...dispatchMethods,
140
92
  };
@@ -13,6 +13,8 @@ export const templGenMethods = {
13
13
  )
14
14
  .join(", ");
15
15
  this.line(`function ${decl.name}(${params}) {`);
16
+ const prevBoxed = this._boxedVars;
17
+ this._boxedVars = new Set(this._boxedGlobals);
16
18
  this.indented(() => {
17
19
  // `@W(xs...)` passes `{ __sp: xs }` like any Go variadic call.
18
20
  const last = decl.params[decl.params.length - 1];
@@ -26,6 +28,7 @@ export const templGenMethods = {
26
28
  });
27
29
  this.line(`}};`);
28
30
  });
31
+ this._boxedVars = prevBoxed;
29
32
  this.line("}");
30
33
  },
31
34
 
@@ -86,11 +89,17 @@ export const templGenMethods = {
86
89
  );
87
90
  case "TemplComponent":
88
91
  return this._genTemplComponentMount(node, p);
89
- case "TemplChildren":
90
- // children is a variadic gom.Node param — mount each child
92
+ case "TemplChildren": {
93
+ // `{ name... }`: mount each node of a variadic gom.Node parameter
94
+ // (nil mounts nothing). `children` may also be absent altogether.
95
+ const slot =
96
+ node.name === "children"
97
+ ? `(typeof children!=="undefined"?children:null)`
98
+ : this._genTemplExpr(node, "null");
91
99
  return this.line(
92
- `if(typeof children!=="undefined")(Array.isArray(children)?children:[...children]).forEach(___c=>___c?.Mount?.(${p}));`,
100
+ `for (const ___c of ${slot} ?? []) ___c?.Mount?.(${p});`,
93
101
  );
102
+ }
94
103
  case "TemplIf":
95
104
  return this._genTemplIf(node, p);
96
105
  case "TemplFor":
@@ -200,15 +209,18 @@ export const templGenMethods = {
200
209
  _genTemplSwitch(node, p) {
201
210
  const { cases } = node;
202
211
  if (cases.length === 0) return;
203
- const exprJs = this._genTemplExpr(node, '""');
212
+ // `switch { case cond: }` compares each condition with `true`.
213
+ const exprJs = this._genTemplExpr(node, "true");
204
214
  this.line(`switch (${exprJs}) {`);
205
215
  this.indented(() => {
206
216
  for (const c of cases) {
207
- const label =
208
- c.caseTokens === null
209
- ? "default"
210
- : `case ${this._genTemplExpr(c, '""')}`;
211
- this.line(`${label}: {`);
217
+ if (c.caseTokens === null) this.line("default: {");
218
+ else {
219
+ // One JS label per value of `case a, b:`.
220
+ if (c.exprError) throw c.exprError;
221
+ const labels = (c.exprs ?? []).map((e) => `case ${this.genExpr(e)}:`);
222
+ this.line(`${labels.join(" ") || 'case "":'} {`);
223
+ }
212
224
  this.indented(() => this._genTemplNodes(c.body, p));
213
225
  this.line(`break; }`);
214
226
  }
@@ -216,48 +228,23 @@ export const templGenMethods = {
216
228
  this.line(`}`);
217
229
  },
218
230
 
231
+ // The header is a parsed Go `for` statement, so it goes through the regular
232
+ // loop generator; its body is a statement that mounts the templ nodes.
219
233
  _genTemplFor(node, p) {
220
234
  if (node.forError) throw node.forError;
221
235
  if (!node.forStmt)
222
236
  throw new Error("templ for header was not parsed by TemplParser");
223
- this._genTemplRangeFor(node.forStmt, node.body, p);
224
- },
225
-
226
- _genTemplRangeFor(forStmt, templBody, mountParent) {
227
- const init = forStmt.init;
228
- if (init?.rhs?.[0]?.kind !== "RangeExpr") {
229
- // Plain for loop (cond-only or three-clause)
230
- const condJs = forStmt.cond ? this.genExpr(forStmt.cond) : "true";
231
- this.line(`while (${condJs}) {`);
232
- this.indented(() => this._genTemplNodes(templBody, mountParent));
233
- this.line("}");
234
- return;
235
- }
236
- // Range for: for key, val := range iter
237
- const range = init.rhs[0];
238
- const iterJs = this.genExpr(range.expr);
239
- const lhs = init.lhs.map((e) => e.name ?? "_");
240
- const keyName = lhs[0] ?? "_";
241
- const valName = lhs[1] ?? null;
242
-
243
- const loopHeader = this._genTemplRangeHeader(keyName, valName, iterJs);
244
- this.line(loopHeader);
245
- this.indented(() => this._genTemplNodes(templBody, mountParent));
246
- this.line("}");
237
+ const mount = { kind: "TemplMount", parent: p };
238
+ // Hidden from the generic AST walkers: they know no templ node kinds.
239
+ Object.defineProperty(mount, "nodes", { value: node.body });
240
+ this.genFor({
241
+ ...node.forStmt,
242
+ body: { kind: "Block", stmts: [mount] },
243
+ });
247
244
  },
248
245
 
249
- _genTemplRangeHeader(keyName, valName, iterJs) {
250
- if (valName === null || valName === undefined) {
251
- return `for (const ${keyName === "_" ? "_$" : keyName} of ${iterJs}) {`;
252
- }
253
- if (keyName === "_" && valName === "_")
254
- return `for (const _$ of ${iterJs}) {`;
255
- if (keyName === "_") return `for (const ${valName} of ${iterJs}) {`;
256
- if (valName === "_")
257
- return `for (const [${keyName}] of Object.entries(${iterJs})) {`;
258
- // Default to .entries() for arrays, Object.entries for objects
259
- this.useHelper("sliceGuard");
260
- return `for (const [${keyName}, ${valName}] of __s(${iterJs}).entries()) {`;
246
+ _genTemplMountStmt(stmt) {
247
+ this._genTemplNodes(stmt.nodes, stmt.parent);
261
248
  },
262
249
 
263
250
  // Emit JS for the Go expression TemplParser parsed onto `node`. `emptyJs` is
@@ -7,7 +7,12 @@
7
7
  // type's kind, identity and a type test (see `__ti` in HELPER_GENERICS).
8
8
 
9
9
  import { forEachChild } from "../../lower/index.js";
10
- import { isAny, typeStr } from "../../typechecker/types.js";
10
+ import {
11
+ isAny,
12
+ isTypedArraySlice,
13
+ typedArrayConstructorForElem,
14
+ typeStr,
15
+ } from "../../typechecker/types.js";
11
16
  import { mentionsTypeParam } from "./expressions.js";
12
17
 
13
18
  // True when type node `node` names one of the type parameters `names`.
@@ -68,8 +73,22 @@ export const typeInfoGenMethods = {
68
73
  return found;
69
74
  },
70
75
 
76
+ // `x.M(...)` where x has a type parameter type: a raw value of a named
77
+ // non-struct type argument must be boxed to reach its methods.
78
+ _isTypeParamMethodCall(call, names) {
79
+ const t = call.func?.expr?._type;
80
+ return (
81
+ call.func?.kind === "SelectorExpr" &&
82
+ !call.func._isMethodExpr &&
83
+ t?.kind === "typeParam" &&
84
+ (!names || names.has(t.name))
85
+ );
86
+ },
87
+
71
88
  _nodeUsesTypeInfo(node, names) {
72
89
  if (node._ifaceKeyDyn && names.has(node._ifaceKeyParam)) return true;
90
+ if (node._ifaceSrc?.kind === "typeParam" && names.has(node._ifaceSrc.name))
91
+ return true;
73
92
  switch (node.kind) {
74
93
  case "TypeAssertExpr":
75
94
  return typeNodeMentions(node.type, names);
@@ -79,11 +98,23 @@ export const typeInfoGenMethods = {
79
98
  return typeNodeMentions(node.targetType, names);
80
99
  case "BinaryExpr":
81
100
  return (
82
- (node.op === "==" || node.op === "!=") &&
83
- node.left._type?.kind === "typeParam"
101
+ ((node.op === "==" || node.op === "!=") &&
102
+ node.left._type?.kind === "typeParam") ||
103
+ (node._type?.kind === "typeParam" && names.has(node._type.name))
104
+ );
105
+ case "AssignStmt":
106
+ return (
107
+ node.op !== "=" &&
108
+ node.lhs?.[0]?._type?.kind === "typeParam" &&
109
+ names.has(node.lhs[0]._type.name)
110
+ );
111
+ case "IncDecStmt":
112
+ return (
113
+ node.expr?._type?.kind === "typeParam" &&
114
+ names.has(node.expr._type.name)
84
115
  );
85
116
  case "CallExpr":
86
- return this._callUsesTypeInfo(node);
117
+ return this._callUsesTypeInfo(node, names);
87
118
  case "CompositeLit":
88
119
  case "GenericTypeName":
89
120
  return typeArgsMention(node);
@@ -92,7 +123,8 @@ export const typeInfoGenMethods = {
92
123
  }
93
124
  },
94
125
 
95
- _callUsesTypeInfo(call) {
126
+ _callUsesTypeInfo(call, names) {
127
+ if (this._isTypeParamMethodCall(call, names)) return true;
96
128
  if (call._isTypeConversion)
97
129
  return call._conversionTargetType?.kind === "typeParam";
98
130
  // A fmt operand of a type parameter type is formatted by its instantiated
@@ -146,7 +178,7 @@ export const typeInfoGenMethods = {
146
178
  const check = this._typeCheckForType(t, "__v");
147
179
  const is = check ? `(__v) => ${check}` : "null";
148
180
  const unbox = this._boxesInInterface(t) ? "true" : "false";
149
- const box = this._ifaceKeyBox(t);
181
+ const box = this._ifaceKeyBox(t) ?? this._ptrMethodBox(t);
150
182
  const fmtBox = this._fmtBoxFor(t);
151
183
  const tail = fmtBox
152
184
  ? `, ${box ?? "null"}, ${fmtBox}`
@@ -156,6 +188,22 @@ export const typeInfoGenMethods = {
156
188
  return `__ti(${base}, ${JSON.stringify(k)}, ${JSON.stringify(typeStr(t))}, ${is}, ${unbox}${tail})`;
157
189
  },
158
190
 
191
+ // How `__ik` tells the dynamic type of an interface map key apart where
192
+ // the JS value cannot: the numeric type name for types sharing a JS
193
+ // representation with `int`/`float64`/`int64`, `*` for pointers (keyed by
194
+ // identity) and `!type` for types Go cannot hash.
195
+ _ifaceKeyTag(t) {
196
+ if (!t || t.kind === "named" || mentionsTypeParam(t)) return null;
197
+ if (t.kind === "pointer") return "*";
198
+ if (t.kind === "slice" || t.kind === "map" || t.kind === "func")
199
+ return `!${this._dynTypeName(t)}`;
200
+ if (t.kind !== "basic") return null;
201
+ const name = this._dynTypeName(t);
202
+ if (["int", "float64", "int64", "string", "bool"].includes(name))
203
+ return null;
204
+ return /^(u?int\d*|uintptr|float32)$/.test(name) ? name : null;
205
+ },
206
+
159
207
  // A function boxing a plain value of named basic type `t`, as converting
160
208
  // it to an interface does; null when `t` is not boxed.
161
209
  _ifaceKeyBox(t) {
@@ -163,9 +211,27 @@ export const typeInfoGenMethods = {
163
211
  return `(__v) => ${this._genNamedIfaceBox("__v", t, false)}`;
164
212
  },
165
213
 
214
+ // A function boxing a pointer to a named non-struct type with methods, as
215
+ // converting it to an interface does; null for any other type.
216
+ _ptrMethodBox(t) {
217
+ if (t?.kind !== "pointer" || !this._boxesInInterface(t)) return null;
218
+ return `(__v) => ${this._genNamedIfaceBox("__v", t.base, true)}`;
219
+ },
220
+
166
221
  // A function giving a plain value of type `t` the type information fmt
167
- // needs once it is an `any` operand (see _fmtDesc); null when it needs none.
222
+ // needs once it is an `any` operand; null when it needs none.
168
223
  _fmtBoxFor(t) {
224
+ const base = this._fmtBoxBase(t);
225
+ // An unnamed slice, map or pointer also carries its type tag, as
226
+ // converting it to an `any` gives it, so a type switch tells it apart.
227
+ const tag = this._dynTypeTag(t);
228
+ if (!tag) return base;
229
+ this.useHelper("ifaceBox");
230
+ const ptr = t.kind === "pointer" ? ", true" : "";
231
+ return `(__v) => __ity(${base ? `(${base})(__v)` : "__v"}, ${JSON.stringify(tag)}${ptr})`;
232
+ },
233
+
234
+ _fmtBoxBase(t) {
169
235
  if (!t || t.kind === "typeParam" || mentionsTypeParam(t)) return null;
170
236
  if (this._ifaceKeyBox(t)) return null; // boxed by the key box already
171
237
  const u = this._underlyingOf(t);
@@ -173,15 +239,113 @@ export const typeInfoGenMethods = {
173
239
  this.useHelper("ifaceBox");
174
240
  return "(__v) => __ifp(__v)";
175
241
  }
176
- const desc = this._fmtDesc(t);
177
- if (desc) this.useHelper("ifd");
178
- const tagged = (v) => (desc ? `__ifd(${v}, ${desc})` : v);
179
242
  if (u?.kind === "map") {
180
243
  this.useHelper("ifaceBox");
181
- return `(__v) => __ifm(${tagged("__v")})`;
244
+ return "(__v) => __ifm(__v)";
245
+ }
246
+ if (u?.kind === "slice") return "(__v) => (__v ?? [])";
247
+ return null;
248
+ },
249
+
250
+ // The run-time type tag of an unnamed slice, map or non-struct pointer
251
+ // entering an interface, or null when the value needs none. The tag lets
252
+ // assertions and type switches tell `[]int32` from `[]uint8`, `*int` from
253
+ // `*string`, and a nil `*int` from the nil interface.
254
+ _dynTypeTag(t) {
255
+ if (!t || mentionsTypeParam(t)) return null;
256
+ if (
257
+ t.kind === "slice" ||
258
+ t.kind === "map" ||
259
+ t.kind === "array" ||
260
+ t.kind === "struct" ||
261
+ t.kind === "func"
262
+ )
263
+ return this._dynTypeName(t);
264
+ if (this._isNamedComposite(t)) return t.name;
265
+ if (t.kind === "pointer") {
266
+ const base = t.base;
267
+ if (base?.kind === "struct" && !base.name) return this._dynTypeName(t);
268
+ if (this._underlyingOf(base)?.kind === "struct") return null;
269
+ if (this._boxesInInterface(t)) return null;
270
+ return this._dynTypeName(t);
271
+ }
272
+ return null;
273
+ },
274
+
275
+ // A named slice, array or map type: its values are plain JS arrays and
276
+ // objects, told apart in an interface by a tag carrying the type's name.
277
+ _isNamedComposite(t) {
278
+ if (t?.kind !== "named" || this._namedBoxInfo(t)) return false;
279
+ const kind = this._underlyingOf(t)?.kind;
280
+ return kind === "slice" || kind === "array" || kind === "map";
281
+ },
282
+
283
+ // `typeStr` with the spellings of identical types unified.
284
+ _dynTypeName(t) {
285
+ return this._dynTypeStr(t)
286
+ .replace(/\bbyte\b/g, "uint8")
287
+ .replace(/\brune\b/g, "int32")
288
+ .replace(/\bany\b/g, "interface {}")
289
+ .replace(/interface\{\.\.\.\}/g, "interface {}");
290
+ },
291
+
292
+ // `typeStr`, but an unnamed struct lists its fields, so two anonymous
293
+ // structs do not share a tag.
294
+ _dynTypeStr(t) {
295
+ const s = (x) => this._dynTypeStr(x);
296
+ switch (t?.kind) {
297
+ case "slice":
298
+ return `[]${s(t.elem)}`;
299
+ case "array":
300
+ return `[${t.size?.value ?? t.size ?? "..."}]${s(t.elem)}`;
301
+ case "map":
302
+ return `map[${s(t.key)}]${s(t.value)}`;
303
+ case "pointer":
304
+ return `*${s(t.base)}`;
305
+ case "struct":
306
+ if (t.name || !t._structFields) return typeStr(t);
307
+ return `struct{${t._structFields
308
+ .map(
309
+ (f) =>
310
+ `${(f.names ?? []).join(",")} ${s(f.type)}${f.tag ? ` ${JSON.stringify(f.tag)}` : ""}${f.embedded ? " embedded" : ""}`,
311
+ )
312
+ .join("; ")}}`;
313
+ default:
314
+ return typeStr(t);
315
+ }
316
+ },
317
+
318
+ // Run-time test that `val` holds a value of the unnamed composite type
319
+ // (or named func type) of assertion target `typeNode`; null when the
320
+ // checker recorded no type for it.
321
+ _dynTypeCheck(typeNode, val) {
322
+ const t = typeNode?._assertType;
323
+ if (!t) return null;
324
+ const u = this._underlyingOf(t);
325
+ const shape = mentionsTypeParam(t);
326
+ const tag = JSON.stringify(shape ? "" : this._dynTypeName(t));
327
+ this.useHelper("ifaceBox");
328
+ const args = (kind, ctor = "null", n = "0") =>
329
+ `__isty(${val}, ${tag}, "${kind}", ${ctor}, ${n})`;
330
+ if (u?.kind === "func") return args("func");
331
+ if (t.kind === "named") return null;
332
+ if (u?.kind === "map") return args("map");
333
+ if (u?.kind === "struct") return args("struct");
334
+ if (u?.kind === "array")
335
+ return args("array", "null", String(u.size?.value ?? u.size ?? 0));
336
+ if (u?.kind === "slice") {
337
+ const ctor = isTypedArraySlice(u)
338
+ ? typedArrayConstructorForElem(u.elem)
339
+ : null;
340
+ return args("slice", ctor ?? "null");
341
+ }
342
+ if (u?.kind === "pointer") {
343
+ if (u.base?.kind === "struct" && !u.base.name) return args("struct");
344
+ if (this._underlyingOf(u.base)?.kind === "struct") return null;
345
+ if (this._boxesInInterface(u)) return null;
346
+ return args("ptr");
182
347
  }
183
- if (u?.kind === "slice") return `(__v) => ${tagged("(__v ?? [])")}`;
184
- return desc ? `(__v) => ${tagged("__v")}` : null;
348
+ return null;
185
349
  },
186
350
 
187
351
  _underlyingOf(t) {
@@ -0,0 +1,20 @@
1
+ // The host half of strconv's fixed-point float formatting: a JS function,
2
+ // as source text, that both backends embed (src/backend/js/natives.js and the
3
+ // WASM host imports in src/backend/wasm/boundary.js).
4
+ //
5
+ // `toFixed` is exact except that it rounds a tie (a value whose decimal
6
+ // expansion ends in a 5 right after the digits kept) up, where Go rounds it
7
+ // to even; the extra digits of a second call tell the two apart. A tie is a
8
+ // multiple of 2^-(p+1), so any other value needs no second call. "" declines.
9
+ export const FIXED_FLOAT_JS = `(x, p) => {
10
+ if (!(x < 1e21)) return "";
11
+ const s = x.toFixed(p);
12
+ if (x % 1 === 0 || (x * 2 ** (p + 1)) % 1 !== 0) return s;
13
+ const t = x.toFixed(p + 30);
14
+ const dot = t.indexOf(".");
15
+ if (/^50*$/.test(t.slice(dot + 1 + p))) {
16
+ const down = t.slice(0, p === 0 ? dot : dot + 1 + p);
17
+ return (down.charCodeAt(down.length - 1) - 48) % 2 === 0 ? down : s;
18
+ }
19
+ return s;
20
+ }`;