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.
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/backend/js/expressions.js +329 -58
- package/src/backend/js/index.js +90 -10
- package/src/backend/js/runtime.js +51 -9
- package/src/backend/js/statements.js +193 -84
- package/src/backend/js/type-info.js +123 -8
- package/src/backend/shared/fmt-types.js +30 -1
- package/src/backend/wasm/emit-builtins.js +250 -9
- package/src/backend/wasm/emit-exprs.js +63 -22
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +239 -205
- package/src/backend/wasm/emit.js +73 -26
- package/src/backend/wasm/globals.js +1 -0
- package/src/backend/wasm/index.js +8 -0
- package/src/backend/wasm/module-emitter.js +211 -57
- package/src/backend/wasm/monomorph.js +36 -2
- package/src/backend/wasm/natives.js +4 -0
- package/src/backend/wasm/types.js +32 -3
- package/src/backend/wasm/variadic.js +6 -1
- package/src/compiler.js +6 -1
- package/src/lexer/lexer.js +1 -0
- package/src/link/stdlib.js +1 -0
- package/src/link/type-names.js +72 -16
- package/src/lower/embedding.js +43 -15
- package/src/lower/fmt-formatters.js +38 -14
- package/src/lower/functions.js +223 -7
- package/src/lower/gen-types.js +21 -2
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +114 -6
- package/src/parser/declarations.js +4 -2
- package/src/parser/expressions.js +25 -41
- package/src/parser/statements.js +107 -0
- package/src/parser/types.js +11 -0
- package/src/typechecker/assignability.js +94 -12
- package/src/typechecker/constants.js +124 -2
- package/src/typechecker/expressions.js +171 -17
- package/src/typechecker/index.js +158 -29
- package/src/typechecker/operand-text.js +1 -0
- package/src/typechecker/resolve.js +308 -39
- package/src/typechecker/statements.js +171 -21
- package/src/typechecker/types.js +84 -2
package/src/typechecker/index.js
CHANGED
|
@@ -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
|
-
|
|
809
|
-
|
|
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
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
-
|
|
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)}`,
|