gofront 1.6.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -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,15 +11,21 @@
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,
21
24
  defaultType,
22
25
  ERROR,
26
+ INTRINSICS,
27
+ isAny,
28
+ isBool,
23
29
  isVoid,
24
30
  Scope,
25
31
  TAINTED_ANY,
@@ -29,6 +35,16 @@ import {
29
35
  VOID,
30
36
  } from "./types.js";
31
37
 
38
+ // Checker method per templ body node kind (see _checkTemplNodes).
39
+ const TEMPL_NODE_CHECKS = {
40
+ TemplElement: "_checkTemplElement",
41
+ TemplExpr: "_checkTemplLeaf",
42
+ TemplComponent: "_checkTemplLeaf",
43
+ TemplIf: "_checkTemplIf",
44
+ TemplFor: "_checkTemplFor",
45
+ TemplSwitch: "_checkTemplSwitch",
46
+ };
47
+
32
48
  function rootIdentName(e) {
33
49
  if (!e) return null;
34
50
  if (e.kind === "Ident") return e.name;
@@ -38,12 +54,56 @@ function rootIdentName(e) {
38
54
  }
39
55
 
40
56
  // Re-export for consumers that import from typechecker.js
41
- export { TypeCheckError, typeStr };
57
+ export { EXCLUDED_STDLIB_PACKAGES, TypeCheckError, typeStr };
58
+
59
+ // JavaScript built-ins that generated JS relies on; a JS class of the same
60
+ // name in the bundle would shadow them (see _checkGlobalTypeNamesPass).
61
+ const JS_BUILTIN_CLASSES = new Set([
62
+ "Array",
63
+ "ArrayBuffer",
64
+ "BigInt",
65
+ "BigInt64Array",
66
+ "BigUint64Array",
67
+ "Boolean",
68
+ "DataView",
69
+ "Date",
70
+ "Error",
71
+ "Float32Array",
72
+ "Float64Array",
73
+ "Int16Array",
74
+ "Int32Array",
75
+ "Int8Array",
76
+ "JSON",
77
+ "Map",
78
+ "Math",
79
+ "Number",
80
+ "Object",
81
+ "Promise",
82
+ "RangeError",
83
+ "Reflect",
84
+ "RegExp",
85
+ "Set",
86
+ "String",
87
+ "Symbol",
88
+ "TextDecoder",
89
+ "TextEncoder",
90
+ "TypeError",
91
+ "Uint16Array",
92
+ "Uint32Array",
93
+ "Uint8Array",
94
+ "Uint8ClampedArray",
95
+ "WeakMap",
96
+ "WebAssembly",
97
+ ]);
42
98
 
43
99
  export class TypeChecker {
44
100
  constructor() {
45
101
  this.types = new Map(); // named types
46
- this.globals = new Scope();
102
+ // Go's universe scope: builtins, browser globals and built-in package
103
+ // namespaces. Package declarations live in `globals` above it, so they
104
+ // shadow predeclared names (`func Array()`, `var document int`).
105
+ this.universe = new Scope();
106
+ this.globals = new Scope(this.universe);
47
107
  this.errors = [];
48
108
  this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
49
109
  this.target = "js";
@@ -57,21 +117,35 @@ export class TypeChecker {
57
117
  this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
58
118
  this._deferCount = 0; // tracks defer usage in current function body
59
119
  this._imports = []; // tracked imports for unused-import detection
120
+ this._constExprs = new Map(); // name -> AST initializer expr
121
+ this._importedConsts = new Map(); // dot-imported const name -> value
122
+ this._externalNames = new Set(); // package-scope names from .d.ts / npm definitions
60
123
  this._setupGlobals();
61
124
  // Register error as a named type (interface)
62
125
  this.types.set("error", ERROR);
63
126
  }
64
127
 
65
128
  _setupGlobals() {
66
- setupGlobals(this.globals, this.types);
129
+ setupGlobals(this.universe, this.types);
67
130
  }
68
131
 
69
- addDefinitions(types, values) {
132
+ // `external` marks values as real JS names (.d.ts / npm definitions) that
133
+ // must not be renamed; dot-imported GoFront packages pass false so their
134
+ // browser-global-named members get the same `$` renaming as local ones.
135
+ // `consts` (dot-imported GoFront packages) maps constant names to their
136
+ // folded values.
137
+ addDefinitions(types, values, { external = true, consts = null } = {}) {
70
138
  for (const [name, type] of types) {
71
139
  this.types.set(name, type);
72
140
  }
73
141
  for (const [name, type] of values) {
74
142
  this.globals.define(name, type);
143
+ if (external) this._externalNames.add(name);
144
+ }
145
+ for (const [name, v] of consts ?? []) {
146
+ if (!v || !values.has(name)) continue;
147
+ this.globals._consts.add(name);
148
+ this._importedConsts.set(name, v);
75
149
  }
76
150
  }
77
151
 
@@ -79,7 +153,9 @@ export class TypeChecker {
79
153
  // e.g. addPackageNamespace('utils', symbolsMap, typesMap)
80
154
  // lets callers type-check `utils.Foo` via SelectorExpr.
81
155
  // `target` is the imported package's compile target ("js"/"wasm"/"both").
82
- addPackageNamespace(pkgName, symbols, types, target = null) {
156
+ // `consts` maps the package's constant names to their folded value (see
157
+ // typechecker/const-fold.js), or null when the folder cannot evaluate one.
158
+ addPackageNamespace(pkgName, symbols, types, target = null, consts = null) {
83
159
  const members = {};
84
160
  for (const [name, type] of symbols) members[name] = type;
85
161
  // _gofront: true marks this as a GoFront package — exported identifier rules apply
@@ -89,8 +165,15 @@ export class TypeChecker {
89
165
  members,
90
166
  _gofront: true,
91
167
  _target: target,
168
+ _consts: consts,
92
169
  });
93
170
  for (const [name, type] of types) {
171
+ const prior = this.types.get(name);
172
+ if (prior && prior !== type && prior.kind === "named") {
173
+ this._ambiguousImported ??= new WeakSet();
174
+ this._ambiguousImported.add(prior);
175
+ this._ambiguousImported.add(type);
176
+ }
94
177
  this.types.set(name, type);
95
178
  this.types.set(`${pkgName}.${name}`, type);
96
179
  }
@@ -107,7 +190,7 @@ export class TypeChecker {
107
190
  // Report any tracked imports whose package name was never referenced.
108
191
  reportUnusedImports() {
109
192
  for (const { name, node, filename, source } of this._imports) {
110
- if (!this.globals._used.has(name)) {
193
+ if (!this.globals._used.has(name) && !this.universe._used.has(name)) {
111
194
  const saved = [this._currentFile, this._currentSource];
112
195
  this._currentFile = filename;
113
196
  this._currentSource = source;
@@ -129,6 +212,15 @@ export class TypeChecker {
129
212
  return out;
130
213
  }
131
214
 
215
+ getExportedConsts() {
216
+ const out = new Map();
217
+ for (const name of this.globals._consts) {
218
+ if (!this.globals.lookupConstExpr(name)) continue;
219
+ out.set(name, this._constValue({ kind: "Ident", name }, this.globals));
220
+ }
221
+ return out;
222
+ }
223
+
132
224
  getExportedTypes() {
133
225
  return new Map(this.types);
134
226
  }
@@ -220,7 +312,11 @@ export class TypeChecker {
220
312
  const type = spec.type
221
313
  ? this.resolveTypeNode(spec.type, this.globals)
222
314
  : ANY;
223
- for (const name of spec.names) this.globals.defineConst(name, type);
315
+ for (let i = 0; i < spec.names.length; i++) {
316
+ const name = spec.names[i];
317
+ const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
318
+ this.globals.defineConst(name, type, expr);
319
+ }
224
320
  }
225
321
  }
226
322
 
@@ -264,11 +360,15 @@ export class TypeChecker {
264
360
  }
265
361
  }
266
362
 
363
+ this._checkDirectivesAndImportsPass(programs);
364
+ this._registerConstExprs(programs);
267
365
  this._collectTypesPass(programs);
268
366
  this._collectFuncsPass(programs);
367
+ this._checkGlobalTypeNamesPass(programs);
269
368
  this._promoteEmbeddedMethods();
270
369
  this._collectVarsConstsPass(programs);
271
370
  this._checkTopDeclsPass(programs);
371
+ this._reportInitCycles(programs);
272
372
 
273
373
  if (this.target === "both") {
274
374
  this._checkMutablePackageVars(programs);
@@ -285,6 +385,42 @@ export class TypeChecker {
285
385
  return this.errors;
286
386
  }
287
387
 
388
+ _checkEmbedDirectives(directives) {
389
+ for (const { line, col } of directives ?? []) {
390
+ this.err("//go:embed is not supported yet", { line, col });
391
+ }
392
+ }
393
+
394
+ _checkDirectivesAndImportsPass(programs) {
395
+ this._importedPaths = new Set();
396
+ this._fileImports = new Map();
397
+ for (const p of programs) {
398
+ this._setCurrentFile(p);
399
+ const names = new Map();
400
+ this._fileImports.set(this._currentFile, names);
401
+ this._checkEmbedDirectives(p._embedDirectives);
402
+ for (const imp of p.imports ?? []) {
403
+ for (const spec of imp.imports ?? []) {
404
+ this._importedPaths.add(spec.path);
405
+ names.set(
406
+ spec.alias ??
407
+ spec.path
408
+ .split("/")
409
+ .filter((s) => !/^v\d+$/.test(s))
410
+ .at(-1),
411
+ spec.path,
412
+ );
413
+ if (EXCLUDED_STDLIB_PACKAGES.has(spec.path)) {
414
+ this.err(
415
+ `package '${spec.path}' is not available: ${EXCLUDED_STDLIB_PACKAGES.get(spec.path)}`,
416
+ spec,
417
+ );
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+
288
424
  _checkMutablePackageVars(programs) {
289
425
  const pkgVars = new Set();
290
426
  for (const p of programs) {
@@ -404,6 +540,18 @@ export class TypeChecker {
404
540
  for (const d of p.decls) {
405
541
  if (d.kind === "TypeDecl" && !d.isAlias && !this.types.has(d.name)) {
406
542
  const placeholder = { kind: "named", name: d.name, underlying: null };
543
+ // A generic type is known to be generic before any field resolves,
544
+ // so types declared earlier can instantiate it.
545
+ if (d.typeParams) {
546
+ placeholder._generic = {
547
+ typeParams: d.typeParams.map((tp) => ({
548
+ kind: "typeParam",
549
+ name: tp.name,
550
+ constraint: tp.constraint,
551
+ })),
552
+ declNode: d,
553
+ };
554
+ }
407
555
  this.types.set(d.name, placeholder);
408
556
  this.globals.define(d.name, placeholder);
409
557
  }
@@ -413,6 +561,7 @@ export class TypeChecker {
413
561
  for (const p of programs) {
414
562
  for (const d of p.decls) if (d.kind === "TypeDecl") all.push({ p, d });
415
563
  }
564
+ this._expandGenericStructDefs(all.map((x) => x.d));
416
565
  // Collect in dependency order so aliases, generics, embedded and by-value
417
566
  // field types declared later (or in other files) are ready when needed.
418
567
  let pending = all;
@@ -431,6 +580,57 @@ export class TypeChecker {
431
580
  }
432
581
  }
433
582
 
583
+ // `type S[T any] Base` with `Base` a struct declared in the package: S is a
584
+ // new struct type with Base's fields (and none of its methods), so its
585
+ // declaration becomes a copy of Base's struct type.
586
+ _expandGenericStructDefs(decls) {
587
+ const byName = new Map(decls.map((d) => [d.name, d]));
588
+ for (const d of decls) {
589
+ if (!d.typeParams || d.isAlias) continue;
590
+ let target = d.type;
591
+ for (let i = 0; i < 16 && target?.kind === "TypeName"; i++) {
592
+ const base = byName.get(target.name);
593
+ if (!base || base.typeParams || base.isAlias) break;
594
+ target = base.type;
595
+ }
596
+ // A struct written in place is already this declaration's own copy;
597
+ // cloning it would also choke on an AST a previous target pass
598
+ // annotated with cycles.
599
+ if (target?.kind === "StructType" && target !== d.type)
600
+ d.type = JSON.parse(JSON.stringify(target));
601
+ }
602
+ }
603
+
604
+ // A struct type, or a named type with methods, becomes a JS class of the
605
+ // same name in `js` and `both` packages. Functions and variables that
606
+ // shadow a browser global get a `$` suffix, but type names reach the
607
+ // output through the checker's types (class declarations, `new T`,
608
+ // `instanceof T`, static methods), so a class named after a JavaScript
609
+ // built-in the generated code calls (`Array.isArray`, `new Error`) is
610
+ // rejected instead. Interfaces and method-less named types emit no class.
611
+ _checkGlobalTypeNamesPass(programs) {
612
+ if (this.target === "wasm" || this.isStdlib) return;
613
+ for (const p of programs) {
614
+ for (const d of p.decls) {
615
+ if (
616
+ d.kind !== "TypeDecl" ||
617
+ d.isAlias ||
618
+ !JS_BUILTIN_CLASSES.has(d.name)
619
+ )
620
+ continue;
621
+ const t = this.types.get(d.name);
622
+ const isClass =
623
+ t?.underlying?.kind === "struct" || (t?.methods?.size ?? 0) > 0;
624
+ if (!isClass) continue;
625
+ this._setCurrentFile(p);
626
+ this.err(
627
+ `type ${d.name} shadows the JavaScript global ${d.name}; rename the type`,
628
+ d,
629
+ );
630
+ }
631
+ }
632
+ }
633
+
434
634
  _typeDeclBlocked(decl, pendingNames) {
435
635
  const visit = (node, indirect) => {
436
636
  if (!node || typeof node !== "object") return false;
@@ -470,6 +670,22 @@ export class TypeChecker {
470
670
  }
471
671
  }
472
672
 
673
+ // Constant initializers are known before types are resolved, so an array
674
+ // length may name a constant declared later (`[N]int`, `[N + 1]T`).
675
+ _registerConstExprs(programs) {
676
+ for (const p of programs) {
677
+ for (const d of p.decls) {
678
+ if (d.kind !== "ConstDecl") continue;
679
+ for (const spec of d.decls) {
680
+ spec.names.forEach((name, i) => {
681
+ const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
682
+ if (expr) this.globals._constExprs.set(name, expr);
683
+ });
684
+ }
685
+ }
686
+ }
687
+ }
688
+
473
689
  _collectVarsConstsPass(programs) {
474
690
  for (const p of programs) {
475
691
  this._setCurrentFile(p);
@@ -480,14 +696,30 @@ export class TypeChecker {
480
696
  }
481
697
  }
482
698
 
699
+ // Go rejects package variables whose initialisers depend on each other.
700
+ _reportInitCycles(programs) {
701
+ if (this.errors.length > 0) return;
702
+ const { cycles } = orderPackageInits(programs);
703
+ for (const c of cycles) {
704
+ const owner = programs.find((p) =>
705
+ p.decls?.some((d) => d.kind === "VarDecl" && d.decls?.includes(c.spec)),
706
+ );
707
+ if (owner) this._setCurrentFile(owner);
708
+ this.err(c.message, c.spec);
709
+ }
710
+ }
711
+
483
712
  _checkTopDeclsPass(programs) {
484
- for (const p of programs) {
485
- this._setCurrentFile(p);
486
- for (const d of p.decls) {
487
- if (d.kind === "VarDecl" || d.kind === "ConstDecl") {
488
- this.checkTopDecl(d, this.globals);
489
- }
490
- }
713
+ // A constant may name one declared later, so each is typed after those.
714
+ for (const e of orderPackageInits(programs).consts) {
715
+ this._setCurrentFile(e.program);
716
+ this.checkTopDecl({ ...e.decl, decls: [e.spec] }, this.globals);
717
+ }
718
+ // A variable's type may come from one declared later (`var a = b + 1;
719
+ // var b = f()`), so initialisers are typed after what they name.
720
+ for (const e of orderVarSpecsForTyping(programs)) {
721
+ this._setCurrentFile(e.program);
722
+ this.checkTopDecl({ ...e.decl, decls: [e.spec] }, this.globals);
491
723
  }
492
724
  for (const p of programs) {
493
725
  this._setCurrentFile(p);
@@ -507,15 +739,20 @@ export class TypeChecker {
507
739
 
508
740
  _promoteEmbeddedMethods() {
509
741
  for (const type of this.types.values()) {
510
- const struct = type.underlying;
511
- if (struct?.kind === "struct" && struct._embeds) {
512
- for (const embed of struct._embeds) {
513
- const base = embed.kind === "named" ? embed.underlying : embed;
514
- if (base?.kind !== "struct" || !base.methods) continue;
515
- for (const [mName, mType] of base.methods.entries()) {
516
- if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
517
- }
518
- }
742
+ this._promoteEmbeddedInto(type.underlying);
743
+ for (const inst of type._generic?._instances?.values() ?? [])
744
+ this._promoteEmbeddedInto(inst.underlying);
745
+ }
746
+ }
747
+
748
+ _promoteEmbeddedInto(struct) {
749
+ if (struct?.kind !== "struct" || !struct._embeds) return;
750
+ for (const embed of struct._embeds) {
751
+ const inner = embed.kind === "pointer" ? embed.base : embed;
752
+ const base = inner.kind === "named" ? inner.underlying : inner;
753
+ if (base?.kind !== "struct" || !base.methods) continue;
754
+ for (const [mName, mType] of base.methods.entries()) {
755
+ if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
519
756
  }
520
757
  }
521
758
  }
@@ -523,30 +760,39 @@ export class TypeChecker {
523
760
  // ── Type collection ──────────────────────────────────────────
524
761
 
525
762
  collectType(decl) {
763
+ if (decl.typeParams && decl.isAlias) {
764
+ this._collectGenericAlias(decl);
765
+ return;
766
+ }
526
767
  if (decl.typeParams) {
527
768
  // Generic type — resolve underlying with type params as ANY for struct fields
528
769
  const typeScope = new Scope(this.globals);
529
- const typeParamTypes = decl.typeParams.map((tp) => {
530
- const t = {
531
- kind: "typeParam",
532
- name: tp.name,
533
- constraint: tp.constraint,
534
- };
770
+ const preset = this.types.get(decl.name)?._generic;
771
+ const typeParamTypes = decl.typeParams.map((tp, i) => {
772
+ const t =
773
+ preset?.declNode === decl
774
+ ? preset.typeParams[i]
775
+ : {
776
+ kind: "typeParam",
777
+ name: tp.name,
778
+ constraint: tp.constraint,
779
+ };
535
780
  typeScope.define(tp.name, t);
536
781
  return t;
537
782
  });
538
- const underlying = this.resolveTypeNode(decl.type, typeScope);
539
783
  const existing = this.types.get(decl.name);
540
784
  const named =
541
785
  existing?.kind === "named"
542
786
  ? existing
543
787
  : { kind: "named", name: decl.name };
788
+ // Registered before the fields resolve so `Next *List[T]` finds the
789
+ // generic declaration it is part of.
790
+ if (preset?.declNode === decl) named._generic = preset;
791
+ else named._generic = { typeParams: typeParamTypes, declNode: decl };
792
+ this.types.set(decl.name, named);
793
+ const underlying = this.resolveTypeNode(decl.type, typeScope);
544
794
  named.underlying = underlying;
545
795
  named._target = this.target;
546
- named._generic = {
547
- typeParams: typeParamTypes,
548
- declNode: decl,
549
- };
550
796
  if (underlying.kind === "struct") {
551
797
  underlying.name = decl.name;
552
798
  underlying.methods = new Map();
@@ -555,7 +801,15 @@ export class TypeChecker {
555
801
  this.globals.define(decl.name, named);
556
802
  return;
557
803
  }
558
- const underlying = this.resolveTypeNode(decl.type, this.globals);
804
+ let underlying = this.resolveTypeNode(decl.type, this.globals);
805
+ // `type W V` with V a named array: W's underlying type is V's array.
806
+ if (!decl.isAlias && underlying.kind === "named") {
807
+ const inner = this._deepUnderlying(underlying);
808
+ if (inner?.kind === "array") underlying = inner;
809
+ // `type S Base` with Base a struct: S gets its own copy of the fields.
810
+ else if (inner?.kind === "struct")
811
+ underlying = { ...inner, name: decl.name, methods: new Map() };
812
+ }
559
813
  if (decl.isAlias) {
560
814
  // type A = B — transparent alias, A and B are identical types
561
815
  this.types.set(decl.name, underlying);
@@ -582,6 +836,29 @@ export class TypeChecker {
582
836
  this.globals.define(decl.name, named);
583
837
  }
584
838
 
839
+ // type A[P any] = T[P] — Go 1.24 generic alias. The declaration is a
840
+ // template only: each instantiation A[X] substitutes X into the aliased
841
+ // type and resolves to exactly that type (no new named type).
842
+ _collectGenericAlias(decl) {
843
+ const typeScope = new Scope(this.globals);
844
+ const typeParamTypes = decl.typeParams.map((tp) => {
845
+ const t = { kind: "typeParam", name: tp.name, constraint: tp.constraint };
846
+ typeScope.define(tp.name, t);
847
+ return t;
848
+ });
849
+ // Resolve once with the parameters in scope to report errors in the
850
+ // aliased type at the declaration.
851
+ const underlying = this.resolveTypeNode(decl.type, typeScope);
852
+ const alias = {
853
+ kind: "named",
854
+ name: decl.name,
855
+ underlying,
856
+ _generic: { typeParams: typeParamTypes, declNode: decl, isAlias: true },
857
+ };
858
+ this.types.set(decl.name, alias);
859
+ this.globals.define(decl.name, alias);
860
+ }
861
+
585
862
  _buildCollectFuncScope(decl) {
586
863
  let resolveScope = this.globals;
587
864
  let typeParamTypes = null;
@@ -617,6 +894,7 @@ export class TypeChecker {
617
894
  name: decl.name,
618
895
  typeParams: typeParamTypes,
619
896
  underlying: funcType,
897
+ declNode: decl,
620
898
  });
621
899
  } else {
622
900
  this.globals.define(decl.name, funcType);
@@ -657,14 +935,18 @@ export class TypeChecker {
657
935
  const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
658
936
  const base = recvType?.underlying ?? recvType;
659
937
  if (base?.kind === "struct") {
938
+ base.methods ??= new Map();
660
939
  base.methods.set(decl.name, funcType);
661
- } else if (recvType?.kind === "named" && recvType.methods) {
940
+ }
941
+ if (recvType?.kind === "named") {
942
+ recvType.methods ??= new Map();
662
943
  recvType.methods.set(decl.name, funcType);
663
944
  }
664
945
  }
665
946
 
666
947
  _attachMethod(decl, funcType) {
667
948
  funcType._ptrRecv = Boolean(decl.recvPointer);
949
+ funcType._decl = decl;
668
950
  const recvNamedType = this.types.get(decl.recvType.name);
669
951
  if (recvNamedType?._generic)
670
952
  this._attachGenericMethod(recvNamedType, decl, funcType);
@@ -691,42 +973,111 @@ export class TypeChecker {
691
973
  case "CssDecl":
692
974
  break;
693
975
  case "TemplDecl":
694
- this._markTemplNodesUsed(decl.body);
976
+ this._checkTemplDecl(decl, scope);
695
977
  break;
696
978
  }
697
979
  }
698
980
 
699
- _markTokensUsed(tokens) {
981
+ // A templ body is checked like a function body whose parameters are the
982
+ // templ's: every Go expression TemplParser attached to a node (`expr`,
983
+ // attribute `expr`, `forStmt`) is type-checked in the enclosing scope.
984
+ _checkTemplDecl(decl, outer) {
985
+ const inner = new Scope(outer);
986
+ for (const p of decl.params)
987
+ inner.define(p.name, this._paramType(p, inner));
988
+ this._checkTemplNodes(decl.body, inner);
989
+ }
990
+
991
+ _checkTemplNodes(nodes, scope) {
992
+ for (const n of nodes ?? []) {
993
+ const m = TEMPL_NODE_CHECKS[n.kind];
994
+ if (m) this[m](n, scope);
995
+ }
996
+ }
997
+
998
+ // A malformed expression has no AST: the code generator reports its parse
999
+ // error. Its names count as used so that error is not preceded by a
1000
+ // misleading `declared and not used`.
1001
+ _checkTemplExpr(node, tokens, scope) {
1002
+ if (node.exprError) {
1003
+ this._markTokensUsed(tokens, scope);
1004
+ return null;
1005
+ }
1006
+ return node.expr ? this.checkExpr(node.expr, scope) : null;
1007
+ }
1008
+
1009
+ _checkTemplElement(n, scope) {
1010
+ for (const a of n.attrs ?? []) {
1011
+ if (a.kind === "expr" || a.kind === "cond-bool")
1012
+ this._checkTemplExpr(a, a.tokens, scope);
1013
+ }
1014
+ this._checkTemplNodes(n.children, scope);
1015
+ }
1016
+
1017
+ _checkTemplLeaf(n, scope) {
1018
+ this._checkTemplExpr(n, n.tokens, scope);
1019
+ }
1020
+
1021
+ _checkTemplIf(n, scope) {
1022
+ const ct = this._checkTemplExpr(n, n.condTokens, scope);
1023
+ if (ct && !isBool(ct) && !isAny(ct))
1024
+ this.err("If condition must be bool", n.expr);
1025
+ this._checkTemplNodes(n.then, scope);
1026
+ this._checkTemplNodes(n.else_, scope);
1027
+ }
1028
+
1029
+ _checkTemplSwitch(n, scope) {
1030
+ this._checkTemplExpr(n, n.exprTokens, scope);
1031
+ for (const c of n.cases ?? []) {
1032
+ this._checkTemplExpr(c, c.caseTokens, scope);
1033
+ this._checkTemplNodes(c.body, scope);
1034
+ }
1035
+ }
1036
+
1037
+ // A header that did not parse is reported by the code generator; its body
1038
+ // is not checked, since the loop variables it would declare are unknown.
1039
+ _checkTemplFor(n, scope) {
1040
+ if (!n.forStmt) {
1041
+ this._markTokensUsed(n.stmtTokens, scope);
1042
+ this._markTemplNodesUsed(n.body, scope);
1043
+ return;
1044
+ }
1045
+ this._checkForStmt(n.forStmt, scope, VOID, (bodyScope) =>
1046
+ this._checkTemplNodes(n.body, bodyScope),
1047
+ );
1048
+ }
1049
+
1050
+ _markTokensUsed(tokens, scope) {
700
1051
  if (!tokens) return;
701
1052
  for (const t of tokens) {
702
1053
  if (t.type === "IDENT" || t.type?.name === "IDENT") {
703
- this.globals.lookup(t.value);
1054
+ scope.lookup(t.value);
704
1055
  }
705
1056
  }
706
1057
  }
707
1058
 
708
- _markTemplNodesUsed(nodes) {
1059
+ _markTemplNodesUsed(nodes, scope) {
709
1060
  if (!nodes) return;
710
1061
  for (const n of nodes) {
711
1062
  if (n.kind === "TemplElement") {
712
1063
  for (const a of n.attrs || []) {
713
- if (a.tokens) this._markTokensUsed(a.tokens);
1064
+ if (a.tokens) this._markTokensUsed(a.tokens, scope);
714
1065
  }
715
- this._markTemplNodesUsed(n.children);
1066
+ this._markTemplNodesUsed(n.children, scope);
716
1067
  } else if (n.kind === "TemplExpr" || n.kind === "TemplComponent") {
717
- this._markTokensUsed(n.tokens);
1068
+ this._markTokensUsed(n.tokens, scope);
718
1069
  } else if (n.kind === "TemplIf") {
719
- this._markTokensUsed(n.condTokens);
720
- this._markTemplNodesUsed(n.then);
721
- this._markTemplNodesUsed(n.else_);
1070
+ this._markTokensUsed(n.condTokens, scope);
1071
+ this._markTemplNodesUsed(n.then, scope);
1072
+ this._markTemplNodesUsed(n.else_, scope);
722
1073
  } else if (n.kind === "TemplFor") {
723
- this._markTokensUsed(n.stmtTokens);
724
- this._markTemplNodesUsed(n.body);
1074
+ this._markTokensUsed(n.stmtTokens, scope);
1075
+ this._markTemplNodesUsed(n.body, scope);
725
1076
  } else if (n.kind === "TemplSwitch") {
726
- this._markTokensUsed(n.exprTokens);
1077
+ this._markTokensUsed(n.exprTokens, scope);
727
1078
  for (const c of n.cases || []) {
728
- this._markTokensUsed(c.caseTokens);
729
- this._markTemplNodesUsed(c.body);
1079
+ this._markTokensUsed(c.caseTokens, scope);
1080
+ this._markTemplNodesUsed(c.body, scope);
730
1081
  }
731
1082
  }
732
1083
  }
@@ -780,7 +1131,24 @@ export class TypeChecker {
780
1131
  this.globals.define(decl.name, funcType);
781
1132
  }
782
1133
 
1134
+ _checkIntrinsic(decl) {
1135
+ const want = INTRINSICS.get(decl.intrinsic);
1136
+ if (!want) return this.err(`unknown intrinsic '${decl.intrinsic}'`, decl);
1137
+ if (!decl._bodyless)
1138
+ return this.err(
1139
+ `intrinsic '${decl.intrinsic}' must not have a body`,
1140
+ decl,
1141
+ );
1142
+ const got = typeStr(this.globals.symbols.get(decl.name));
1143
+ if (got !== want)
1144
+ this.err(
1145
+ `intrinsic '${decl.intrinsic}' must have type ${want}, not ${got}`,
1146
+ decl,
1147
+ );
1148
+ }
1149
+
783
1150
  checkFuncDecl(decl, outer) {
1151
+ if (decl.intrinsic) this._checkIntrinsic(decl);
784
1152
  if (
785
1153
  (this.target === "wasm" || this.target === "both") &&
786
1154
  (decl.async || decl.isAsync)
@@ -794,9 +1162,9 @@ export class TypeChecker {
794
1162
  const inner = new Scope(outer);
795
1163
  this._injectTypeParams(decl, inner, outer);
796
1164
  for (const p of decl.params) {
797
- inner.define(p.name, this.resolveTypeNode(p.type, inner));
1165
+ inner.define(p.name, this._paramType(p, inner));
798
1166
  }
799
- const returnType = this._setupFuncReturnType(decl, inner, outer);
1167
+ const returnType = this._setupFuncReturnType(decl, inner);
800
1168
  const savedSharedAlloc = this._sharedAllocOk;
801
1169
  this._sharedAllocOk = decl.name === "init";
802
1170
  this._runFuncBody(decl, inner, returnType);
@@ -822,19 +1190,26 @@ export class TypeChecker {
822
1190
  : decl.recvType.name;
823
1191
  const recvNamedType = this.types.get(recvTypeName);
824
1192
  this._injectGenericReceiverTypeParams(inner, recvNamedType);
825
- const recvType = this.resolveTypeNodeName(decl.recvType, outer);
1193
+ const recvType = this._methodReceiverType(decl, recvNamedType, outer);
826
1194
  inner.define(
827
1195
  decl.recvName,
828
1196
  decl.recvPointer ? { kind: "pointer", base: recvType } : recvType,
829
1197
  );
830
1198
  for (const p of decl.params) {
831
- inner.define(p.name, this.resolveTypeNode(p.type, inner));
1199
+ inner.define(p.name, this._paramType(p, inner));
832
1200
  }
833
- const returnType = this._setupFuncReturnType(decl, inner, inner);
1201
+ const returnType = this._setupFuncReturnType(decl, inner);
834
1202
  this._runFuncBody(decl, inner, returnType);
835
1203
  this._checkMissingReturn(decl, returnType, "method");
836
1204
  }
837
1205
 
1206
+ // A variadic parameter `xs ...T` is a `[]T` inside the body (the signature
1207
+ // keeps the element type plus the `variadic` flag).
1208
+ _paramType(p, scope) {
1209
+ const t = this.resolveTypeNode(p.type, scope);
1210
+ return p.variadic ? { kind: "slice", elem: t } : t;
1211
+ }
1212
+
838
1213
  _injectTypeParams(decl, inner, outer) {
839
1214
  if (!decl.typeParams) return;
840
1215
  for (const tp of decl.typeParams) {
@@ -845,6 +1220,16 @@ export class TypeChecker {
845
1220
  }
846
1221
  }
847
1222
 
1223
+ // Inside a method of `G[T]` the receiver is the instance `G[T]` over the
1224
+ // receiver's own type parameters, so it is assignable to results and locals
1225
+ // written as `G[T]`.
1226
+ _methodReceiverType(decl, recvNamedType, outer) {
1227
+ const generic = recvNamedType?._generic;
1228
+ if (!generic || generic.isAlias || !generic.typeParams?.length)
1229
+ return this.resolveTypeNodeName(decl.recvType, outer);
1230
+ return this.instantiateGenericWithArgs(generic, generic.typeParams);
1231
+ }
1232
+
848
1233
  _injectGenericReceiverTypeParams(inner, recvNamedType) {
849
1234
  if (!recvNamedType?._generic) return;
850
1235
  for (const tp of recvNamedType._generic.typeParams) {
@@ -852,15 +1237,11 @@ export class TypeChecker {
852
1237
  }
853
1238
  }
854
1239
 
855
- _setupFuncReturnType(decl, inner, outer) {
1240
+ _setupFuncReturnType(decl, inner) {
856
1241
  let returnType = decl.returnType
857
1242
  ? this.resolveTypeNode(decl.returnType, inner)
858
1243
  : VOID;
859
- const hasNamedReturns = this._injectNamedReturns(
860
- decl.returnType,
861
- inner,
862
- outer,
863
- );
1244
+ const hasNamedReturns = this._injectNamedReturns(decl.returnType, inner);
864
1245
  if (hasNamedReturns && returnType)
865
1246
  returnType = { ...returnType, _hasNamedReturns: true };
866
1247
  return returnType;
@@ -884,10 +1265,12 @@ export class TypeChecker {
884
1265
  }
885
1266
  }
886
1267
 
887
- _injectNamedReturns(returnTypeNode, scope, outer) {
1268
+ // Named result types resolve in the function scope, so a result of a type
1269
+ // parameter's type (`(r T)`) sees T.
1270
+ _injectNamedReturns(returnTypeNode, scope) {
888
1271
  if (!returnTypeNode?._namedReturns) return false;
889
1272
  for (const { name, type } of returnTypeNode._namedReturns) {
890
- if (name) scope.define(name, this.resolveTypeNode(type, outer));
1273
+ if (name) scope.define(name, this.resolveTypeNode(type, scope));
891
1274
  }
892
1275
  return true;
893
1276
  }
@@ -899,14 +1282,38 @@ export class TypeChecker {
899
1282
  let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
900
1283
  if (spec.value) {
901
1284
  const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
902
- if (!type) type = defaultType(valTypes[0]) ?? ANY;
1285
+ const isTuple = valTypes.length === 1 && valTypes[0]?.kind === "tuple";
1286
+ const valTypesFlat = isTuple ? valTypes[0].types : valTypes;
1287
+ if (spec.names.length !== valTypesFlat.length) {
1288
+ const vars =
1289
+ spec.names.length === 1
1290
+ ? "1 variable"
1291
+ : `${spec.names.length} variables`;
1292
+ const vals = isTuple
1293
+ ? `returns ${valTypesFlat.length} values`
1294
+ : valTypesFlat.length === 1
1295
+ ? "1 value"
1296
+ : `${valTypesFlat.length} values`;
1297
+ this.err(`assignment mismatch: ${vars} but ${vals}`, spec);
1298
+ }
903
1299
  for (let i = 0; i < spec.names.length; i++) {
904
- const vt = valTypes[i] ?? ANY;
905
- this.assertAssignable(type, vt, spec.value[i]);
1300
+ const vt = valTypesFlat[i] ?? ANY;
1301
+ const varType = type ?? defaultType(vt) ?? ANY;
1302
+ if (!type && spec.names.length === spec.value.length)
1303
+ this._checkConstDefault(vt, spec.value[i], "variable declaration");
1304
+ if (type)
1305
+ this.assertAssignable(
1306
+ type,
1307
+ vt,
1308
+ spec.value[i] ?? spec.value[0],
1309
+ "variable declaration",
1310
+ );
1311
+ scope.defineLocal(spec.names[i], varType);
906
1312
  }
1313
+ } else {
1314
+ if (!type) type = ANY;
1315
+ for (const name of spec.names) scope.defineLocal(name, type);
907
1316
  }
908
- if (!type) type = ANY;
909
- for (const name of spec.names) scope.defineLocal(name, type);
910
1317
  }
911
1318
  this._sharedAllocOk = savedSharedAlloc;
912
1319
  }
@@ -918,7 +1325,18 @@ export class TypeChecker {
918
1325
  const type = spec.type
919
1326
  ? this.resolveTypeNode(spec.type, scope)
920
1327
  : (valTypes[0] ?? ANY);
921
- for (const name of spec.names) scope.defineConst(name, type);
1328
+ for (let i = 0; i < spec.names.length; i++) {
1329
+ const name = spec.names[i];
1330
+ const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
1331
+ if (spec.type && expr)
1332
+ this._checkConstOverflow(
1333
+ type,
1334
+ valTypes[i] ?? valTypes[0],
1335
+ expr,
1336
+ "constant declaration",
1337
+ );
1338
+ scope.defineConst(name, type, expr);
1339
+ }
922
1340
  }
923
1341
  }
924
1342
 
@@ -930,3 +1348,4 @@ Object.assign(TypeChecker.prototype, expressionCheckMethods);
930
1348
  Object.assign(TypeChecker.prototype, terminationMethods);
931
1349
  Object.assign(TypeChecker.prototype, resolveMethods);
932
1350
  Object.assign(TypeChecker.prototype, assignabilityMethods);
1351
+ Object.assign(TypeChecker.prototype, constantCheckMethods);