gofront 1.9.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 (42) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/backend/js/expressions.js +329 -58
  4. package/src/backend/js/index.js +90 -10
  5. package/src/backend/js/runtime.js +51 -9
  6. package/src/backend/js/statements.js +193 -84
  7. package/src/backend/js/type-info.js +123 -8
  8. package/src/backend/shared/fmt-types.js +30 -1
  9. package/src/backend/wasm/emit-builtins.js +250 -9
  10. package/src/backend/wasm/emit-exprs.js +63 -22
  11. package/src/backend/wasm/emit-shared.js +4 -1
  12. package/src/backend/wasm/emit-stmts.js +239 -205
  13. package/src/backend/wasm/emit.js +73 -26
  14. package/src/backend/wasm/globals.js +1 -0
  15. package/src/backend/wasm/index.js +8 -0
  16. package/src/backend/wasm/module-emitter.js +211 -57
  17. package/src/backend/wasm/monomorph.js +36 -2
  18. package/src/backend/wasm/natives.js +4 -0
  19. package/src/backend/wasm/types.js +32 -3
  20. package/src/backend/wasm/variadic.js +6 -1
  21. package/src/compiler.js +6 -1
  22. package/src/lexer/lexer.js +1 -0
  23. package/src/link/stdlib.js +1 -0
  24. package/src/link/type-names.js +72 -16
  25. package/src/lower/embedding.js +43 -15
  26. package/src/lower/fmt-formatters.js +38 -14
  27. package/src/lower/functions.js +223 -7
  28. package/src/lower/gen-types.js +21 -2
  29. package/src/lower/index.js +7 -1
  30. package/src/lower/json-codecs.js +114 -6
  31. package/src/parser/declarations.js +4 -2
  32. package/src/parser/expressions.js +25 -41
  33. package/src/parser/statements.js +107 -0
  34. package/src/parser/types.js +11 -0
  35. package/src/typechecker/assignability.js +94 -12
  36. package/src/typechecker/constants.js +124 -2
  37. package/src/typechecker/expressions.js +171 -17
  38. package/src/typechecker/index.js +158 -29
  39. package/src/typechecker/operand-text.js +1 -0
  40. package/src/typechecker/resolve.js +308 -39
  41. package/src/typechecker/statements.js +171 -21
  42. package/src/typechecker/types.js +84 -2
@@ -29,6 +29,7 @@ import {
29
29
  isBool,
30
30
  isVoid,
31
31
  NATIVES,
32
+ promotedMethods,
32
33
  Scope,
33
34
  TAINTED_ANY,
34
35
  TypeCheckError,
@@ -99,6 +100,50 @@ const JS_BUILTIN_CLASSES = new Set([
99
100
  "WebAssembly",
100
101
  ]);
101
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
+
102
147
  export class TypeChecker {
103
148
  constructor() {
104
149
  this.types = new Map(); // named types
@@ -130,6 +175,8 @@ export class TypeChecker {
130
175
  this._constExprs = new Map(); // name -> AST initializer expr
131
176
  this._importedConsts = new Map(); // dot-imported const name -> value
132
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
133
180
  this._setupGlobals();
134
181
  // Register error as a named type (interface)
135
182
  this.types.set("error", ERROR);
@@ -189,6 +236,7 @@ export class TypeChecker {
189
236
  // otherwise reuse the imported placeholder. It is reached as `pkg.Pt`.
190
237
  if (type.name === name) this.types.set(name, type);
191
238
  this.types.set(`${pkgName}.${name}`, type);
239
+ this._importedTypes.add(type);
192
240
  }
193
241
  }
194
242
 
@@ -383,13 +431,17 @@ export class TypeChecker {
383
431
 
384
432
  this._checkDirectivesAndImportsPass(programs);
385
433
  this._registerConstExprs(programs);
434
+ this._registerVarSpecs(programs);
435
+ this._markConstraintPositions(programs);
386
436
  this._collectTypesPass(programs);
437
+ this._flushConstraintUses();
387
438
  this._collectFuncsPass(programs);
388
439
  this._checkGlobalTypeNamesPass(programs);
389
440
  this._promoteEmbeddedMethods();
390
441
  this._collectVarsConstsPass(programs);
391
442
  this._checkTopDeclsPass(programs);
392
443
  this._reportInitCycles(programs);
444
+ this._flushConstraintUses();
393
445
 
394
446
  if (this.target === "both") {
395
447
  this._checkMutablePackageVars(programs);
@@ -707,6 +759,19 @@ export class TypeChecker {
707
759
  }
708
760
  }
709
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
+
710
775
  _collectVarsConstsPass(programs) {
711
776
  for (const p of programs) {
712
777
  this._setCurrentFile(p);
@@ -798,45 +863,100 @@ export class TypeChecker {
798
863
  }
799
864
  }
800
865
  const count = () =>
801
- structs.reduce(
802
- (n, st) => n + (st?.methods?.size ?? 0) + (st?.fields?.size ?? 0),
803
- 0,
804
- );
866
+ structs.reduce((n, st) => n + (st?.fields?.size ?? 0), 0);
805
867
  let before;
868
+ let changed;
806
869
  do {
807
870
  before = count();
808
- for (const st of structs) this._promoteEmbeddedInto(st);
809
- } while (count() !== before);
871
+ changed = false;
872
+ for (const st of structs)
873
+ if (this._promoteEmbeddedInto(st)) changed = true;
874
+ } while (changed || count() !== before);
810
875
  }
811
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.
812
882
  _promoteEmbeddedInto(struct) {
813
- if (struct?.kind !== "struct" || !struct._embeds) return;
814
- for (const embed of struct._embeds) {
815
- const inner = embed.kind === "pointer" ? embed.base : embed;
816
- const base = inner.kind === "named" ? inner.underlying : inner;
817
- // Fields of an embedded type declared after the embedder were not
818
- // there when the embedder's fields were collected.
819
- if (base?.kind === "struct" && struct.fields)
820
- for (const [k, v] of base.fields)
821
- if (!struct.fields.has(k)) struct.fields.set(k, v);
822
- // A named non-struct type (`time.Duration`) keeps its methods on
823
- // the named type itself.
824
- const methods =
825
- base?.kind === "struct"
826
- ? base.methods
827
- : inner.kind === "named" && base?.kind !== "interface"
828
- ? inner.methods
829
- : null;
830
- if (!methods) continue;
831
- for (const [mName, mType] of methods.entries()) {
832
- if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
833
- }
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;
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
+ );
834
950
  }
951
+ for (const [mName, p] of innerPromoted ?? [])
952
+ if (p.ambiguous) offer(mName, 1 + p.depth, null, false, true);
835
953
  }
836
954
 
837
955
  // ── Type collection ──────────────────────────────────────────
838
956
 
839
957
  collectType(decl) {
958
+ // `type N interface{ ~int }` and `type D N` declare constraints.
959
+ this._markConstraintPos(decl.type);
840
960
  if (decl.typeParams && decl.isAlias) {
841
961
  this._collectGenericAlias(decl);
842
962
  return;
@@ -897,8 +1017,10 @@ export class TypeChecker {
897
1017
  return;
898
1018
  }
899
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.
900
1022
  const named =
901
- existing?.kind === "named"
1023
+ existing?.kind === "named" && !this._importedTypes.has(existing)
902
1024
  ? existing
903
1025
  : { kind: "named", name: decl.name };
904
1026
  named.underlying = underlying;
@@ -917,7 +1039,8 @@ export class TypeChecker {
917
1039
  }
918
1040
 
919
1041
  _registerLocalTypePlaceholder(decl) {
920
- if (decl.isAlias || this.types.has(decl.name)) return;
1042
+ const prior = this.types.get(decl.name);
1043
+ if (decl.isAlias || (prior && !this._importedTypes.has(prior))) return;
921
1044
  const placeholder = { kind: "named", name: decl.name, underlying: null };
922
1045
  this.types.set(decl.name, placeholder);
923
1046
  this.globals.define(decl.name, placeholder);
@@ -1510,6 +1633,12 @@ export class TypeChecker {
1510
1633
  );
1511
1634
  // `const D Duration = 1` is a BigInt in JS.
1512
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
+ );
1513
1642
  scope.defineConst(name, type, expr);
1514
1643
  }
1515
1644
  }
@@ -16,6 +16,7 @@ function litText(node) {
16
16
  const PRINTERS = {
17
17
  Ident: (n) => n.name,
18
18
  BasicLit: litText,
19
+ ImagLit: (n) => `${n.value}i`,
19
20
  ParenExpr: (n) => `(${exprText(n.expr)})`,
20
21
  SelectorExpr: (n) => `${exprText(n.expr)}.${n.field}`,
21
22
  UnaryExpr: (n) => `${n.op}${exprText(n.operand)}`,