gofront 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -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,18 @@ 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, a type-switch
231
+ // `case func():` or a type argument `f[func(int)]`), `,`, `)`, `]` and
232
+ // `:` also end the type
233
+ if (
234
+ inTypeExpr &&
235
+ (this.check(T.COMMA) ||
236
+ this.check(T.RPAREN) ||
237
+ this.check(T.RBRACKET) ||
238
+ this.check(T.COLON) ||
239
+ this.check(T.STRING) ||
240
+ this.check(T.ASSIGN))
241
+ )
217
242
  return null;
218
243
  // Multiple return values: (int, string) or named (result int, ok bool)
219
244
  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
  }
@@ -332,48 +402,20 @@ export const expressionParserMethods = {
332
402
  return { kind: "CallExpr", func: fn, args };
333
403
  },
334
404
 
335
- // Returns true if the current position looks like a composite literal argument,
336
- // e.g. []int{1,2,3} or map[string]int{"a":1}. We scan ahead past the type
337
- // tokens to check whether a { follows, without advancing this.pos permanently.
405
+ // Returns true if the current position starts a composite literal argument,
406
+ // e.g. []int{1,2,3}, map[string]int{"a":1} or []func() int{f}: the type is
407
+ // parsed speculatively and must be followed by `{`. The position is
408
+ // always restored.
338
409
  looksLikeCompositeLitArg() {
339
- let i = this.pos;
340
- const src = this.tokens;
341
- // Skip over the type tokens: [, ], map, [, K, ], V, ident, etc.
342
- // Simple approach: skip until we hit something that can't be part of a type
343
- let depth = 0;
344
- while (i < src.length) {
345
- const tt = src[i].type;
346
- if (tt === T.LBRACKET) {
347
- depth++;
348
- i++;
349
- continue;
350
- }
351
- if (tt === T.RBRACKET) {
352
- depth--;
353
- i++;
354
- continue;
355
- }
356
- if (depth > 0) {
357
- i++;
358
- continue;
359
- }
360
- if (
361
- tt === T.MAP ||
362
- tt === T.IDENT ||
363
- tt === T.DOT || // qualified types: []pkg.Type{...}
364
- tt === T.STAR ||
365
- tt === T.INT ||
366
- tt === T.FLOAT ||
367
- tt === T.STRING
368
- ) {
369
- i++;
370
- continue;
371
- }
372
- break;
410
+ const start = this.pos;
411
+ try {
412
+ this.parseType();
413
+ return this.check(T.LBRACE);
414
+ } catch {
415
+ return false;
416
+ } finally {
417
+ this.pos = start;
373
418
  }
374
- // Skip whitespace tokens (shouldn't exist post-lex, but be safe)
375
- while (i < src.length && src[i].type === T.SEMICOLON) i++;
376
- return i < src.length && src[i].type === T.LBRACE;
377
419
  },
378
420
 
379
421
  parseCompositeLit(typeExpr) {
@@ -424,7 +466,9 @@ export const expressionParserMethods = {
424
466
  // Literals
425
467
  if (t.type === T.INT) {
426
468
  this.advance();
427
- return { kind: "BasicLit", litKind: "INT", value: t.value };
469
+ const lit = { kind: "BasicLit", litKind: "INT", value: t.value };
470
+ if (t.rune) lit.rune = true;
471
+ return lit;
428
472
  }
429
473
  if (t.type === T.FLOAT) {
430
474
  this.advance();
@@ -519,6 +563,16 @@ export const expressionParserMethods = {
519
563
  return this.parseCompositeLit(typeExpr);
520
564
  }
521
565
 
566
+ // Interface conversion: interface{ M() }(v). `interface{…}{` is not a
567
+ // composite literal in Go, so only the conversion form is accepted.
568
+ if (t.type === T.INTERFACE) {
569
+ const targetType = this.parseInterfaceType();
570
+ this.expect(T.LPAREN);
571
+ const expr = this.parseExpr();
572
+ this.expect(T.RPAREN);
573
+ return { kind: "TypeConversion", targetType, expr };
574
+ }
575
+
522
576
  if (t.type === T.CHAN) {
523
577
  this.err("channels are not supported in GoFront");
524
578
  }
@@ -2,6 +2,57 @@
2
2
 
3
3
  import { T } from "../lexer/tokens.js";
4
4
 
5
+ // Statement keywords: none of them can appear inside a composite literal or a
6
+ // statement header's expressions.
7
+ const STMT_KEYWORDS = new Set([
8
+ T.VAR,
9
+ T.CONST,
10
+ T.TYPE,
11
+ T.IF,
12
+ T.ELSE,
13
+ T.FOR,
14
+ T.RETURN,
15
+ T.BREAK,
16
+ T.CONTINUE,
17
+ T.SWITCH,
18
+ T.CASE,
19
+ T.DEFAULT,
20
+ T.SELECT,
21
+ T.DEFER,
22
+ T.GO,
23
+ T.FALLTHROUGH,
24
+ ]);
25
+
26
+ // Tokens that only appear in statements: a brace pair holding one at its top
27
+ // level is a block, not a composite literal.
28
+ const STMT_ONLY_TOKENS = new Set([
29
+ ...STMT_KEYWORDS,
30
+ T.SEMICOLON,
31
+ T.ASSIGN,
32
+ T.DEFINE,
33
+ T.PLUS_ASSIGN,
34
+ T.MINUS_ASSIGN,
35
+ T.STAR_ASSIGN,
36
+ T.SLASH_ASSIGN,
37
+ T.PERCENT_ASSIGN,
38
+ T.AMP_ASSIGN,
39
+ T.PIPE_ASSIGN,
40
+ T.AND_NOT_ASSIGN,
41
+ T.CARET_ASSIGN,
42
+ T.LSHIFT_ASSIGN,
43
+ T.RSHIFT_ASSIGN,
44
+ T.INC,
45
+ T.DEC,
46
+ ]);
47
+
48
+ const OPEN_BRACKETS = new Set([T.LBRACE, T.LPAREN, T.LBRACKET]);
49
+ const CLOSE_BRACKETS = new Set([T.RBRACE, T.RPAREN, T.RBRACKET]);
50
+
51
+ function bracketDelta(tt) {
52
+ if (OPEN_BRACKETS.has(tt)) return 1;
53
+ return CLOSE_BRACKETS.has(tt) ? -1 : 0;
54
+ }
55
+
5
56
  // Method-name dispatch for pure-delegation cases in _parseStmt.
6
57
  const PARSE_STMT_DELEGATE = {
7
58
  [T.VAR]: "parseVarDecl",
@@ -24,6 +75,7 @@ const ASSIGN_OPS = new Set([
24
75
  T.PERCENT_ASSIGN,
25
76
  T.AMP_ASSIGN,
26
77
  T.PIPE_ASSIGN,
78
+ T.AND_NOT_ASSIGN,
27
79
  T.CARET_ASSIGN,
28
80
  T.LSHIFT_ASSIGN,
29
81
  T.RSHIFT_ASSIGN,
@@ -129,6 +181,7 @@ export const statementParserMethods = {
129
181
  let cond;
130
182
  // if init; cond { } — use noCompLit to avoid "{" being parsed as composite literal
131
183
  const first = this.parseSimpleStmtRaw(true);
184
+ this._rejectHeaderCompositeLit("if", 0, first);
132
185
  // Only treat as "init; cond" if semicolon is followed by a non-brace token
133
186
  if (this.check(T.SEMICOLON) && this.peek2().type !== T.LBRACE) {
134
187
  this.advance(); // consume ;
@@ -165,6 +218,7 @@ export const statementParserMethods = {
165
218
  let first = null;
166
219
  if (!this.check(T.SEMICOLON)) {
167
220
  first = this.parseSimpleStmtRaw(true);
221
+ this._rejectHeaderCompositeLit("for", 1, first);
168
222
 
169
223
  if (this.check(T.LBRACE)) {
170
224
  // for k, v := range expr { } — range stmt ends right before {
@@ -200,6 +254,59 @@ export const statementParserMethods = {
200
254
  return { kind: "ForStmt", init: first, cond, post, body };
201
255
  },
202
256
 
257
+ // `if s := &T{f: 1}; s.f > 0 {`: in a header, `{` after a type name opens
258
+ // the body, so Go requires the literal to be parenthesized. Reported when
259
+ // the brace pair holds no statement tokens, is followed by an explicit `;`,
260
+ // and the header then continues to its body `{` (with at most `semis`
261
+ // further `;`, one for a `for` post statement). A bare expression or a
262
+ // range clause before the `{` (`if ok { f() };`) means it is the body.
263
+ _rejectHeaderCompositeLit(keyword, semis, first) {
264
+ if (!this.check(T.LBRACE)) return;
265
+ if (first?.kind === "ExprStmt" || this.isRangeStmt(first)) return;
266
+ const prev = this.tokens[this.pos - 1]?.type;
267
+ if (prev !== T.IDENT && prev !== T.RBRACKET) return;
268
+ const close = this._closingBrace(this.pos);
269
+ if (close < 0) return;
270
+ const semi = this.tokens[close + 1];
271
+ if (semi?.type !== T.SEMICOLON || !this._isExplicitSemi(semi)) return;
272
+ if (!this._headerContinues(close + 2, semis)) return;
273
+ this.err(`composite literal in ${keyword} header must be parenthesized`);
274
+ },
275
+
276
+ // Index of the `}` matching the `{` at `open`, or -1 when the pair holds a
277
+ // statement-only token at its top level (then it is a block).
278
+ _closingBrace(open) {
279
+ let depth = 0;
280
+ for (let i = open; i < this.tokens.length; i++) {
281
+ const tt = this.tokens[i].type;
282
+ depth += bracketDelta(tt);
283
+ if (depth === 0) return tt === T.RBRACE ? i : -1;
284
+ if (tt === T.EOF || (depth === 1 && STMT_ONLY_TOKENS.has(tt))) return -1;
285
+ }
286
+ return -1;
287
+ },
288
+
289
+ _isExplicitSemi(tok) {
290
+ if (!this.source) return false;
291
+ this._sourceLines ??= this.source.split("\n");
292
+ return this._sourceLines[tok.line - 1]?.[tok.col - 1] === ";";
293
+ },
294
+
295
+ // Whether the tokens from `i` reach a top-level `{` (the body) without a
296
+ // statement keyword and with at most `semis` semicolons on the way.
297
+ _headerContinues(i, semis) {
298
+ let depth = 0;
299
+ for (; i < this.tokens.length; i++) {
300
+ const tt = this.tokens[i].type;
301
+ if (tt === T.LBRACE && depth === 0) return true;
302
+ depth += bracketDelta(tt);
303
+ if (tt === T.SEMICOLON && depth === 0) semis--;
304
+ if (depth < 0 || semis < 0) return false;
305
+ if (tt === T.EOF || STMT_KEYWORDS.has(tt)) return false;
306
+ }
307
+ return false;
308
+ },
309
+
203
310
  isRangeStmt(stmt) {
204
311
  if (!stmt) return false;
205
312
  if (stmt.kind !== "DefineStmt" && stmt.kind !== "AssignStmt") return false;
@@ -212,6 +319,7 @@ export const statementParserMethods = {
212
319
  let tag = null;
213
320
  if (!this.check(T.LBRACE)) {
214
321
  const first = this.parseSimpleStmtRaw(true);
322
+ this._rejectHeaderCompositeLit("switch", 0, first);
215
323
  if (this.match(T.SEMICOLON)) {
216
324
  init = first;
217
325
  if (!this.check(T.LBRACE))