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
@@ -9,17 +9,21 @@ import { readFileSync } from "node:fs";
9
9
  import { basename, dirname, join, resolve } from "node:path";
10
10
  import { CodeGen } from "../backend/js/index.js";
11
11
  import { Lexer } from "../lexer/lexer.js";
12
+ import { generateErrorsAs } from "../lower/errors-as.js";
13
+ import { generateFmtFormatters } from "../lower/fmt-formatters.js";
12
14
  import { generateJsonCodecs } from "../lower/json-codecs.js";
13
- import { reachableStdlibDecls } from "../lower/reachability.js";
15
+ import { reachableStdlibDecls, StdlibReach } from "../lower/reachability.js";
14
16
  import { forEachChild } from "../lower/walk.js";
15
17
  import { Parser } from "../parser/index.js";
16
- import {
17
- isBuiltinPackage,
18
- isLocalPath,
19
- resolveStdlibSource,
20
- } from "../resolver.js";
18
+ import { isLocalPath, resolveStdlibSource } from "../resolver.js";
21
19
  import { lowerStdlibRefs, registerWasmUnit } from "./wasm.js";
22
20
 
21
+ export {
22
+ mangleCollidingTypes,
23
+ renameLocalTypes,
24
+ withOriginalTypeNames,
25
+ } from "./type-names.js";
26
+
23
27
  // ── Go-source stdlib ──────────────────────────────────────────
24
28
  //
25
29
  // Packages under stdlib/<path>/ are GoFront Go. Each is type-checked
@@ -27,27 +31,56 @@ import { lowerStdlibRefs, registerWasmUnit } from "./wasm.js";
27
31
  // is bundled for js/both importers and its programs are linked into app.wasm
28
32
  // for wasm/both importers, never through the boundary facade. Top-level
29
33
  // names are mangled to `Name$$path` so they cannot collide with user
30
- // declarations in the flat JS bundle or the linked module. A directory for an
31
- // emitter-backed package (`errors`) is an overlay: its members join that
32
- // namespace and unqualified references to emitter members become `errors.X`.
34
+ // declarations in the flat JS bundle or the linked module.
33
35
 
34
36
  function stdlibSuffix(importPath) {
35
37
  return `$$${importPath.replaceAll("/", "$")}`;
36
38
  }
37
39
 
38
- function parseStdlibFiles(dir, deps) {
40
+ // Each file keeps its own `//gofront:target` (default `both`). A package
41
+ // with `js` files is compiled whole for `js` importers (the `js` variant) and
42
+ // without those files for `wasm` and `both` importers (the `shared` variant).
43
+ function parseStdlibFiles(dir, deps, variant = "js", target = "both") {
44
+ return deps
45
+ .gwFilesIn(dir)
46
+ .map((f) => {
47
+ const source = readFileSync(f, "utf8");
48
+ const filename = basename(f);
49
+ const tokens = new Lexer(source, filename).tokenize();
50
+ const ast = new Parser(tokens, filename, source).parse();
51
+ ast._source = source;
52
+ ast._path = resolve(f);
53
+ ast._fileTarget = tokens.target ?? "both";
54
+ return ast;
55
+ })
56
+ .filter((ast) => variant === "js" || ast._fileTarget !== "js")
57
+ .map((ast) => Object.assign(ast, { target }));
58
+ }
59
+
60
+ // Targets of the package's files, read without parsing.
61
+ function stdlibFileTargets(dir, deps) {
39
62
  return deps.gwFilesIn(dir).map((f) => {
40
- const source = readFileSync(f, "utf8");
41
- const filename = basename(f);
42
- const tokens = new Lexer(source, filename).tokenize();
43
- const ast = new Parser(tokens, filename, source).parse();
44
- ast._source = source;
45
- ast._path = resolve(f);
46
- ast.target = "both";
47
- return ast;
63
+ const m = /^\/\/gofront:target\s+(\S+)/m.exec(readFileSync(f, "utf8"));
64
+ return m?.[1] ?? "both";
48
65
  });
49
66
  }
50
67
 
68
+ // Exported names declared by the `js` files (what a `wasm` or `both` importer
69
+ // cannot use).
70
+ function stdlibJsOnlyNames(dir, deps) {
71
+ const programs = parseStdlibFiles(dir, deps).filter(
72
+ (p) => p._fileTarget === "js",
73
+ );
74
+ const { values, types } = stdlibTopLevelNames(programs);
75
+ return new Set([...values, ...types].filter((n) => /^[A-Z]/.test(n)));
76
+ }
77
+
78
+ // Whether every file of the package is `js`-only.
79
+ export function stdlibIsJsOnly(dir, deps) {
80
+ const targets = stdlibFileTargets(dir, deps);
81
+ return targets.length > 0 && targets.every((t) => t === "js");
82
+ }
83
+
51
84
  // Top-level value and type names declared by the package.
52
85
  function stdlibTopLevelNames(programs) {
53
86
  const values = new Set();
@@ -68,13 +101,27 @@ function stdlibTopLevelNames(programs) {
68
101
  // `refs` holds the `file:line:col` of identifiers the first type-check
69
102
  // resolved to package scope; type names are resolved by name (GoFront types
70
103
  // are package-flat) except where a type parameter shadows them.
71
- export function mangleStdlibAst(programs, ctx) {
104
+ // With `lazy`, function and method bodies are left alone: they are mangled
105
+ // by `mangleBody` once a lazily checked body is first reached.
106
+ export function mangleStdlibAst(programs, ctx, { lazy = false } = {}) {
72
107
  for (const p of programs) {
73
108
  for (const d of p.decls ?? []) mangleDeclNames(d, ctx);
74
- mangleRefs(p.decls, p._filename, new Set(), ctx);
109
+ for (const d of p.decls ?? []) {
110
+ const deferred =
111
+ lazy && (d.kind === "FuncDecl" || d.kind === "MethodDecl");
112
+ const body = d.body;
113
+ if (deferred) d.body = null;
114
+ mangleRefs(d, p._filename, new Set(), ctx);
115
+ if (deferred) d.body = body;
116
+ }
75
117
  }
76
118
  }
77
119
 
120
+ function mangleBody(decl, file, ctx) {
121
+ const tparams = new Set((decl.typeParams ?? []).map((t) => t.name));
122
+ mangleRefs(decl.body, file, tparams, ctx);
123
+ }
124
+
78
125
  function mangleDeclNames(d, { suffix, values }) {
79
126
  if (d.kind === "TypeDecl" || (d.kind === "FuncDecl" && values.has(d.name))) {
80
127
  d.name += suffix;
@@ -123,43 +170,25 @@ function mangleRefs(node, file, tparams, ctx) {
123
170
  forEachChild(node, (child) => mangleRefs(child, file, tp, ctx));
124
171
  }
125
172
 
126
- function mangleTypeRef(node, tparams, { suffix, types, overlay }) {
173
+ function mangleTypeRef(node, tparams, { suffix, types }) {
127
174
  if (types.has(node.name) && !tparams.has(node.name)) {
128
175
  node.name += suffix;
129
- } else if (overlay && node.name in overlay.members) {
130
- node.name = `${overlay.name}.${node.name}`;
131
176
  }
132
177
  }
133
178
 
134
- // A package-scope identifier: a package declaration gets the suffix; in an
135
- // overlay, an emitter member (`New` in errors) becomes `errors.New`.
136
- function mangleIdentRef(node, { suffix, values, types, overlay }) {
179
+ // A package-scope identifier: a package declaration gets the suffix.
180
+ function mangleIdentRef(node, { suffix, values, types }) {
137
181
  if (values.has(node.name) || types.has(node.name)) {
138
182
  node.name += suffix;
139
- } else if (overlay && node.name in overlay.members) {
140
- const field = node.name;
141
- delete node.name;
142
- Object.assign(node, {
143
- kind: "SelectorExpr",
144
- expr: { kind: "Ident", name: overlay.name, _line: node._line },
145
- field,
146
- });
147
183
  }
148
184
  }
149
185
 
150
- function checkStdlibPrograms(programs, pkgName, options, overlay, deps) {
186
+ function checkStdlibPrograms(programs, pkgName, options, deps, target) {
151
187
  const checker = new deps.TypeChecker();
152
- checker.target = "both";
188
+ checker.target = target;
153
189
  checker.pkgName = pkgName;
154
190
  checker.isStdlib = true;
155
- if (overlay) {
156
- for (const [name, t] of Object.entries(overlay.members)) {
157
- checker.globals.define(name, t);
158
- if (t.kind === "interface" || t.kind === "named" || t.kind === "struct") {
159
- checker.types.set(name, t);
160
- }
161
- }
162
- }
191
+ checker.lazyBodies = true;
163
192
  deps.resolveImports(
164
193
  programs,
165
194
  programs[0]._path,
@@ -176,49 +205,100 @@ function checkStdlibPrograms(programs, pkgName, options, overlay, deps) {
176
205
  // Errors in stdlib source are compiler bugs; the importer reports them at its
177
206
  // import (see reportStdlibLoadError), so only the message text is kept, never
178
207
  // a location inside stdlib/.
179
- function throwStdlibErrors(errors) {
180
- if (errors.length === 0) return;
208
+ function stdlibErrorText(errors) {
181
209
  const detail = (e) =>
182
210
  e.message
183
211
  .split("\n")[0]
184
212
  .replace(/^Type error(?: in \S+)?(?: at line \d+:\d+)?:\s*/, "");
185
- throw new Error([...new Set(errors.map(detail))].join("; "));
213
+ return [...new Set(errors.map(detail))].join("; ");
214
+ }
215
+
216
+ function throwStdlibErrors(errors) {
217
+ if (errors.length === 0) return;
218
+ throw new Error(stdlibErrorText(errors));
186
219
  }
187
220
 
188
221
  // Pass 1: type-check the package as written and record which identifiers
189
- // refer to package scope. An overlay sees its emitter members unqualified.
190
- function probeStdlibPackage(_importPath, dir, options, overlay, deps) {
191
- const programs = parseStdlibFiles(dir, deps);
222
+ // refer to package scope.
223
+ function probeStdlibPackage(_importPath, dir, options, deps, v) {
224
+ const programs = parseStdlibFiles(dir, deps, v.variant, v.target);
192
225
  const pkgName = programs[0].pkg.name;
193
226
  const { values, types } = stdlibTopLevelNames(programs);
194
- for (const n of [...values, ...types])
195
- if (overlay && n in overlay.members)
196
- throw new Error(`overlay redeclares ${overlay.name}.${n}`);
197
- const probe = checkStdlibPrograms(programs, pkgName, options, overlay, deps);
227
+ const probe = checkStdlibPrograms(programs, pkgName, options, deps, v.target);
198
228
  probe._pkgRefs = new Set();
199
229
  throwStdlibErrors(probe.checkAll(programs));
200
- return { pkgName, values, types, refs: probe._pkgRefs };
230
+ return {
231
+ pkgName,
232
+ values,
233
+ types,
234
+ refs: probe._pkgRefs,
235
+ probe,
236
+ probePrograms: programs,
237
+ };
238
+ }
239
+
240
+ // Function and method bodies are checked when first reached (see
241
+ // `StdlibReach`), in both passes: the probe records which identifiers in the
242
+ // body name package scope, the body is mangled with them, and the mangled
243
+ // body is checked. `decl._ensure()` does all three once and returns the
244
+ // error text, or null.
245
+ function installLazyBodies(info, programs, checker) {
246
+ const { probe, probePrograms } = info;
247
+ programs.forEach((p, pi) => {
248
+ p.decls.forEach((decl, di) => {
249
+ if (decl.kind !== "FuncDecl" && decl.kind !== "MethodDecl") return;
250
+ const probeDecl = probePrograms[pi].decls[di];
251
+ let result;
252
+ decl._ensure = () => {
253
+ if (result !== undefined) return result;
254
+ result = null; // a body that reaches itself is in progress
255
+ const before = [probe.errors.length, checker.errors.length];
256
+ probe.checkBody(probeDecl);
257
+ if (probe.errors.length === before[0]) {
258
+ mangleBody(decl, p._filename, info);
259
+ checker.checkBody(decl);
260
+ lowerStdlibRefs(decl);
261
+ }
262
+ const errors = [
263
+ ...probe.errors.slice(before[0]),
264
+ ...checker.errors.slice(before[1]),
265
+ ];
266
+ if (errors.length > 0) result = stdlibErrorText(errors);
267
+ return result;
268
+ };
269
+ });
270
+ });
201
271
  }
202
272
 
203
273
  // Exported members as seen by importers (original name -> type) and the
204
- // original -> mangled name map; an overlay keeps the emitter members too.
205
- function stdlibNamespace(checker, { pkgName, values, types, suffix }, overlay) {
206
- const members = overlay ? { ...overlay.members } : {};
274
+ // original -> mangled name map.
275
+ function stdlibNamespace(checker, { pkgName, values, types, suffix }, jsOnly) {
276
+ const members = {};
207
277
  const mangle = new Map();
278
+ const consts = new Map();
208
279
  for (const name of [...values, ...types]) {
209
280
  const t = checker.globals.symbols.get(name + suffix);
210
281
  if (!/^[A-Z]/.test(name) || t === undefined) continue;
211
282
  members[name] = t;
212
283
  mangle.set(name, name + suffix);
284
+ // An exported constant's folded value, so importers fold `pkg.Name`.
285
+ if (checker.globals._consts.has(name + suffix)) {
286
+ const v = checker._constValue(
287
+ { kind: "Ident", name: name + suffix },
288
+ checker.globals,
289
+ );
290
+ if (v) consts.set(name, v);
291
+ }
213
292
  }
214
293
  return {
215
- ...(overlay ?? {}),
216
294
  kind: "namespace",
217
295
  name: pkgName,
218
296
  members,
219
- _gofront: !overlay,
297
+ _gofront: true,
220
298
  _target: "both",
299
+ _jsOnly: jsOnly,
221
300
  _mangle: mangle,
301
+ _consts: consts,
222
302
  };
223
303
  }
224
304
 
@@ -236,59 +316,94 @@ function stdlibExportedTypes(checker, { pkgName, suffix }) {
236
316
  return out;
237
317
  }
238
318
 
319
+ // The compilation a package gets for an importer: `js` importers see every
320
+ // file, `wasm` and `both` importers only the files for both targets (cached
321
+ // as `<path>#shared` when that differs). Returns { key, variant, target }.
322
+ function stdlibVariant(importPath, dir, importerTarget, deps) {
323
+ const hasJs = stdlibFileTargets(dir, deps).includes("js");
324
+ importerTarget ??= "js";
325
+ if (!hasJs) return { key: importPath, variant: "js", target: "both" };
326
+ if (importerTarget === "js")
327
+ return { key: importPath, variant: "js", target: "js" };
328
+ return { key: `${importPath}#shared`, variant: "shared", target: "both" };
329
+ }
330
+
239
331
  // Loads (once per build) and compiles the Go-source package at `dir`. A
240
332
  // failure is cached too, so every importer reports it.
241
- function loadStdlibPackage(importPath, dir, options, deps) {
333
+ function loadStdlibPackage(
334
+ importPath,
335
+ dir,
336
+ options,
337
+ deps,
338
+ importerTarget = "js",
339
+ ) {
340
+ const v = stdlibVariant(importPath, dir, importerTarget, deps);
242
341
  const cache = options.stdlibCache;
243
- const cached = cache.get(importPath);
342
+ const cached = cache.get(v.key);
244
343
  if (cached === null)
245
344
  throw new Error(`import cycle in Go-source stdlib: ${importPath}`);
246
345
  if (cached instanceof Error) throw cached;
247
346
  if (cached) return cached;
248
- cache.set(importPath, null);
347
+ cache.set(v.key, null);
249
348
  try {
250
- const pkg = buildStdlibPackage(importPath, dir, options, deps);
251
- cache.set(importPath, pkg);
349
+ const pkg = buildStdlibPackage(importPath, dir, options, deps, v);
350
+ cache.set(v.key, pkg);
252
351
  return pkg;
253
352
  } catch (e) {
254
- cache.set(importPath, e);
353
+ cache.set(v.key, e);
255
354
  throw e;
256
355
  }
257
356
  }
258
357
 
259
- function buildStdlibPackage(importPath, dir, options, deps) {
358
+ function buildStdlibPackage(importPath, dir, options, deps, v) {
260
359
  const cache = options.stdlibCache;
261
- const overlay = isBuiltinPackage(importPath)
262
- ? new deps.TypeChecker().globals.lookup(importPath.split("/").at(-1))
263
- : null;
264
360
  const info = {
265
- ...probeStdlibPackage(importPath, dir, options, overlay, deps),
361
+ ...probeStdlibPackage(importPath, dir, options, deps, v),
266
362
  suffix: stdlibSuffix(importPath),
267
363
  };
268
364
 
269
365
  // Pass 2: mangle a fresh parse and check it again.
270
- const programs = parseStdlibFiles(dir, deps);
271
- mangleStdlibAst(programs, { ...info, overlay });
366
+ const programs = parseStdlibFiles(dir, deps, v.variant, v.target);
367
+ mangleStdlibAst(programs, info, { lazy: true });
272
368
  const checker = checkStdlibPrograms(
273
369
  programs,
274
370
  info.pkgName,
275
371
  options,
276
- null,
277
372
  deps,
373
+ v.target,
278
374
  );
279
375
  throwStdlibErrors(checker.checkAll(programs));
280
376
  lowerStdlibRefs(programs);
377
+ installLazyBodies(info, programs, checker);
378
+
379
+ // A user reference to a member checks the bodies it reaches, so a type
380
+ // error in the standard library surfaces at the call. Failures from other
381
+ // packages' bodies surface when the output is assembled.
382
+ const reach = new StdlibReach(programs);
383
+ const jsOnly =
384
+ v.variant === "shared" ? stdlibJsOnlyNames(dir, deps) : new Set();
385
+ const namespace = stdlibNamespace(checker, info, jsOnly);
386
+ namespace._path = importPath;
387
+ namespace._reach = (name) => {
388
+ const seen = reach.failures.length;
389
+ reach.add([{ kind: "Ident", name }]);
390
+ return reach.failures[seen] ?? null;
391
+ };
281
392
 
282
393
  const pkg = {
283
394
  importPath,
395
+ key: v.key,
284
396
  checker,
285
397
  pkgName: info.pkgName,
286
- overlay,
287
- namespace: stdlibNamespace(checker, info, overlay),
398
+ namespace,
288
399
  exportedTypes: stdlibExportedTypes(checker, info),
289
400
  deps: programs
290
401
  .flatMap((p) => p.imports.flatMap((imp) => imp.imports))
291
- .map(({ path }) => cache.get(path))
402
+ .map(({ path }) =>
403
+ v.variant === "shared"
404
+ ? (cache.get(`${path}#shared`) ?? cache.get(path))
405
+ : cache.get(path),
406
+ )
292
407
  .filter(Boolean),
293
408
  unit: {
294
409
  key: `stdlib:${importPath}`,
@@ -307,19 +422,20 @@ function buildStdlibPackage(importPath, dir, options, deps) {
307
422
  // emitted (see lower/reachability.js). Importers leave a marker line.
308
423
  const STDLIB_MARKER_RE = /^\/\*__GOFRONT_STDLIB:([^*]*)\*\/$/;
309
424
 
310
- function stdlibMarker(importPath) {
311
- return `/*__GOFRONT_STDLIB:${importPath}*/`;
425
+ function stdlibMarker(key) {
426
+ return `/*__GOFRONT_STDLIB:${key}*/`;
312
427
  }
313
428
 
314
- function stdlibJs(pkg, reached) {
429
+ function stdlibJs(pkg, reached, natives) {
315
430
  const programs = pkg.unit.programs.map((p) => ({
316
431
  ...p,
317
432
  decls: p.decls.filter((d) => reached.has(d)),
318
433
  }));
319
434
  if (programs.every((p) => p.decls.length === 0)) return "";
320
435
  return new CodeGen(pkg.checker, new Map(), new Set(), {
321
- target: "both",
436
+ target: pkg.checker.target,
322
437
  strict: true,
438
+ natives,
323
439
  }).generateAll(programs, { isTest: false });
324
440
  }
325
441
 
@@ -346,11 +462,15 @@ function spliceStdlibJs(merged, options) {
346
462
  const pkgs = lines
347
463
  .map((l) => l.match(STDLIB_MARKER_RE)?.[1])
348
464
  .filter(Boolean)
349
- .map((path) => options.stdlibCache.get(path));
465
+ .map((key) => options.stdlibCache.get(key));
350
466
  if (pkgs.length === 0) return;
467
+ // The whole package carries everything its shared variant has.
468
+ const keys = new Set(pkgs.map((pkg) => pkg.key));
469
+ const emitted = (pkg) =>
470
+ pkg.key.endsWith("#shared") ? !keys.has(pkg.importPath) : true;
351
471
  const reached = reachableStdlibDecls(
352
472
  [options.jsRootPrograms, wasmExportedSignatures(options.wasmUnits)],
353
- pkgs.flatMap((pkg) => pkg.unit.programs),
473
+ pkgs.filter(emitted).flatMap((pkg) => pkg.unit.programs),
354
474
  );
355
475
  const out = [];
356
476
  const spliced = new Set();
@@ -362,7 +482,8 @@ function spliceStdlibJs(merged, options) {
362
482
  }
363
483
  if (spliced.has(path)) continue;
364
484
  spliced.add(path);
365
- const js = stdlibJs(options.stdlibCache.get(path), reached);
485
+ const pkg = options.stdlibCache.get(path);
486
+ const js = emitted(pkg) ? stdlibJs(pkg, reached, options.natives) : "";
366
487
  const code = js === "" ? [] : js.split("\n");
367
488
  const delta = code.length - 1;
368
489
  for (const m of merged.mappings)
@@ -392,10 +513,10 @@ function emitStdlibPackage(
392
513
  emitStdlibPackage(dep, importerTarget, preambles, bundledDirs, options);
393
514
  // Every importer leaves a JS marker: the facade of a wasm package may
394
515
  // expose a stdlib type; unreachable code splices to nothing.
395
- const key = `${pkg.unit.key}:js`;
516
+ const key = `${pkg.key}:js`;
396
517
  if (!bundledDirs.has(key)) {
397
518
  bundledDirs.add(key);
398
- preambles.push(stdlibMarker(pkg.importPath));
519
+ preambles.push(stdlibMarker(pkg.key));
399
520
  }
400
521
  if (importerTarget !== "js") registerWasmUnit(options, pkg.unit);
401
522
  }
@@ -422,47 +543,75 @@ function bindStdlibImport(checker, p, spec, pkg) {
422
543
  return;
423
544
  }
424
545
  const name = alias ?? pkg.pkgName;
425
- // Emitter-backed members are emitted by package name, so an overlaid
426
- // package cannot be renamed (aliases of emitter packages never worked).
427
- if (pkg.overlay && name !== pkg.pkgName) {
428
- errAtImport(
429
- checker,
430
- p,
431
- spec,
432
- `import alias for '${path}' is not supported`,
433
- );
434
- return;
435
- }
436
- checker.globals.define(name, { ...pkg.namespace, name });
546
+ const ns = { ...pkg.namespace, name };
547
+ checker.globals.define(name, ns);
437
548
  for (const [k, t] of pkg.exportedTypes) {
438
549
  const dot = k.indexOf(".");
439
550
  checker.types.set(dot < 0 ? k : `${name}${k.slice(dot)}`, t);
440
551
  }
441
- if (!pkg.overlay)
552
+ if (!spec._implicit)
442
553
  checker.trackImport(name, importNode(spec), p._filename, p._source);
443
554
  }
444
555
 
445
- // Generates, imports and checks the codecs for the package's `encoding/json`
446
- // calls as one more (wasm-only) file. Returns the generated programs: none
447
- // for a js package, a package with errors, or one that makes no json calls.
448
- export function checkJsonCodecs(programs, checker, preambles, options, deps) {
449
- const wasm = checker.target === "wasm" || checker.target === "both";
450
- if (!wasm || checker.errors.length > 0) return [];
451
- const gen = generateJsonCodecs(programs, checker, checker.pkgName);
452
- if (!gen) return [];
453
- for (const { program, node, message, hint } of gen.diagnostics) {
454
- checker._setCurrentFile(program);
455
- checker.recordBlocker("unsupported stdlib member", message);
456
- checker.err(message, node, hint);
556
+ // The generators behind the package's `gofront_gen.go`: each scans the
557
+ // checked programs for its call sites, marks them with `_genEntry` and returns
558
+ // null (no sites) or { body, imports, diagnostics }.
559
+ const GENERATORS = [
560
+ generateJsonCodecs,
561
+ generateErrorsAs,
562
+ generateFmtFormatters,
563
+ ];
564
+
565
+ // Generates, imports and checks the package's generated Go (json codecs,
566
+ // `errors.As` functions) as one more file. Returns the generated programs:
567
+ // none for a package with errors or one with no generated call sites.
568
+ export function checkGeneratedGo(
569
+ programs,
570
+ checker,
571
+ preambles,
572
+ options,
573
+ deps,
574
+ generators = GENERATORS,
575
+ ) {
576
+ if (checker.errors.length > 0) return [];
577
+ const parts = generators
578
+ .map((gen) => gen(programs, checker, checker.pkgName, options))
579
+ .filter(Boolean);
580
+ if (parts.length === 0) return [];
581
+ let failed = false;
582
+ for (const { diagnostics } of parts)
583
+ for (const { program, node, message, hint } of diagnostics) {
584
+ failed = true;
585
+ checker._setCurrentFile(program);
586
+ checker.recordBlocker("unsupported stdlib member", message);
587
+ checker.err(message, node, hint);
588
+ }
589
+ if (failed) return [];
590
+ // An import is a path or { path, alias }.
591
+ const specs = new Map();
592
+ for (const imp of parts.flatMap((p) => p.imports)) {
593
+ const { path, alias } = typeof imp === "string" ? { path: imp } : imp;
594
+ specs.set(`${alias ?? ""} ${path}`, { path, alias });
457
595
  }
458
- if (gen.diagnostics.length > 0) return [];
459
- const filename = "json_codecs.go";
596
+ const imports = [...specs.values()];
597
+ const source = [
598
+ `package ${checker.pkgName}`,
599
+ ...(imports.length > 0
600
+ ? [
601
+ `import (\n${imports.map(({ path, alias }) => `\t${alias ? `${alias} ` : ""}${JSON.stringify(path)}`).join("\n")}\n)`,
602
+ ]
603
+ : []),
604
+ ...parts.map((p) => p.body),
605
+ "",
606
+ ].join("\n\n");
607
+ const filename = "gofront_gen.go";
460
608
  const ast = new Parser(
461
- new Lexer(gen.source, filename).tokenize(),
609
+ new Lexer(source, filename).tokenize(),
462
610
  filename,
463
- gen.source,
611
+ source,
464
612
  ).parse();
465
- ast._source = gen.source;
613
+ ast._source = source;
614
+ ast._generated = true;
466
615
  ast._path = join(dirname(programs[0]._path ?? "."), filename);
467
616
  ast.target = checker.target;
468
617
  importStdlibPackages(
@@ -481,12 +630,94 @@ export function checkJsonCodecs(programs, checker, preambles, options, deps) {
481
630
  .map((e) => e.message)
482
631
  .join("\n");
483
632
  throw new Error(
484
- `internal error in the generated encoding/json codecs; please report\n${detail}`,
633
+ `internal error in the generated Go; please report\n${detail}`,
485
634
  );
486
635
  }
636
+ for (const { link } of parts) link?.(checker);
487
637
  return [ast];
488
638
  }
489
639
 
640
+ // Packages a `js` package may use without importing them, as the compiler
641
+ // has always allowed: a program that selects from the identifier (or, for
642
+ // `gom`, declares a templ component, typed as `gom.Node`) without declaring
643
+ // or importing it gets the import.
644
+ const IMPLICIT_PACKAGES = ["fmt", "gom", "regexp"];
645
+
646
+ function implicitImports(programs) {
647
+ const declared = (name) =>
648
+ programs.some(
649
+ (p) =>
650
+ p.imports.some((imp) =>
651
+ imp.imports.some((s) => s.path === name || s.alias === name),
652
+ ) || stdlibTopLevelNames([p]).values.has(name),
653
+ );
654
+ const used = new Set();
655
+ const visit = (node) => {
656
+ if (!node || typeof node !== "object") return;
657
+ if (Array.isArray(node)) return node.forEach(visit);
658
+ if (node.kind === "TemplDecl") used.add("gom");
659
+ else if (
660
+ node.kind === "SelectorExpr" &&
661
+ IMPLICIT_PACKAGES.includes(node.expr?.name)
662
+ )
663
+ used.add(node.expr.name);
664
+ else if (typeof node.name === "string" && node.kind?.endsWith("TypeName"))
665
+ for (const name of IMPLICIT_PACKAGES)
666
+ if (node.name.startsWith(`${name}.`)) used.add(name);
667
+ forEachChild(node, visit);
668
+ };
669
+ for (const p of programs) visit(p.decls);
670
+ return [...used]
671
+ .filter((name) => !declared(name))
672
+ .map((path) => ({ path, _line: 1, _col: 1, _implicit: true }));
673
+ }
674
+
675
+ // Packages that keep their own wording for the unavailable-in-wasm error.
676
+ const NOT_IN_WASM = new Map([["gom", "gom usage"]]);
677
+
678
+ // Reports an import of a `js`-only package from a `wasm` or `both` package.
679
+ function errJsOnlyImport(checker, p, spec) {
680
+ // Its uses are not a second root cause.
681
+ checker._unavailableImports ??= new Set();
682
+ checker._unavailableImports.add(spec.alias ?? spec.path.split("/").at(-1));
683
+ const category = NOT_IN_WASM.get(spec.path);
684
+ if (category) {
685
+ checker.recordBlocker(category, spec.path);
686
+ const msg = `package '${spec.path}' is not available in wasm packages`;
687
+ return errAtImport(checker, p, spec, msg);
688
+ }
689
+ checker.recordBlocker("unsupported stdlib import", spec.path);
690
+ const msg = `package '${spec.path}' is only available in js packages`;
691
+ errAtImport(checker, p, spec, msg, "move this code to a js package");
692
+ }
693
+
694
+ // Loads and binds one import of program `p`.
695
+ function importStdlibSpec(
696
+ p,
697
+ spec,
698
+ checker,
699
+ preambles,
700
+ bundledDirs,
701
+ options,
702
+ deps,
703
+ ) {
704
+ if (spec.path.startsWith("js:") || isLocalPath(spec.path)) return;
705
+ const dir = resolveStdlibSource(spec.path, options.stdlibRoot);
706
+ if (!dir) return;
707
+ const wasmImporter = checker.target === "wasm" || checker.target === "both";
708
+ if (wasmImporter && stdlibIsJsOnly(dir, deps))
709
+ return errJsOnlyImport(checker, p, spec);
710
+ let pkg;
711
+ try {
712
+ pkg = loadStdlibPackage(spec.path, dir, options, deps, checker.target);
713
+ } catch (e) {
714
+ const msg = `internal stdlib error in ${spec.path}; please report`;
715
+ return errAtImport(checker, p, spec, msg, e.message);
716
+ }
717
+ emitStdlibPackage(pkg, checker.target, preambles, bundledDirs, options);
718
+ bindStdlibImport(checker, p, spec, pkg);
719
+ }
720
+
490
721
  export function importStdlibPackages(
491
722
  programs,
492
723
  checker,
@@ -495,21 +726,12 @@ export function importStdlibPackages(
495
726
  options,
496
727
  deps,
497
728
  ) {
729
+ const wasmImporter = checker.target === "wasm" || checker.target === "both";
730
+ const implicit = wasmImporter ? [] : implicitImports(programs);
498
731
  for (const p of programs) {
499
- for (const spec of p.imports.flatMap((imp) => imp.imports)) {
500
- if (spec.path.startsWith("js:") || isLocalPath(spec.path)) continue;
501
- const dir = resolveStdlibSource(spec.path, options.stdlibRoot);
502
- if (!dir) continue;
503
- let pkg;
504
- try {
505
- pkg = loadStdlibPackage(spec.path, dir, options, deps);
506
- } catch (e) {
507
- const msg = `internal stdlib error in ${spec.path}; please report`;
508
- errAtImport(checker, p, spec, msg, e.message);
509
- continue;
510
- }
511
- emitStdlibPackage(pkg, checker.target, preambles, bundledDirs, options);
512
- bindStdlibImport(checker, p, spec, pkg);
513
- }
732
+ const specs = p.imports.flatMap((imp) => imp.imports);
733
+ if (p === programs[0]) specs.push(...implicit);
734
+ for (const spec of specs)
735
+ importStdlibSpec(p, spec, checker, preambles, bundledDirs, options, deps);
514
736
  }
515
737
  }