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.
- package/README.md +3 -3
- package/package.json +6 -5
- package/src/backend/js/expressions.js +1157 -408
- package/src/backend/js/index.js +327 -153
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +559 -2552
- package/src/backend/js/statements.js +670 -158
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +298 -19
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +91 -170
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +884 -256
- package/src/backend/wasm/emit-exprs.js +185 -77
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +524 -240
- package/src/backend/wasm/emit.js +176 -38
- package/src/backend/wasm/globals.js +48 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +44 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +299 -770
- package/src/backend/wasm/monomorph.js +139 -8
- package/src/backend/wasm/natives.js +600 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +97 -20
- package/src/backend/wasm/variadic.js +9 -4
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +113 -130
- package/src/lexer/lexer.js +106 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +355 -132
- package/src/link/type-names.js +204 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +62 -5
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +929 -0
- package/src/lower/functions.js +225 -7
- package/src/lower/gen-types.js +293 -0
- package/src/lower/index.js +14 -2
- package/src/lower/json-codecs.js +334 -245
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +30 -5
- package/src/parser/expressions.js +111 -57
- package/src/parser/statements.js +108 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +12 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +214 -19
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +156 -8
- package/src/typechecker/expressions.js +490 -108
- package/src/typechecker/index.js +346 -42
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +41 -0
- package/src/typechecker/resolve.js +490 -65
- package/src/typechecker/statements.js +288 -27
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +287 -575
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +391 -452
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +235 -50
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
package/src/typechecker/index.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
743
|
-
for (const inst of type._generic?._instances?.values() ?? [])
|
|
744
|
-
this.
|
|
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
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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"
|
|
1012
|
-
|
|
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.
|
|
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,
|
|
1475
|
+
_injectTypeParams(decl, inner, _outer) {
|
|
1214
1476
|
if (!decl.typeParams) return;
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
inner.define(tp.name,
|
|
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 (
|
|
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
|
|
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
|
}
|