gofront 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // Split into sub-modules under typechecker/:
5
5
  // types.js — shared type constants, predicates, Scope
6
- // stdlib.js — browser globals + all built-in package registrations
6
+ // stdlib/index.js — browser globals + all built-in package registrations
7
7
  // statements.js — checkBlock, checkStmt
8
8
  // expressions.js — checkExpr, checkCall, checkBuiltin, checkCompositeLit
9
9
  // termination.js — _isTerminating* family
@@ -11,16 +11,24 @@
11
11
  // assignability.js — assertAssignable, binaryResultType, implements
12
12
 
13
13
  import { assignabilityMethods } from "./assignability.js";
14
+ import { constantCheckMethods } from "./constants.js";
14
15
  import { expressionCheckMethods } from "./expressions.js";
16
+ import { orderPackageInits, orderVarSpecsForTyping } from "./init-order.js";
15
17
  import { resolveMethods } from "./resolve.js";
16
18
  import { statementCheckMethods } from "./statements.js";
17
- import { setupGlobals } from "./stdlib.js";
19
+ import { EXCLUDED_STDLIB_PACKAGES } from "./stdlib/excluded.js";
20
+ import { setupGlobals } from "./stdlib/index.js";
18
21
  import { terminationMethods } from "./termination.js";
19
22
  import {
20
23
  ANY,
24
+ BOOL,
21
25
  defaultType,
22
26
  ERROR,
27
+ INTRINSICS,
28
+ isAny,
29
+ isBool,
23
30
  isVoid,
31
+ NATIVES,
24
32
  Scope,
25
33
  TAINTED_ANY,
26
34
  TypeCheckError,
@@ -29,6 +37,17 @@ import {
29
37
  VOID,
30
38
  } from "./types.js";
31
39
 
40
+ // Checker method per templ body node kind (see _checkTemplNodes).
41
+ const TEMPL_NODE_CHECKS = {
42
+ TemplElement: "_checkTemplElement",
43
+ TemplExpr: "_checkTemplLeaf",
44
+ TemplChildren: "_checkTemplChildren",
45
+ TemplComponent: "_checkTemplLeaf",
46
+ TemplIf: "_checkTemplIf",
47
+ TemplFor: "_checkTemplFor",
48
+ TemplSwitch: "_checkTemplSwitch",
49
+ };
50
+
32
51
  function rootIdentName(e) {
33
52
  if (!e) return null;
34
53
  if (e.kind === "Ident") return e.name;
@@ -38,13 +57,58 @@ function rootIdentName(e) {
38
57
  }
39
58
 
40
59
  // Re-export for consumers that import from typechecker.js
41
- export { TypeCheckError, typeStr };
60
+ export { EXCLUDED_STDLIB_PACKAGES, TypeCheckError, typeStr };
61
+
62
+ // JavaScript built-ins that generated JS relies on; a JS class of the same
63
+ // name in the bundle would shadow them (see _checkGlobalTypeNamesPass).
64
+ const JS_BUILTIN_CLASSES = new Set([
65
+ "Array",
66
+ "ArrayBuffer",
67
+ "BigInt",
68
+ "BigInt64Array",
69
+ "BigUint64Array",
70
+ "Boolean",
71
+ "DataView",
72
+ "Date",
73
+ "Error",
74
+ "Float32Array",
75
+ "Float64Array",
76
+ "Int16Array",
77
+ "Int32Array",
78
+ "Int8Array",
79
+ "JSON",
80
+ "Map",
81
+ "Math",
82
+ "Number",
83
+ "Object",
84
+ "Promise",
85
+ "RangeError",
86
+ "Reflect",
87
+ "RegExp",
88
+ "Set",
89
+ "String",
90
+ "Symbol",
91
+ "TextDecoder",
92
+ "TextEncoder",
93
+ "TypeError",
94
+ "Uint16Array",
95
+ "Uint32Array",
96
+ "Uint8Array",
97
+ "Uint8ClampedArray",
98
+ "WeakMap",
99
+ "WebAssembly",
100
+ ]);
42
101
 
43
102
  export class TypeChecker {
44
103
  constructor() {
45
104
  this.types = new Map(); // named types
46
- this.globals = new Scope();
105
+ // Go's universe scope: builtins, browser globals and built-in package
106
+ // namespaces. Package declarations live in `globals` above it, so they
107
+ // shadow predeclared names (`func Array()`, `var document int`).
108
+ this.universe = new Scope();
109
+ this.globals = new Scope(this.universe);
47
110
  this.errors = [];
111
+ this._reportedAt = new WeakMap();
48
112
  this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
49
113
  this.target = "js";
50
114
  this.pkgName = null;
@@ -57,22 +121,41 @@ export class TypeChecker {
57
121
  this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
58
122
  this._deferCount = 0; // tracks defer usage in current function body
59
123
  this._imports = []; // tracked imports for unused-import detection
124
+ // Lazy body checking (Go-source stdlib): with `lazyBodies` set, checkAll
125
+ // types declarations only and `checkBody` types a function or method
126
+ // body when it is first reached.
127
+ this.lazyBodies = false;
128
+ this.stats = { bodiesChecked: 0 };
129
+ this._lazyBodies = new Map(); // FuncDecl/MethodDecl -> its program
60
130
  this._constExprs = new Map(); // name -> AST initializer expr
131
+ this._importedConsts = new Map(); // dot-imported const name -> value
132
+ this._externalNames = new Set(); // package-scope names from .d.ts / npm definitions
61
133
  this._setupGlobals();
62
134
  // Register error as a named type (interface)
63
135
  this.types.set("error", ERROR);
64
136
  }
65
137
 
66
138
  _setupGlobals() {
67
- setupGlobals(this.globals, this.types);
139
+ setupGlobals(this.universe, this.types);
68
140
  }
69
141
 
70
- addDefinitions(types, values) {
142
+ // `external` marks values as real JS names (.d.ts / npm definitions) that
143
+ // must not be renamed; dot-imported GoFront packages pass false so their
144
+ // browser-global-named members get the same `$` renaming as local ones.
145
+ // `consts` (dot-imported GoFront packages) maps constant names to their
146
+ // folded values.
147
+ addDefinitions(types, values, { external = true, consts = null } = {}) {
71
148
  for (const [name, type] of types) {
72
149
  this.types.set(name, type);
73
150
  }
74
151
  for (const [name, type] of values) {
75
152
  this.globals.define(name, type);
153
+ if (external) this._externalNames.add(name);
154
+ }
155
+ for (const [name, v] of consts ?? []) {
156
+ if (!v || !values.has(name)) continue;
157
+ this.globals._consts.add(name);
158
+ this._importedConsts.set(name, v);
76
159
  }
77
160
  }
78
161
 
@@ -80,7 +163,9 @@ export class TypeChecker {
80
163
  // e.g. addPackageNamespace('utils', symbolsMap, typesMap)
81
164
  // lets callers type-check `utils.Foo` via SelectorExpr.
82
165
  // `target` is the imported package's compile target ("js"/"wasm"/"both").
83
- addPackageNamespace(pkgName, symbols, types, target = null) {
166
+ // `consts` maps the package's constant names to their folded value (see
167
+ // typechecker/const-fold.js), or null when the folder cannot evaluate one.
168
+ addPackageNamespace(pkgName, symbols, types, target = null, consts = null) {
84
169
  const members = {};
85
170
  for (const [name, type] of symbols) members[name] = type;
86
171
  // _gofront: true marks this as a GoFront package — exported identifier rules apply
@@ -90,9 +175,19 @@ export class TypeChecker {
90
175
  members,
91
176
  _gofront: true,
92
177
  _target: target,
178
+ _consts: consts,
93
179
  });
94
180
  for (const [name, type] of types) {
95
- this.types.set(name, type);
181
+ const prior = this.types.get(name);
182
+ if (prior && prior !== type && prior.kind === "named") {
183
+ this._ambiguousImported ??= new WeakSet();
184
+ this._ambiguousImported.add(prior);
185
+ this._ambiguousImported.add(type);
186
+ }
187
+ // A renamed type (`Pt` declared as `Pt$$b`) never takes the bare name:
188
+ // another package owns it, and this package's own declaration would
189
+ // otherwise reuse the imported placeholder. It is reached as `pkg.Pt`.
190
+ if (type.name === name) this.types.set(name, type);
96
191
  this.types.set(`${pkgName}.${name}`, type);
97
192
  }
98
193
  }
@@ -108,7 +203,7 @@ export class TypeChecker {
108
203
  // Report any tracked imports whose package name was never referenced.
109
204
  reportUnusedImports() {
110
205
  for (const { name, node, filename, source } of this._imports) {
111
- if (!this.globals._used.has(name)) {
206
+ if (!this.globals._used.has(name) && !this.universe._used.has(name)) {
112
207
  const saved = [this._currentFile, this._currentSource];
113
208
  this._currentFile = filename;
114
209
  this._currentSource = source;
@@ -130,6 +225,15 @@ export class TypeChecker {
130
225
  return out;
131
226
  }
132
227
 
228
+ getExportedConsts() {
229
+ const out = new Map();
230
+ for (const name of this.globals._consts) {
231
+ if (!this.globals.lookupConstExpr(name)) continue;
232
+ out.set(name, this._constValue({ kind: "Ident", name }, this.globals));
233
+ }
234
+ return out;
235
+ }
236
+
133
237
  getExportedTypes() {
134
238
  return new Map(this.types);
135
239
  }
@@ -164,6 +268,14 @@ export class TypeChecker {
164
268
  }
165
269
 
166
270
  err(msg, node, hint = null) {
271
+ // One diagnostic per root cause: a node revisited by a later pass (a
272
+ // signature resolved at declaration and again in the body) reports once.
273
+ if (node && typeof node === "object") {
274
+ const seen = this._reportedAt.get(node) ?? new Set();
275
+ this._reportedAt.set(node, seen);
276
+ if (seen.has(msg)) return TAINTED_ANY;
277
+ seen.add(msg);
278
+ }
167
279
  const e = new TypeCheckError(
168
280
  msg,
169
281
  node,
@@ -269,11 +381,15 @@ export class TypeChecker {
269
381
  }
270
382
  }
271
383
 
384
+ this._checkDirectivesAndImportsPass(programs);
385
+ this._registerConstExprs(programs);
272
386
  this._collectTypesPass(programs);
273
387
  this._collectFuncsPass(programs);
388
+ this._checkGlobalTypeNamesPass(programs);
274
389
  this._promoteEmbeddedMethods();
275
390
  this._collectVarsConstsPass(programs);
276
391
  this._checkTopDeclsPass(programs);
392
+ this._reportInitCycles(programs);
277
393
 
278
394
  if (this.target === "both") {
279
395
  this._checkMutablePackageVars(programs);
@@ -290,6 +406,42 @@ export class TypeChecker {
290
406
  return this.errors;
291
407
  }
292
408
 
409
+ _checkEmbedDirectives(directives) {
410
+ for (const { line, col } of directives ?? []) {
411
+ this.err("//go:embed is not supported yet", { line, col });
412
+ }
413
+ }
414
+
415
+ _checkDirectivesAndImportsPass(programs) {
416
+ this._importedPaths = new Set();
417
+ this._fileImports = new Map();
418
+ for (const p of programs) {
419
+ this._setCurrentFile(p);
420
+ const names = new Map();
421
+ this._fileImports.set(this._currentFile, names);
422
+ this._checkEmbedDirectives(p._embedDirectives);
423
+ for (const imp of p.imports ?? []) {
424
+ for (const spec of imp.imports ?? []) {
425
+ this._importedPaths.add(spec.path);
426
+ names.set(
427
+ spec.alias ??
428
+ spec.path
429
+ .split("/")
430
+ .filter((s) => !/^v\d+$/.test(s))
431
+ .at(-1),
432
+ spec.path,
433
+ );
434
+ if (EXCLUDED_STDLIB_PACKAGES.has(spec.path)) {
435
+ this.err(
436
+ `package '${spec.path}' is not available: ${EXCLUDED_STDLIB_PACKAGES.get(spec.path)}`,
437
+ spec,
438
+ );
439
+ }
440
+ }
441
+ }
442
+ }
443
+ }
444
+
293
445
  _checkMutablePackageVars(programs) {
294
446
  const pkgVars = new Set();
295
447
  for (const p of programs) {
@@ -409,6 +561,18 @@ export class TypeChecker {
409
561
  for (const d of p.decls) {
410
562
  if (d.kind === "TypeDecl" && !d.isAlias && !this.types.has(d.name)) {
411
563
  const placeholder = { kind: "named", name: d.name, underlying: null };
564
+ // A generic type is known to be generic before any field resolves,
565
+ // so types declared earlier can instantiate it.
566
+ if (d.typeParams) {
567
+ placeholder._generic = {
568
+ typeParams: d.typeParams.map((tp) => ({
569
+ kind: "typeParam",
570
+ name: tp.name,
571
+ constraint: tp.constraint,
572
+ })),
573
+ declNode: d,
574
+ };
575
+ }
412
576
  this.types.set(d.name, placeholder);
413
577
  this.globals.define(d.name, placeholder);
414
578
  }
@@ -418,6 +582,7 @@ export class TypeChecker {
418
582
  for (const p of programs) {
419
583
  for (const d of p.decls) if (d.kind === "TypeDecl") all.push({ p, d });
420
584
  }
585
+ this._expandGenericStructDefs(all.map((x) => x.d));
421
586
  // Collect in dependency order so aliases, generics, embedded and by-value
422
587
  // field types declared later (or in other files) are ready when needed.
423
588
  let pending = all;
@@ -436,6 +601,57 @@ export class TypeChecker {
436
601
  }
437
602
  }
438
603
 
604
+ // `type S[T any] Base` with `Base` a struct declared in the package: S is a
605
+ // new struct type with Base's fields (and none of its methods), so its
606
+ // declaration becomes a copy of Base's struct type.
607
+ _expandGenericStructDefs(decls) {
608
+ const byName = new Map(decls.map((d) => [d.name, d]));
609
+ for (const d of decls) {
610
+ if (!d.typeParams || d.isAlias) continue;
611
+ let target = d.type;
612
+ for (let i = 0; i < 16 && target?.kind === "TypeName"; i++) {
613
+ const base = byName.get(target.name);
614
+ if (!base || base.typeParams || base.isAlias) break;
615
+ target = base.type;
616
+ }
617
+ // A struct written in place is already this declaration's own copy;
618
+ // cloning it would also choke on an AST a previous target pass
619
+ // annotated with cycles.
620
+ if (target?.kind === "StructType" && target !== d.type)
621
+ d.type = JSON.parse(JSON.stringify(target));
622
+ }
623
+ }
624
+
625
+ // A struct type, or a named type with methods, becomes a JS class of the
626
+ // same name in `js` and `both` packages. Functions and variables that
627
+ // shadow a browser global get a `$` suffix, but type names reach the
628
+ // output through the checker's types (class declarations, `new T`,
629
+ // `instanceof T`, static methods), so a class named after a JavaScript
630
+ // built-in the generated code calls (`Array.isArray`, `new Error`) is
631
+ // rejected instead. Interfaces and method-less named types emit no class.
632
+ _checkGlobalTypeNamesPass(programs) {
633
+ if (this.target === "wasm" || this.isStdlib) return;
634
+ for (const p of programs) {
635
+ for (const d of p.decls) {
636
+ if (
637
+ d.kind !== "TypeDecl" ||
638
+ d.isAlias ||
639
+ !JS_BUILTIN_CLASSES.has(d.name)
640
+ )
641
+ continue;
642
+ const t = this.types.get(d.name);
643
+ const isClass =
644
+ t?.underlying?.kind === "struct" || (t?.methods?.size ?? 0) > 0;
645
+ if (!isClass) continue;
646
+ this._setCurrentFile(p);
647
+ this.err(
648
+ `type ${d.name} shadows the JavaScript global ${d.name}; rename the type`,
649
+ d,
650
+ );
651
+ }
652
+ }
653
+ }
654
+
439
655
  _typeDeclBlocked(decl, pendingNames) {
440
656
  const visit = (node, indirect) => {
441
657
  if (!node || typeof node !== "object") return false;
@@ -475,6 +691,22 @@ export class TypeChecker {
475
691
  }
476
692
  }
477
693
 
694
+ // Constant initializers are known before types are resolved, so an array
695
+ // length may name a constant declared later (`[N]int`, `[N + 1]T`).
696
+ _registerConstExprs(programs) {
697
+ for (const p of programs) {
698
+ for (const d of p.decls) {
699
+ if (d.kind !== "ConstDecl") continue;
700
+ for (const spec of d.decls) {
701
+ spec.names.forEach((name, i) => {
702
+ const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
703
+ if (expr) this.globals._constExprs.set(name, expr);
704
+ });
705
+ }
706
+ }
707
+ }
708
+ }
709
+
478
710
  _collectVarsConstsPass(programs) {
479
711
  for (const p of programs) {
480
712
  this._setCurrentFile(p);
@@ -485,19 +717,44 @@ export class TypeChecker {
485
717
  }
486
718
  }
487
719
 
720
+ // Go rejects package variables whose initialisers depend on each other.
721
+ _reportInitCycles(programs) {
722
+ if (this.errors.length > 0) return;
723
+ const { cycles } = orderPackageInits(programs);
724
+ for (const c of cycles) {
725
+ const owner = programs.find((p) =>
726
+ p.decls?.some(
727
+ (d) =>
728
+ (d.kind === "VarDecl" || d.kind === "ConstDecl") &&
729
+ d.decls?.includes(c.spec),
730
+ ),
731
+ );
732
+ if (owner) this._setCurrentFile(owner);
733
+ this.err(c.message, c.spec);
734
+ }
735
+ }
736
+
488
737
  _checkTopDeclsPass(programs) {
489
- for (const p of programs) {
490
- this._setCurrentFile(p);
491
- for (const d of p.decls) {
492
- if (d.kind === "VarDecl" || d.kind === "ConstDecl") {
493
- this.checkTopDecl(d, this.globals);
494
- }
495
- }
738
+ // A constant may name one declared later, so each is typed after those.
739
+ for (const e of orderPackageInits(programs).consts) {
740
+ this._setCurrentFile(e.program);
741
+ this.checkTopDecl({ ...e.decl, decls: [e.spec] }, this.globals);
742
+ }
743
+ // A variable's type may come from one declared later (`var a = b + 1;
744
+ // var b = f()`), so initialisers are typed after what they name.
745
+ for (const e of orderVarSpecsForTyping(programs)) {
746
+ this._setCurrentFile(e.program);
747
+ this.checkTopDecl({ ...e.decl, decls: [e.spec] }, this.globals);
496
748
  }
497
749
  for (const p of programs) {
498
750
  this._setCurrentFile(p);
499
751
  for (const d of p.decls) {
500
752
  if (
753
+ this.lazyBodies &&
754
+ (d.kind === "FuncDecl" || d.kind === "MethodDecl")
755
+ )
756
+ this._deferBody(d, p);
757
+ else if (
501
758
  d.kind === "FuncDecl" ||
502
759
  d.kind === "MethodDecl" ||
503
760
  d.kind === "TemplDecl"
@@ -508,20 +765,71 @@ export class TypeChecker {
508
765
  }
509
766
  }
510
767
 
768
+ // Lazy mode: validate what does not need the body now, check it on demand.
769
+ _deferBody(decl, program) {
770
+ if (decl.intrinsic) this._checkIntrinsic(decl);
771
+ if (decl.native) this._checkNative(decl);
772
+ this._lazyBodies.set(decl, program);
773
+ }
774
+
775
+ // Types the body of a function or method deferred by `lazyBodies`; a
776
+ // declaration checked already (or not deferred) is left alone.
777
+ checkBody(decl) {
778
+ const program = this._lazyBodies.get(decl);
779
+ if (!program) return;
780
+ this._lazyBodies.delete(decl);
781
+ this._setCurrentFile(program);
782
+ this.checkTopDecl(decl, this.globals);
783
+ }
784
+
511
785
  // ── Shared pass helpers ──────────────────────────────────────
512
786
 
787
+ // Instances made while types were collected (`struct{ Inner[int] }`) have
788
+ // no methods yet, and a type embedding another embedder (`Deep` over `E`
789
+ // over `Base`) needs the inner promotion first, so repeat until nothing
790
+ // new is promoted.
513
791
  _promoteEmbeddedMethods() {
792
+ const structs = [];
514
793
  for (const type of this.types.values()) {
515
- const struct = type.underlying;
516
- if (struct?.kind === "struct" && struct._embeds) {
517
- for (const embed of struct._embeds) {
518
- const inner = embed.kind === "pointer" ? embed.base : embed;
519
- const base = inner.kind === "named" ? inner.underlying : inner;
520
- if (base?.kind !== "struct" || !base.methods) continue;
521
- for (const [mName, mType] of base.methods.entries()) {
522
- if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
523
- }
524
- }
794
+ structs.push(type.underlying);
795
+ for (const inst of type._generic?._instances?.values() ?? []) {
796
+ this._syncInstanceMethods(type._generic, inst);
797
+ structs.push(inst.underlying);
798
+ }
799
+ }
800
+ const count = () =>
801
+ structs.reduce(
802
+ (n, st) => n + (st?.methods?.size ?? 0) + (st?.fields?.size ?? 0),
803
+ 0,
804
+ );
805
+ let before;
806
+ do {
807
+ before = count();
808
+ for (const st of structs) this._promoteEmbeddedInto(st);
809
+ } while (count() !== before);
810
+ }
811
+
812
+ _promoteEmbeddedInto(struct) {
813
+ if (struct?.kind !== "struct" || !struct._embeds) return;
814
+ for (const embed of struct._embeds) {
815
+ const inner = embed.kind === "pointer" ? embed.base : embed;
816
+ const base = inner.kind === "named" ? inner.underlying : inner;
817
+ // Fields of an embedded type declared after the embedder were not
818
+ // there when the embedder's fields were collected.
819
+ if (base?.kind === "struct" && struct.fields)
820
+ for (const [k, v] of base.fields)
821
+ if (!struct.fields.has(k)) struct.fields.set(k, v);
822
+ // A named non-struct type (`time.Duration`) keeps its methods on
823
+ // the named type itself.
824
+ const methods =
825
+ base?.kind === "struct"
826
+ ? base.methods
827
+ : inner.kind === "named" && base?.kind !== "interface"
828
+ ? inner.methods
829
+ : null;
830
+ if (!methods) continue;
831
+ for (const [mName, mType] of methods.entries()) {
832
+ if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
525
833
  }
526
834
  }
527
835
  }
@@ -529,30 +837,39 @@ export class TypeChecker {
529
837
  // ── Type collection ──────────────────────────────────────────
530
838
 
531
839
  collectType(decl) {
840
+ if (decl.typeParams && decl.isAlias) {
841
+ this._collectGenericAlias(decl);
842
+ return;
843
+ }
532
844
  if (decl.typeParams) {
533
845
  // Generic type — resolve underlying with type params as ANY for struct fields
534
846
  const typeScope = new Scope(this.globals);
535
- const typeParamTypes = decl.typeParams.map((tp) => {
536
- const t = {
537
- kind: "typeParam",
538
- name: tp.name,
539
- constraint: tp.constraint,
540
- };
847
+ const preset = this.types.get(decl.name)?._generic;
848
+ const typeParamTypes = decl.typeParams.map((tp, i) => {
849
+ const t =
850
+ preset?.declNode === decl
851
+ ? preset.typeParams[i]
852
+ : {
853
+ kind: "typeParam",
854
+ name: tp.name,
855
+ constraint: tp.constraint,
856
+ };
541
857
  typeScope.define(tp.name, t);
542
858
  return t;
543
859
  });
544
- const underlying = this.resolveTypeNode(decl.type, typeScope);
545
860
  const existing = this.types.get(decl.name);
546
861
  const named =
547
862
  existing?.kind === "named"
548
863
  ? existing
549
864
  : { kind: "named", name: decl.name };
865
+ // Registered before the fields resolve so `Next *List[T]` finds the
866
+ // generic declaration it is part of.
867
+ if (preset?.declNode === decl) named._generic = preset;
868
+ else named._generic = { typeParams: typeParamTypes, declNode: decl };
869
+ this.types.set(decl.name, named);
870
+ const underlying = this.resolveTypeNode(decl.type, typeScope);
550
871
  named.underlying = underlying;
551
872
  named._target = this.target;
552
- named._generic = {
553
- typeParams: typeParamTypes,
554
- declNode: decl,
555
- };
556
873
  if (underlying.kind === "struct") {
557
874
  underlying.name = decl.name;
558
875
  underlying.methods = new Map();
@@ -561,7 +878,18 @@ export class TypeChecker {
561
878
  this.globals.define(decl.name, named);
562
879
  return;
563
880
  }
564
- const underlying = this.resolveTypeNode(decl.type, this.globals);
881
+ // A type declared inside a function is registered before its fields
882
+ // resolve so `next *L` finds it; package-level types already are.
883
+ this._registerLocalTypePlaceholder(decl);
884
+ let underlying = this.resolveTypeNode(decl.type, this.globals);
885
+ // `type W V` with V a named array: W's underlying type is V's array.
886
+ if (!decl.isAlias && underlying.kind === "named") {
887
+ const inner = this._deepUnderlying(underlying);
888
+ if (inner?.kind === "array") underlying = inner;
889
+ // `type S Base` with Base a struct: S gets its own copy of the fields.
890
+ else if (inner?.kind === "struct")
891
+ underlying = { ...inner, name: decl.name, methods: new Map() };
892
+ }
565
893
  if (decl.isAlias) {
566
894
  // type A = B — transparent alias, A and B are identical types
567
895
  this.types.set(decl.name, underlying);
@@ -588,6 +916,36 @@ export class TypeChecker {
588
916
  this.globals.define(decl.name, named);
589
917
  }
590
918
 
919
+ _registerLocalTypePlaceholder(decl) {
920
+ if (decl.isAlias || this.types.has(decl.name)) return;
921
+ const placeholder = { kind: "named", name: decl.name, underlying: null };
922
+ this.types.set(decl.name, placeholder);
923
+ this.globals.define(decl.name, placeholder);
924
+ }
925
+
926
+ // type A[P any] = T[P] — Go 1.24 generic alias. The declaration is a
927
+ // template only: each instantiation A[X] substitutes X into the aliased
928
+ // type and resolves to exactly that type (no new named type).
929
+ _collectGenericAlias(decl) {
930
+ const typeScope = new Scope(this.globals);
931
+ const typeParamTypes = decl.typeParams.map((tp) => {
932
+ const t = { kind: "typeParam", name: tp.name, constraint: tp.constraint };
933
+ typeScope.define(tp.name, t);
934
+ return t;
935
+ });
936
+ // Resolve once with the parameters in scope to report errors in the
937
+ // aliased type at the declaration.
938
+ const underlying = this.resolveTypeNode(decl.type, typeScope);
939
+ const alias = {
940
+ kind: "named",
941
+ name: decl.name,
942
+ underlying,
943
+ _generic: { typeParams: typeParamTypes, declNode: decl, isAlias: true },
944
+ };
945
+ this.types.set(decl.name, alias);
946
+ this.globals.define(decl.name, alias);
947
+ }
948
+
591
949
  _buildCollectFuncScope(decl) {
592
950
  let resolveScope = this.globals;
593
951
  let typeParamTypes = null;
@@ -617,12 +975,16 @@ export class TypeChecker {
617
975
  _registerFuncDecl(decl, funcType, typeParamTypes) {
618
976
  if (this.globals.symbols.has(decl.name) && decl.name !== "init")
619
977
  this.err(`${decl.name} redeclared in this block`, decl);
978
+ if (decl.native) funcType.native = decl.native;
979
+ this._funcDeclNames ??= new Set();
980
+ this._funcDeclNames.add(decl.name);
620
981
  if (typeParamTypes) {
621
982
  this.globals.define(decl.name, {
622
983
  kind: "generic",
623
984
  name: decl.name,
624
985
  typeParams: typeParamTypes,
625
986
  underlying: funcType,
987
+ declNode: decl,
626
988
  });
627
989
  } else {
628
990
  this.globals.define(decl.name, funcType);
@@ -660,17 +1022,24 @@ export class TypeChecker {
660
1022
  }
661
1023
 
662
1024
  _attachNonGenericMethod(decl, funcType) {
1025
+ // Codegen of an importing package reads the body of a Go-source
1026
+ // method (does it accept a nil receiver?).
1027
+ funcType._methodDecl = decl;
663
1028
  const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
664
1029
  const base = recvType?.underlying ?? recvType;
665
1030
  if (base?.kind === "struct") {
1031
+ base.methods ??= new Map();
666
1032
  base.methods.set(decl.name, funcType);
667
- } else if (recvType?.kind === "named" && recvType.methods) {
1033
+ }
1034
+ if (recvType?.kind === "named") {
1035
+ recvType.methods ??= new Map();
668
1036
  recvType.methods.set(decl.name, funcType);
669
1037
  }
670
1038
  }
671
1039
 
672
1040
  _attachMethod(decl, funcType) {
673
1041
  funcType._ptrRecv = Boolean(decl.recvPointer);
1042
+ funcType._decl = decl;
674
1043
  const recvNamedType = this.types.get(decl.recvType.name);
675
1044
  if (recvNamedType?._generic)
676
1045
  this._attachGenericMethod(recvNamedType, decl, funcType);
@@ -697,42 +1066,130 @@ export class TypeChecker {
697
1066
  case "CssDecl":
698
1067
  break;
699
1068
  case "TemplDecl":
700
- this._markTemplNodesUsed(decl.body);
1069
+ this._checkTemplDecl(decl, scope);
701
1070
  break;
702
1071
  }
703
1072
  }
704
1073
 
705
- _markTokensUsed(tokens) {
1074
+ // A templ body is checked like a function body whose parameters are the
1075
+ // templ's: every Go expression TemplParser attached to a node (`expr`,
1076
+ // attribute `expr`, `forStmt`) is type-checked in the enclosing scope.
1077
+ _checkTemplDecl(decl, outer) {
1078
+ const inner = new Scope(outer);
1079
+ for (const p of decl.params)
1080
+ inner.define(p.name, this._paramType(p, inner));
1081
+ this._checkTemplNodes(decl.body, inner);
1082
+ }
1083
+
1084
+ _checkTemplNodes(nodes, scope) {
1085
+ for (const n of nodes ?? []) {
1086
+ const m = TEMPL_NODE_CHECKS[n.kind];
1087
+ if (m) this[m](n, scope);
1088
+ }
1089
+ }
1090
+
1091
+ // A malformed expression has no AST: the code generator reports its parse
1092
+ // error. Its names count as used so that error is not preceded by a
1093
+ // misleading `declared and not used`.
1094
+ _checkTemplExpr(node, tokens, scope) {
1095
+ if (node.exprError) {
1096
+ this._markTokensUsed(tokens, scope);
1097
+ return null;
1098
+ }
1099
+ return node.expr ? this.checkExpr(node.expr, scope) : null;
1100
+ }
1101
+
1102
+ _checkTemplElement(n, scope) {
1103
+ for (const a of n.attrs ?? []) {
1104
+ if (a.kind === "expr") this._checkTemplExpr(a, a.tokens, scope);
1105
+ else if (a.kind === "cond-bool") this._checkTemplCondAttr(a, scope);
1106
+ }
1107
+ this._checkTemplNodes(n.children, scope);
1108
+ }
1109
+
1110
+ // `name?={ cond }` sets the attribute when `cond` is true.
1111
+ _checkTemplCondAttr(a, scope) {
1112
+ const t = this._checkTemplExpr(a, a.tokens, scope);
1113
+ if (t && !isBool(t) && !isAny(t))
1114
+ this.err(`Conditional attribute '${a.name}' must be bool`, a.expr);
1115
+ }
1116
+
1117
+ // `{ name... }` names a variadic parameter. A templ without a `children`
1118
+ // parameter may still write `{ children... }` (it mounts nothing).
1119
+ _checkTemplChildren(n, scope) {
1120
+ if (n.name === "children" && !scope.lookup("children")) return;
1121
+ this._checkTemplExpr(n, [], scope);
1122
+ }
1123
+
1124
+ _checkTemplLeaf(n, scope) {
1125
+ this._checkTemplExpr(n, n.tokens, scope);
1126
+ }
1127
+
1128
+ _checkTemplIf(n, scope) {
1129
+ const ct = this._checkTemplExpr(n, n.condTokens, scope);
1130
+ if (ct && !isBool(ct) && !isAny(ct))
1131
+ this.err("If condition must be bool", n.expr);
1132
+ this._checkTemplNodes(n.then, scope);
1133
+ this._checkTemplNodes(n.else_, scope);
1134
+ }
1135
+
1136
+ _checkTemplSwitch(n, scope) {
1137
+ this._checkTemplExpr(n, n.exprTokens, scope);
1138
+ // A tag that failed to parse leaves `n.expr` unset: not a tagless switch.
1139
+ const tagless = !n.exprTokens?.length;
1140
+ for (const c of n.cases ?? []) {
1141
+ this._checkTemplExpr(c, c.caseTokens, scope);
1142
+ for (const e of c.exprs?.slice(1) ?? []) this.checkExpr(e, scope);
1143
+ if (n.expr || tagless)
1144
+ for (const e of c.exprs ?? []) this._checkCaseValue(n.expr, e);
1145
+ this._checkTemplNodes(c.body, scope);
1146
+ }
1147
+ }
1148
+
1149
+ // A header that did not parse is reported by the code generator; its body
1150
+ // is not checked, since the loop variables it would declare are unknown.
1151
+ _checkTemplFor(n, scope) {
1152
+ if (!n.forStmt) {
1153
+ this._markTokensUsed(n.stmtTokens, scope);
1154
+ this._markTemplNodesUsed(n.body, scope);
1155
+ return;
1156
+ }
1157
+ this._checkForStmt(n.forStmt, scope, VOID, (bodyScope) =>
1158
+ this._checkTemplNodes(n.body, bodyScope),
1159
+ );
1160
+ }
1161
+
1162
+ _markTokensUsed(tokens, scope) {
706
1163
  if (!tokens) return;
707
1164
  for (const t of tokens) {
708
1165
  if (t.type === "IDENT" || t.type?.name === "IDENT") {
709
- this.globals.lookup(t.value);
1166
+ scope.lookup(t.value);
710
1167
  }
711
1168
  }
712
1169
  }
713
1170
 
714
- _markTemplNodesUsed(nodes) {
1171
+ _markTemplNodesUsed(nodes, scope) {
715
1172
  if (!nodes) return;
716
1173
  for (const n of nodes) {
717
1174
  if (n.kind === "TemplElement") {
718
1175
  for (const a of n.attrs || []) {
719
- if (a.tokens) this._markTokensUsed(a.tokens);
1176
+ if (a.tokens) this._markTokensUsed(a.tokens, scope);
720
1177
  }
721
- this._markTemplNodesUsed(n.children);
1178
+ this._markTemplNodesUsed(n.children, scope);
722
1179
  } else if (n.kind === "TemplExpr" || n.kind === "TemplComponent") {
723
- this._markTokensUsed(n.tokens);
1180
+ this._markTokensUsed(n.tokens, scope);
724
1181
  } else if (n.kind === "TemplIf") {
725
- this._markTokensUsed(n.condTokens);
726
- this._markTemplNodesUsed(n.then);
727
- this._markTemplNodesUsed(n.else_);
1182
+ this._markTokensUsed(n.condTokens, scope);
1183
+ this._markTemplNodesUsed(n.then, scope);
1184
+ this._markTemplNodesUsed(n.else_, scope);
728
1185
  } else if (n.kind === "TemplFor") {
729
- this._markTokensUsed(n.stmtTokens);
730
- this._markTemplNodesUsed(n.body);
1186
+ this._markTokensUsed(n.stmtTokens, scope);
1187
+ this._markTemplNodesUsed(n.body, scope);
731
1188
  } else if (n.kind === "TemplSwitch") {
732
- this._markTokensUsed(n.exprTokens);
1189
+ this._markTokensUsed(n.exprTokens, scope);
733
1190
  for (const c of n.cases || []) {
734
- this._markTokensUsed(c.caseTokens);
735
- this._markTemplNodesUsed(c.body);
1191
+ this._markTokensUsed(c.caseTokens, scope);
1192
+ this._markTemplNodesUsed(c.body, scope);
736
1193
  }
737
1194
  }
738
1195
  }
@@ -786,7 +1243,43 @@ export class TypeChecker {
786
1243
  this.globals.define(decl.name, funcType);
787
1244
  }
788
1245
 
1246
+ _checkIntrinsic(decl) {
1247
+ const want = INTRINSICS.get(decl.intrinsic);
1248
+ if (!want) return this.err(`unknown intrinsic '${decl.intrinsic}'`, decl);
1249
+ if (!decl._bodyless)
1250
+ return this.err(
1251
+ `intrinsic '${decl.intrinsic}' must not have a body`,
1252
+ decl,
1253
+ );
1254
+ const got = typeStr(this.globals.symbols.get(decl.name));
1255
+ if (got !== want)
1256
+ this.err(
1257
+ `intrinsic '${decl.intrinsic}' must have type ${want}, not ${got}`,
1258
+ decl,
1259
+ );
1260
+ }
1261
+
1262
+ _checkNative(decl) {
1263
+ if (!this.isStdlib && !this.nativesChecked)
1264
+ return this.err(
1265
+ "//gofront:native is reserved for the standard library",
1266
+ decl,
1267
+ );
1268
+ if (decl.kind !== "FuncDecl")
1269
+ return this.err(
1270
+ `native '${decl.native}' must mark a function, not a method`,
1271
+ decl,
1272
+ );
1273
+ if (!NATIVES.has(decl.native))
1274
+ return this.err(`unknown native '${decl.native}'`, decl);
1275
+ if (decl._bodyless)
1276
+ this.err(`native '${decl.native}' must have a Go body`, decl);
1277
+ }
1278
+
789
1279
  checkFuncDecl(decl, outer) {
1280
+ this.stats.bodiesChecked++;
1281
+ if (decl.intrinsic) this._checkIntrinsic(decl);
1282
+ if (decl.native) this._checkNative(decl);
790
1283
  if (
791
1284
  (this.target === "wasm" || this.target === "both") &&
792
1285
  (decl.async || decl.isAsync)
@@ -800,9 +1293,9 @@ export class TypeChecker {
800
1293
  const inner = new Scope(outer);
801
1294
  this._injectTypeParams(decl, inner, outer);
802
1295
  for (const p of decl.params) {
803
- inner.define(p.name, this.resolveTypeNode(p.type, inner));
1296
+ inner.define(p.name, this._paramType(p, inner));
804
1297
  }
805
- const returnType = this._setupFuncReturnType(decl, inner, outer);
1298
+ const returnType = this._setupFuncReturnType(decl, inner);
806
1299
  const savedSharedAlloc = this._sharedAllocOk;
807
1300
  this._sharedAllocOk = decl.name === "init";
808
1301
  this._runFuncBody(decl, inner, returnType);
@@ -811,6 +1304,8 @@ export class TypeChecker {
811
1304
  }
812
1305
 
813
1306
  checkMethodDecl(decl, outer) {
1307
+ this.stats.bodiesChecked++;
1308
+ if (decl.native) this._checkNative(decl);
814
1309
  if (
815
1310
  (this.target === "wasm" || this.target === "both") &&
816
1311
  (decl.async || decl.isAsync)
@@ -828,27 +1323,55 @@ export class TypeChecker {
828
1323
  : decl.recvType.name;
829
1324
  const recvNamedType = this.types.get(recvTypeName);
830
1325
  this._injectGenericReceiverTypeParams(inner, recvNamedType);
831
- const recvType = this.resolveTypeNodeName(decl.recvType, outer);
1326
+ const recvType = this._methodReceiverType(decl, recvNamedType, outer);
832
1327
  inner.define(
833
1328
  decl.recvName,
834
1329
  decl.recvPointer ? { kind: "pointer", base: recvType } : recvType,
835
1330
  );
836
1331
  for (const p of decl.params) {
837
- inner.define(p.name, this.resolveTypeNode(p.type, inner));
1332
+ inner.define(p.name, this._paramType(p, inner));
1333
+ }
1334
+ const returnType = this._setupFuncReturnType(decl, inner);
1335
+ const savedRecvGeneric = this._recvGeneric;
1336
+ this._recvGeneric = recvNamedType?._generic;
1337
+ try {
1338
+ this._runFuncBody(decl, inner, returnType);
1339
+ } finally {
1340
+ this._recvGeneric = savedRecvGeneric;
838
1341
  }
839
- const returnType = this._setupFuncReturnType(decl, inner, inner);
840
- this._runFuncBody(decl, inner, returnType);
841
1342
  this._checkMissingReturn(decl, returnType, "method");
842
1343
  }
843
1344
 
844
- _injectTypeParams(decl, inner, outer) {
1345
+ // A variadic parameter `xs ...T` is a `[]T` inside the body (the signature
1346
+ // keeps the element type plus the `variadic` flag).
1347
+ _paramType(p, scope) {
1348
+ const t = this.resolveTypeNode(p.type, scope);
1349
+ return p.variadic ? { kind: "slice", elem: t } : t;
1350
+ }
1351
+
1352
+ _injectTypeParams(decl, inner, _outer) {
845
1353
  if (!decl.typeParams) return;
846
- for (const tp of decl.typeParams) {
847
- const constraint = tp.constraint
848
- ? this.resolveTypeNode(tp.constraint, outer)
849
- : ANY;
850
- inner.define(tp.name, { kind: "typeParam", name: tp.name, constraint });
851
- }
1354
+ // Declare every parameter first so a constraint may mention one that
1355
+ // comes later in the list (`[S ~[]E, E any]`).
1356
+ const params = decl.typeParams.map((tp) => {
1357
+ const t = { kind: "typeParam", name: tp.name, constraint: ANY };
1358
+ inner.define(tp.name, t);
1359
+ return t;
1360
+ });
1361
+ decl.typeParams.forEach((tp, i) => {
1362
+ if (tp.constraint)
1363
+ params[i].constraint = this.resolveTypeNode(tp.constraint, inner);
1364
+ });
1365
+ }
1366
+
1367
+ // Inside a method of `G[T]` the receiver is the instance `G[T]` over the
1368
+ // receiver's own type parameters, so it is assignable to results and locals
1369
+ // written as `G[T]`.
1370
+ _methodReceiverType(decl, recvNamedType, outer) {
1371
+ const generic = recvNamedType?._generic;
1372
+ if (!generic || generic.isAlias || !generic.typeParams?.length)
1373
+ return this.resolveTypeNodeName(decl.recvType, outer);
1374
+ return this.instantiateGenericWithArgs(generic, generic.typeParams);
852
1375
  }
853
1376
 
854
1377
  _injectGenericReceiverTypeParams(inner, recvNamedType) {
@@ -858,15 +1381,11 @@ export class TypeChecker {
858
1381
  }
859
1382
  }
860
1383
 
861
- _setupFuncReturnType(decl, inner, outer) {
1384
+ _setupFuncReturnType(decl, inner) {
862
1385
  let returnType = decl.returnType
863
1386
  ? this.resolveTypeNode(decl.returnType, inner)
864
1387
  : VOID;
865
- const hasNamedReturns = this._injectNamedReturns(
866
- decl.returnType,
867
- inner,
868
- outer,
869
- );
1388
+ const hasNamedReturns = this._injectNamedReturns(decl.returnType, inner);
870
1389
  if (hasNamedReturns && returnType)
871
1390
  returnType = { ...returnType, _hasNamedReturns: true };
872
1391
  return returnType;
@@ -883,17 +1402,23 @@ export class TypeChecker {
883
1402
  }
884
1403
 
885
1404
  _checkMissingReturn(decl, returnType, kind) {
886
- if (!isVoid(returnType) && !returnType._hasNamedReturns) {
1405
+ if (
1406
+ !decl._bodyless &&
1407
+ !isVoid(returnType) &&
1408
+ !returnType._hasNamedReturns
1409
+ ) {
887
1410
  if (!this._isTerminating(decl.body)) {
888
1411
  this.err(`missing return at end of ${kind} '${decl.name}'`, decl);
889
1412
  }
890
1413
  }
891
1414
  }
892
1415
 
893
- _injectNamedReturns(returnTypeNode, scope, outer) {
1416
+ // Named result types resolve in the function scope, so a result of a type
1417
+ // parameter's type (`(r T)`) sees T.
1418
+ _injectNamedReturns(returnTypeNode, scope) {
894
1419
  if (!returnTypeNode?._namedReturns) return false;
895
1420
  for (const { name, type } of returnTypeNode._namedReturns) {
896
- if (name) scope.define(name, this.resolveTypeNode(type, outer));
1421
+ if (name) scope.define(name, this.resolveTypeNode(type, scope));
897
1422
  }
898
1423
  return true;
899
1424
  }
@@ -904,26 +1429,19 @@ export class TypeChecker {
904
1429
  for (const spec of decl.decls) {
905
1430
  let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
906
1431
  if (spec.value) {
907
- const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
908
- const isTuple = valTypes.length === 1 && valTypes[0]?.kind === "tuple";
909
- const valTypesFlat = isTuple ? valTypes[0].types : valTypes;
910
- if (spec.names.length !== valTypesFlat.length) {
911
- const vars =
912
- spec.names.length === 1
913
- ? "1 variable"
914
- : `${spec.names.length} variables`;
915
- const vals = isTuple
916
- ? `returns ${valTypesFlat.length} values`
917
- : valTypesFlat.length === 1
918
- ? "1 value"
919
- : `${valTypesFlat.length} values`;
920
- this.err(`assignment mismatch: ${vars} but ${vals}`, spec);
921
- }
1432
+ const valTypesFlat = this._varSpecValueTypes(spec, scope);
922
1433
  for (let i = 0; i < spec.names.length; i++) {
923
1434
  const vt = valTypesFlat[i] ?? ANY;
924
1435
  const varType = type ?? defaultType(vt) ?? ANY;
1436
+ if (!type && spec.names.length === spec.value.length)
1437
+ this._checkConstDefault(vt, spec.value[i], "variable declaration");
925
1438
  if (type)
926
- this.assertAssignable(type, vt, spec.value[i] ?? spec.value[0]);
1439
+ this.assertAssignable(
1440
+ type,
1441
+ vt,
1442
+ spec.value[i] ?? spec.value[0],
1443
+ "variable declaration",
1444
+ );
927
1445
  scope.defineLocal(spec.names[i], varType);
928
1446
  }
929
1447
  } else {
@@ -934,6 +1452,45 @@ export class TypeChecker {
934
1452
  this._sharedAllocOk = savedSharedAlloc;
935
1453
  }
936
1454
 
1455
+ // The type of each value a `var` spec binds (a multi-value call or a
1456
+ // comma-ok form spreads over several names); reports a count mismatch.
1457
+ _varSpecValueTypes(spec, scope) {
1458
+ const commaOk = this._checkVarCommaOk(spec, scope);
1459
+ const valTypes = commaOk ?? spec.value.map((v) => this.checkExpr(v, scope));
1460
+ const isTuple =
1461
+ !commaOk && valTypes.length === 1 && valTypes[0]?.kind === "tuple";
1462
+ const flat = isTuple ? valTypes[0].types : valTypes;
1463
+ if (spec.names.length !== flat.length && !flat.some((t) => t?._tainted))
1464
+ this._reportAssignMismatch(spec.names.length, flat.length, isTuple, spec);
1465
+ return flat;
1466
+ }
1467
+
1468
+ _reportAssignMismatch(nameCount, valueCount, isTuple, node) {
1469
+ const vars = nameCount === 1 ? "1 variable" : `${nameCount} variables`;
1470
+ const vals = isTuple
1471
+ ? `returns ${valueCount} values`
1472
+ : valueCount === 1
1473
+ ? "1 value"
1474
+ : `${valueCount} values`;
1475
+ this.err(`assignment mismatch: ${vars} but ${vals}`, node);
1476
+ }
1477
+
1478
+ // `var v, ok = m[k]` / `var v, ok = x.(T)`: the comma-ok forms yield the
1479
+ // value and a bool. Returns their types, or null for any other spec.
1480
+ _checkVarCommaOk(spec, scope) {
1481
+ if (spec.names.length !== 2 || spec.value.length !== 1) return null;
1482
+ const value = spec.value[0];
1483
+ if (value.kind === "TypeAssertExpr") {
1484
+ value._commaOk = true;
1485
+ spec._commaOk = true;
1486
+ return [this.checkExpr(value, scope), BOOL];
1487
+ }
1488
+ const stmt = { lhs: spec.names, rhs: spec.value };
1489
+ const types = this._checkAssignCommaOkMap(stmt, scope);
1490
+ if (types) spec._commaOk = true;
1491
+ return types;
1492
+ }
1493
+
937
1494
  checkConstDecl(decl, scope) {
938
1495
  for (const spec of decl.decls) {
939
1496
  const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
@@ -944,6 +1501,15 @@ export class TypeChecker {
944
1501
  for (let i = 0; i < spec.names.length; i++) {
945
1502
  const name = spec.names[i];
946
1503
  const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
1504
+ if (spec.type && expr)
1505
+ this._checkConstOverflow(
1506
+ type,
1507
+ valTypes[i] ?? valTypes[0],
1508
+ expr,
1509
+ "constant declaration",
1510
+ );
1511
+ // `const D Duration = 1` is a BigInt in JS.
1512
+ if (spec.type && expr) this._tag64BitNode(this.resolveType(type), expr);
947
1513
  scope.defineConst(name, type, expr);
948
1514
  }
949
1515
  }
@@ -957,3 +1523,4 @@ Object.assign(TypeChecker.prototype, expressionCheckMethods);
957
1523
  Object.assign(TypeChecker.prototype, terminationMethods);
958
1524
  Object.assign(TypeChecker.prototype, resolveMethods);
959
1525
  Object.assign(TypeChecker.prototype, assignabilityMethods);
1526
+ Object.assign(TypeChecker.prototype, constantCheckMethods);