gofront 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -21,12 +21,14 @@ import { setupGlobals } from "./stdlib/index.js";
21
21
  import { terminationMethods } from "./termination.js";
22
22
  import {
23
23
  ANY,
24
+ BOOL,
24
25
  defaultType,
25
26
  ERROR,
26
27
  INTRINSICS,
27
28
  isAny,
28
29
  isBool,
29
30
  isVoid,
31
+ NATIVES,
30
32
  Scope,
31
33
  TAINTED_ANY,
32
34
  TypeCheckError,
@@ -39,6 +41,7 @@ import {
39
41
  const TEMPL_NODE_CHECKS = {
40
42
  TemplElement: "_checkTemplElement",
41
43
  TemplExpr: "_checkTemplLeaf",
44
+ TemplChildren: "_checkTemplChildren",
42
45
  TemplComponent: "_checkTemplLeaf",
43
46
  TemplIf: "_checkTemplIf",
44
47
  TemplFor: "_checkTemplFor",
@@ -105,6 +108,7 @@ export class TypeChecker {
105
108
  this.universe = new Scope();
106
109
  this.globals = new Scope(this.universe);
107
110
  this.errors = [];
111
+ this._reportedAt = new WeakMap();
108
112
  this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
109
113
  this.target = "js";
110
114
  this.pkgName = null;
@@ -117,6 +121,12 @@ export class TypeChecker {
117
121
  this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
118
122
  this._deferCount = 0; // tracks defer usage in current function body
119
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
120
130
  this._constExprs = new Map(); // name -> AST initializer expr
121
131
  this._importedConsts = new Map(); // dot-imported const name -> value
122
132
  this._externalNames = new Set(); // package-scope names from .d.ts / npm definitions
@@ -174,7 +184,10 @@ export class TypeChecker {
174
184
  this._ambiguousImported.add(prior);
175
185
  this._ambiguousImported.add(type);
176
186
  }
177
- this.types.set(name, type);
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);
178
191
  this.types.set(`${pkgName}.${name}`, type);
179
192
  }
180
193
  }
@@ -255,6 +268,14 @@ export class TypeChecker {
255
268
  }
256
269
 
257
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
+ }
258
279
  const e = new TypeCheckError(
259
280
  msg,
260
281
  node,
@@ -702,7 +723,11 @@ export class TypeChecker {
702
723
  const { cycles } = orderPackageInits(programs);
703
724
  for (const c of cycles) {
704
725
  const owner = programs.find((p) =>
705
- p.decls?.some((d) => d.kind === "VarDecl" && d.decls?.includes(c.spec)),
726
+ p.decls?.some(
727
+ (d) =>
728
+ (d.kind === "VarDecl" || d.kind === "ConstDecl") &&
729
+ d.decls?.includes(c.spec),
730
+ ),
706
731
  );
707
732
  if (owner) this._setCurrentFile(owner);
708
733
  this.err(c.message, c.spec);
@@ -725,6 +750,11 @@ export class TypeChecker {
725
750
  this._setCurrentFile(p);
726
751
  for (const d of p.decls) {
727
752
  if (
753
+ this.lazyBodies &&
754
+ (d.kind === "FuncDecl" || d.kind === "MethodDecl")
755
+ )
756
+ this._deferBody(d, p);
757
+ else if (
728
758
  d.kind === "FuncDecl" ||
729
759
  d.kind === "MethodDecl" ||
730
760
  d.kind === "TemplDecl"
@@ -735,14 +765,48 @@ export class TypeChecker {
735
765
  }
736
766
  }
737
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
+
738
785
  // ── Shared pass helpers ──────────────────────────────────────
739
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.
740
791
  _promoteEmbeddedMethods() {
792
+ const structs = [];
741
793
  for (const type of this.types.values()) {
742
- this._promoteEmbeddedInto(type.underlying);
743
- for (const inst of type._generic?._instances?.values() ?? [])
744
- this._promoteEmbeddedInto(inst.underlying);
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
+ }
745
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);
746
810
  }
747
811
 
748
812
  _promoteEmbeddedInto(struct) {
@@ -750,8 +814,21 @@ export class TypeChecker {
750
814
  for (const embed of struct._embeds) {
751
815
  const inner = embed.kind === "pointer" ? embed.base : embed;
752
816
  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()) {
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()) {
755
832
  if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
756
833
  }
757
834
  }
@@ -801,6 +878,9 @@ export class TypeChecker {
801
878
  this.globals.define(decl.name, named);
802
879
  return;
803
880
  }
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);
804
884
  let underlying = this.resolveTypeNode(decl.type, this.globals);
805
885
  // `type W V` with V a named array: W's underlying type is V's array.
806
886
  if (!decl.isAlias && underlying.kind === "named") {
@@ -836,6 +916,13 @@ export class TypeChecker {
836
916
  this.globals.define(decl.name, named);
837
917
  }
838
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
+
839
926
  // type A[P any] = T[P] — Go 1.24 generic alias. The declaration is a
840
927
  // template only: each instantiation A[X] substitutes X into the aliased
841
928
  // type and resolves to exactly that type (no new named type).
@@ -888,6 +975,9 @@ export class TypeChecker {
888
975
  _registerFuncDecl(decl, funcType, typeParamTypes) {
889
976
  if (this.globals.symbols.has(decl.name) && decl.name !== "init")
890
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);
891
981
  if (typeParamTypes) {
892
982
  this.globals.define(decl.name, {
893
983
  kind: "generic",
@@ -932,6 +1022,9 @@ export class TypeChecker {
932
1022
  }
933
1023
 
934
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;
935
1028
  const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
936
1029
  const base = recvType?.underlying ?? recvType;
937
1030
  if (base?.kind === "struct") {
@@ -1008,12 +1101,26 @@ export class TypeChecker {
1008
1101
 
1009
1102
  _checkTemplElement(n, scope) {
1010
1103
  for (const a of n.attrs ?? []) {
1011
- if (a.kind === "expr" || a.kind === "cond-bool")
1012
- this._checkTemplExpr(a, a.tokens, scope);
1104
+ if (a.kind === "expr") this._checkTemplExpr(a, a.tokens, scope);
1105
+ else if (a.kind === "cond-bool") this._checkTemplCondAttr(a, scope);
1013
1106
  }
1014
1107
  this._checkTemplNodes(n.children, scope);
1015
1108
  }
1016
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
+
1017
1124
  _checkTemplLeaf(n, scope) {
1018
1125
  this._checkTemplExpr(n, n.tokens, scope);
1019
1126
  }
@@ -1028,8 +1135,13 @@ export class TypeChecker {
1028
1135
 
1029
1136
  _checkTemplSwitch(n, scope) {
1030
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;
1031
1140
  for (const c of n.cases ?? []) {
1032
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);
1033
1145
  this._checkTemplNodes(c.body, scope);
1034
1146
  }
1035
1147
  }
@@ -1147,8 +1259,27 @@ export class TypeChecker {
1147
1259
  );
1148
1260
  }
1149
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
+
1150
1279
  checkFuncDecl(decl, outer) {
1280
+ this.stats.bodiesChecked++;
1151
1281
  if (decl.intrinsic) this._checkIntrinsic(decl);
1282
+ if (decl.native) this._checkNative(decl);
1152
1283
  if (
1153
1284
  (this.target === "wasm" || this.target === "both") &&
1154
1285
  (decl.async || decl.isAsync)
@@ -1173,6 +1304,8 @@ export class TypeChecker {
1173
1304
  }
1174
1305
 
1175
1306
  checkMethodDecl(decl, outer) {
1307
+ this.stats.bodiesChecked++;
1308
+ if (decl.native) this._checkNative(decl);
1176
1309
  if (
1177
1310
  (this.target === "wasm" || this.target === "both") &&
1178
1311
  (decl.async || decl.isAsync)
@@ -1199,7 +1332,13 @@ export class TypeChecker {
1199
1332
  inner.define(p.name, this._paramType(p, inner));
1200
1333
  }
1201
1334
  const returnType = this._setupFuncReturnType(decl, inner);
1202
- this._runFuncBody(decl, inner, returnType);
1335
+ const savedRecvGeneric = this._recvGeneric;
1336
+ this._recvGeneric = recvNamedType?._generic;
1337
+ try {
1338
+ this._runFuncBody(decl, inner, returnType);
1339
+ } finally {
1340
+ this._recvGeneric = savedRecvGeneric;
1341
+ }
1203
1342
  this._checkMissingReturn(decl, returnType, "method");
1204
1343
  }
1205
1344
 
@@ -1210,14 +1349,19 @@ export class TypeChecker {
1210
1349
  return p.variadic ? { kind: "slice", elem: t } : t;
1211
1350
  }
1212
1351
 
1213
- _injectTypeParams(decl, inner, outer) {
1352
+ _injectTypeParams(decl, inner, _outer) {
1214
1353
  if (!decl.typeParams) return;
1215
- for (const tp of decl.typeParams) {
1216
- const constraint = tp.constraint
1217
- ? this.resolveTypeNode(tp.constraint, outer)
1218
- : ANY;
1219
- inner.define(tp.name, { kind: "typeParam", name: tp.name, constraint });
1220
- }
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
+ });
1221
1365
  }
1222
1366
 
1223
1367
  // Inside a method of `G[T]` the receiver is the instance `G[T]` over the
@@ -1258,7 +1402,11 @@ export class TypeChecker {
1258
1402
  }
1259
1403
 
1260
1404
  _checkMissingReturn(decl, returnType, kind) {
1261
- if (!isVoid(returnType) && !returnType._hasNamedReturns) {
1405
+ if (
1406
+ !decl._bodyless &&
1407
+ !isVoid(returnType) &&
1408
+ !returnType._hasNamedReturns
1409
+ ) {
1262
1410
  if (!this._isTerminating(decl.body)) {
1263
1411
  this.err(`missing return at end of ${kind} '${decl.name}'`, decl);
1264
1412
  }
@@ -1281,21 +1429,7 @@ export class TypeChecker {
1281
1429
  for (const spec of decl.decls) {
1282
1430
  let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
1283
1431
  if (spec.value) {
1284
- const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
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
- }
1432
+ const valTypesFlat = this._varSpecValueTypes(spec, scope);
1299
1433
  for (let i = 0; i < spec.names.length; i++) {
1300
1434
  const vt = valTypesFlat[i] ?? ANY;
1301
1435
  const varType = type ?? defaultType(vt) ?? ANY;
@@ -1318,6 +1452,45 @@ export class TypeChecker {
1318
1452
  this._sharedAllocOk = savedSharedAlloc;
1319
1453
  }
1320
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
+
1321
1494
  checkConstDecl(decl, scope) {
1322
1495
  for (const spec of decl.decls) {
1323
1496
  const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
@@ -1335,6 +1508,8 @@ export class TypeChecker {
1335
1508
  expr,
1336
1509
  "constant declaration",
1337
1510
  );
1511
+ // `const D Duration = 1` is a BigInt in JS.
1512
+ if (spec.type && expr) this._tag64BitNode(this.resolveType(type), expr);
1338
1513
  scope.defineConst(name, type, expr);
1339
1514
  }
1340
1515
  }
@@ -8,10 +8,9 @@
8
8
  // (a constant may name one declared later).
9
9
  //
10
10
  // The checker reports cycles; both backends emit initialisers in this order.
11
- // Shadowing is handled conservatively: a name declared anywhere in a function
12
- // body (or as a parameter) is never counted as a reference to the package-level
13
- // name inside that function, so the analysis can miss a dependency but never
14
- // invents a cycle.
11
+ // Shadowing follows Go's block scoping: a name declared by a parameter, result,
12
+ // receiver or statement hides the package-level one from the next statement to
13
+ // the end of its block, and not outside it.
15
14
 
16
15
  function forEachChildNode(node, fn) {
17
16
  for (const key of Object.keys(node)) {
@@ -21,34 +20,35 @@ function forEachChildNode(node, fn) {
21
20
  }
22
21
  }
23
22
 
24
- // Names declared inside a function-like node (parameters, results, locals).
25
- function declaredNames(node, out = new Set()) {
26
- if (!node || typeof node !== "object") return out;
27
- if (Array.isArray(node)) {
28
- for (const n of node) declaredNames(n, out);
29
- return out;
23
+ // A lexical scope of locally declared names.
24
+ class Shadow {
25
+ constructor(parent = null) {
26
+ this.parent = parent;
27
+ this.names = new Set();
30
28
  }
31
- if (node.kind === "DefineStmt") {
32
- for (const l of [node.lhs].flat()) if (l?.kind === "Ident") out.add(l.name);
33
- } else if (node.kind === "VarDecl" || node.kind === "ConstDecl") {
34
- for (const spec of node.decls ?? []) for (const n of spec.names) out.add(n);
35
- } else if (node.kind === "RangeStmt" || node.kind === "ForRangeStmt") {
36
- for (const k of [node.key, node.value, node.keyVar, node.valueVar])
37
- if (k?.kind === "Ident") out.add(k.name);
38
- if (typeof node.key === "string") out.add(node.key);
39
- if (typeof node.value === "string") out.add(node.value);
40
- } else if (node.kind === "TypeSwitchStmt" && node.assign) {
41
- out.add(node.assign);
29
+
30
+ has(name) {
31
+ for (let s = this; s; s = s.parent) if (s.names.has(name)) return true;
32
+ return false;
42
33
  }
43
- if (Array.isArray(node.params))
44
- for (const p of node.params) if (p?.name) out.add(p.name);
45
- if (node.recvName) out.add(node.recvName);
46
- for (const r of node.returnType?._namedReturns ?? [])
47
- if (r.name) out.add(r.name);
48
- forEachChildNode(node, (c) => declaredNames(c, out));
34
+
35
+ add(...names) {
36
+ for (const n of names) if (n) this.names.add(n);
37
+ }
38
+ }
39
+
40
+ // Names a function-like node declares for its body: receiver, parameters and
41
+ // named results.
42
+ function signatureNames(node) {
43
+ const out = [];
44
+ if (node.recvName) out.push(node.recvName);
45
+ for (const p of node.params ?? []) if (p?.name) out.push(p.name);
46
+ for (const r of node.returnType?._namedReturns ?? []) out.push(r.name);
49
47
  return out;
50
48
  }
51
49
 
50
+ const lhsName = (l) => (typeof l === "string" ? l : l?.name);
51
+
52
52
  // The `Type.method` key of the declaration `name` selects on a value of type
53
53
  // `t`, found the way the checker finds it: on the type itself, else on an
54
54
  // embedded type (through pointers too). Interface methods have no body to
@@ -83,6 +83,60 @@ function litHasExprKeys(lit) {
83
83
  return ["MapType", "SliceType", "ArrayType"].includes(lit.typeExpr?.kind);
84
84
  }
85
85
 
86
+ // Statements that open a scope and declare names as they run. Each handler
87
+ // walks the node's children in order, adding declarations to `shadow` after
88
+ // the expressions that cannot see them.
89
+ const SCOPED = {
90
+ FuncDecl: walkFunc,
91
+ MethodDecl: walkFunc,
92
+ FuncLit: walkFunc,
93
+ Block: (node, shadow, ctx, out) =>
94
+ walkRefs(node.stmts, new Shadow(shadow), ctx, out),
95
+ DefineStmt: (node, shadow, ctx, out) => {
96
+ walkRefs(node.rhs, shadow, ctx, out);
97
+ shadow.add(...[node.lhs].flat().map(lhsName));
98
+ },
99
+ VarDecl: walkDeclSpecs,
100
+ ConstDecl: walkDeclSpecs,
101
+ IfStmt: (node, shadow, ctx, out) => {
102
+ const inner = new Shadow(shadow);
103
+ for (const c of [node.init, node.cond, node.body, node.elseBody])
104
+ walkRefs(c, inner, ctx, out);
105
+ },
106
+ ForStmt: (node, shadow, ctx, out) => {
107
+ const inner = new Shadow(shadow);
108
+ for (const c of [node.init, node.cond, node.post, node.body])
109
+ walkRefs(c, inner, ctx, out);
110
+ },
111
+ SwitchStmt: walkSwitch,
112
+ TypeSwitchStmt: walkSwitch,
113
+ };
114
+
115
+ function walkFunc(node, shadow, ctx, out) {
116
+ const inner = new Shadow(shadow);
117
+ inner.add(...signatureNames(node));
118
+ walkRefs(node.body, inner, ctx, out);
119
+ }
120
+
121
+ function walkDeclSpecs(node, shadow, ctx, out) {
122
+ for (const spec of node.decls ?? []) {
123
+ walkRefs(spec.value, shadow, ctx, out);
124
+ shadow.add(...(spec.names ?? []));
125
+ }
126
+ }
127
+
128
+ function walkSwitch(node, shadow, ctx, out) {
129
+ const inner = new Shadow(shadow);
130
+ walkRefs(node.init, inner, ctx, out);
131
+ walkRefs(node.tag ?? node.expr, inner, ctx, out);
132
+ for (const c of node.cases ?? []) {
133
+ const clause = new Shadow(inner);
134
+ if (node.assign) clause.add(node.assign);
135
+ walkRefs(c.list, clause, ctx, out);
136
+ walkRefs(c.stmts, clause, ctx, out);
137
+ }
138
+ }
139
+
86
140
  function walkRefs(node, shadow, ctx, out) {
87
141
  if (!node || typeof node !== "object") return;
88
142
  if (Array.isArray(node)) {
@@ -90,10 +144,17 @@ function walkRefs(node, shadow, ctx, out) {
90
144
  return;
91
145
  }
92
146
  if (node.kind === "Ident") {
93
- if (ctx.pkgNames.has(node.name) && !shadow.has(node.name))
147
+ // The blank identifier names nothing, whatever `var _` the package has.
148
+ if (
149
+ node.name !== "_" &&
150
+ ctx.pkgNames.has(node.name) &&
151
+ !shadow.has(node.name)
152
+ )
94
153
  out.names.add(node.name);
95
154
  return;
96
155
  }
156
+ const scoped = SCOPED[node.kind];
157
+ if (scoped) return scoped(node, shadow, ctx, out);
97
158
  if (node.kind === "SelectorExpr") {
98
159
  const key = methodKeyOf(node.expr?._type, node.field, ctx.funcs);
99
160
  if (key) out.methods.add(key);
@@ -118,11 +179,10 @@ function walkRefs(node, shadow, ctx, out) {
118
179
  /**
119
180
  * Collects what `root` (an initialiser expression or a function declaration)
120
181
  * refers to: package-level names (`names`) and declarations selected as methods
121
- * (`methods`, keyed `Type.method`). The single place shadowing is decided:
122
- * a name declared anywhere inside `root` never counts as the package-level one.
182
+ * (`methods`, keyed `Type.method`), honouring block-scoped shadowing.
123
183
  */
124
184
  function collectRefs(root, ctx, out) {
125
- walkRefs(root, declaredNames(root), ctx, out);
185
+ walkRefs(root, new Shadow(), ctx, out);
126
186
  }
127
187
 
128
188
  const emptyRefs = () => ({ names: new Set(), methods: new Set() });
@@ -261,24 +321,52 @@ function orderVars(varEntries, depsOf) {
261
321
  return { vars, cycles: [] };
262
322
  }
263
323
 
264
- // Constants depend on other constants only.
324
+ // Constants depend on other constants only; a constant that reaches itself is
325
+ // a cycle (reported once, at the constant where the walk closed it).
265
326
  function orderConsts(constEntries) {
266
327
  const constNames = new Set(constEntries.flatMap((e) => e.names));
267
328
  const ctx = { pkgNames: constNames, funcs: new Map() };
268
329
  const owner = new Map();
269
330
  for (const e of constEntries) for (const n of e.names) owner.set(n, e);
270
331
  const consts = [];
271
- const state = new Set();
332
+ const cycles = [];
333
+ const state = new Map(); // entry -> 1 while on the walk's path, 2 when done
334
+ const path = [];
272
335
  const visit = (e) => {
273
- if (state.has(e)) return;
274
- state.add(e);
336
+ state.set(e, 1);
337
+ path.push(e);
275
338
  const refs = emptyRefs();
276
339
  for (const v of e.spec.value ?? []) collectRefs(v, ctx, refs);
277
- for (const n of refs.names) if (owner.has(n)) visit(owner.get(n));
340
+ for (const n of refs.names) {
341
+ const dep = owner.get(n);
342
+ if (!dep) continue;
343
+ if (state.get(dep) === 1) {
344
+ const names = path.slice(path.indexOf(dep)).map((p) => p.names[0]);
345
+ const chain = names.length === 1 ? names : [...names, names[0]];
346
+ cycles.push({ spec: dep.spec, message: cycleMessage(dep, chain) });
347
+ } else if (!state.has(dep)) visit(dep);
348
+ }
349
+ path.pop();
350
+ state.set(e, 2);
278
351
  consts.push(e);
279
352
  };
280
- for (const e of constEntries) visit(e);
281
- return consts;
353
+ for (const e of constEntries) if (!state.has(e)) visit(e);
354
+ return { consts, cycles };
355
+ }
356
+
357
+ // A literal initialiser (or none: the zero value) reads nothing and has no
358
+ // effect, and gc initialises it statically, so a function that runs during
359
+ // another variable's initialiser sees it set even when it is declared later
360
+ // (an interface method call adds no dependency in the spec).
361
+ const isStaticValue = (v) =>
362
+ v?.kind === "BasicLit" ||
363
+ (v?.kind === "UnaryExpr" &&
364
+ ["-", "+"].includes(v.op) &&
365
+ isStaticValue(v.operand));
366
+
367
+ function staticInitsFirst(vars) {
368
+ const isStatic = (e) => (e.spec.value ?? []).every(isStaticValue);
369
+ return [...vars.filter(isStatic), ...vars.filter((e) => !isStatic(e))];
282
370
  }
283
371
 
284
372
  /**
@@ -290,8 +378,16 @@ function orderConsts(constEntries) {
290
378
  export function orderPackageInits(programs) {
291
379
  const decls = collectDecls(programs);
292
380
  const depsOf = buildVarDeps(decls);
293
- const { vars, cycles } = orderVars(decls.varEntries, depsOf);
294
- return { consts: orderConsts(decls.constEntries), vars, cycles };
381
+ const ordered = orderVars(decls.varEntries, depsOf);
382
+ const { cycles } = ordered;
383
+ const vars =
384
+ cycles.length === 0 ? staticInitsFirst(ordered.vars) : ordered.vars;
385
+ const constOrder = orderConsts(decls.constEntries);
386
+ return {
387
+ consts: constOrder.consts,
388
+ vars,
389
+ cycles: [...constOrder.cycles, ...cycles],
390
+ };
295
391
  }
296
392
 
297
393
  /**