gofront 1.6.1 → 1.8.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 (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -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 +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -0,0 +1,144 @@
1
+ // src/parser/struct-tag.js
2
+ // Parse Go struct field tags matching Go's reflect.StructTag specification.
3
+
4
+ function unquoteTagValue(s) {
5
+ if (s.length < 2 || s[0] !== '"' || s[s.length - 1] !== '"') return s;
6
+ const inner = s.slice(1, -1);
7
+ let res = "";
8
+ for (let i = 0; i < inner.length; i++) {
9
+ if (inner[i] === "\\" && i + 1 < inner.length) {
10
+ i++;
11
+ const esc = inner[i];
12
+ switch (esc) {
13
+ case "n":
14
+ res += "\n";
15
+ break;
16
+ case "t":
17
+ res += "\t";
18
+ break;
19
+ case "r":
20
+ res += "\r";
21
+ break;
22
+ case '"':
23
+ res += '"';
24
+ break;
25
+ case "\\":
26
+ res += "\\";
27
+ break;
28
+ case "a":
29
+ res += "\x07";
30
+ break;
31
+ case "b":
32
+ res += "\b";
33
+ break;
34
+ case "f":
35
+ res += "\f";
36
+ break;
37
+ case "v":
38
+ res += "\v";
39
+ break;
40
+ case "0":
41
+ res += "\0";
42
+ break;
43
+ default:
44
+ res += `\\${esc}`;
45
+ break;
46
+ }
47
+ } else {
48
+ res += inner[i];
49
+ }
50
+ }
51
+ return res;
52
+ }
53
+
54
+ /**
55
+ * Parses a raw struct tag string into a Map of key -> unquoted string value,
56
+ * adhering to Go reflect.StructTag convention.
57
+ *
58
+ * @param {string | null | undefined} tag
59
+ * @returns {Map<string, string>}
60
+ */
61
+ export function parseStructTag(tag) {
62
+ const map = new Map();
63
+ if (!tag || typeof tag !== "string") return map;
64
+
65
+ let str = tag;
66
+ while (str.length > 0) {
67
+ // Skip leading spaces
68
+ let i = 0;
69
+ while (i < str.length && str.charCodeAt(i) === 32) i++;
70
+ str = str.slice(i);
71
+ if (str.length === 0) break;
72
+
73
+ // Scan to colon: key must not contain space, quote, colon, or control characters
74
+ i = 0;
75
+ while (
76
+ i < str.length &&
77
+ str.charCodeAt(i) > 32 &&
78
+ str[i] !== ":" &&
79
+ str[i] !== '"' &&
80
+ str.charCodeAt(i) !== 127
81
+ ) {
82
+ i++;
83
+ }
84
+ if (
85
+ i === 0 ||
86
+ i + 1 >= str.length ||
87
+ str[i] !== ":" ||
88
+ str[i + 1] !== '"'
89
+ ) {
90
+ break;
91
+ }
92
+ const key = str.slice(0, i);
93
+ str = str.slice(i + 1); // skip colon, now points to opening '"'
94
+
95
+ // Scan quoted string to find closing '"'
96
+ i = 1;
97
+ while (i < str.length && str[i] !== '"') {
98
+ if (str[i] === "\\") i++; // skip escaped char
99
+ i++;
100
+ }
101
+ if (i >= str.length) break;
102
+
103
+ const qval = str.slice(0, i + 1);
104
+ str = str.slice(i + 1);
105
+
106
+ if (!map.has(key)) {
107
+ map.set(key, unquoteTagValue(qval));
108
+ }
109
+ }
110
+ return map;
111
+ }
112
+
113
+ /**
114
+ * Parses the "json" key from a struct tag string or parsed Map according to encoding/json rules.
115
+ *
116
+ * @param {string | Map<string, string> | null | undefined} tag
117
+ * @returns {{ name: string | null, omitempty: boolean, ignored: boolean, options: string[] }}
118
+ */
119
+ export function parseJsonTag(tag) {
120
+ if (!tag) {
121
+ return { name: null, omitempty: false, ignored: false, options: [] };
122
+ }
123
+ const map = typeof tag === "string" ? parseStructTag(tag) : tag;
124
+ if (!map.has("json")) {
125
+ return { name: null, omitempty: false, ignored: false, options: [] };
126
+ }
127
+ const raw = map.get("json");
128
+ const parts = raw.split(",");
129
+ const rawName = parts[0];
130
+ const options = parts.slice(1);
131
+ const omitempty = options.includes("omitempty");
132
+
133
+ if (rawName === "-" && parts.length === 1) {
134
+ return { name: "-", omitempty: false, ignored: true, options };
135
+ }
136
+
137
+ const name = rawName === "" ? null : rawName;
138
+ return {
139
+ name,
140
+ omitempty,
141
+ ignored: false,
142
+ options,
143
+ };
144
+ }
@@ -15,10 +15,27 @@
15
15
  // TemplFor { kind, stmtTokens, body: TemplNode[] }
16
16
  // TemplSwitch { kind, exprTokens, cases: TemplCase[] }
17
17
  // TemplCase { kind, caseTokens: tokens | null (null = default), body: TemplNode[] }
18
+ //
19
+ // Go expressions embedded in templ (`{ expr }`, attribute values, `if`
20
+ // conditions, `switch` and `case` expressions, `@Comp(...)` calls) and `for`
21
+ // headers are also parsed here, so the code generator never runs the parser.
22
+ // The results sit on the node as non-enumerable properties (the generic AST
23
+ // walkers in rename/lowering visit enumerable keys only, and these ASTs have
24
+ // no type information):
25
+ // expr Expression AST, or null when there are no tokens to parse
26
+ // exprError The ParseError a malformed expression raised. The parser keeps
27
+ // it instead of throwing, so the error still surfaces when the
28
+ // code generator reads the node (after type-checking), as before
29
+ // raw TemplComponent only: `@templ.Raw(arg)`; `expr` is then `arg`
30
+ // forStmt TemplFor only: the parsed ForStmt, or null when the header does
31
+ // not parse as a Go `for` clause
32
+ // forError TemplFor only: the ParseError the header raised (kept, like
33
+ // exprError, for the code generator to throw)
34
+ // attr.expr / attr.exprError on `expr` and `cond-bool` attributes
18
35
 
19
- import { Parser } from "./parser/index.js";
20
- import { TT } from "./templ-lexer.js";
21
- import { T } from "./tokens.js";
36
+ import { TT } from "../lexer/templ-lexer.js";
37
+ import { T, Token } from "../lexer/tokens.js";
38
+ import { Parser } from "./index.js";
22
39
 
23
40
  const VOID_ELEMENTS = new Set([
24
41
  "area",
@@ -37,6 +54,90 @@ const VOID_ELEMENTS = new Set([
37
54
  "wbr",
38
55
  ]);
39
56
 
57
+ function attach(node, props) {
58
+ for (const [name, value] of Object.entries(props)) {
59
+ Object.defineProperty(node, name, {
60
+ value,
61
+ enumerable: false,
62
+ writable: true,
63
+ configurable: true,
64
+ });
65
+ }
66
+ return node;
67
+ }
68
+
69
+ // Parses `tokens` as one Go expression, ending at a synthetic EOF token.
70
+ function parseExprTokens(tokens, allowEmpty = true) {
71
+ if (allowEmpty && (!tokens || tokens.length === 0)) return { expr: null };
72
+ try {
73
+ const eofTok = new Token(T.EOF, "", 1, 1);
74
+ return { expr: new Parser([...tokens, eofTok]).parseExpr() };
75
+ } catch (exprError) {
76
+ return { expr: null, exprError };
77
+ }
78
+ }
79
+
80
+ // Parses an `if` condition; the lexer may leave the `if` keyword in front.
81
+ function parseCondTokens(tokens) {
82
+ if (!tokens || tokens.length === 0) return { expr: null };
83
+ return parseExprTokens(
84
+ tokens[0]?.type === T.IF ? tokens.slice(1) : tokens,
85
+ false,
86
+ );
87
+ }
88
+
89
+ function isTemplRaw(toks) {
90
+ return (
91
+ toks.length >= 4 &&
92
+ toks[0]?.value === "templ" &&
93
+ toks[1]?.type === T.DOT &&
94
+ toks[2]?.value === "Raw" &&
95
+ toks[3]?.type === T.LPAREN
96
+ );
97
+ }
98
+
99
+ // Strips the trailing semicolons the Go lexer inserts at a newline.
100
+ function trimSemicolons(tokens) {
101
+ let end = tokens.length;
102
+ while (end > 0 && tokens[end - 1].type === T.SEMICOLON) end--;
103
+ return tokens.slice(0, end);
104
+ }
105
+
106
+ // Parses a `for` header (the `for` keyword plus the clause, no body) as a
107
+ // ForStmt, with a fake empty body `{ }` so the parser can complete it. A header
108
+ // that is not a valid Go `for` clause gives `forStmt: null` plus its error.
109
+ function parseForHeader(tokens) {
110
+ const eofTok = new Token(T.EOF, "", 1, 1);
111
+ const lbrace = new Token(T.LBRACE, "{", 1, 1);
112
+ const rbrace = new Token(T.RBRACE, "}", 1, 1);
113
+ const semi = new Token(T.SEMICOLON, ";", 1, 1);
114
+ const withFor =
115
+ tokens[0]?.type === T.FOR
116
+ ? [...tokens, lbrace, rbrace, semi, eofTok]
117
+ : [
118
+ new Token(T.FOR, "for", 1, 1),
119
+ ...tokens,
120
+ lbrace,
121
+ rbrace,
122
+ semi,
123
+ eofTok,
124
+ ];
125
+ try {
126
+ return { forStmt: new Parser(withFor).parseFor() };
127
+ } catch (forError) {
128
+ return { forStmt: null, forError };
129
+ }
130
+ }
131
+
132
+ // Parses the `expr` and `cond-bool` attribute expressions of an element.
133
+ function parseAttrExprs(attrs) {
134
+ for (const attr of attrs) {
135
+ if (attr.kind === "expr" || attr.kind === "cond-bool")
136
+ attach(attr, parseExprTokens(attr.tokens));
137
+ }
138
+ return attrs;
139
+ }
140
+
40
141
  export class TemplParser extends Parser {
41
142
  // ── Top-level declaration override ───────────────────────────
42
143
 
@@ -101,7 +202,7 @@ export class TemplParser extends Parser {
101
202
  return {
102
203
  kind: "TemplElement",
103
204
  tag: t.value.tag,
104
- attrs: t.value.attrs,
205
+ attrs: parseAttrExprs(t.value.attrs),
105
206
  children: [],
106
207
  };
107
208
  }
@@ -110,10 +211,13 @@ export class TemplParser extends Parser {
110
211
  return { kind: "TemplText", value: t.value };
111
212
  case TT.TEMPL_EXPR:
112
213
  this.advance();
113
- return { kind: "TemplExpr", tokens: t.value };
214
+ return attach(
215
+ { kind: "TemplExpr", tokens: t.value },
216
+ parseExprTokens(t.value),
217
+ );
114
218
  case TT.TEMPL_COMP:
115
219
  this.advance();
116
- return { kind: "TemplComponent", tokens: t.value };
220
+ return this.templComponent(t.value);
117
221
  case TT.TEMPL_CHILDREN:
118
222
  this.advance();
119
223
  return { kind: "TemplChildren" };
@@ -136,10 +240,18 @@ export class TemplParser extends Parser {
136
240
  }
137
241
  }
138
242
 
243
+ // `@templ.Raw(arg)` parses `arg`; any other component parses the whole call.
244
+ templComponent(tokens) {
245
+ const raw = isTemplRaw(tokens);
246
+ const parsed = parseExprTokens(raw ? tokens.slice(4, -1) : tokens);
247
+ return attach({ kind: "TemplComponent", tokens }, { raw, ...parsed });
248
+ }
249
+
139
250
  // Parse children of an element until the matching close tag.
140
251
  parseHtmlElement() {
141
252
  const openTok = this.advance(); // HTML_OPEN
142
- const { tag, attrs } = openTok.value;
253
+ const { tag } = openTok.value;
254
+ const attrs = parseAttrExprs(openTok.value.attrs);
143
255
 
144
256
  // Void elements have no children and no close tag even without />
145
257
  if (VOID_ELEMENTS.has(tag)) {
@@ -186,14 +298,20 @@ export class TemplParser extends Parser {
186
298
  else_ = this.parseHtmlChildren(); // else body until RBRACE
187
299
  }
188
300
  }
189
- return { kind: "TemplIf", condTokens, then, else_ };
301
+ return attach(
302
+ { kind: "TemplIf", condTokens, then, else_ },
303
+ parseCondTokens(condTokens),
304
+ );
190
305
  }
191
306
 
192
307
  // Parse a TemplFor node. `tok` is the TEMPL_FOR token (already advanced).
193
308
  parseTemplFor(tok) {
194
309
  const stmtTokens = tok.value;
195
310
  const body = this.parseHtmlChildren();
196
- return { kind: "TemplFor", stmtTokens, body };
311
+ return attach(
312
+ { kind: "TemplFor", stmtTokens, body },
313
+ parseForHeader(trimSemicolons(stmtTokens)),
314
+ );
197
315
  }
198
316
 
199
317
  // Parse a TemplSwitch node. `tok` is the TEMPL_SWITCH token (already advanced).
@@ -209,17 +327,22 @@ export class TemplParser extends Parser {
209
327
  const isDefault = this.check(TT.TEMPL_DEFAULT);
210
328
  const caseTok = this.advance();
211
329
  const body = this.parseTemplCaseBody();
212
- cases.push({
213
- kind: "TemplCase",
214
- caseTokens: isDefault ? null : caseTok.value,
215
- body,
216
- });
330
+ const caseTokens = isDefault ? null : caseTok.value;
331
+ cases.push(
332
+ attach(
333
+ { kind: "TemplCase", caseTokens, body },
334
+ isDefault ? { expr: null } : parseExprTokens(caseTokens),
335
+ ),
336
+ );
217
337
  } else {
218
338
  this.advance(); // skip unexpected
219
339
  }
220
340
  }
221
341
  if (this.check(TT.TEMPL_END)) this.advance(); // consume closing }
222
- return { kind: "TemplSwitch", exprTokens, cases };
342
+ return attach(
343
+ { kind: "TemplSwitch", exprTokens, cases },
344
+ parseExprTokens(exprTokens),
345
+ );
223
346
  }
224
347
 
225
348
  // Parse HTML nodes for a switch case body — stops at CASE/DEFAULT/END/RBRACE.
@@ -1,6 +1,6 @@
1
1
  // Parser type-expression methods — installed as a mixin on Parser.prototype.
2
2
 
3
- import { T } from "../tokens.js";
3
+ import { T } from "../lexer/tokens.js";
4
4
 
5
5
  // Token types that directly map to parseTypeName().
6
6
  const TYPE_NAME_TOKENS = new Set([
@@ -76,8 +76,10 @@ export const typeParserMethods = {
76
76
  i++;
77
77
  continue;
78
78
  }
79
- // Reject literals — it's an index/array expression
80
- if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
79
+ // Reject literals — it's an index/array expression. A literal nested
80
+ // deeper is an array length inside a type argument: Stack[[2]int].
81
+ if (depth === 1 && (tt === T.INT || tt === T.FLOAT || tt === T.STRING))
82
+ return false;
81
83
  i++;
82
84
  }
83
85
  return depth === 0;
@@ -86,11 +88,17 @@ export const typeParserMethods = {
86
88
  parseTypeParamList() {
87
89
  this.expect(T.LBRACKET);
88
90
  const params = [];
89
- do {
90
- const name = this.expect(T.IDENT).value;
91
+ while (!this.check(T.RBRACKET) && !this.check(T.EOF)) {
92
+ const names = [this.expect(T.IDENT).value];
93
+ while (this.match(T.COMMA)) {
94
+ names.push(this.expect(T.IDENT).value);
95
+ }
91
96
  const constraint = this.parseConstraint();
92
- params.push({ kind: "TypeParam", name, constraint });
93
- } while (this.match(T.COMMA));
97
+ for (const name of names) {
98
+ params.push({ kind: "TypeParam", name, constraint });
99
+ }
100
+ if (!this.match(T.COMMA)) break;
101
+ }
94
102
  this.expect(T.RBRACKET);
95
103
  return params;
96
104
  },
@@ -175,29 +183,138 @@ export const typeParserMethods = {
175
183
  this.expect(T.LBRACE);
176
184
  const fields = [];
177
185
  while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
186
+ // Embedded pointer field: *Base or *pkg.Base
187
+ if (this.check(T.STAR)) {
188
+ this.advance();
189
+ let typeName = this.expect(T.IDENT).value;
190
+ if (this.check(T.DOT)) {
191
+ this.advance();
192
+ typeName += `.${this.expect(T.IDENT).value}`;
193
+ }
194
+ const type = {
195
+ kind: "PointerType",
196
+ base: this._embeddedTypeName(typeName, true),
197
+ };
198
+ let tag = null;
199
+ if (this.check(T.STRING)) {
200
+ tag = this.advance().value;
201
+ }
202
+ fields.push({
203
+ names: [],
204
+ type,
205
+ embedded: true,
206
+ embeddedName: type.base.name,
207
+ tag,
208
+ });
209
+ this.semi();
210
+ continue;
211
+ }
212
+
178
213
  const names = this.parseIdentList();
179
- // Check if this is an embedded field (just a type name)
180
- // e.g., type T struct { Base }
181
- if (this.check(T.SEMICOLON) || this.check(T.RBRACE)) {
182
- const typeName = names[0]; // assuming no comma list for embedded
214
+ // Check if this is a qualified embedded field: pkg.Base
215
+ if (names.length === 1 && this.check(T.DOT)) {
216
+ this.advance();
217
+ const typeName = `${names[0]}.${this.expect(T.IDENT).value}`;
218
+ const embeddedType = this._embeddedTypeName(typeName, true);
219
+ let tag = null;
220
+ if (this.check(T.STRING)) {
221
+ tag = this.advance().value;
222
+ }
223
+ fields.push({
224
+ names: [],
225
+ type: embeddedType,
226
+ embedded: true,
227
+ embeddedName: typeName,
228
+ tag,
229
+ });
230
+ this.semi();
231
+ continue;
232
+ }
233
+
234
+ // Embedded generic instance: Inner[T] (not an array field `a [N]T`)
235
+ if (
236
+ names.length === 1 &&
237
+ this.check(T.LBRACKET) &&
238
+ this._bracketEndsEmbeddedField()
239
+ ) {
240
+ const embeddedType = this._embeddedTypeName(names[0], true);
241
+ let tag = null;
242
+ if (this.check(T.STRING)) {
243
+ tag = this.advance().value;
244
+ }
245
+ fields.push({
246
+ names: [],
247
+ type: embeddedType,
248
+ embedded: true,
249
+ embeddedName: names[0],
250
+ tag,
251
+ });
252
+ this.semi();
253
+ continue;
254
+ }
255
+
256
+ // Embedded field (just a type name, e.g. Base), optionally tagged
257
+ if (
258
+ names.length === 1 &&
259
+ (this.check(T.SEMICOLON) ||
260
+ this.check(T.RBRACE) ||
261
+ this.check(T.STRING))
262
+ ) {
263
+ const typeName = names[0];
264
+ let tag = null;
265
+ if (this.check(T.STRING)) {
266
+ tag = this.advance().value;
267
+ }
183
268
  fields.push({
184
269
  names: [],
185
270
  type: { kind: "TypeName", name: typeName },
186
271
  embedded: true,
272
+ tag,
187
273
  });
188
274
  this.semi();
189
275
  continue;
190
276
  }
277
+
191
278
  const type = this.parseType();
192
279
  // Consume optional struct tag (backtick string), e.g. `json:"name"`
193
- if (this.check(T.STRING)) this.advance();
194
- fields.push({ names, type });
280
+ let tag = null;
281
+ if (this.check(T.STRING)) {
282
+ tag = this.advance().value;
283
+ }
284
+ fields.push({ names, type, tag });
195
285
  this.semi();
196
286
  }
197
287
  this.expect(T.RBRACE);
198
288
  return { kind: "StructType", fields };
199
289
  },
200
290
 
291
+ // True when the `[...]` at the cursor is followed by the end of a field
292
+ // (`Inner[T]` embedded), rather than an element type (`a [N]T`).
293
+ _bracketEndsEmbeddedField() {
294
+ let depth = 0;
295
+ for (let i = this.pos; i < this.tokens.length; i++) {
296
+ const tt = this.tokens[i].type;
297
+ if (tt === T.LBRACKET) depth++;
298
+ else if (tt === T.RBRACKET && --depth === 0) {
299
+ const next = this.tokens[i + 1]?.type;
300
+ return next === T.SEMICOLON || next === T.RBRACE || next === T.STRING;
301
+ }
302
+ }
303
+ return false;
304
+ },
305
+
306
+ // Embedded field type: `name`, or `name[Args]` when type arguments follow.
307
+ _embeddedTypeName(name, allowArgs) {
308
+ if (allowArgs && this.check(T.LBRACKET)) {
309
+ this.expect(T.LBRACKET);
310
+ const typeArgs = [this.parseType()];
311
+ while (this.match(T.COMMA)) typeArgs.push(this.parseType());
312
+ this.expect(T.RBRACKET);
313
+ return { kind: "GenericTypeName", name, typeArgs };
314
+ }
315
+ return { kind: "TypeName", name };
316
+ },
317
+
201
318
  parseInterfaceType() {
202
319
  this.expect(T.INTERFACE);
203
320
  this.expect(T.LBRACE);
package/src/resolver.js CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { existsSync, readdirSync, readFileSync } from "node:fs";
14
14
  import { dirname, join, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
15
16
 
16
17
  // Walk up from startDir looking for a node_modules directory
17
18
  function findNodeModules(startDir) {
@@ -25,6 +26,7 @@ function findNodeModules(startDir) {
25
26
  }
26
27
  }
27
28
 
29
+ import { EXCLUDED_STDLIB_PACKAGES } from "./typechecker/stdlib/excluded.js";
28
30
  import { STDLIB_PACKAGES } from "./typechecker/types.js";
29
31
 
30
32
  // Returns true for local relative paths (start with ./ or ../)
@@ -36,6 +38,23 @@ export function isBuiltinPackage(importPath) {
36
38
  return STDLIB_PACKAGES.has(importPath);
37
39
  }
38
40
 
41
+ // Go-source stdlib: `stdlib/<import path>/*.go`, written in GoFront Go
42
+ // and compiled into the caller's target. A directory for a path that is also
43
+ // in STDLIB_PACKAGES is an overlay: its declarations extend that namespace.
44
+ const STDLIB_SOURCE_ROOT = fileURLToPath(
45
+ new URL("../stdlib/", import.meta.url),
46
+ );
47
+ const STDLIB_PATH_RE = /^[a-z0-9]+(\/[a-z0-9]+)*$/;
48
+
49
+ // Returns the source directory for a Go-source stdlib import path, or null.
50
+ // `root` replaces stdlib/ (tests use a temporary tree).
51
+ export function resolveStdlibSource(importPath, root = null) {
52
+ if (!STDLIB_PATH_RE.test(importPath)) return null;
53
+ const dir = join(root ?? STDLIB_SOURCE_ROOT, importPath);
54
+ if (!existsSync(dir)) return null;
55
+ return readdirSync(dir).some((f) => f.endsWith(".go")) ? dir : null;
56
+ }
57
+
39
58
  // Resolve a local relative import path to an absolute directory of .go files.
40
59
  // Returns the absolute dir path, or null if it doesn't contain any .go files.
41
60
  export function resolveGwDir(importPath, fromFile) {
@@ -86,14 +105,16 @@ function resolveImport(importPath, fromFile) {
86
105
 
87
106
  // Given an AST's import list, resolve all imports.
88
107
  // Returns Map<importPath, { dtsPath, importPath, types, values }>
89
- export function resolveAll(imports, fromFile, parseDts) {
108
+ export function resolveAll(imports, fromFile, parseDts, stdlibRoot = null) {
90
109
  const resolved = new Map();
91
110
 
92
111
  for (const imp of imports) {
93
112
  for (const { path } of imp.imports) {
94
113
  if (path.startsWith("js:")) continue; // handled separately
95
114
  if (isLocalPath(path)) continue; // local .go packages — handled by compiler
115
+ if (EXCLUDED_STDLIB_PACKAGES.has(path)) continue; // the checker reports it
96
116
  if (isBuiltinPackage(path)) continue; // built-in stdlib namespaces — always available
117
+ if (resolveStdlibSource(path, stdlibRoot)) continue; // Go-source stdlib — handled by compiler
97
118
 
98
119
  if (resolved.has(path)) continue;
99
120