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
@@ -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,8 +7,14 @@
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";
11
- import { mentionsTypeParam } from "./expressions.js";
10
+ import {
11
+ isAny,
12
+ isTypedArraySlice,
13
+ typedArrayConstructorForElem,
14
+ typeStr,
15
+ } from "../../typechecker/types.js";
16
+ import { goTypeName } from "../shared/fmt-types.js";
17
+ import { isGenericComposite, mentionsTypeParam } from "./expressions.js";
12
18
 
13
19
  // True when type node `node` names one of the type parameters `names`.
14
20
  function typeNodeMentions(node, names) {
@@ -68,8 +74,24 @@ export const typeInfoGenMethods = {
68
74
  return found;
69
75
  },
70
76
 
77
+ // `x.M(...)` where x has a type parameter type: a raw value of a named
78
+ // non-struct type argument must be boxed to reach its methods.
79
+ _isTypeParamMethodCall(call, names) {
80
+ const t = call.func?.expr?._type;
81
+ return (
82
+ call.func?.kind === "SelectorExpr" &&
83
+ !call.func._isMethodExpr &&
84
+ t?.kind === "typeParam" &&
85
+ (!names || names.has(t.name))
86
+ );
87
+ },
88
+
71
89
  _nodeUsesTypeInfo(node, names) {
72
90
  if (node._ifaceKeyDyn && names.has(node._ifaceKeyParam)) return true;
91
+ // A map or slice entering an interface is tagged by its instantiated type.
92
+ if (isGenericComposite(node._ifaceSrc)) return true;
93
+ if (node._ifaceSrc?.kind === "typeParam" && names.has(node._ifaceSrc.name))
94
+ return true;
73
95
  switch (node.kind) {
74
96
  case "TypeAssertExpr":
75
97
  return typeNodeMentions(node.type, names);
@@ -79,11 +101,23 @@ export const typeInfoGenMethods = {
79
101
  return typeNodeMentions(node.targetType, names);
80
102
  case "BinaryExpr":
81
103
  return (
82
- (node.op === "==" || node.op === "!=") &&
83
- node.left._type?.kind === "typeParam"
104
+ ((node.op === "==" || node.op === "!=") &&
105
+ node.left._type?.kind === "typeParam") ||
106
+ (node._type?.kind === "typeParam" && names.has(node._type.name))
107
+ );
108
+ case "AssignStmt":
109
+ return (
110
+ node.op !== "=" &&
111
+ node.lhs?.[0]?._type?.kind === "typeParam" &&
112
+ names.has(node.lhs[0]._type.name)
113
+ );
114
+ case "IncDecStmt":
115
+ return (
116
+ node.expr?._type?.kind === "typeParam" &&
117
+ names.has(node.expr._type.name)
84
118
  );
85
119
  case "CallExpr":
86
- return this._callUsesTypeInfo(node);
120
+ return this._callUsesTypeInfo(node, names);
87
121
  case "CompositeLit":
88
122
  case "GenericTypeName":
89
123
  return typeArgsMention(node);
@@ -92,7 +126,8 @@ export const typeInfoGenMethods = {
92
126
  }
93
127
  },
94
128
 
95
- _callUsesTypeInfo(call) {
129
+ _callUsesTypeInfo(call, names) {
130
+ if (this._isTypeParamMethodCall(call, names)) return true;
96
131
  if (call._isTypeConversion)
97
132
  return call._conversionTargetType?.kind === "typeParam";
98
133
  // A fmt operand of a type parameter type is formatted by its instantiated
@@ -146,16 +181,45 @@ export const typeInfoGenMethods = {
146
181
  const check = this._typeCheckForType(t, "__v");
147
182
  const is = check ? `(__v) => ${check}` : "null";
148
183
  const unbox = this._boxesInInterface(t) ? "true" : "false";
149
- const box = this._ifaceKeyBox(t);
184
+ const box = this._ifaceKeyBox(t) ?? this._ptrMethodBox(t);
150
185
  const fmtBox = this._fmtBoxFor(t);
151
- const tail = fmtBox
152
- ? `, ${box ?? "null"}, ${fmtBox}`
153
- : box
154
- ? `, ${box}`
155
- : "";
186
+ const tail = this._typeInfoTail(t, box, fmtBox);
156
187
  return `__ti(${base}, ${JSON.stringify(k)}, ${JSON.stringify(typeStr(t))}, ${is}, ${unbox}${tail})`;
157
188
  },
158
189
 
190
+ // The optional trailing arguments of `__ti` (`b`, `fb`, `nm`). `typeStr`
191
+ // drops the `...` of a variadic func type, so a func type also carries its
192
+ // spelled name (see `__tnm`).
193
+ _typeInfoTail(t, box, fmtBox) {
194
+ if (t.kind === "func")
195
+ return `, ${box ?? "null"}, ${fmtBox ?? "null"}, ${JSON.stringify(this._dynTypeName(t))}`;
196
+ if (fmtBox) return `, ${box ?? "null"}, ${fmtBox}`;
197
+ return box ? `, ${box}` : "";
198
+ },
199
+
200
+ // How `__ik` tells the dynamic type of an interface map key apart where
201
+ // the JS value cannot: the numeric type name for types sharing a JS
202
+ // representation with `int`/`float64`/`int64`, `*` for pointers (keyed by
203
+ // identity) and `!type` for types Go cannot hash.
204
+ _ifaceKeyTag(t) {
205
+ if (!t || mentionsTypeParam(t)) return null;
206
+ if (t.kind === "named") {
207
+ // A defined slice, map or func type: a literal of it carries no tag.
208
+ const u = this._underlyingOf(t)?.kind;
209
+ return u === "slice" || u === "map" || u === "func"
210
+ ? `!${goTypeName(t)}`
211
+ : null;
212
+ }
213
+ if (t.kind === "pointer") return "*";
214
+ if (t.kind === "slice" || t.kind === "map" || t.kind === "func")
215
+ return `!${this._dynTypeName(t)}`;
216
+ if (t.kind !== "basic") return null;
217
+ const name = this._dynTypeName(t);
218
+ if (["int", "float64", "int64", "string", "bool"].includes(name))
219
+ return null;
220
+ return /^(u?int\d*|uintptr|float32)$/.test(name) ? name : null;
221
+ },
222
+
159
223
  // A function boxing a plain value of named basic type `t`, as converting
160
224
  // it to an interface does; null when `t` is not boxed.
161
225
  _ifaceKeyBox(t) {
@@ -163,9 +227,27 @@ export const typeInfoGenMethods = {
163
227
  return `(__v) => ${this._genNamedIfaceBox("__v", t, false)}`;
164
228
  },
165
229
 
230
+ // A function boxing a pointer to a named non-struct type with methods, as
231
+ // converting it to an interface does; null for any other type.
232
+ _ptrMethodBox(t) {
233
+ if (t?.kind !== "pointer" || !this._boxesInInterface(t)) return null;
234
+ return `(__v) => ${this._genNamedIfaceBox("__v", t.base, true)}`;
235
+ },
236
+
166
237
  // 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.
238
+ // needs once it is an `any` operand; null when it needs none.
168
239
  _fmtBoxFor(t) {
240
+ const base = this._fmtBoxBase(t);
241
+ // An unnamed slice, map or pointer also carries its type tag, as
242
+ // converting it to an `any` gives it, so a type switch tells it apart.
243
+ const tag = this._dynTypeTag(t);
244
+ if (!tag) return base;
245
+ this.useHelper("ifaceBox");
246
+ const ptr = this._dynTagFlags(t);
247
+ return `(__v) => __ity(${base ? `(${base})(__v)` : "__v"}, ${JSON.stringify(tag)}${ptr})`;
248
+ },
249
+
250
+ _fmtBoxBase(t) {
169
251
  if (!t || t.kind === "typeParam" || mentionsTypeParam(t)) return null;
170
252
  if (this._ifaceKeyBox(t)) return null; // boxed by the key box already
171
253
  const u = this._underlyingOf(t);
@@ -173,15 +255,212 @@ export const typeInfoGenMethods = {
173
255
  this.useHelper("ifaceBox");
174
256
  return "(__v) => __ifp(__v)";
175
257
  }
176
- const desc = this._fmtDesc(t);
177
- if (desc) this.useHelper("ifd");
178
- const tagged = (v) => (desc ? `__ifd(${v}, ${desc})` : v);
258
+ // A struct value is marked as a value, so a pointer boxed earlier by the
259
+ // same generic code does not make it read as `*T`.
260
+ if (u?.kind === "struct" && t.kind !== "pointer") {
261
+ this.useHelper("ifaceBox");
262
+ return "(__v) => __ifv(__v)";
263
+ }
179
264
  if (u?.kind === "map") {
180
265
  this.useHelper("ifaceBox");
181
- return `(__v) => __ifm(${tagged("__v")})`;
266
+ return "(__v) => __ifm(__v)";
267
+ }
268
+ if (u?.kind === "slice") return "(__v) => (__v ?? [])";
269
+ return null;
270
+ },
271
+
272
+ // Extra `__ity` arguments: `, true` marks a pointer and `, false, true` a
273
+ // defined slice type, whose nil value keeps its type as a tagged sentinel.
274
+ _dynTagFlags(t) {
275
+ if (t?.kind === "pointer") return ", true";
276
+ if (this._isNamedComposite(t) && this._underlyingOf(t)?.kind === "slice")
277
+ return ", false, true";
278
+ return "";
279
+ },
280
+
281
+ // JS expression for the run-time type tag of a map, slice or array type `t`
282
+ // that mentions type parameters, built from their type information (see
283
+ // `__ti`); null when some part cannot be spelled.
284
+ _genericMapTag(t) {
285
+ const part = (x) => {
286
+ if (!mentionsTypeParam(x)) return JSON.stringify(this._dynTypeName(x));
287
+ switch (x.kind) {
288
+ case "typeParam": {
289
+ const f = this._typeParamZeroFactory(x.name);
290
+ return f ? `__tnm(${f})` : null;
291
+ }
292
+ case "slice": {
293
+ const e = part(x.elem);
294
+ return e && `"[]" + ${e}`;
295
+ }
296
+ case "array": {
297
+ const e = part(x.elem);
298
+ return e && `"[${x.size}]" + ${e}`;
299
+ }
300
+ case "pointer": {
301
+ const e = part(x.base);
302
+ return e && `"*" + ${e}`;
303
+ }
304
+ case "map": {
305
+ const k = part(x.key);
306
+ const v = part(x.value);
307
+ return k && v && `"map[" + ${k} + "]" + ${v}`;
308
+ }
309
+ default:
310
+ return null;
311
+ }
312
+ };
313
+ return part(t);
314
+ },
315
+
316
+ // A defined func type whose values carry their name as a run-time tag
317
+ // (one with methods is boxed as a class instead).
318
+ _isTaggedFuncType(t) {
319
+ return (
320
+ t?.kind === "named" &&
321
+ !mentionsTypeParam(t) &&
322
+ !t.typeArgs?.length &&
323
+ this._underlyingOf(t)?.kind === "func" &&
324
+ !this._namedBoxInfo(t)
325
+ );
326
+ },
327
+
328
+ // The run-time type tag of an unnamed slice, map or non-struct pointer
329
+ // entering an interface, or null when the value needs none. The tag lets
330
+ // assertions and type switches tell `[]int32` from `[]uint8`, `*int` from
331
+ // `*string`, and a nil `*int` from the nil interface.
332
+ _dynTypeTag(t) {
333
+ if (!t || mentionsTypeParam(t)) return null;
334
+ if (
335
+ t.kind === "slice" ||
336
+ t.kind === "map" ||
337
+ t.kind === "array" ||
338
+ t.kind === "struct" ||
339
+ t.kind === "func"
340
+ )
341
+ return this._dynTypeName(t);
342
+ if (this._isNamedComposite(t)) return t.name;
343
+ if (t.kind === "pointer") {
344
+ const base = t.base;
345
+ if (base?.kind === "struct" && !base.name) return this._dynTypeName(t);
346
+ if (this._underlyingOf(base)?.kind === "struct") return null;
347
+ if (this._boxesInInterface(t)) return null;
348
+ return this._dynTypeName(t);
349
+ }
350
+ return null;
351
+ },
352
+
353
+ // Whether Go cannot hash a value of type `t` (it contains a slice, map or
354
+ // func), so using it as an interface map key panics.
355
+ _isUnhashableType(t, depth = 0) {
356
+ if (!t || depth > 8 || mentionsTypeParam(t)) return false;
357
+ const u = this._underlyingOf(t) ?? t;
358
+ switch (u.kind) {
359
+ case "slice":
360
+ case "map":
361
+ case "func":
362
+ return true;
363
+ case "array":
364
+ return this._isUnhashableType(u.elem, depth + 1);
365
+ case "struct":
366
+ return [...(u.fields?.values() ?? [])].some((f) =>
367
+ this._isUnhashableType(f, depth + 1),
368
+ );
369
+ default:
370
+ return false;
371
+ }
372
+ },
373
+
374
+ // A named slice, array or map type: its values are plain JS arrays and
375
+ // objects, told apart in an interface by a tag carrying the type's name.
376
+ _isNamedComposite(t) {
377
+ if (t?.kind !== "named" || this._namedBoxInfo(t)) return false;
378
+ const kind = this._underlyingOf(t)?.kind;
379
+ return kind === "slice" || kind === "array" || kind === "map";
380
+ },
381
+
382
+ // `typeStr` with the spellings of identical types unified.
383
+ _dynTypeName(t) {
384
+ return this._dynTypeStr(t)
385
+ .replace(/\bbyte\b/g, "uint8")
386
+ .replace(/\brune\b/g, "int32")
387
+ .replace(/\bany\b/g, "interface {}")
388
+ .replace(/interface\{\.\.\.\}/g, "interface {}");
389
+ },
390
+
391
+ // `typeStr`, but an unnamed struct lists its fields, so two anonymous
392
+ // structs do not share a tag.
393
+ _dynTypeStr(t) {
394
+ const s = (x) => this._dynTypeStr(x);
395
+ switch (t?.kind) {
396
+ case "slice":
397
+ return `[]${s(t.elem)}`;
398
+ case "array":
399
+ return `[${t.size?.value ?? t.size ?? "..."}]${s(t.elem)}`;
400
+ case "map":
401
+ return `map[${s(t.key)}]${s(t.value)}`;
402
+ case "pointer":
403
+ return `*${s(t.base)}`;
404
+ case "func": {
405
+ // Go's spelling: no result is `func()`, not `func() void`.
406
+ const rs = (t.returns ?? []).filter(
407
+ (r) => !(r?.kind === "basic" && r.name === "void"),
408
+ );
409
+ const ps = (t.params ?? []).map((p, i) =>
410
+ t.variadic && i === t.params.length - 1 ? `...${s(p)}` : s(p),
411
+ );
412
+ const res =
413
+ rs.length === 0
414
+ ? ""
415
+ : rs.length === 1
416
+ ? ` ${s(rs[0])}`
417
+ : ` (${rs.map(s).join(", ")})`;
418
+ return `func(${ps.join(", ")})${res}`;
419
+ }
420
+ case "struct":
421
+ if (t.name || !t._structFields) return typeStr(t);
422
+ return `struct{${t._structFields
423
+ .map(
424
+ (f) =>
425
+ `${(f.names ?? []).join(",")} ${s(f.type)}${f.tag ? ` ${JSON.stringify(f.tag)}` : ""}${f.embedded ? " embedded" : ""}`,
426
+ )
427
+ .join("; ")}}`;
428
+ default:
429
+ return typeStr(t);
430
+ }
431
+ },
432
+
433
+ // Run-time test that `val` holds a value of the unnamed composite type
434
+ // (or named func type) of assertion target `typeNode`; null when the
435
+ // checker recorded no type for it.
436
+ _dynTypeCheck(typeNode, val) {
437
+ const t = typeNode?._assertType;
438
+ if (!t) return null;
439
+ const u = this._underlyingOf(t);
440
+ const shape = mentionsTypeParam(t);
441
+ const tag = JSON.stringify(shape ? "" : this._dynTypeName(t));
442
+ this.useHelper("ifaceBox");
443
+ const args = (kind, ctor = "null", n = "0") =>
444
+ `__isty(${val}, ${tag}, "${kind}", ${ctor}, ${n})`;
445
+ if (u?.kind === "func") return args("func");
446
+ if (t.kind === "named") return null;
447
+ if (u?.kind === "map") return args("map");
448
+ if (u?.kind === "struct") return args("struct");
449
+ if (u?.kind === "array")
450
+ return args("array", "null", String(u.size?.value ?? u.size ?? 0));
451
+ if (u?.kind === "slice") {
452
+ const ctor = isTypedArraySlice(u)
453
+ ? typedArrayConstructorForElem(u.elem)
454
+ : null;
455
+ return args("slice", ctor ?? "null");
456
+ }
457
+ if (u?.kind === "pointer") {
458
+ if (u.base?.kind === "struct" && !u.base.name) return args("struct");
459
+ if (this._underlyingOf(u.base)?.kind === "struct") return null;
460
+ if (this._boxesInInterface(u)) return null;
461
+ return args("ptr");
182
462
  }
183
- if (u?.kind === "slice") return `(__v) => ${tagged("(__v ?? [])")}`;
184
- return desc ? `(__v) => ${tagged("__v")}` : null;
463
+ return null;
185
464
  },
186
465
 
187
466
  _underlyingOf(t) {