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,7 +1,7 @@
1
1
  // src/lower/functions.js
2
2
  // Analyzes function signatures, named returns and defer structures.
3
3
 
4
- import { forEachChild, someChild } from "./walk.js";
4
+ import { forEachChild, hasCallOrReceive, someChild } from "./walk.js";
5
5
 
6
6
  export function extractNamedReturns(decl) {
7
7
  const named = decl?.returnType?._namedReturns;
@@ -146,6 +146,80 @@ function deferCalleeMayRecover(funcExpr, ctx) {
146
146
  return true;
147
147
  }
148
148
 
149
+ // A constant argument (`2.5`, `"a"`, `nil`, `k` for a constant `k`, `-1`)
150
+ // has no value to capture at the `defer` statement, so it stays in the
151
+ // deferred call, where it takes the callee's parameter type exactly as in a
152
+ // direct call. Hoisting it into a temporary would default its type (`float64`,
153
+ // `int`) and reject a defined-type, interface, pointer or generic parameter.
154
+ function isDeferConstant(arg) {
155
+ if (arg?.kind === "BasicLit" && arg.litKind === "NIL") return true;
156
+ if (arg?._type?.kind !== "untyped") return false;
157
+ if (arg._const) return true;
158
+ switch (arg.kind) {
159
+ case "BasicLit":
160
+ case "ImagLit":
161
+ return true;
162
+ case "ParenExpr":
163
+ return isDeferConstant(arg.expr);
164
+ case "UnaryExpr":
165
+ return isDeferConstant(arg.operand);
166
+ case "BinaryExpr":
167
+ return isDeferConstant(arg.left) && isDeferConstant(arg.right);
168
+ default:
169
+ return false;
170
+ }
171
+ }
172
+
173
+ // A package-level basic or defined type a temporary can be declared with by
174
+ // its name; other parameter types (qualified, generic, composite) are not
175
+ // hoisted with a declared type.
176
+ function isNamedScalarParam(t) {
177
+ if (t?.kind === "basic")
178
+ return t.name !== "any" && /^[A-Za-z_]\w*$/.test(t.name ?? "");
179
+ return (
180
+ t?.kind === "named" &&
181
+ t.underlying?.kind === "basic" &&
182
+ !t.typeArgs?.length &&
183
+ /^[A-Za-z_]\w*$/.test(t.name ?? "")
184
+ );
185
+ }
186
+
187
+ // A non-constant untyped expression (`1 << n`) the typechecker already gave
188
+ // its parameter's type: it is still untyped when the lowered code is checked
189
+ // again, so its temporary needs that type declared.
190
+ function isRetypedUntyped(e) {
191
+ if (!e || e._const) return false;
192
+ switch (e.kind) {
193
+ case "ParenExpr":
194
+ return isRetypedUntyped(e.expr);
195
+ case "UnaryExpr":
196
+ return isRetypedUntyped(e.operand);
197
+ case "BinaryExpr":
198
+ if (e.op === "<<" || e.op === ">>") return Boolean(e.left?._const);
199
+ return isRetypedUntyped(e.left) || isRetypedUntyped(e.right);
200
+ default:
201
+ return false;
202
+ }
203
+ }
204
+
205
+ // The type of the temporary that holds deferred argument `a`: an untyped
206
+ // non-constant value (a shift, a comparison) takes its basic or defined
207
+ // scalar parameter's type, as it does in a direct call. A variadic call's
208
+ // trailing arguments take the element type.
209
+ function deferTempType(funcType, a, arg) {
210
+ const argType = arg._type;
211
+ if (argType?.kind !== "untyped" && !isRetypedUntyped(arg)) return argType;
212
+ const params = funcType?.params;
213
+ let param = params?.[a];
214
+ if (funcType?.variadic && a >= params.length - 1) {
215
+ // The last parameter's type is the element type (a slice only when
216
+ // a table spells it that way).
217
+ param = params[params.length - 1];
218
+ if (!isNamedScalarParam(param)) param = param?.elem;
219
+ }
220
+ return isNamedScalarParam(param) ? param : argType;
221
+ }
222
+
149
223
  function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
150
224
  if (stmt.call?.kind !== "CallExpr") return 0;
151
225
  const call = stmt.call;
@@ -245,19 +319,40 @@ function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
245
319
  lhs: tupleLhs,
246
320
  rhs: [arg],
247
321
  });
322
+ } else if (isDeferConstant(arg)) {
323
+ tempIdents.push(arg);
248
324
  } else {
249
325
  defargCounter++;
250
326
  const tempName = `__defarg$${defargCounter}`;
327
+ const tempType = deferTempType(call.func?._type, a, arg);
251
328
  tempIdents.push({
252
329
  kind: "Ident",
253
330
  name: tempName,
254
- _type: arg._type,
255
- });
256
- tempStmts.push({
257
- kind: "DefineStmt",
258
- lhs: [{ kind: "Ident", name: tempName, _type: arg._type }],
259
- rhs: [arg],
331
+ _type: tempType,
332
+ // A spread argument stays one: the call gets the slice itself.
333
+ ...(arg._spread && { _spread: true }),
260
334
  });
335
+ tempStmts.push(
336
+ tempType === arg._type &&
337
+ !(isRetypedUntyped(arg) && isNamedScalarParam(tempType))
338
+ ? {
339
+ kind: "DefineStmt",
340
+ lhs: [{ kind: "Ident", name: tempName, _type: tempType }],
341
+ rhs: [arg],
342
+ }
343
+ : {
344
+ // Declared with the parameter's type so the lowered code
345
+ // type-checks again with the constant converted.
346
+ kind: "VarDecl",
347
+ decls: [
348
+ {
349
+ names: [tempName],
350
+ type: { kind: "TypeName", name: tempType.name },
351
+ value: [arg],
352
+ },
353
+ ],
354
+ },
355
+ );
261
356
  }
262
357
  }
263
358
  }
@@ -271,6 +366,8 @@ function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
271
366
  ...call,
272
367
  func: funcExpr,
273
368
  args: callArgs,
369
+ // A deferred native call runs the Go body, as on the JS backend.
370
+ _noNative: true,
274
371
  _multiForward: tempIdents.length > 0 ? false : call._multiForward,
275
372
  };
276
373
  const synthFuncLit = {
@@ -300,3 +397,124 @@ function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
300
397
 
301
398
  return tempStmts.length;
302
399
  }
400
+
401
+ let fwdCounter = 0;
402
+
403
+ /**
404
+ * Splits a multi-value call forwarded into interfaces into temps:
405
+ * `return f()` where some result is an interface becomes
406
+ * `__fwd$1, __fwd$2 := f(); return __fwd$1, __fwd$2`, and `g(f())` where
407
+ * some parameter is an interface becomes `…; g(__fwd$1, __fwd$2)`. The
408
+ * typechecker records per-element interface-box marks in `call._fwdIface`;
409
+ * each temp takes its element's marks, so both backends box it through their
410
+ * ordinary per-value path. Forwards with no interface element are left alone.
411
+ *
412
+ * `g(f())` is split only as the operand of a statement (expression
413
+ * statement, single-value `:=`/`=`/`var`/`return`) whose callee evaluates
414
+ * no call, so hoisting `f()` keeps Go's evaluation order.
415
+ */
416
+ export function splitForwardedTuples(programOrPrograms) {
417
+ fwdCounter = 0;
418
+ const programs = Array.isArray(programOrPrograms)
419
+ ? programOrPrograms
420
+ : [programOrPrograms];
421
+ for (const program of programs)
422
+ for (const d of program?.decls ?? []) splitForwardsIn(d);
423
+ }
424
+
425
+ function splitForwardsIn(node) {
426
+ if (!node || typeof node !== "object") return;
427
+ if (Array.isArray(node)) {
428
+ for (let i = 0; i < node.length; i++) {
429
+ const item = node[i];
430
+ splitForwardsIn(item);
431
+ const defines = splitForwardsInStmt(item);
432
+ node.splice(i, 0, ...defines);
433
+ i += defines.length;
434
+ }
435
+ return;
436
+ }
437
+ forEachChild(node, splitForwardsIn);
438
+ }
439
+
440
+ // The `:=` statements to insert before `stmt` (in order) after rewriting its
441
+ // forwarded calls to read the temps.
442
+ function splitForwardsInStmt(stmt) {
443
+ const defines = [];
444
+ const operand = statementOperand(stmt);
445
+ if (
446
+ operand?.kind === "CallExpr" &&
447
+ operand._multiForward &&
448
+ operand.args?.length === 1 &&
449
+ !hasCallOrReceive(operand.func)
450
+ ) {
451
+ const split = forwardTemps(operand.args[0]);
452
+ if (split) {
453
+ operand.args = split.operands;
454
+ operand._multiForward = false;
455
+ defines.push(split.define);
456
+ }
457
+ }
458
+ if (stmt?.kind === "ReturnStmt") {
459
+ const define = splitForwardedReturn(stmt);
460
+ if (define) defines.push(define);
461
+ }
462
+ return defines;
463
+ }
464
+
465
+ // The single expression a statement evaluates last, or null.
466
+ function statementOperand(stmt) {
467
+ switch (stmt?.kind) {
468
+ case "ExprStmt":
469
+ return stmt.expr;
470
+ case "DefineStmt":
471
+ case "AssignStmt":
472
+ return stmt.rhs?.length === 1 &&
473
+ stmt.lhs?.every((e) => e.kind === "Ident")
474
+ ? stmt.rhs[0]
475
+ : null;
476
+ case "ReturnStmt":
477
+ return stmt.values?.length === 1 ? stmt.values[0] : null;
478
+ case "VarDecl":
479
+ return stmt.decls?.length === 1 && stmt.decls[0].value?.length === 1
480
+ ? stmt.decls[0].value[0]
481
+ : null;
482
+ default:
483
+ return null;
484
+ }
485
+ }
486
+
487
+ // Splits one forwarded call into temps: returns the `:=` statement defining
488
+ // them and the operand idents (carrying each element's interface-box marks),
489
+ // or null when the call needs no split.
490
+ function forwardTemps(call) {
491
+ const holders = call?._fwdIface;
492
+ const types = call?._type?.types;
493
+ if (!holders || !Array.isArray(types) || types.length !== holders.length)
494
+ return null;
495
+ const names = types.map(() => `__fwd$${++fwdCounter}`);
496
+ const define = {
497
+ kind: "DefineStmt",
498
+ lhs: names.map((name, i) => ({ kind: "Ident", name, _type: types[i] })),
499
+ rhs: [call],
500
+ };
501
+ // The call is no longer a forward; a cached AST must not split it again.
502
+ call._fwdIface = undefined;
503
+ const operands = names.map((name, i) => ({
504
+ kind: "Ident",
505
+ name,
506
+ _type: types[i],
507
+ ...holders[i],
508
+ }));
509
+ return { define, operands };
510
+ }
511
+
512
+ function splitForwardedReturn(stmt) {
513
+ if (stmt.values?.length !== 1) return null;
514
+ const split = forwardTemps(stmt.values[0]);
515
+ if (!split) return null;
516
+ stmt.values = split.operands;
517
+ if (Array.isArray(stmt._types))
518
+ stmt._types = split.operands.map((o) => o._type);
519
+ return split.define;
520
+ }
@@ -0,0 +1,293 @@
1
+ // src/lower/gen-types.js
2
+ // Spells checked types as Go source for the generated per-package file
3
+ // (src/lower/json-codecs.js, src/lower/errors-as.js,
4
+ // src/lower/fmt-formatters.js), and holds what those generators share: the
5
+ // imports the spelled code needs and the enumeration of the program's types.
6
+
7
+ import { defaultType } from "../typechecker/types.js";
8
+ import { forEachChild } from "./walk.js";
9
+
10
+ export function under(t) {
11
+ return t?.kind === "named" ? under(t.underlying) : t;
12
+ }
13
+
14
+ export const isEmptyIface = (t) =>
15
+ (t?.kind === "basic" && t.name === "any") ||
16
+ (under(t)?.kind === "interface" && under(t).methods?.size === 0);
17
+
18
+ // Arrays and maps of these share one WebAssembly representation (and so one
19
+ // type-switch case), so only literals may register them: a `[2]byte` conversion
20
+ // must not shadow a `[2]int32` that is stored in an interface. A slice of
21
+ // them is tagged with its Go type in an interface and registers like any other.
22
+ export const SHARED_REPR = new Set([
23
+ "bool",
24
+ "int8",
25
+ "int16",
26
+ "int32",
27
+ "uint8",
28
+ "uint16",
29
+ "uint32",
30
+ "byte",
31
+ "rune",
32
+ ]);
33
+
34
+ export function hasSharedRepr(t, depth = 0) {
35
+ if (!t || depth > 8) return false;
36
+ const u = under(t);
37
+ if (u?.kind === "basic") return SHARED_REPR.has(u.name);
38
+ if (u?.kind === "slice" || u?.kind === "array")
39
+ return hasSharedRepr(u.elem, depth + 1);
40
+ if (u?.kind === "map")
41
+ return hasSharedRepr(u.key, depth + 1) || hasSharedRepr(u.value, depth + 1);
42
+ return false;
43
+ }
44
+
45
+ // A method's results; a multi-value result is one tuple type.
46
+ export function results(m) {
47
+ const r = (m.returns ?? []).filter(
48
+ (t) => !(t?.kind === "basic" && t.name === "void"),
49
+ );
50
+ return r.length === 1 && r[0]?.kind === "tuple" ? r[0].types : r;
51
+ }
52
+
53
+ // Calls every `fn(node)` for the AST nodes under `root`.
54
+ export function walkNodes(root, fn) {
55
+ if (!root || typeof root !== "object") return;
56
+ if (Array.isArray(root)) {
57
+ for (const n of root) walkNodes(n, fn);
58
+ return;
59
+ }
60
+ fn(root);
61
+ forEachChild(root, (c) => walkNodes(c, fn));
62
+ // templ bodies keep their Go expressions in non-enumerable properties
63
+ // (see parser/templ.js), which forEachChild does not visit.
64
+ for (const name of TEMPL_EXPR_PROPS) {
65
+ const d = Object.getOwnPropertyDescriptor(root, name);
66
+ if (d && !d.enumerable) walkNodes(d.value, fn);
67
+ }
68
+ }
69
+
70
+ const TEMPL_EXPR_PROPS = ["expr", "exprs", "forStmt"];
71
+
72
+ // True when `t` mentions a type parameter (a generic declaration, or a type
73
+ // built from one inside a generic function).
74
+ export function mentionsTypeParam(t, seen = new Set()) {
75
+ if (!t || seen.has(t)) return false;
76
+ seen.add(t);
77
+ const m = (x) => mentionsTypeParam(x, seen);
78
+ switch (t.kind) {
79
+ case "typeParam":
80
+ return true;
81
+ case "named":
82
+ return (t.typeArgs ?? []).some(m) || m(t.underlying);
83
+ case "pointer":
84
+ return m(t.base);
85
+ case "slice":
86
+ case "array":
87
+ return m(t.elem);
88
+ case "map":
89
+ return m(t.key) || m(t.value);
90
+ case "struct":
91
+ return (t._structFields ?? []).some((f) => m(f.type));
92
+ default:
93
+ return false;
94
+ }
95
+ }
96
+
97
+ // The last element of an import path: the package's name.
98
+ const canonicalName = (modPath) => modPath.split("/").pop();
99
+
100
+ // The names the programs declare at package level.
101
+ function declaredNames(programs) {
102
+ const names = new Set();
103
+ for (const p of programs)
104
+ for (const d of p.decls ?? []) {
105
+ if (d.kind === "TypeDecl" || d.kind === "FuncDecl") names.add(d.name);
106
+ else if (d.kind === "VarDecl" || d.kind === "ConstDecl")
107
+ for (const spec of d.decls ?? [])
108
+ for (const n of spec.names ?? []) names.add(n);
109
+ }
110
+ return names;
111
+ }
112
+
113
+ // Every name an import answers to (its alias, its package name) -> its path.
114
+ function importPaths(programs) {
115
+ const paths = new Map();
116
+ for (const p of programs)
117
+ for (const imp of p.imports ?? [])
118
+ for (const spec of imp.imports) {
119
+ if (spec.alias === "_" || spec.alias === ".") continue;
120
+ paths.set(canonicalName(spec.path), spec.path);
121
+ if (spec.alias) paths.set(spec.alias, spec.path);
122
+ }
123
+ return paths;
124
+ }
125
+
126
+ // The generated file's imports for the qualifiers (name -> path) its code
127
+ // spells: a path, or { path, alias } for a qualifier renamed to stay clear of
128
+ // a package-level name.
129
+ export function importsFor(qualifiers) {
130
+ return [...qualifiers].map(([name, path]) =>
131
+ name === canonicalName(path) ? path : { path, alias: name },
132
+ );
133
+ }
134
+
135
+ // Every type a closed-world generator may need to describe: the entries
136
+ // { type, node, via } for the type arguments of generic calls (`typeArg`),
137
+ // the type of every expression (`expr`), the static type of every value
138
+ // converted to an interface (`boxed`) and the package's declared named types
139
+ // (`declared`, node null).
140
+ export function collectProgramTypes(programs, checker) {
141
+ const found = [];
142
+ for (const p of programs)
143
+ walkNodes(p.decls, (node) => {
144
+ for (const a of node._typeArgs ?? [])
145
+ found.push({ type: defaultType(a), node, via: "typeArg" });
146
+ if (node._type)
147
+ found.push({ type: defaultType(node._type), node, via: "expr" });
148
+ if (node._ifaceSrc)
149
+ found.push({ type: defaultType(node._ifaceSrc), node, via: "boxed" });
150
+ // A multi-value call forwarded into interfaces (`return f()`,
151
+ // `g(f())`) keeps one mark holder per element until lowering
152
+ // splits it.
153
+ for (const h of node._fwdIface ?? [])
154
+ if (h?._ifaceSrc)
155
+ found.push({ type: defaultType(h._ifaceSrc), node, via: "boxed" });
156
+ });
157
+ for (const [name, t] of checker.types) {
158
+ if (t?.kind !== "named" || t.name !== name) continue;
159
+ if (name.includes(".") || name.includes("$$")) continue;
160
+ if (t._genericOf || t.typeArgs?.length) continue;
161
+ found.push({ type: t, node: null, via: "declared" });
162
+ }
163
+ return found;
164
+ }
165
+
166
+ export class TypeSpeller {
167
+ // `qualifiers` receives name -> import path for each package the spelled
168
+ // code names.
169
+ constructor(pkgName, qualifiers, programs = []) {
170
+ this.pkgName = pkgName;
171
+ this.qualifiers = qualifiers;
172
+ this.programs = programs;
173
+ this.importPaths = importPaths(programs);
174
+ this.declared = declaredNames(programs);
175
+ }
176
+
177
+ // Go source for the type.
178
+ spell(t) {
179
+ const sp = (x) => this.spell(x);
180
+ const byKind = {
181
+ basic: () => t.name,
182
+ named: () => this.spellNamed(t),
183
+ pointer: () => `*${sp(t.base)}`,
184
+ slice: () => `[]${sp(t.elem)}`,
185
+ array: () => `[${t.size}]${sp(t.elem)}`,
186
+ map: () => `map[${sp(t.key)}]${sp(t.value)}`,
187
+ struct: () => this.spellStruct(t),
188
+ interface: () => (t.name === "error" ? "error" : "any"),
189
+ func: () => this.funcString(t, sp),
190
+ };
191
+ if (!byKind[t.kind])
192
+ throw new Error(`json: cannot spell type kind ${t.kind}`);
193
+ return byKind[t.kind]();
194
+ }
195
+
196
+ // The package name `%T` and errors print for the bare-named type `t`: this
197
+ // package, or the local package an importer reaches it through as `p.Name`.
198
+ declaringPackage(t) {
199
+ if (this.declared.has(t.name) || !this.checker) return this.pkgName;
200
+ for (const [key, v] of this.checker.types) {
201
+ const dot = key.indexOf(".");
202
+ if (v !== t || dot <= 0 || key.slice(dot + 1) !== t.name) continue;
203
+ const modPath = this.importPaths.get(key.slice(0, dot));
204
+ if (modPath) return canonicalName(modPath);
205
+ }
206
+ return this.pkgName;
207
+ }
208
+
209
+ // The name the generated file uses for the package at `modPath`: its
210
+ // package name, or `_gf_<name>` when the package declares that name itself
211
+ // or another path took it.
212
+ qualifierFor(modPath) {
213
+ for (const [name, path] of this.qualifiers)
214
+ if (path === modPath) return name;
215
+ const base = canonicalName(modPath);
216
+ const taken = (n) => this.declared.has(n) || this.qualifiers.has(n);
217
+ let name = base;
218
+ for (let i = 1; taken(name); i++) name = `_gf_${base}${i > 1 ? i : ""}`;
219
+ this.qualifiers.set(name, modPath);
220
+ return name;
221
+ }
222
+
223
+ spellNamed(t) {
224
+ let name = t.name;
225
+ const at = name.indexOf("$$");
226
+ const dot = name.indexOf(".");
227
+ if (at >= 0) {
228
+ const orig = name.slice(0, at);
229
+ const modPath = name.slice(at + 2).replaceAll("$", "/");
230
+ name = `${this.qualifierFor(modPath)}.${orig}`;
231
+ } else if (dot > 0) {
232
+ const modPath = this.importPaths.get(name.slice(0, dot));
233
+ if (modPath) name = `${this.qualifierFor(modPath)}${name.slice(dot)}`;
234
+ }
235
+ return t.typeArgs?.length
236
+ ? `${name}[${t.typeArgs.map((a) => this.spell(a)).join(", ")}]`
237
+ : name;
238
+ }
239
+
240
+ spellStruct(t) {
241
+ const fields = (t._structFields ?? []).map((f) => {
242
+ const tag = f.tag ? ` \`${f.tag}\`` : "";
243
+ if (f.embedded) return `${this.spell(f.type)}${tag}`;
244
+ return `${f.names.join(", ")} ${this.spell(f.type)}${tag}`;
245
+ });
246
+ return `struct { ${fields.join("; ")} }`;
247
+ }
248
+
249
+ funcString(t, name) {
250
+ const rs = results(t);
251
+ const res =
252
+ rs.length === 0
253
+ ? ""
254
+ : rs.length === 1
255
+ ? ` ${name(rs[0])}`
256
+ : ` (${rs.map(name).join(", ")})`;
257
+ // The signature keeps a variadic parameter's element type.
258
+ const params = t.params.map((p, i) =>
259
+ t.variadic && i === t.params.length - 1 ? `...${name(p)}` : name(p),
260
+ );
261
+ return `func(${params.join(", ")})${res}`;
262
+ }
263
+
264
+ // True when the type can be written as Go source in the generated file.
265
+ // Subclasses decide what a named type, an interface and a struct field
266
+ // need.
267
+ spellable(t, depth = 0, seen = new Set()) {
268
+ if (!t || depth > 8) return false;
269
+ const ok = (x) => this.spellable(x, depth + 1, seen);
270
+ const byKind = {
271
+ basic: () => t.name !== "void" && t.name !== "nil",
272
+ named: () => this.spellableNamed(t, ok, seen),
273
+ // A JS-interop (.d.ts) type has no Go source to write.
274
+ namespace: () => false,
275
+ pointer: () => ok(t.base),
276
+ slice: () => ok(t.elem),
277
+ array: () => ok(t.elem),
278
+ map: () => ok(t.key) && ok(t.value),
279
+ interface: () => this.spellableInterface(t),
280
+ struct: () => this.spellableStruct(t, ok),
281
+ func: () => t.params.every(ok) && results(t).every(ok),
282
+ };
283
+ return byKind[t.kind]?.() ?? false;
284
+ }
285
+
286
+ spellableInterface(_t) {
287
+ return true;
288
+ }
289
+
290
+ spellableStruct(t, ok) {
291
+ return (t._structFields ?? []).every((f) => ok(f.type));
292
+ }
293
+ }
@@ -16,9 +16,16 @@ import { scanAddressTaken } from "./boxing.js";
16
16
  import { analyzeCaptures } from "./captures.js";
17
17
  import { computeEmbeddedStubs } from "./embedding.js";
18
18
  import { paramEscapes } from "./escape.js";
19
- import { extractNamedReturns, hasDefer, normalizeDefers } from "./functions.js";
19
+ import {
20
+ extractNamedReturns,
21
+ hasDefer,
22
+ normalizeDefers,
23
+ splitForwardedTuples,
24
+ } from "./functions.js";
20
25
  import { computeGlobalAnalysis } from "./globals.js";
21
- import { OwnershipContext } from "./ownership.js";
26
+ import { OwnershipContext, takesElementAddress } from "./ownership.js";
27
+
28
+ export { takesElementAddress };
22
29
 
23
30
  class LowerResult {
24
31
  constructor() {
@@ -40,6 +47,8 @@ class LowerResult {
40
47
  this.initPathFunctions = new Set();
41
48
  // Map<ASTNode, Array<string>>
42
49
  this.cachedGlobalsByFunc = new Map();
50
+ // True when some code takes `&xs[i]` (see takesElementAddress)
51
+ this.elemAddrTaken = false;
43
52
  }
44
53
  }
45
54
 
@@ -79,8 +88,11 @@ export function lower(programOrPrograms, checker = null) {
79
88
  ? programOrPrograms
80
89
  : [programOrPrograms];
81
90
 
91
+ splitForwardedTuples(programs);
82
92
  normalizeDefers(programs);
83
93
 
94
+ res.elemAddrTaken = programs.some((p) => takesElementAddress(p?.decls));
95
+
84
96
  // 1. Aggregate methods across all files in the package
85
97
  const methodMap = new Map();
86
98
  for (const program of programs) {