gofront 1.7.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 (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -0,0 +1,737 @@
1
+ // Go-source stdlib pipeline: packages under stdlib/<path>/ are parsed, checked
2
+ // twice (probe, then mangled), linked into app.wasm and spliced into the JS
3
+ // bundle. compiler.js owns import resolution, so the functions that need it
4
+ // (and the parser/checker helpers it also exports) arrive through an injected
5
+ // `deps` argument: `{ gwFilesIn, resolveImports, TypeChecker }`. Importing
6
+ // compiler.js from here would create a cycle.
7
+
8
+ import { readFileSync } from "node:fs";
9
+ import { basename, dirname, join, resolve } from "node:path";
10
+ import { CodeGen } from "../backend/js/index.js";
11
+ import { Lexer } from "../lexer/lexer.js";
12
+ import { generateErrorsAs } from "../lower/errors-as.js";
13
+ import { generateFmtFormatters } from "../lower/fmt-formatters.js";
14
+ import { generateJsonCodecs } from "../lower/json-codecs.js";
15
+ import { reachableStdlibDecls, StdlibReach } from "../lower/reachability.js";
16
+ import { forEachChild } from "../lower/walk.js";
17
+ import { Parser } from "../parser/index.js";
18
+ import { isLocalPath, resolveStdlibSource } from "../resolver.js";
19
+ import { lowerStdlibRefs, registerWasmUnit } from "./wasm.js";
20
+
21
+ export {
22
+ mangleCollidingTypes,
23
+ renameLocalTypes,
24
+ withOriginalTypeNames,
25
+ } from "./type-names.js";
26
+
27
+ // ── Go-source stdlib ──────────────────────────────────────────
28
+ //
29
+ // Packages under stdlib/<path>/ are GoFront Go. Each is type-checked
30
+ // and compiled once per build as a `both` package (strict numerics): its JS
31
+ // is bundled for js/both importers and its programs are linked into app.wasm
32
+ // for wasm/both importers, never through the boundary facade. Top-level
33
+ // names are mangled to `Name$$path` so they cannot collide with user
34
+ // declarations in the flat JS bundle or the linked module.
35
+
36
+ function stdlibSuffix(importPath) {
37
+ return `$$${importPath.replaceAll("/", "$")}`;
38
+ }
39
+
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) {
62
+ return deps.gwFilesIn(dir).map((f) => {
63
+ const m = /^\/\/gofront:target\s+(\S+)/m.exec(readFileSync(f, "utf8"));
64
+ return m?.[1] ?? "both";
65
+ });
66
+ }
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
+
84
+ // Top-level value and type names declared by the package.
85
+ function stdlibTopLevelNames(programs) {
86
+ const values = new Set();
87
+ const types = new Set();
88
+ for (const p of programs) {
89
+ for (const d of p.decls ?? []) {
90
+ if (d.kind === "TypeDecl") types.add(d.name);
91
+ else if (d.kind === "FuncDecl" && d.name !== "init") values.add(d.name);
92
+ else if (d.kind === "VarDecl" || d.kind === "ConstDecl")
93
+ for (const spec of d.decls ?? [])
94
+ for (const n of spec.names ?? []) if (n !== "_") values.add(n);
95
+ }
96
+ }
97
+ return { values, types };
98
+ }
99
+
100
+ // Renames the package's top-level declarations and every reference to them.
101
+ // `refs` holds the `file:line:col` of identifiers the first type-check
102
+ // resolved to package scope; type names are resolved by name (GoFront types
103
+ // are package-flat) except where a type parameter shadows them.
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 } = {}) {
107
+ for (const p of programs) {
108
+ for (const d of p.decls ?? []) mangleDeclNames(d, 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
+ }
117
+ }
118
+ }
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
+
125
+ function mangleDeclNames(d, { suffix, values }) {
126
+ if (d.kind === "TypeDecl" || (d.kind === "FuncDecl" && values.has(d.name))) {
127
+ d.name += suffix;
128
+ } else if (d.kind === "VarDecl" || d.kind === "ConstDecl") {
129
+ for (const spec of d.decls ?? [])
130
+ spec.names = spec.names.map((n) => (values.has(n) ? n + suffix : n));
131
+ }
132
+ }
133
+
134
+ function mangleRefs(node, file, tparams, ctx) {
135
+ if (!node || typeof node !== "object") return;
136
+ if (Array.isArray(node)) {
137
+ for (const n of node) mangleRefs(n, file, tparams, ctx);
138
+ return;
139
+ }
140
+ const tp =
141
+ node.typeParams?.length > 0
142
+ ? new Set([...tparams, ...node.typeParams.map((t) => t.name)])
143
+ : tparams;
144
+ switch (node.kind) {
145
+ case "TypeName":
146
+ case "GenericTypeName":
147
+ mangleTypeRef(node, tp, ctx);
148
+ break;
149
+ case "CompositeLit":
150
+ if (node.typeExpr?.kind === "Ident")
151
+ mangleTypeRef(node.typeExpr, tp, ctx);
152
+ break;
153
+ case "InstantiationExpr": // `Stack[T]{}`, `Pair[K, V](x)`
154
+ case "IndexExpr":
155
+ if (node.expr?.kind === "Ident") mangleTypeRef(node.expr, tp, ctx);
156
+ break;
157
+ case "CallExpr":
158
+ if (
159
+ (node.func?.name === "new" || node.func?.name === "make") &&
160
+ node.args?.[0]?.kind === "Ident"
161
+ ) {
162
+ mangleTypeRef(node.args[0], tp, ctx);
163
+ }
164
+ break;
165
+ case "Ident":
166
+ if (ctx.refs.has(`${file}:${node._line}:${node._col}`))
167
+ mangleIdentRef(node, ctx);
168
+ break;
169
+ }
170
+ forEachChild(node, (child) => mangleRefs(child, file, tp, ctx));
171
+ }
172
+
173
+ function mangleTypeRef(node, tparams, { suffix, types }) {
174
+ if (types.has(node.name) && !tparams.has(node.name)) {
175
+ node.name += suffix;
176
+ }
177
+ }
178
+
179
+ // A package-scope identifier: a package declaration gets the suffix.
180
+ function mangleIdentRef(node, { suffix, values, types }) {
181
+ if (values.has(node.name) || types.has(node.name)) {
182
+ node.name += suffix;
183
+ }
184
+ }
185
+
186
+ function checkStdlibPrograms(programs, pkgName, options, deps, target) {
187
+ const checker = new deps.TypeChecker();
188
+ checker.target = target;
189
+ checker.pkgName = pkgName;
190
+ checker.isStdlib = true;
191
+ checker.lazyBodies = true;
192
+ deps.resolveImports(
193
+ programs,
194
+ programs[0]._path,
195
+ checker,
196
+ new Map(),
197
+ new Set(),
198
+ [],
199
+ new Set(),
200
+ options,
201
+ );
202
+ return checker;
203
+ }
204
+
205
+ // Errors in stdlib source are compiler bugs; the importer reports them at its
206
+ // import (see reportStdlibLoadError), so only the message text is kept, never
207
+ // a location inside stdlib/.
208
+ function stdlibErrorText(errors) {
209
+ const detail = (e) =>
210
+ e.message
211
+ .split("\n")[0]
212
+ .replace(/^Type error(?: in \S+)?(?: at line \d+:\d+)?:\s*/, "");
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));
219
+ }
220
+
221
+ // Pass 1: type-check the package as written and record which identifiers
222
+ // refer to package scope.
223
+ function probeStdlibPackage(_importPath, dir, options, deps, v) {
224
+ const programs = parseStdlibFiles(dir, deps, v.variant, v.target);
225
+ const pkgName = programs[0].pkg.name;
226
+ const { values, types } = stdlibTopLevelNames(programs);
227
+ const probe = checkStdlibPrograms(programs, pkgName, options, deps, v.target);
228
+ probe._pkgRefs = new Set();
229
+ throwStdlibErrors(probe.checkAll(programs));
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
+ });
271
+ }
272
+
273
+ // Exported members as seen by importers (original name -> type) and the
274
+ // original -> mangled name map.
275
+ function stdlibNamespace(checker, { pkgName, values, types, suffix }, jsOnly) {
276
+ const members = {};
277
+ const mangle = new Map();
278
+ const consts = new Map();
279
+ for (const name of [...values, ...types]) {
280
+ const t = checker.globals.symbols.get(name + suffix);
281
+ if (!/^[A-Z]/.test(name) || t === undefined) continue;
282
+ members[name] = t;
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
+ }
292
+ }
293
+ return {
294
+ kind: "namespace",
295
+ name: pkgName,
296
+ members,
297
+ _gofront: true,
298
+ _target: "both",
299
+ _jsOnly: jsOnly,
300
+ _mangle: mangle,
301
+ _consts: consts,
302
+ };
303
+ }
304
+
305
+ // Mangled type names (needed by the backends) plus `pkg.T` for exported ones.
306
+ function stdlibExportedTypes(checker, { pkgName, suffix }) {
307
+ const out = new Map();
308
+ for (const [k, t] of checker.types) {
309
+ const at = k.indexOf("$$");
310
+ if (at < 0) continue;
311
+ out.set(k, t);
312
+ const orig = k.slice(0, at);
313
+ if (k === orig + suffix && /^[A-Z]/.test(orig))
314
+ out.set(`${pkgName}.${orig}`, t);
315
+ }
316
+ return out;
317
+ }
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
+
331
+ // Loads (once per build) and compiles the Go-source package at `dir`. A
332
+ // failure is cached too, so every importer reports it.
333
+ function loadStdlibPackage(
334
+ importPath,
335
+ dir,
336
+ options,
337
+ deps,
338
+ importerTarget = "js",
339
+ ) {
340
+ const v = stdlibVariant(importPath, dir, importerTarget, deps);
341
+ const cache = options.stdlibCache;
342
+ const cached = cache.get(v.key);
343
+ if (cached === null)
344
+ throw new Error(`import cycle in Go-source stdlib: ${importPath}`);
345
+ if (cached instanceof Error) throw cached;
346
+ if (cached) return cached;
347
+ cache.set(v.key, null);
348
+ try {
349
+ const pkg = buildStdlibPackage(importPath, dir, options, deps, v);
350
+ cache.set(v.key, pkg);
351
+ return pkg;
352
+ } catch (e) {
353
+ cache.set(v.key, e);
354
+ throw e;
355
+ }
356
+ }
357
+
358
+ function buildStdlibPackage(importPath, dir, options, deps, v) {
359
+ const cache = options.stdlibCache;
360
+ const info = {
361
+ ...probeStdlibPackage(importPath, dir, options, deps, v),
362
+ suffix: stdlibSuffix(importPath),
363
+ };
364
+
365
+ // Pass 2: mangle a fresh parse and check it again.
366
+ const programs = parseStdlibFiles(dir, deps, v.variant, v.target);
367
+ mangleStdlibAst(programs, info, { lazy: true });
368
+ const checker = checkStdlibPrograms(
369
+ programs,
370
+ info.pkgName,
371
+ options,
372
+ deps,
373
+ v.target,
374
+ );
375
+ throwStdlibErrors(checker.checkAll(programs));
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
+ };
392
+
393
+ const pkg = {
394
+ importPath,
395
+ key: v.key,
396
+ checker,
397
+ pkgName: info.pkgName,
398
+ namespace,
399
+ exportedTypes: stdlibExportedTypes(checker, info),
400
+ deps: programs
401
+ .flatMap((p) => p.imports.flatMap((imp) => imp.imports))
402
+ .map(({ path }) =>
403
+ v.variant === "shared"
404
+ ? (cache.get(`${path}#shared`) ?? cache.get(path))
405
+ : cache.get(path),
406
+ )
407
+ .filter(Boolean),
408
+ unit: {
409
+ key: `stdlib:${importPath}`,
410
+ pkgName: info.pkgName,
411
+ target: "both",
412
+ programs,
413
+ types: checker.types,
414
+ bundledPackages: new Set(),
415
+ },
416
+ };
417
+ return pkg;
418
+ }
419
+
420
+ // A Go-source package's JS is generated at the root compile, once every
421
+ // JS-emitted user program is known, so only reachable declarations are
422
+ // emitted (see lower/reachability.js). Importers leave a marker line.
423
+ const STDLIB_MARKER_RE = /^\/\*__GOFRONT_STDLIB:([^*]*)\*\/$/;
424
+
425
+ function stdlibMarker(key) {
426
+ return `/*__GOFRONT_STDLIB:${key}*/`;
427
+ }
428
+
429
+ function stdlibJs(pkg, reached, natives) {
430
+ const programs = pkg.unit.programs.map((p) => ({
431
+ ...p,
432
+ decls: p.decls.filter((d) => reached.has(d)),
433
+ }));
434
+ if (programs.every((p) => p.decls.length === 0)) return "";
435
+ return new CodeGen(pkg.checker, new Map(), new Set(), {
436
+ target: pkg.checker.target,
437
+ strict: true,
438
+ natives,
439
+ }).generateAll(programs, { isTest: false });
440
+ }
441
+
442
+ // The API a wasm package exposes to JS through the facade: exported func and
443
+ // method signatures and exported type declarations.
444
+ function wasmExportedSignatures(units) {
445
+ const out = [];
446
+ for (const u of units) {
447
+ if (u.target !== "wasm" || u.key.startsWith("stdlib:")) continue;
448
+ for (const d of u.programs.flatMap((p) => p.decls ?? [])) {
449
+ if (!/^[A-Z]/.test(d.name ?? "")) continue;
450
+ if (d.kind === "TypeDecl") out.push(d.type);
451
+ else if (d.kind === "FuncDecl" || d.kind === "MethodDecl")
452
+ out.push(d.params, d.returnType);
453
+ }
454
+ }
455
+ return out;
456
+ }
457
+
458
+ // Replaces each marker with the package's reachable code (no line at all when
459
+ // nothing is reachable) and shifts the source-map lines that follow.
460
+ function spliceStdlibJs(merged, options) {
461
+ const lines = merged.js.split("\n");
462
+ const pkgs = lines
463
+ .map((l) => l.match(STDLIB_MARKER_RE)?.[1])
464
+ .filter(Boolean)
465
+ .map((key) => options.stdlibCache.get(key));
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;
471
+ const reached = reachableStdlibDecls(
472
+ [options.jsRootPrograms, wasmExportedSignatures(options.wasmUnits)],
473
+ pkgs.filter(emitted).flatMap((pkg) => pkg.unit.programs),
474
+ );
475
+ const out = [];
476
+ const spliced = new Set();
477
+ for (const line of lines) {
478
+ const path = line.match(STDLIB_MARKER_RE)?.[1];
479
+ if (!path) {
480
+ out.push(line);
481
+ continue;
482
+ }
483
+ if (spliced.has(path)) continue;
484
+ spliced.add(path);
485
+ const pkg = options.stdlibCache.get(path);
486
+ const js = emitted(pkg) ? stdlibJs(pkg, reached, options.natives) : "";
487
+ const code = js === "" ? [] : js.split("\n");
488
+ const delta = code.length - 1;
489
+ for (const m of merged.mappings)
490
+ if (m.genLine > out.length) m.genLine += delta;
491
+ out.push(...code);
492
+ }
493
+ merged.js = out.join("\n");
494
+ }
495
+
496
+ // Records a package's JS-emitted programs as reachability roots; the root
497
+ // compile then splices in the stdlib code they reach.
498
+ export function finishStdlibJs(merged, options, programs, emitTarget) {
499
+ if (emitTarget !== "wasm") options.jsRootPrograms.push(...programs);
500
+ if (!options.isDependency) spliceStdlibJs(merged, options);
501
+ }
502
+
503
+ // Adds a loaded package (and, first, its Go-source dependencies) to the
504
+ // importer's output: JS for js/both importers, a link unit for wasm/both.
505
+ function emitStdlibPackage(
506
+ pkg,
507
+ importerTarget,
508
+ preambles,
509
+ bundledDirs,
510
+ options,
511
+ ) {
512
+ for (const dep of pkg.deps)
513
+ emitStdlibPackage(dep, importerTarget, preambles, bundledDirs, options);
514
+ // Every importer leaves a JS marker: the facade of a wasm package may
515
+ // expose a stdlib type; unreachable code splices to nothing.
516
+ const key = `${pkg.key}:js`;
517
+ if (!bundledDirs.has(key)) {
518
+ bundledDirs.add(key);
519
+ preambles.push(stdlibMarker(pkg.key));
520
+ }
521
+ if (importerTarget !== "js") registerWasmUnit(options, pkg.unit);
522
+ }
523
+
524
+ function importNode({ _line, _col, line, col }) {
525
+ return { _line, _col, line: line ?? _line, col: col ?? _col ?? 1 };
526
+ }
527
+
528
+ // Reports `msg` at an import spec of program `p`.
529
+ function errAtImport(checker, p, spec, msg, hint = null) {
530
+ const saved = [checker._currentFile, checker._currentSource];
531
+ checker._currentFile = p._filename ?? null;
532
+ checker._currentSource = p._source ?? null;
533
+ checker.err(msg, importNode(spec), hint);
534
+ [checker._currentFile, checker._currentSource] = saved;
535
+ }
536
+
537
+ // Defines the import's namespace (and `pkg.T` type names) in the importer.
538
+ function bindStdlibImport(checker, p, spec, pkg) {
539
+ const { path, alias } = spec;
540
+ if (alias === "_") return;
541
+ if (alias === ".") {
542
+ errAtImport(checker, p, spec, `dot-import of '${path}' is not supported`);
543
+ return;
544
+ }
545
+ const name = alias ?? pkg.pkgName;
546
+ const ns = { ...pkg.namespace, name };
547
+ checker.globals.define(name, ns);
548
+ for (const [k, t] of pkg.exportedTypes) {
549
+ const dot = k.indexOf(".");
550
+ checker.types.set(dot < 0 ? k : `${name}${k.slice(dot)}`, t);
551
+ }
552
+ if (!spec._implicit)
553
+ checker.trackImport(name, importNode(spec), p._filename, p._source);
554
+ }
555
+
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 });
595
+ }
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";
608
+ const ast = new Parser(
609
+ new Lexer(source, filename).tokenize(),
610
+ filename,
611
+ source,
612
+ ).parse();
613
+ ast._source = source;
614
+ ast._generated = true;
615
+ ast._path = join(dirname(programs[0]._path ?? "."), filename);
616
+ ast.target = checker.target;
617
+ importStdlibPackages(
618
+ [ast],
619
+ checker,
620
+ preambles,
621
+ options.bundledDirs ?? new Set(),
622
+ options,
623
+ deps,
624
+ );
625
+ const before = checker.errors.length;
626
+ checker.checkAll([ast]);
627
+ if (checker.errors.length > before) {
628
+ const detail = checker.errors
629
+ .slice(before)
630
+ .map((e) => e.message)
631
+ .join("\n");
632
+ throw new Error(
633
+ `internal error in the generated Go; please report\n${detail}`,
634
+ );
635
+ }
636
+ for (const { link } of parts) link?.(checker);
637
+ return [ast];
638
+ }
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
+
721
+ export function importStdlibPackages(
722
+ programs,
723
+ checker,
724
+ preambles,
725
+ bundledDirs,
726
+ options,
727
+ deps,
728
+ ) {
729
+ const wasmImporter = checker.target === "wasm" || checker.target === "both";
730
+ const implicit = wasmImporter ? [] : implicitImports(programs);
731
+ for (const p of programs) {
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);
736
+ }
737
+ }