gofront 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -21,12 +21,15 @@ 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,
32
+ promotedMethods,
30
33
  Scope,
31
34
  TAINTED_ANY,
32
35
  TypeCheckError,
@@ -39,6 +42,7 @@ import {
39
42
  const TEMPL_NODE_CHECKS = {
40
43
  TemplElement: "_checkTemplElement",
41
44
  TemplExpr: "_checkTemplLeaf",
45
+ TemplChildren: "_checkTemplChildren",
42
46
  TemplComponent: "_checkTemplLeaf",
43
47
  TemplIf: "_checkTemplIf",
44
48
  TemplFor: "_checkTemplFor",
@@ -96,6 +100,50 @@ const JS_BUILTIN_CLASSES = new Set([
96
100
  "WebAssembly",
97
101
  ]);
98
102
 
103
+ // The promoted-method entry for candidate `cand`, reusing `prev` when
104
+ // nothing changed so the method keeps its identity across passes. A pointer
105
+ // method reached through an embedded pointer is in the value method set.
106
+ function promotedEntry(cand, prev) {
107
+ if (cand.ambiguous) return { depth: cand.depth, ambiguous: true };
108
+ if (
109
+ !prev?.ambiguous &&
110
+ prev?.source === cand.source &&
111
+ prev.viaPtr === cand.viaPtr &&
112
+ prev.depth === cand.depth
113
+ )
114
+ return prev;
115
+ const mType =
116
+ cand.viaPtr && cand.source?._ptrRecv
117
+ ? { ...cand.source, _ptrRecv: false }
118
+ : cand.source;
119
+ return { ...cand, mType };
120
+ }
121
+
122
+ // Offers every method of the embedded interface `iface` at depth 1.
123
+ function offerInterfaceMethods(iface, offer) {
124
+ for (const [mName, mType] of iface.methods ?? [])
125
+ offer(mName, 1, mType, false);
126
+ }
127
+
128
+ // Replaces the promoted methods `old` with `next` in `methods`, leaving
129
+ // the struct's own methods alone; returns whether anything changed.
130
+ function applyPromotions(methods, old, next, isOwn) {
131
+ let changed = old.size !== next.size;
132
+ for (const [mName, prev] of old) {
133
+ const entry = next.get(mName);
134
+ if (entry?.mType) continue;
135
+ if (prev.mType && !isOwn(mName)) methods.delete(mName);
136
+ if (entry?.ambiguous !== prev.ambiguous || entry?.depth !== prev.depth)
137
+ changed = true;
138
+ }
139
+ for (const [mName, entry] of next) {
140
+ if (entry.ambiguous) continue;
141
+ if (old.get(mName)?.mType !== entry.mType) changed = true;
142
+ methods.set(mName, entry.mType);
143
+ }
144
+ return changed;
145
+ }
146
+
99
147
  export class TypeChecker {
100
148
  constructor() {
101
149
  this.types = new Map(); // named types
@@ -105,6 +153,7 @@ export class TypeChecker {
105
153
  this.universe = new Scope();
106
154
  this.globals = new Scope(this.universe);
107
155
  this.errors = [];
156
+ this._reportedAt = new WeakMap();
108
157
  this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
109
158
  this.target = "js";
110
159
  this.pkgName = null;
@@ -117,9 +166,17 @@ export class TypeChecker {
117
166
  this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
118
167
  this._deferCount = 0; // tracks defer usage in current function body
119
168
  this._imports = []; // tracked imports for unused-import detection
169
+ // Lazy body checking (Go-source stdlib): with `lazyBodies` set, checkAll
170
+ // types declarations only and `checkBody` types a function or method
171
+ // body when it is first reached.
172
+ this.lazyBodies = false;
173
+ this.stats = { bodiesChecked: 0 };
174
+ this._lazyBodies = new Map(); // FuncDecl/MethodDecl -> its program
120
175
  this._constExprs = new Map(); // name -> AST initializer expr
121
176
  this._importedConsts = new Map(); // dot-imported const name -> value
122
177
  this._externalNames = new Set(); // package-scope names from .d.ts / npm definitions
178
+ this._importedTypes = new WeakSet(); // named types reached through an imported package
179
+ this._pendingConstraintUses = []; // constraint-interface checks waiting on a type declaration
123
180
  this._setupGlobals();
124
181
  // Register error as a named type (interface)
125
182
  this.types.set("error", ERROR);
@@ -174,8 +231,12 @@ export class TypeChecker {
174
231
  this._ambiguousImported.add(prior);
175
232
  this._ambiguousImported.add(type);
176
233
  }
177
- this.types.set(name, type);
234
+ // A renamed type (`Pt` declared as `Pt$$b`) never takes the bare name:
235
+ // another package owns it, and this package's own declaration would
236
+ // otherwise reuse the imported placeholder. It is reached as `pkg.Pt`.
237
+ if (type.name === name) this.types.set(name, type);
178
238
  this.types.set(`${pkgName}.${name}`, type);
239
+ this._importedTypes.add(type);
179
240
  }
180
241
  }
181
242
 
@@ -255,6 +316,14 @@ export class TypeChecker {
255
316
  }
256
317
 
257
318
  err(msg, node, hint = null) {
319
+ // One diagnostic per root cause: a node revisited by a later pass (a
320
+ // signature resolved at declaration and again in the body) reports once.
321
+ if (node && typeof node === "object") {
322
+ const seen = this._reportedAt.get(node) ?? new Set();
323
+ this._reportedAt.set(node, seen);
324
+ if (seen.has(msg)) return TAINTED_ANY;
325
+ seen.add(msg);
326
+ }
258
327
  const e = new TypeCheckError(
259
328
  msg,
260
329
  node,
@@ -362,13 +431,17 @@ export class TypeChecker {
362
431
 
363
432
  this._checkDirectivesAndImportsPass(programs);
364
433
  this._registerConstExprs(programs);
434
+ this._registerVarSpecs(programs);
435
+ this._markConstraintPositions(programs);
365
436
  this._collectTypesPass(programs);
437
+ this._flushConstraintUses();
366
438
  this._collectFuncsPass(programs);
367
439
  this._checkGlobalTypeNamesPass(programs);
368
440
  this._promoteEmbeddedMethods();
369
441
  this._collectVarsConstsPass(programs);
370
442
  this._checkTopDeclsPass(programs);
371
443
  this._reportInitCycles(programs);
444
+ this._flushConstraintUses();
372
445
 
373
446
  if (this.target === "both") {
374
447
  this._checkMutablePackageVars(programs);
@@ -686,6 +759,19 @@ export class TypeChecker {
686
759
  }
687
760
  }
688
761
 
762
+ // Package variables by name, so an array length such as `[len(base)]T`
763
+ // can use the type of one that is declared later.
764
+ _registerVarSpecs(programs) {
765
+ this._pkgVarSpecs = new Map();
766
+ for (const p of programs)
767
+ for (const d of p.decls)
768
+ if (d.kind === "VarDecl")
769
+ for (const spec of d.decls)
770
+ spec.names.forEach((name, index) => {
771
+ this._pkgVarSpecs.set(name, { spec, index, busy: false });
772
+ });
773
+ }
774
+
689
775
  _collectVarsConstsPass(programs) {
690
776
  for (const p of programs) {
691
777
  this._setCurrentFile(p);
@@ -702,7 +788,11 @@ export class TypeChecker {
702
788
  const { cycles } = orderPackageInits(programs);
703
789
  for (const c of cycles) {
704
790
  const owner = programs.find((p) =>
705
- p.decls?.some((d) => d.kind === "VarDecl" && d.decls?.includes(c.spec)),
791
+ p.decls?.some(
792
+ (d) =>
793
+ (d.kind === "VarDecl" || d.kind === "ConstDecl") &&
794
+ d.decls?.includes(c.spec),
795
+ ),
706
796
  );
707
797
  if (owner) this._setCurrentFile(owner);
708
798
  this.err(c.message, c.spec);
@@ -725,6 +815,11 @@ export class TypeChecker {
725
815
  this._setCurrentFile(p);
726
816
  for (const d of p.decls) {
727
817
  if (
818
+ this.lazyBodies &&
819
+ (d.kind === "FuncDecl" || d.kind === "MethodDecl")
820
+ )
821
+ this._deferBody(d, p);
822
+ else if (
728
823
  d.kind === "FuncDecl" ||
729
824
  d.kind === "MethodDecl" ||
730
825
  d.kind === "TemplDecl"
@@ -735,31 +830,133 @@ export class TypeChecker {
735
830
  }
736
831
  }
737
832
 
833
+ // Lazy mode: validate what does not need the body now, check it on demand.
834
+ _deferBody(decl, program) {
835
+ if (decl.intrinsic) this._checkIntrinsic(decl);
836
+ if (decl.native) this._checkNative(decl);
837
+ this._lazyBodies.set(decl, program);
838
+ }
839
+
840
+ // Types the body of a function or method deferred by `lazyBodies`; a
841
+ // declaration checked already (or not deferred) is left alone.
842
+ checkBody(decl) {
843
+ const program = this._lazyBodies.get(decl);
844
+ if (!program) return;
845
+ this._lazyBodies.delete(decl);
846
+ this._setCurrentFile(program);
847
+ this.checkTopDecl(decl, this.globals);
848
+ }
849
+
738
850
  // ── Shared pass helpers ──────────────────────────────────────
739
851
 
852
+ // Instances made while types were collected (`struct{ Inner[int] }`) have
853
+ // no methods yet, and a type embedding another embedder (`Deep` over `E`
854
+ // over `Base`) needs the inner promotion first, so repeat until nothing
855
+ // new is promoted.
740
856
  _promoteEmbeddedMethods() {
857
+ const structs = [];
741
858
  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);
859
+ structs.push(type.underlying);
860
+ for (const inst of type._generic?._instances?.values() ?? []) {
861
+ this._syncInstanceMethods(type._generic, inst);
862
+ structs.push(inst.underlying);
863
+ }
745
864
  }
746
- }
747
-
865
+ const count = () =>
866
+ structs.reduce((n, st) => n + (st?.fields?.size ?? 0), 0);
867
+ let before;
868
+ let changed;
869
+ do {
870
+ before = count();
871
+ changed = false;
872
+ for (const st of structs)
873
+ if (this._promoteEmbeddedInto(st)) changed = true;
874
+ } while (changed || count() !== before);
875
+ }
876
+
877
+ // Recomputes the methods `struct` gets from its embedded fields, as Go's
878
+ // method set: the shallowest method of each name wins, a method declared
879
+ // on the struct itself shadows every promoted one, and a pointer method
880
+ // reached through an embedded pointer belongs to the value method set.
881
+ // Returns whether the promoted methods changed.
748
882
  _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);
756
- }
883
+ if (struct?.kind !== "struct" || !struct._embeds) return false;
884
+ const old = promotedMethods.get(struct) ?? new Map();
885
+ const best = new Map();
886
+ for (const embed of struct._embeds)
887
+ this._offerEmbedMethods(struct, embed, best);
888
+ if (best.size === 0 && old.size === 0) return false;
889
+ struct.methods ??= new Map();
890
+ // A method declared on the struct (or set since the last pass) is
891
+ // not one this pass promoted.
892
+ const isOwn = (mName) =>
893
+ struct.methods.has(mName) &&
894
+ struct.methods.get(mName) !== old.get(mName)?.mType;
895
+ const next = new Map();
896
+ for (const [mName, cand] of best)
897
+ if (!isOwn(mName)) next.set(mName, promotedEntry(cand, old.get(mName)));
898
+ promotedMethods.set(struct, next);
899
+ return applyPromotions(struct.methods, old, next, isOwn);
900
+ }
901
+
902
+ // Adds the methods one embedded field offers to `best` (name -> the
903
+ // shallowest candidate), and copies its fields into the embedder.
904
+ _offerEmbedMethods(struct, embed, best) {
905
+ const isPtr = embed.kind === "pointer";
906
+ const inner = isPtr ? embed.base : embed;
907
+ const base = inner.kind === "named" ? inner.underlying : inner;
908
+ // Two candidates at the same depth make the name ambiguous, as does
909
+ // an ambiguous name of the embedded type.
910
+ const offer = (name, depth, source, viaPtr, ambiguous = false) => {
911
+ const cur = best.get(name);
912
+ if (!cur || depth < cur.depth)
913
+ best.set(name, { depth, source, viaPtr, ambiguous });
914
+ else if (depth === cur.depth) cur.ambiguous = true;
915
+ };
916
+ if (base?.kind === "struct") {
917
+ this._offerStructEmbed(struct, base, isPtr, offer);
918
+ return;
919
+ }
920
+ // An embedded interface (named, literal or `error`) promotes every
921
+ // method of its method set, as a value method.
922
+ if (base?.kind === "interface") {
923
+ if (!isPtr) offerInterfaceMethods(base, offer);
924
+ return;
757
925
  }
926
+ // A named non-struct type (`time.Duration`) keeps its methods on the
927
+ // named type itself.
928
+ if (inner.kind === "named")
929
+ for (const [mName, mType] of inner.methods ?? [])
930
+ offer(mName, 1, mType, isPtr);
931
+ }
932
+
933
+ // Offers the methods of the embedded struct `base`, and copies its fields
934
+ // into the embedder.
935
+ _offerStructEmbed(struct, base, isPtr, offer) {
936
+ // Fields of an embedded type declared after the embedder were not
937
+ // there when the embedder's fields were collected.
938
+ if (struct.fields)
939
+ for (const [k, v] of base.fields)
940
+ if (!struct.fields.has(k)) struct.fields.set(k, v);
941
+ const innerPromoted = promotedMethods.get(base);
942
+ for (const [mName, mType] of base.methods ?? []) {
943
+ const p = innerPromoted?.get(mName);
944
+ offer(
945
+ mName,
946
+ 1 + (p?.depth ?? 0),
947
+ p?.source ?? mType,
948
+ isPtr || Boolean(p?.viaPtr),
949
+ );
950
+ }
951
+ for (const [mName, p] of innerPromoted ?? [])
952
+ if (p.ambiguous) offer(mName, 1 + p.depth, null, false, true);
758
953
  }
759
954
 
760
955
  // ── Type collection ──────────────────────────────────────────
761
956
 
762
957
  collectType(decl) {
958
+ // `type N interface{ ~int }` and `type D N` declare constraints.
959
+ this._markConstraintPos(decl.type);
763
960
  if (decl.typeParams && decl.isAlias) {
764
961
  this._collectGenericAlias(decl);
765
962
  return;
@@ -801,6 +998,9 @@ export class TypeChecker {
801
998
  this.globals.define(decl.name, named);
802
999
  return;
803
1000
  }
1001
+ // A type declared inside a function is registered before its fields
1002
+ // resolve so `next *L` finds it; package-level types already are.
1003
+ this._registerLocalTypePlaceholder(decl);
804
1004
  let underlying = this.resolveTypeNode(decl.type, this.globals);
805
1005
  // `type W V` with V a named array: W's underlying type is V's array.
806
1006
  if (!decl.isAlias && underlying.kind === "named") {
@@ -817,8 +1017,10 @@ export class TypeChecker {
817
1017
  return;
818
1018
  }
819
1019
  const existing = this.types.get(decl.name);
1020
+ // An imported package's type is never reused: a local declaration of
1021
+ // the same name gets its own type and leaves `pkg.T` untouched.
820
1022
  const named =
821
- existing?.kind === "named"
1023
+ existing?.kind === "named" && !this._importedTypes.has(existing)
822
1024
  ? existing
823
1025
  : { kind: "named", name: decl.name };
824
1026
  named.underlying = underlying;
@@ -836,6 +1038,14 @@ export class TypeChecker {
836
1038
  this.globals.define(decl.name, named);
837
1039
  }
838
1040
 
1041
+ _registerLocalTypePlaceholder(decl) {
1042
+ const prior = this.types.get(decl.name);
1043
+ if (decl.isAlias || (prior && !this._importedTypes.has(prior))) return;
1044
+ const placeholder = { kind: "named", name: decl.name, underlying: null };
1045
+ this.types.set(decl.name, placeholder);
1046
+ this.globals.define(decl.name, placeholder);
1047
+ }
1048
+
839
1049
  // type A[P any] = T[P] — Go 1.24 generic alias. The declaration is a
840
1050
  // template only: each instantiation A[X] substitutes X into the aliased
841
1051
  // type and resolves to exactly that type (no new named type).
@@ -888,6 +1098,9 @@ export class TypeChecker {
888
1098
  _registerFuncDecl(decl, funcType, typeParamTypes) {
889
1099
  if (this.globals.symbols.has(decl.name) && decl.name !== "init")
890
1100
  this.err(`${decl.name} redeclared in this block`, decl);
1101
+ if (decl.native) funcType.native = decl.native;
1102
+ this._funcDeclNames ??= new Set();
1103
+ this._funcDeclNames.add(decl.name);
891
1104
  if (typeParamTypes) {
892
1105
  this.globals.define(decl.name, {
893
1106
  kind: "generic",
@@ -932,6 +1145,9 @@ export class TypeChecker {
932
1145
  }
933
1146
 
934
1147
  _attachNonGenericMethod(decl, funcType) {
1148
+ // Codegen of an importing package reads the body of a Go-source
1149
+ // method (does it accept a nil receiver?).
1150
+ funcType._methodDecl = decl;
935
1151
  const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
936
1152
  const base = recvType?.underlying ?? recvType;
937
1153
  if (base?.kind === "struct") {
@@ -1008,12 +1224,26 @@ export class TypeChecker {
1008
1224
 
1009
1225
  _checkTemplElement(n, scope) {
1010
1226
  for (const a of n.attrs ?? []) {
1011
- if (a.kind === "expr" || a.kind === "cond-bool")
1012
- this._checkTemplExpr(a, a.tokens, scope);
1227
+ if (a.kind === "expr") this._checkTemplExpr(a, a.tokens, scope);
1228
+ else if (a.kind === "cond-bool") this._checkTemplCondAttr(a, scope);
1013
1229
  }
1014
1230
  this._checkTemplNodes(n.children, scope);
1015
1231
  }
1016
1232
 
1233
+ // `name?={ cond }` sets the attribute when `cond` is true.
1234
+ _checkTemplCondAttr(a, scope) {
1235
+ const t = this._checkTemplExpr(a, a.tokens, scope);
1236
+ if (t && !isBool(t) && !isAny(t))
1237
+ this.err(`Conditional attribute '${a.name}' must be bool`, a.expr);
1238
+ }
1239
+
1240
+ // `{ name... }` names a variadic parameter. A templ without a `children`
1241
+ // parameter may still write `{ children... }` (it mounts nothing).
1242
+ _checkTemplChildren(n, scope) {
1243
+ if (n.name === "children" && !scope.lookup("children")) return;
1244
+ this._checkTemplExpr(n, [], scope);
1245
+ }
1246
+
1017
1247
  _checkTemplLeaf(n, scope) {
1018
1248
  this._checkTemplExpr(n, n.tokens, scope);
1019
1249
  }
@@ -1028,8 +1258,13 @@ export class TypeChecker {
1028
1258
 
1029
1259
  _checkTemplSwitch(n, scope) {
1030
1260
  this._checkTemplExpr(n, n.exprTokens, scope);
1261
+ // A tag that failed to parse leaves `n.expr` unset: not a tagless switch.
1262
+ const tagless = !n.exprTokens?.length;
1031
1263
  for (const c of n.cases ?? []) {
1032
1264
  this._checkTemplExpr(c, c.caseTokens, scope);
1265
+ for (const e of c.exprs?.slice(1) ?? []) this.checkExpr(e, scope);
1266
+ if (n.expr || tagless)
1267
+ for (const e of c.exprs ?? []) this._checkCaseValue(n.expr, e);
1033
1268
  this._checkTemplNodes(c.body, scope);
1034
1269
  }
1035
1270
  }
@@ -1147,8 +1382,27 @@ export class TypeChecker {
1147
1382
  );
1148
1383
  }
1149
1384
 
1385
+ _checkNative(decl) {
1386
+ if (!this.isStdlib && !this.nativesChecked)
1387
+ return this.err(
1388
+ "//gofront:native is reserved for the standard library",
1389
+ decl,
1390
+ );
1391
+ if (decl.kind !== "FuncDecl")
1392
+ return this.err(
1393
+ `native '${decl.native}' must mark a function, not a method`,
1394
+ decl,
1395
+ );
1396
+ if (!NATIVES.has(decl.native))
1397
+ return this.err(`unknown native '${decl.native}'`, decl);
1398
+ if (decl._bodyless)
1399
+ this.err(`native '${decl.native}' must have a Go body`, decl);
1400
+ }
1401
+
1150
1402
  checkFuncDecl(decl, outer) {
1403
+ this.stats.bodiesChecked++;
1151
1404
  if (decl.intrinsic) this._checkIntrinsic(decl);
1405
+ if (decl.native) this._checkNative(decl);
1152
1406
  if (
1153
1407
  (this.target === "wasm" || this.target === "both") &&
1154
1408
  (decl.async || decl.isAsync)
@@ -1173,6 +1427,8 @@ export class TypeChecker {
1173
1427
  }
1174
1428
 
1175
1429
  checkMethodDecl(decl, outer) {
1430
+ this.stats.bodiesChecked++;
1431
+ if (decl.native) this._checkNative(decl);
1176
1432
  if (
1177
1433
  (this.target === "wasm" || this.target === "both") &&
1178
1434
  (decl.async || decl.isAsync)
@@ -1199,7 +1455,13 @@ export class TypeChecker {
1199
1455
  inner.define(p.name, this._paramType(p, inner));
1200
1456
  }
1201
1457
  const returnType = this._setupFuncReturnType(decl, inner);
1202
- this._runFuncBody(decl, inner, returnType);
1458
+ const savedRecvGeneric = this._recvGeneric;
1459
+ this._recvGeneric = recvNamedType?._generic;
1460
+ try {
1461
+ this._runFuncBody(decl, inner, returnType);
1462
+ } finally {
1463
+ this._recvGeneric = savedRecvGeneric;
1464
+ }
1203
1465
  this._checkMissingReturn(decl, returnType, "method");
1204
1466
  }
1205
1467
 
@@ -1210,14 +1472,19 @@ export class TypeChecker {
1210
1472
  return p.variadic ? { kind: "slice", elem: t } : t;
1211
1473
  }
1212
1474
 
1213
- _injectTypeParams(decl, inner, outer) {
1475
+ _injectTypeParams(decl, inner, _outer) {
1214
1476
  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
- }
1477
+ // Declare every parameter first so a constraint may mention one that
1478
+ // comes later in the list (`[S ~[]E, E any]`).
1479
+ const params = decl.typeParams.map((tp) => {
1480
+ const t = { kind: "typeParam", name: tp.name, constraint: ANY };
1481
+ inner.define(tp.name, t);
1482
+ return t;
1483
+ });
1484
+ decl.typeParams.forEach((tp, i) => {
1485
+ if (tp.constraint)
1486
+ params[i].constraint = this.resolveTypeNode(tp.constraint, inner);
1487
+ });
1221
1488
  }
1222
1489
 
1223
1490
  // Inside a method of `G[T]` the receiver is the instance `G[T]` over the
@@ -1258,7 +1525,11 @@ export class TypeChecker {
1258
1525
  }
1259
1526
 
1260
1527
  _checkMissingReturn(decl, returnType, kind) {
1261
- if (!isVoid(returnType) && !returnType._hasNamedReturns) {
1528
+ if (
1529
+ !decl._bodyless &&
1530
+ !isVoid(returnType) &&
1531
+ !returnType._hasNamedReturns
1532
+ ) {
1262
1533
  if (!this._isTerminating(decl.body)) {
1263
1534
  this.err(`missing return at end of ${kind} '${decl.name}'`, decl);
1264
1535
  }
@@ -1281,21 +1552,7 @@ export class TypeChecker {
1281
1552
  for (const spec of decl.decls) {
1282
1553
  let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
1283
1554
  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
- }
1555
+ const valTypesFlat = this._varSpecValueTypes(spec, scope);
1299
1556
  for (let i = 0; i < spec.names.length; i++) {
1300
1557
  const vt = valTypesFlat[i] ?? ANY;
1301
1558
  const varType = type ?? defaultType(vt) ?? ANY;
@@ -1318,6 +1575,45 @@ export class TypeChecker {
1318
1575
  this._sharedAllocOk = savedSharedAlloc;
1319
1576
  }
1320
1577
 
1578
+ // The type of each value a `var` spec binds (a multi-value call or a
1579
+ // comma-ok form spreads over several names); reports a count mismatch.
1580
+ _varSpecValueTypes(spec, scope) {
1581
+ const commaOk = this._checkVarCommaOk(spec, scope);
1582
+ const valTypes = commaOk ?? spec.value.map((v) => this.checkExpr(v, scope));
1583
+ const isTuple =
1584
+ !commaOk && valTypes.length === 1 && valTypes[0]?.kind === "tuple";
1585
+ const flat = isTuple ? valTypes[0].types : valTypes;
1586
+ if (spec.names.length !== flat.length && !flat.some((t) => t?._tainted))
1587
+ this._reportAssignMismatch(spec.names.length, flat.length, isTuple, spec);
1588
+ return flat;
1589
+ }
1590
+
1591
+ _reportAssignMismatch(nameCount, valueCount, isTuple, node) {
1592
+ const vars = nameCount === 1 ? "1 variable" : `${nameCount} variables`;
1593
+ const vals = isTuple
1594
+ ? `returns ${valueCount} values`
1595
+ : valueCount === 1
1596
+ ? "1 value"
1597
+ : `${valueCount} values`;
1598
+ this.err(`assignment mismatch: ${vars} but ${vals}`, node);
1599
+ }
1600
+
1601
+ // `var v, ok = m[k]` / `var v, ok = x.(T)`: the comma-ok forms yield the
1602
+ // value and a bool. Returns their types, or null for any other spec.
1603
+ _checkVarCommaOk(spec, scope) {
1604
+ if (spec.names.length !== 2 || spec.value.length !== 1) return null;
1605
+ const value = spec.value[0];
1606
+ if (value.kind === "TypeAssertExpr") {
1607
+ value._commaOk = true;
1608
+ spec._commaOk = true;
1609
+ return [this.checkExpr(value, scope), BOOL];
1610
+ }
1611
+ const stmt = { lhs: spec.names, rhs: spec.value };
1612
+ const types = this._checkAssignCommaOkMap(stmt, scope);
1613
+ if (types) spec._commaOk = true;
1614
+ return types;
1615
+ }
1616
+
1321
1617
  checkConstDecl(decl, scope) {
1322
1618
  for (const spec of decl.decls) {
1323
1619
  const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
@@ -1335,6 +1631,14 @@ export class TypeChecker {
1335
1631
  expr,
1336
1632
  "constant declaration",
1337
1633
  );
1634
+ // `const D Duration = 1` is a BigInt in JS.
1635
+ if (spec.type && expr) this._tag64BitNode(this.resolveType(type), expr);
1636
+ if (spec.type && expr)
1637
+ this._tagFloat32Const(
1638
+ this.resolveType(type),
1639
+ valTypes[i] ?? valTypes[0],
1640
+ expr,
1641
+ );
1338
1642
  scope.defineConst(name, type, expr);
1339
1643
  }
1340
1644
  }