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