gofront 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -13,6 +13,31 @@ export function rootIdentName(e) {
13
13
  return null;
14
14
  }
15
15
 
16
+ // True when `e` names an element of a slice or array, possibly through
17
+ // fields (`xs[i]`, `xs[i].f.g`).
18
+ function isElementPath(e) {
19
+ while (e) {
20
+ if (e.kind === "IndexExpr") return !e._mapValueType;
21
+ if (e.kind !== "SelectorExpr" && e.kind !== "ParenExpr") return false;
22
+ e = e.expr;
23
+ }
24
+ return false;
25
+ }
26
+
27
+ // True when `node` contains `&xs[i]` (or `&xs[i].f`): some code may then hold a
28
+ // pointer to an element, so a whole-element store must write into it.
29
+ export function takesElementAddress(node) {
30
+ if (!node || typeof node !== "object") return false;
31
+ if (Array.isArray(node)) return node.some(takesElementAddress);
32
+ if (
33
+ node.kind === "UnaryExpr" &&
34
+ node.op === "&" &&
35
+ isElementPath(node.operand)
36
+ )
37
+ return true;
38
+ return someChild(node, takesElementAddress);
39
+ }
40
+
16
41
  // True when `node` itself (not its children) writes to variable `name`.
17
42
  export function nodeWritesVar(node, name) {
18
43
  const root = (e) => rootIdentName(e) === name;
@@ -160,13 +185,6 @@ export class OwnershipContext {
160
185
  constructor(body) {
161
186
  this.body = body;
162
187
  this.mut = new Map();
163
- this.owned = new Set();
164
- this.borrowed = new Set();
165
- }
166
-
167
- markOwnership(name, owned) {
168
- if (!name || name === "_") return;
169
- (owned ? this.owned : this.borrowed).add(name);
170
188
  }
171
189
 
172
190
  fnMutates(name, addrOnly = false) {
@@ -11,10 +11,12 @@
11
11
  import { forEachChild } from "./walk.js";
12
12
 
13
13
  // Methods the runtime or emitter-backed stdlib call without a selector in
14
- // GoFront code (fmt verbs, errors.Is/As/Unwrap, encoding/json).
14
+ // GoFront code (fmt verbs and Fprint* writers, errors.Is/As/Unwrap,
15
+ // encoding/json).
15
16
  const IMPLICIT_METHODS = new Set([
16
17
  "Error",
17
18
  "String",
19
+ "Write",
18
20
  "GoString",
19
21
  "Format",
20
22
  "Unwrap",
@@ -48,6 +50,9 @@ function collectRefs(node, names, fields) {
48
50
  )
49
51
  names.add(node.name);
50
52
  else if (node.kind === "SelectorExpr") fields.add(node.field);
53
+ // A reached interface needs its methods on the types that satisfy it.
54
+ else if (node.kind === "InterfaceType")
55
+ for (const m of node.methods ?? []) fields.add(m.name);
51
56
  forEachChild(node, (child) => collectRefs(child, names, fields));
52
57
  }
53
58
 
@@ -70,63 +75,76 @@ function indexStdlib(stdlibPrograms) {
70
75
  return { byName, methods, inits };
71
76
  }
72
77
 
73
- const JSON_NAMES = [
74
- "SyntaxError$$internal$jsonrt",
75
- "UnmarshalTypeError$$internal$jsonrt",
76
- "InvalidUnmarshalError$$internal$jsonrt",
77
- "UnsupportedValueError$$internal$jsonrt",
78
- "UnsupportedTypeError$$internal$jsonrt",
79
- "MarshalerError$$internal$jsonrt",
80
- "CheckValid$$internal$jsonrt",
81
- "Number$$internal$jsonrt",
82
- "OptEscapeHTML$$internal$jsonrt",
83
- "OptUseNumber$$internal$jsonrt",
84
- "OptDisallowUnknownFields$$internal$jsonrt",
85
- ];
78
+ // Incremental closure over one or more Go-source stdlib packages. A
79
+ // declaration's body may still be unchecked (lazy checking): `decl._ensure()`,
80
+ // when present, checks and mangles it first and returns an error text on
81
+ // failure, so the references collected from it are the mangled ones. Failed
82
+ // declarations are listed in `failures`.
83
+ export class StdlibReach {
84
+ constructor(stdlibPrograms) {
85
+ this.programs = stdlibPrograms;
86
+ const { byName, methods, inits } = indexStdlib(stdlibPrograms);
87
+ this.byName = byName;
88
+ this.methods = methods;
89
+ this.inits = inits;
90
+ this.reached = new Set();
91
+ this.names = new Set();
92
+ this.fields = new Set(IMPLICIT_METHODS);
93
+ this.pending = [];
94
+ this.failures = [];
95
+ }
96
+
97
+ // Adds `rootNodes` (AST that is emitted) and closes over what they use.
98
+ add(rootNodes) {
99
+ const roots = new Set();
100
+ collectRefs(rootNodes, roots, this.fields);
101
+ for (const n of roots) this._name(n);
102
+ this._close();
103
+ }
86
104
 
87
- function addExtraRoots(names) {
88
- if (names.has("strconv")) names.add("NumError$$strconv");
89
- if (names.has("json")) {
90
- for (const n of JSON_NAMES) names.add(n);
105
+ _name(n) {
106
+ if (this.names.has(n)) return;
107
+ this.names.add(n);
108
+ this.pending.push(n);
109
+ }
110
+
111
+ _reach(d) {
112
+ if (this.reached.has(d)) return;
113
+ this.reached.add(d);
114
+ const failure = d._ensure?.();
115
+ if (failure) this.failures.push(failure);
116
+ const local = new Set();
117
+ collectRefs(d, local, this.fields);
118
+ for (const n of local) this._name(n);
119
+ }
120
+
121
+ _close() {
122
+ let changed = true;
123
+ while (changed) {
124
+ while (this.pending.length > 0)
125
+ for (const d of this.byName.get(this.pending.pop()) ?? [])
126
+ this._reach(d);
127
+ const size = this.reached.size;
128
+ for (const m of this.methods)
129
+ if (this.names.has(m.recv) && this.fields.has(m.decl.name))
130
+ this._reach(m.decl);
131
+ for (const p of this.programs)
132
+ if (p.decls.some((d) => this.reached.has(d)))
133
+ for (const d of this.inits.get(p) ?? []) this._reach(d);
134
+ changed = this.reached.size !== size || this.pending.length > 0;
135
+ }
91
136
  }
92
137
  }
93
138
 
94
139
  /**
95
140
  * Returns the set of declarations of `stdlibPrograms` reachable from
96
141
  * `rootPrograms` (all user code that is emitted). A program's `init` is
97
- * kept when anything else in that program is reached.
142
+ * kept when anything else in that program is reached. A reached body that
143
+ * fails its (lazy) check throws.
98
144
  */
99
145
  export function reachableStdlibDecls(rootPrograms, stdlibPrograms) {
100
- const { byName, methods, inits } = indexStdlib(stdlibPrograms);
101
- const reached = new Set();
102
- const names = new Set();
103
- const fields = new Set(IMPLICIT_METHODS);
104
- collectRefs(rootPrograms, names, fields);
105
- addExtraRoots(names);
106
-
107
- const pending = [...names];
108
- const reach = (d) => {
109
- if (reached.has(d)) return;
110
- reached.add(d);
111
- const local = new Set();
112
- collectRefs(d, local, fields);
113
- for (const n of local) {
114
- if (names.has(n)) continue;
115
- names.add(n);
116
- pending.push(n);
117
- }
118
- };
119
- let changed = true;
120
- while (changed) {
121
- while (pending.length > 0)
122
- for (const d of byName.get(pending.pop()) ?? []) reach(d);
123
- const size = reached.size;
124
- for (const m of methods)
125
- if (names.has(m.recv) && fields.has(m.decl.name)) reach(m.decl);
126
- for (const p of stdlibPrograms)
127
- if (p.decls.some((d) => reached.has(d)))
128
- for (const d of inits.get(p) ?? []) reach(d);
129
- changed = reached.size !== size || pending.length > 0;
130
- }
131
- return reached;
146
+ const reach = new StdlibReach(stdlibPrograms);
147
+ reach.add(rootPrograms);
148
+ if (reach.failures.length > 0) throw new Error(reach.failures[0]);
149
+ return reach.reached;
132
150
  }
package/src/lower/walk.js CHANGED
@@ -23,3 +23,15 @@ export function someChild(node, pred) {
23
23
  }
24
24
  return false;
25
25
  }
26
+
27
+ /**
28
+ * True when evaluating `node` runs a call or a channel receive (conversions
29
+ * excluded), the only operands Go still evaluates for a constant `len`.
30
+ */
31
+ export function hasCallOrReceive(node) {
32
+ if (!node || typeof node !== "object") return false;
33
+ if (Array.isArray(node)) return node.some(hasCallOrReceive);
34
+ if (node.kind === "CallExpr" && !node._isTypeConversion) return true;
35
+ if (node.kind === "UnaryExpr" && node.op === "<-") return true;
36
+ return someChild(node, hasCallOrReceive);
37
+ }
@@ -34,10 +34,12 @@ export const declarationParseMethods = {
34
34
  this.expect(T.RPAREN);
35
35
  const name = this.expect(T.IDENT).value;
36
36
  const { params, returnType } = this.parseSignature();
37
+ const native = this.tokens?.natives?.get(_line - 1) ?? null;
37
38
  const body = this.parseBlock();
38
39
  this.semi();
39
40
  return {
40
41
  kind: "MethodDecl",
42
+ ...(native && { native }),
41
43
  recvName,
42
44
  recvType,
43
45
  recvPointer,
@@ -59,7 +61,11 @@ export const declarationParseMethods = {
59
61
  // `//gofront:intrinsic name` on the line before a body-less func: the
60
62
  // backend provides the implementation; every pass sees an empty body.
61
63
  const intrinsic = this.tokens?.intrinsics?.get(_line - 1) ?? null;
62
- const bodyless = intrinsic !== null && !this.check(T.LBRACE);
64
+ // `//gofront:native name` marks a func that keeps its Go body; a
65
+ // body-less one parses (and is rejected by the checker).
66
+ const native = this.tokens?.natives?.get(_line - 1) ?? null;
67
+ const bodyless =
68
+ (intrinsic !== null || native !== null) && !this.check(T.LBRACE);
63
69
  const body = bodyless ? { kind: "Block", stmts: [] } : this.parseBlock();
64
70
  this.semi();
65
71
  return {
@@ -70,6 +76,7 @@ export const declarationParseMethods = {
70
76
  returnType,
71
77
  body,
72
78
  ...(intrinsic && { intrinsic, _bodyless: bodyless }),
79
+ ...(native && { native, _bodyless: bodyless }),
73
80
  _line,
74
81
  };
75
82
  },
@@ -119,10 +126,16 @@ export const declarationParseMethods = {
119
126
  // `Name[…]` followed by `,` or `)` in a result list is an instantiated
120
127
  // generic type; a result name is followed by its type (`a []int`).
121
128
  _isGenericResultType() {
122
- if (this.peek2()?.type !== T.LBRACKET) return false;
129
+ return this._isGenericInstanceAt(this.pos);
130
+ },
131
+
132
+ // True when the IDENT at `at` starts an instantiated type (`Box[int]`) that
133
+ // ends a list entry, rather than `name [N]T` / `name []T`.
134
+ _isGenericInstanceAt(at) {
135
+ if (this.tokens[at + 1]?.type !== T.LBRACKET) return false;
123
136
  const src = this.tokens;
124
137
  let depth = 0;
125
- for (let i = this.pos + 1; i < src.length; i++) {
138
+ for (let i = at + 1; i < src.length; i++) {
126
139
  const t = src[i].type;
127
140
  if (t === T.LBRACKET) depth++;
128
141
  else if (t === T.RBRACKET && --depth === 0) {
@@ -179,11 +192,13 @@ export const declarationParseMethods = {
179
192
  // if a type-start token follows — if so, the IDENTs are names; if ) or ,
180
193
  // follows, the IDENTs are themselves types.
181
194
  _isNamedParam() {
195
+ if (this._isGenericInstanceAt(this.pos)) return false;
182
196
  let pos = this.pos + 1; // skip current IDENT
183
197
  while (
184
198
  this.tokens[pos]?.type === T.COMMA &&
185
199
  this.tokens[pos + 1]?.type === T.IDENT
186
200
  ) {
201
+ if (this._isGenericInstanceAt(pos + 1)) return false;
187
202
  pos += 2;
188
203
  }
189
204
  const next = this.tokens[pos];
@@ -212,8 +227,16 @@ export const declarationParseMethods = {
212
227
  this.check(T.EOF)
213
228
  )
214
229
  return null;
215
- // Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
216
- if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
230
+ // Inside a type expression (e.g. func type in a tuple or a type-switch
231
+ // `case func():`), `,`, `)` and `:` also end the type
232
+ if (
233
+ inTypeExpr &&
234
+ (this.check(T.COMMA) ||
235
+ this.check(T.RPAREN) ||
236
+ this.check(T.COLON) ||
237
+ this.check(T.STRING) ||
238
+ this.check(T.ASSIGN))
239
+ )
217
240
  return null;
218
241
  // Multiple return values: (int, string) or named (result int, ok bool)
219
242
  if (this.check(T.LPAREN)) {
@@ -97,6 +97,18 @@ function _isDisallowedInTypeArgs(tt) {
97
97
  return DISALLOWED_IN_TYPE_ARGS.has(tt);
98
98
  }
99
99
 
100
+ // `[T]` followed by `(` or a composite literal's `{` is an instantiation. A
101
+ // `{` after the condition of if/for/switch opens the block (`if v == s[i] {`).
102
+ function _opensArgs(after, noCompLit) {
103
+ return after.type === T.LPAREN || (after.type === T.LBRACE && !noCompLit);
104
+ }
105
+
106
+ function _isCallParen(src, i, braces) {
107
+ return (
108
+ src[i].type === T.LPAREN && braces === 0 && src[i - 1]?.type === T.IDENT
109
+ );
110
+ }
111
+
100
112
  function _isTypeIntroducerToken(tt) {
101
113
  return (
102
114
  tt === T.STAR ||
@@ -173,11 +185,20 @@ export const expressionParserMethods = {
173
185
  } else if (t.type === T.LBRACKET) {
174
186
  // Check if this looks like generic type args: Ident[Type](...) or Ident[Type]{...}
175
187
  // `pkg.F[int](x)` / `pkg.T[X]{...}` instantiate through a qualifier.
176
- if (
177
- (expr.kind === "Ident" ||
178
- (expr.kind === "SelectorExpr" && expr.expr.kind === "Ident")) &&
179
- this.looksLikeTypeArgList()
180
- ) {
188
+ const nameLike =
189
+ expr.kind === "Ident" ||
190
+ (expr.kind === "SelectorExpr" && expr.expr.kind === "Ident");
191
+ const plainArgs = nameLike && this.looksLikeTypeArgList(noCompLit);
192
+ const literalArgs =
193
+ nameLike && !plainArgs ? this.tryParseTypeLiteralArgs() : null;
194
+ if (literalArgs) {
195
+ expr = {
196
+ kind: "InstantiationExpr",
197
+ expr,
198
+ typeArgs: literalArgs,
199
+ _line: expr._line,
200
+ };
201
+ } else if (plainArgs) {
181
202
  this.advance(); // consume [
182
203
  const typeArgs = [this.parseType()];
183
204
  while (this.match(T.COMMA)) typeArgs.push(this.parseType());
@@ -233,15 +254,59 @@ export const expressionParserMethods = {
233
254
  );
234
255
  },
235
256
 
257
+ // `Id[[]int, int]`, `slices.Sort[map[string]int]`: type arguments that are
258
+ // type literals. Returns the parsed type list, or null (position restored)
259
+ // when the brackets hold an index expression such as `a[[]int{1}[0]]`.
260
+ tryParseTypeLiteralArgs() {
261
+ const first = this.peek2()?.type;
262
+ const startsLiteral =
263
+ first === T.LBRACKET || first === T.MAP || first === T.FUNC;
264
+ // `*T` could be a dereference in an index, but an index has no comma.
265
+ if (!startsLiteral && !(first === T.STAR || this._hasTopLevelComma()))
266
+ return null;
267
+ const start = this.pos;
268
+ try {
269
+ this.expect(T.LBRACKET);
270
+ const typeArgs = [this.parseType()];
271
+ while (this.match(T.COMMA)) typeArgs.push(this.parseType());
272
+ this.expect(T.RBRACKET);
273
+ if (!startsLiteral && typeArgs.length < 2) throw new Error("not types");
274
+ // `a[foo, bar]` is not an instantiation: names alone need a literal.
275
+ if (!startsLiteral && typeArgs.every((a) => a.kind === "TypeName"))
276
+ throw new Error("not type literals");
277
+ return typeArgs;
278
+ } catch {
279
+ this.pos = start;
280
+ return null;
281
+ }
282
+ },
283
+
284
+ // Whether the bracket pair at the current `[` contains a comma outside any
285
+ // nested brackets, parentheses or braces.
286
+ _hasTopLevelComma() {
287
+ let depth = 0;
288
+ for (let i = this.pos; i < this.tokens.length; i++) {
289
+ const tt = this.tokens[i].type;
290
+ if (tt === T.LBRACKET || tt === T.LPAREN || tt === T.LBRACE) depth++;
291
+ else if (tt === T.RBRACKET || tt === T.RPAREN || tt === T.RBRACE) {
292
+ depth--;
293
+ if (depth === 0) return false;
294
+ } else if (tt === T.COMMA && depth === 1) return true;
295
+ else if (tt === T.EOF) return false;
296
+ }
297
+ return false;
298
+ },
299
+
236
300
  // Lookahead: does the [ ... ] after an Ident look like generic type args?
237
301
  // Returns true if: only type-like tokens inside brackets, AND followed by a valid continuation
238
- looksLikeTypeArgList() {
302
+ looksLikeTypeArgList(noCompLit = false) {
239
303
  let i = this.pos + 1; // skip [
240
304
  const src = this.tokens;
241
305
  let depth = 1;
242
306
  let hasComma = false;
243
307
  let hasTypeKeyword = false;
244
308
  let hasUpperIdent = false;
309
+ let braces = 0;
245
310
  while (i < src.length && depth > 0) {
246
311
  const tt = src[i].type;
247
312
  if (tt === T.LBRACKET) {
@@ -257,6 +322,13 @@ export const expressionParserMethods = {
257
322
  i++;
258
323
  continue;
259
324
  }
325
+ // `k.(int)` is a type assertion on a value: an index expression
326
+ if (tt === T.DOT && src[i + 1]?.type === T.LPAREN) return false;
327
+ // `uint(u)` / `f(x)`: a call or conversion is a value (a method
328
+ // signature inside `interface{ M() }` is the only type that has one).
329
+ if (tt === T.LBRACE) braces++;
330
+ else if (tt === T.RBRACE) braces--;
331
+ else if (_isCallParen(src, i, braces)) return false;
260
332
  // Reject literals — it's an index expression
261
333
  if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
262
334
  // Reject binary operators that wouldn't appear in type args
@@ -281,7 +353,7 @@ export const expressionParserMethods = {
281
353
  const after = src[i + 1];
282
354
  if (!after) return false;
283
355
  // Call or composite lit — always valid as type args
284
- if (after.type === T.LPAREN || after.type === T.LBRACE) return true;
356
+ if (_opensArgs(after, noCompLit)) return true;
285
357
  // Function value in argument list: Identity[int], 99) — only if clearly a type
286
358
  if (after.type === T.COMMA || after.type === T.RPAREN) {
287
359
  return hasComma || hasTypeKeyword || hasUpperIdent;
@@ -307,17 +379,15 @@ export const expressionParserMethods = {
307
379
  (t.type === T.MAP || isSliceOrArrayType) &&
308
380
  !this.looksLikeCompositeLitArg()
309
381
  ) {
382
+ const start = this.pos;
310
383
  const typeNode = this.parseType();
311
- // []byte(s) / []rune(s) used as a call argument — it's a type conversion
384
+ // `[]byte(s)` / `[]int(nil)` is a conversion: an ordinary
385
+ // operand that may be compared, spread or selected from.
312
386
  if (this.check(T.LPAREN)) {
313
- this.advance();
314
- const convExpr = this.parseExpr();
315
- this.expect(T.RPAREN);
316
- args.push({
317
- kind: "TypeConversion",
318
- targetType: typeNode,
319
- expr: convExpr,
320
- });
387
+ this.pos = start;
388
+ const arg = this.parseExpr();
389
+ if (this.match(T.ELLIPSIS)) arg._spread = true;
390
+ args.push(arg);
321
391
  } else {
322
392
  args.push({ kind: "TypeExpr", type: typeNode });
323
393
  }
@@ -24,6 +24,7 @@ const ASSIGN_OPS = new Set([
24
24
  T.PERCENT_ASSIGN,
25
25
  T.AMP_ASSIGN,
26
26
  T.PIPE_ASSIGN,
27
+ T.AND_NOT_ASSIGN,
27
28
  T.CARET_ASSIGN,
28
29
  T.LSHIFT_ASSIGN,
29
30
  T.RSHIFT_ASSIGN,
@@ -10,11 +10,12 @@
10
10
  // TemplText { kind, value }
11
11
  // TemplExpr { kind, tokens } ← raw Go token array
12
12
  // TemplComponent { kind, tokens } ← raw Go token array for call
13
- // TemplChildren { kind }
13
+ // TemplChildren { kind, name } ← `{ name... }`, name of a variadic slot
14
14
  // TemplIf { kind, condTokens, then: TemplNode[], else_: TemplNode[] | null }
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
+ // (a `case a, b:` also carries `exprs`, one expression per value)
18
19
  //
19
20
  // Go expressions embedded in templ (`{ expr }`, attribute values, `if`
20
21
  // conditions, `switch` and `case` expressions, `@Comp(...)` calls) and `for`
@@ -71,12 +72,45 @@ function parseExprTokens(tokens, allowEmpty = true) {
71
72
  if (allowEmpty && (!tokens || tokens.length === 0)) return { expr: null };
72
73
  try {
73
74
  const eofTok = new Token(T.EOF, "", 1, 1);
74
- return { expr: new Parser([...tokens, eofTok]).parseExpr() };
75
+ const p = new Parser([...tokens, eofTok]);
76
+ const expr = p.parseExpr();
77
+ while (p.check(T.SEMICOLON)) p.advance();
78
+ if (!p.check(T.EOF))
79
+ p.err(`unexpected '${p.peek().value}' after expression`);
80
+ return { expr };
75
81
  } catch (exprError) {
76
82
  return { expr: null, exprError };
77
83
  }
78
84
  }
79
85
 
86
+ // Parses `tokens` as a comma-separated expression list (`case 1, 2:`). `expr`
87
+ // and `exprError` describe the first expression (or the first error); `exprs`
88
+ // holds every parsed expression.
89
+ function parseExprListTokens(tokens) {
90
+ if (!tokens || tokens.length === 0) return { expr: null };
91
+ const parts = [[]];
92
+ let depth = 0;
93
+ for (const t of trimSemicolons(tokens ?? [])) {
94
+ if (t.type === T.LPAREN || t.type === T.LBRACKET || t.type === T.LBRACE)
95
+ depth++;
96
+ else if (
97
+ t.type === T.RPAREN ||
98
+ t.type === T.RBRACKET ||
99
+ t.type === T.RBRACE
100
+ )
101
+ depth--;
102
+ if (t.type === T.COMMA && depth === 0) parts.push([]);
103
+ else parts[parts.length - 1].push(t);
104
+ }
105
+ const exprs = [];
106
+ for (const part of parts) {
107
+ const parsed = parseExprTokens(part, false);
108
+ if (parsed.exprError) return { expr: null, exprError: parsed.exprError };
109
+ exprs.push(parsed.expr);
110
+ }
111
+ return { expr: exprs[0] ?? null, exprs };
112
+ }
113
+
80
114
  // Parses an `if` condition; the lexer may leave the `if` keyword in front.
81
115
  function parseCondTokens(tokens) {
82
116
  if (!tokens || tokens.length === 0) return { expr: null };
@@ -220,7 +254,10 @@ export class TemplParser extends Parser {
220
254
  return this.templComponent(t.value);
221
255
  case TT.TEMPL_CHILDREN:
222
256
  this.advance();
223
- return { kind: "TemplChildren" };
257
+ return attach(
258
+ { kind: "TemplChildren", name: t.value },
259
+ parseExprTokens([new Token(T.IDENT, t.value, t.line, t.col)]),
260
+ );
224
261
  case TT.TEMPL_IF: {
225
262
  this.advance();
226
263
  return this.parseTemplIf(t);
@@ -243,7 +280,9 @@ export class TemplParser extends Parser {
243
280
  // `@templ.Raw(arg)` parses `arg`; any other component parses the whole call.
244
281
  templComponent(tokens) {
245
282
  const raw = isTemplRaw(tokens);
246
- const parsed = parseExprTokens(raw ? tokens.slice(4, -1) : tokens);
283
+ const parsed = parseExprTokens(
284
+ raw ? trimSemicolons(tokens).slice(4, -1) : tokens,
285
+ );
247
286
  return attach({ kind: "TemplComponent", tokens }, { raw, ...parsed });
248
287
  }
249
288
 
@@ -331,7 +370,7 @@ export class TemplParser extends Parser {
331
370
  cases.push(
332
371
  attach(
333
372
  { kind: "TemplCase", caseTokens, body },
334
- isDefault ? { expr: null } : parseExprTokens(caseTokens),
373
+ isDefault ? { expr: null } : parseExprListTokens(caseTokens),
335
374
  ),
336
375
  );
337
376
  } else {
@@ -269,6 +269,7 @@ export const typeParserMethods = {
269
269
  names: [],
270
270
  type: { kind: "TypeName", name: typeName },
271
271
  embedded: true,
272
+ embeddedName: typeName,
272
273
  tag,
273
274
  });
274
275
  this.semi();
package/src/resolver.js CHANGED
@@ -39,8 +39,7 @@ export function isBuiltinPackage(importPath) {
39
39
  }
40
40
 
41
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.
42
+ // and compiled into the caller's target.
44
43
  const STDLIB_SOURCE_ROOT = fileURLToPath(
45
44
  new URL("../stdlib/", import.meta.url),
46
45
  );