gofront 1.7.0 → 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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  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 +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  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 +10 -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 +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  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 +512 -77
  34. package/src/backend/wasm/encode.js +43 -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/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /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";
@@ -58,21 +118,34 @@ export class TypeChecker {
58
118
  this._deferCount = 0; // tracks defer usage in current function body
59
119
  this._imports = []; // tracked imports for unused-import detection
60
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
61
123
  this._setupGlobals();
62
124
  // Register error as a named type (interface)
63
125
  this.types.set("error", ERROR);
64
126
  }
65
127
 
66
128
  _setupGlobals() {
67
- setupGlobals(this.globals, this.types);
129
+ setupGlobals(this.universe, this.types);
68
130
  }
69
131
 
70
- 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 } = {}) {
71
138
  for (const [name, type] of types) {
72
139
  this.types.set(name, type);
73
140
  }
74
141
  for (const [name, type] of values) {
75
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);
76
149
  }
77
150
  }
78
151
 
@@ -80,7 +153,9 @@ export class TypeChecker {
80
153
  // e.g. addPackageNamespace('utils', symbolsMap, typesMap)
81
154
  // lets callers type-check `utils.Foo` via SelectorExpr.
82
155
  // `target` is the imported package's compile target ("js"/"wasm"/"both").
83
- 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) {
84
159
  const members = {};
85
160
  for (const [name, type] of symbols) members[name] = type;
86
161
  // _gofront: true marks this as a GoFront package — exported identifier rules apply
@@ -90,8 +165,15 @@ export class TypeChecker {
90
165
  members,
91
166
  _gofront: true,
92
167
  _target: target,
168
+ _consts: consts,
93
169
  });
94
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
+ }
95
177
  this.types.set(name, type);
96
178
  this.types.set(`${pkgName}.${name}`, type);
97
179
  }
@@ -108,7 +190,7 @@ export class TypeChecker {
108
190
  // Report any tracked imports whose package name was never referenced.
109
191
  reportUnusedImports() {
110
192
  for (const { name, node, filename, source } of this._imports) {
111
- if (!this.globals._used.has(name)) {
193
+ if (!this.globals._used.has(name) && !this.universe._used.has(name)) {
112
194
  const saved = [this._currentFile, this._currentSource];
113
195
  this._currentFile = filename;
114
196
  this._currentSource = source;
@@ -130,6 +212,15 @@ export class TypeChecker {
130
212
  return out;
131
213
  }
132
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
+
133
224
  getExportedTypes() {
134
225
  return new Map(this.types);
135
226
  }
@@ -269,11 +360,15 @@ export class TypeChecker {
269
360
  }
270
361
  }
271
362
 
363
+ this._checkDirectivesAndImportsPass(programs);
364
+ this._registerConstExprs(programs);
272
365
  this._collectTypesPass(programs);
273
366
  this._collectFuncsPass(programs);
367
+ this._checkGlobalTypeNamesPass(programs);
274
368
  this._promoteEmbeddedMethods();
275
369
  this._collectVarsConstsPass(programs);
276
370
  this._checkTopDeclsPass(programs);
371
+ this._reportInitCycles(programs);
277
372
 
278
373
  if (this.target === "both") {
279
374
  this._checkMutablePackageVars(programs);
@@ -290,6 +385,42 @@ export class TypeChecker {
290
385
  return this.errors;
291
386
  }
292
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
+
293
424
  _checkMutablePackageVars(programs) {
294
425
  const pkgVars = new Set();
295
426
  for (const p of programs) {
@@ -409,6 +540,18 @@ export class TypeChecker {
409
540
  for (const d of p.decls) {
410
541
  if (d.kind === "TypeDecl" && !d.isAlias && !this.types.has(d.name)) {
411
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
+ }
412
555
  this.types.set(d.name, placeholder);
413
556
  this.globals.define(d.name, placeholder);
414
557
  }
@@ -418,6 +561,7 @@ export class TypeChecker {
418
561
  for (const p of programs) {
419
562
  for (const d of p.decls) if (d.kind === "TypeDecl") all.push({ p, d });
420
563
  }
564
+ this._expandGenericStructDefs(all.map((x) => x.d));
421
565
  // Collect in dependency order so aliases, generics, embedded and by-value
422
566
  // field types declared later (or in other files) are ready when needed.
423
567
  let pending = all;
@@ -436,6 +580,57 @@ export class TypeChecker {
436
580
  }
437
581
  }
438
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
+
439
634
  _typeDeclBlocked(decl, pendingNames) {
440
635
  const visit = (node, indirect) => {
441
636
  if (!node || typeof node !== "object") return false;
@@ -475,6 +670,22 @@ export class TypeChecker {
475
670
  }
476
671
  }
477
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
+
478
689
  _collectVarsConstsPass(programs) {
479
690
  for (const p of programs) {
480
691
  this._setCurrentFile(p);
@@ -485,14 +696,30 @@ export class TypeChecker {
485
696
  }
486
697
  }
487
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
+
488
712
  _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
- }
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);
496
723
  }
497
724
  for (const p of programs) {
498
725
  this._setCurrentFile(p);
@@ -512,16 +739,20 @@ export class TypeChecker {
512
739
 
513
740
  _promoteEmbeddedMethods() {
514
741
  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
- }
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);
525
756
  }
526
757
  }
527
758
  }
@@ -529,30 +760,39 @@ export class TypeChecker {
529
760
  // ── Type collection ──────────────────────────────────────────
530
761
 
531
762
  collectType(decl) {
763
+ if (decl.typeParams && decl.isAlias) {
764
+ this._collectGenericAlias(decl);
765
+ return;
766
+ }
532
767
  if (decl.typeParams) {
533
768
  // Generic type — resolve underlying with type params as ANY for struct fields
534
769
  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
- };
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
+ };
541
780
  typeScope.define(tp.name, t);
542
781
  return t;
543
782
  });
544
- const underlying = this.resolveTypeNode(decl.type, typeScope);
545
783
  const existing = this.types.get(decl.name);
546
784
  const named =
547
785
  existing?.kind === "named"
548
786
  ? existing
549
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);
550
794
  named.underlying = underlying;
551
795
  named._target = this.target;
552
- named._generic = {
553
- typeParams: typeParamTypes,
554
- declNode: decl,
555
- };
556
796
  if (underlying.kind === "struct") {
557
797
  underlying.name = decl.name;
558
798
  underlying.methods = new Map();
@@ -561,7 +801,15 @@ export class TypeChecker {
561
801
  this.globals.define(decl.name, named);
562
802
  return;
563
803
  }
564
- 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
+ }
565
813
  if (decl.isAlias) {
566
814
  // type A = B — transparent alias, A and B are identical types
567
815
  this.types.set(decl.name, underlying);
@@ -588,6 +836,29 @@ export class TypeChecker {
588
836
  this.globals.define(decl.name, named);
589
837
  }
590
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
+
591
862
  _buildCollectFuncScope(decl) {
592
863
  let resolveScope = this.globals;
593
864
  let typeParamTypes = null;
@@ -623,6 +894,7 @@ export class TypeChecker {
623
894
  name: decl.name,
624
895
  typeParams: typeParamTypes,
625
896
  underlying: funcType,
897
+ declNode: decl,
626
898
  });
627
899
  } else {
628
900
  this.globals.define(decl.name, funcType);
@@ -663,14 +935,18 @@ export class TypeChecker {
663
935
  const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
664
936
  const base = recvType?.underlying ?? recvType;
665
937
  if (base?.kind === "struct") {
938
+ base.methods ??= new Map();
666
939
  base.methods.set(decl.name, funcType);
667
- } else if (recvType?.kind === "named" && recvType.methods) {
940
+ }
941
+ if (recvType?.kind === "named") {
942
+ recvType.methods ??= new Map();
668
943
  recvType.methods.set(decl.name, funcType);
669
944
  }
670
945
  }
671
946
 
672
947
  _attachMethod(decl, funcType) {
673
948
  funcType._ptrRecv = Boolean(decl.recvPointer);
949
+ funcType._decl = decl;
674
950
  const recvNamedType = this.types.get(decl.recvType.name);
675
951
  if (recvNamedType?._generic)
676
952
  this._attachGenericMethod(recvNamedType, decl, funcType);
@@ -697,42 +973,111 @@ export class TypeChecker {
697
973
  case "CssDecl":
698
974
  break;
699
975
  case "TemplDecl":
700
- this._markTemplNodesUsed(decl.body);
976
+ this._checkTemplDecl(decl, scope);
701
977
  break;
702
978
  }
703
979
  }
704
980
 
705
- _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) {
706
1051
  if (!tokens) return;
707
1052
  for (const t of tokens) {
708
1053
  if (t.type === "IDENT" || t.type?.name === "IDENT") {
709
- this.globals.lookup(t.value);
1054
+ scope.lookup(t.value);
710
1055
  }
711
1056
  }
712
1057
  }
713
1058
 
714
- _markTemplNodesUsed(nodes) {
1059
+ _markTemplNodesUsed(nodes, scope) {
715
1060
  if (!nodes) return;
716
1061
  for (const n of nodes) {
717
1062
  if (n.kind === "TemplElement") {
718
1063
  for (const a of n.attrs || []) {
719
- if (a.tokens) this._markTokensUsed(a.tokens);
1064
+ if (a.tokens) this._markTokensUsed(a.tokens, scope);
720
1065
  }
721
- this._markTemplNodesUsed(n.children);
1066
+ this._markTemplNodesUsed(n.children, scope);
722
1067
  } else if (n.kind === "TemplExpr" || n.kind === "TemplComponent") {
723
- this._markTokensUsed(n.tokens);
1068
+ this._markTokensUsed(n.tokens, scope);
724
1069
  } else if (n.kind === "TemplIf") {
725
- this._markTokensUsed(n.condTokens);
726
- this._markTemplNodesUsed(n.then);
727
- this._markTemplNodesUsed(n.else_);
1070
+ this._markTokensUsed(n.condTokens, scope);
1071
+ this._markTemplNodesUsed(n.then, scope);
1072
+ this._markTemplNodesUsed(n.else_, scope);
728
1073
  } else if (n.kind === "TemplFor") {
729
- this._markTokensUsed(n.stmtTokens);
730
- this._markTemplNodesUsed(n.body);
1074
+ this._markTokensUsed(n.stmtTokens, scope);
1075
+ this._markTemplNodesUsed(n.body, scope);
731
1076
  } else if (n.kind === "TemplSwitch") {
732
- this._markTokensUsed(n.exprTokens);
1077
+ this._markTokensUsed(n.exprTokens, scope);
733
1078
  for (const c of n.cases || []) {
734
- this._markTokensUsed(c.caseTokens);
735
- this._markTemplNodesUsed(c.body);
1079
+ this._markTokensUsed(c.caseTokens, scope);
1080
+ this._markTemplNodesUsed(c.body, scope);
736
1081
  }
737
1082
  }
738
1083
  }
@@ -786,7 +1131,24 @@ export class TypeChecker {
786
1131
  this.globals.define(decl.name, funcType);
787
1132
  }
788
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
+
789
1150
  checkFuncDecl(decl, outer) {
1151
+ if (decl.intrinsic) this._checkIntrinsic(decl);
790
1152
  if (
791
1153
  (this.target === "wasm" || this.target === "both") &&
792
1154
  (decl.async || decl.isAsync)
@@ -800,9 +1162,9 @@ export class TypeChecker {
800
1162
  const inner = new Scope(outer);
801
1163
  this._injectTypeParams(decl, inner, outer);
802
1164
  for (const p of decl.params) {
803
- inner.define(p.name, this.resolveTypeNode(p.type, inner));
1165
+ inner.define(p.name, this._paramType(p, inner));
804
1166
  }
805
- const returnType = this._setupFuncReturnType(decl, inner, outer);
1167
+ const returnType = this._setupFuncReturnType(decl, inner);
806
1168
  const savedSharedAlloc = this._sharedAllocOk;
807
1169
  this._sharedAllocOk = decl.name === "init";
808
1170
  this._runFuncBody(decl, inner, returnType);
@@ -828,19 +1190,26 @@ export class TypeChecker {
828
1190
  : decl.recvType.name;
829
1191
  const recvNamedType = this.types.get(recvTypeName);
830
1192
  this._injectGenericReceiverTypeParams(inner, recvNamedType);
831
- const recvType = this.resolveTypeNodeName(decl.recvType, outer);
1193
+ const recvType = this._methodReceiverType(decl, recvNamedType, outer);
832
1194
  inner.define(
833
1195
  decl.recvName,
834
1196
  decl.recvPointer ? { kind: "pointer", base: recvType } : recvType,
835
1197
  );
836
1198
  for (const p of decl.params) {
837
- inner.define(p.name, this.resolveTypeNode(p.type, inner));
1199
+ inner.define(p.name, this._paramType(p, inner));
838
1200
  }
839
- const returnType = this._setupFuncReturnType(decl, inner, inner);
1201
+ const returnType = this._setupFuncReturnType(decl, inner);
840
1202
  this._runFuncBody(decl, inner, returnType);
841
1203
  this._checkMissingReturn(decl, returnType, "method");
842
1204
  }
843
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
+
844
1213
  _injectTypeParams(decl, inner, outer) {
845
1214
  if (!decl.typeParams) return;
846
1215
  for (const tp of decl.typeParams) {
@@ -851,6 +1220,16 @@ export class TypeChecker {
851
1220
  }
852
1221
  }
853
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
+
854
1233
  _injectGenericReceiverTypeParams(inner, recvNamedType) {
855
1234
  if (!recvNamedType?._generic) return;
856
1235
  for (const tp of recvNamedType._generic.typeParams) {
@@ -858,15 +1237,11 @@ export class TypeChecker {
858
1237
  }
859
1238
  }
860
1239
 
861
- _setupFuncReturnType(decl, inner, outer) {
1240
+ _setupFuncReturnType(decl, inner) {
862
1241
  let returnType = decl.returnType
863
1242
  ? this.resolveTypeNode(decl.returnType, inner)
864
1243
  : VOID;
865
- const hasNamedReturns = this._injectNamedReturns(
866
- decl.returnType,
867
- inner,
868
- outer,
869
- );
1244
+ const hasNamedReturns = this._injectNamedReturns(decl.returnType, inner);
870
1245
  if (hasNamedReturns && returnType)
871
1246
  returnType = { ...returnType, _hasNamedReturns: true };
872
1247
  return returnType;
@@ -890,10 +1265,12 @@ export class TypeChecker {
890
1265
  }
891
1266
  }
892
1267
 
893
- _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) {
894
1271
  if (!returnTypeNode?._namedReturns) return false;
895
1272
  for (const { name, type } of returnTypeNode._namedReturns) {
896
- if (name) scope.define(name, this.resolveTypeNode(type, outer));
1273
+ if (name) scope.define(name, this.resolveTypeNode(type, scope));
897
1274
  }
898
1275
  return true;
899
1276
  }
@@ -922,8 +1299,15 @@ export class TypeChecker {
922
1299
  for (let i = 0; i < spec.names.length; i++) {
923
1300
  const vt = valTypesFlat[i] ?? ANY;
924
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");
925
1304
  if (type)
926
- this.assertAssignable(type, vt, spec.value[i] ?? spec.value[0]);
1305
+ this.assertAssignable(
1306
+ type,
1307
+ vt,
1308
+ spec.value[i] ?? spec.value[0],
1309
+ "variable declaration",
1310
+ );
927
1311
  scope.defineLocal(spec.names[i], varType);
928
1312
  }
929
1313
  } else {
@@ -944,6 +1328,13 @@ export class TypeChecker {
944
1328
  for (let i = 0; i < spec.names.length; i++) {
945
1329
  const name = spec.names[i];
946
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
+ );
947
1338
  scope.defineConst(name, type, expr);
948
1339
  }
949
1340
  }
@@ -957,3 +1348,4 @@ Object.assign(TypeChecker.prototype, expressionCheckMethods);
957
1348
  Object.assign(TypeChecker.prototype, terminationMethods);
958
1349
  Object.assign(TypeChecker.prototype, resolveMethods);
959
1350
  Object.assign(TypeChecker.prototype, assignabilityMethods);
1351
+ Object.assign(TypeChecker.prototype, constantCheckMethods);