gofront 0.0.3 → 0.0.5
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 +110 -135
- package/package.json +2 -3
- package/src/codegen/expressions.js +529 -234
- package/src/codegen/statements.js +259 -14
- package/src/codegen.js +98 -9
- package/src/dts-parser.js +1 -1
- package/src/index.js +13 -12
- package/src/lexer.js +24 -1
- package/src/minifier.js +698 -0
- package/src/parser/expressions.js +115 -15
- package/src/parser/statements.js +4 -4
- package/src/parser/types.js +108 -3
- package/src/parser.js +93 -18
- package/src/typechecker/expressions.js +370 -13
- package/src/typechecker/statements.js +103 -5
- package/src/typechecker/types.js +96 -28
- package/src/typechecker.js +658 -22
package/src/typechecker.js
CHANGED
|
@@ -13,21 +13,26 @@ import {
|
|
|
13
13
|
BASIC_TYPES,
|
|
14
14
|
BOOL,
|
|
15
15
|
CMP_OPS,
|
|
16
|
+
COMPLEX128,
|
|
16
17
|
defaultType,
|
|
17
18
|
ERROR,
|
|
18
19
|
FLOAT64,
|
|
19
20
|
INT,
|
|
20
21
|
isAny,
|
|
21
|
-
|
|
22
|
+
isComplex,
|
|
23
|
+
isComplexOrNumeric,
|
|
22
24
|
isNil,
|
|
23
25
|
isNumeric,
|
|
26
|
+
isPointer,
|
|
24
27
|
isString,
|
|
25
28
|
isUntyped,
|
|
29
|
+
isVoid,
|
|
26
30
|
LOG_OPS,
|
|
27
31
|
Scope,
|
|
28
32
|
STRING,
|
|
29
33
|
TypeCheckError,
|
|
30
34
|
typeStr,
|
|
35
|
+
UNTYPED_COMPLEX,
|
|
31
36
|
UNTYPED_FLOAT,
|
|
32
37
|
UNTYPED_INT,
|
|
33
38
|
VOID,
|
|
@@ -49,6 +54,8 @@ export class TypeChecker {
|
|
|
49
54
|
this._deferCount = 0; // tracks defer usage in current function body
|
|
50
55
|
this._imports = []; // tracked imports for unused-import detection
|
|
51
56
|
this._setupGlobals();
|
|
57
|
+
// Register error as a named type (interface)
|
|
58
|
+
this.types.set("error", ERROR);
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
_setupGlobals() {
|
|
@@ -193,6 +200,47 @@ export class TypeChecker {
|
|
|
193
200
|
},
|
|
194
201
|
});
|
|
195
202
|
|
|
203
|
+
// bytes package — operates on []byte (JS arrays of numbers)
|
|
204
|
+
const BYTE_SLICE = { kind: "slice", elem: INT };
|
|
205
|
+
const byFn1 = (ret) => ({
|
|
206
|
+
kind: "func",
|
|
207
|
+
params: [BYTE_SLICE],
|
|
208
|
+
returns: [ret],
|
|
209
|
+
});
|
|
210
|
+
const byFn2 = (p2, ret) => ({
|
|
211
|
+
kind: "func",
|
|
212
|
+
params: [BYTE_SLICE, p2],
|
|
213
|
+
returns: [ret],
|
|
214
|
+
});
|
|
215
|
+
this.globals.define("bytes", {
|
|
216
|
+
kind: "namespace",
|
|
217
|
+
name: "bytes",
|
|
218
|
+
members: {
|
|
219
|
+
Contains: byFn2(BYTE_SLICE, BOOL),
|
|
220
|
+
HasPrefix: byFn2(BYTE_SLICE, BOOL),
|
|
221
|
+
HasSuffix: byFn2(BYTE_SLICE, BOOL),
|
|
222
|
+
Index: byFn2(BYTE_SLICE, INT),
|
|
223
|
+
Count: byFn2(BYTE_SLICE, INT),
|
|
224
|
+
Repeat: byFn2(INT, BYTE_SLICE),
|
|
225
|
+
Replace: {
|
|
226
|
+
kind: "func",
|
|
227
|
+
params: [BYTE_SLICE, BYTE_SLICE, BYTE_SLICE, INT],
|
|
228
|
+
returns: [BYTE_SLICE],
|
|
229
|
+
},
|
|
230
|
+
ToUpper: byFn1(BYTE_SLICE),
|
|
231
|
+
ToLower: byFn1(BYTE_SLICE),
|
|
232
|
+
TrimSpace: byFn1(BYTE_SLICE),
|
|
233
|
+
Trim: byFn2(STRING, BYTE_SLICE),
|
|
234
|
+
Equal: byFn2(BYTE_SLICE, BOOL),
|
|
235
|
+
Split: byFn2(BYTE_SLICE, { kind: "slice", elem: BYTE_SLICE }),
|
|
236
|
+
Join: {
|
|
237
|
+
kind: "func",
|
|
238
|
+
params: [{ kind: "slice", elem: BYTE_SLICE }, BYTE_SLICE],
|
|
239
|
+
returns: [BYTE_SLICE],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
196
244
|
// strconv package
|
|
197
245
|
this.globals.define("strconv", {
|
|
198
246
|
kind: "namespace",
|
|
@@ -328,6 +376,8 @@ export class TypeChecker {
|
|
|
328
376
|
name: "errors",
|
|
329
377
|
members: {
|
|
330
378
|
New: { kind: "func", params: [STRING], returns: [ERROR] },
|
|
379
|
+
Is: { kind: "func", params: [ERROR, ERROR], returns: [BOOL] },
|
|
380
|
+
Unwrap: { kind: "func", params: [ERROR], returns: [ERROR] },
|
|
331
381
|
},
|
|
332
382
|
});
|
|
333
383
|
|
|
@@ -346,6 +396,41 @@ export class TypeChecker {
|
|
|
346
396
|
},
|
|
347
397
|
});
|
|
348
398
|
|
|
399
|
+
// unicode package
|
|
400
|
+
const runeToStr = { kind: "func", params: [INT], returns: [BOOL] };
|
|
401
|
+
const runeTrans = { kind: "func", params: [INT], returns: [INT] };
|
|
402
|
+
this.globals.define("unicode", {
|
|
403
|
+
kind: "namespace",
|
|
404
|
+
name: "unicode",
|
|
405
|
+
members: {
|
|
406
|
+
IsLetter: runeToStr,
|
|
407
|
+
IsDigit: runeToStr,
|
|
408
|
+
IsSpace: runeToStr,
|
|
409
|
+
IsUpper: runeToStr,
|
|
410
|
+
IsLower: runeToStr,
|
|
411
|
+
IsPunct: runeToStr,
|
|
412
|
+
IsControl: runeToStr,
|
|
413
|
+
IsPrint: runeToStr,
|
|
414
|
+
IsGraphic: runeToStr,
|
|
415
|
+
ToUpper: runeTrans,
|
|
416
|
+
ToLower: runeTrans,
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// os package (JS-friendly subset)
|
|
421
|
+
this.globals.define("os", {
|
|
422
|
+
kind: "namespace",
|
|
423
|
+
name: "os",
|
|
424
|
+
members: {
|
|
425
|
+
Exit: { kind: "func", params: [INT], returns: [VOID] },
|
|
426
|
+
Args: { kind: "slice", elem: STRING },
|
|
427
|
+
Getenv: { kind: "func", params: [STRING], returns: [STRING] },
|
|
428
|
+
Stdout: ANY,
|
|
429
|
+
Stderr: ANY,
|
|
430
|
+
Stdin: ANY,
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
|
|
349
434
|
this.globals.define("append", { kind: "builtin", name: "append" });
|
|
350
435
|
this.globals.define("len", { kind: "builtin", name: "len" });
|
|
351
436
|
this.globals.define("cap", { kind: "builtin", name: "cap" });
|
|
@@ -361,6 +446,9 @@ export class TypeChecker {
|
|
|
361
446
|
this.globals.define("min", { kind: "builtin", name: "min" });
|
|
362
447
|
this.globals.define("max", { kind: "builtin", name: "max" });
|
|
363
448
|
this.globals.define("clear", { kind: "builtin", name: "clear" });
|
|
449
|
+
this.globals.define("complex", { kind: "builtin", name: "complex" });
|
|
450
|
+
this.globals.define("real", { kind: "builtin", name: "real" });
|
|
451
|
+
this.globals.define("imag", { kind: "builtin", name: "imag" });
|
|
364
452
|
}
|
|
365
453
|
|
|
366
454
|
addDefinitions(types, values) {
|
|
@@ -378,7 +466,13 @@ export class TypeChecker {
|
|
|
378
466
|
addPackageNamespace(pkgName, symbols, types) {
|
|
379
467
|
const members = {};
|
|
380
468
|
for (const [name, type] of symbols) members[name] = type;
|
|
381
|
-
this
|
|
469
|
+
// _gofront: true marks this as a GoFront package — exported identifier rules apply
|
|
470
|
+
this.globals.define(pkgName, {
|
|
471
|
+
kind: "namespace",
|
|
472
|
+
name: pkgName,
|
|
473
|
+
members,
|
|
474
|
+
_gofront: true,
|
|
475
|
+
});
|
|
382
476
|
for (const [name, type] of types) this.types.set(name, type);
|
|
383
477
|
}
|
|
384
478
|
|
|
@@ -542,6 +636,36 @@ export class TypeChecker {
|
|
|
542
636
|
// ── Type collection ──────────────────────────────────────────
|
|
543
637
|
|
|
544
638
|
collectType(decl) {
|
|
639
|
+
if (decl.typeParams) {
|
|
640
|
+
// Generic type — resolve underlying with type params as ANY for struct fields
|
|
641
|
+
const typeScope = new Scope(this.globals);
|
|
642
|
+
const typeParamTypes = decl.typeParams.map((tp) => {
|
|
643
|
+
const t = {
|
|
644
|
+
kind: "typeParam",
|
|
645
|
+
name: tp.name,
|
|
646
|
+
constraint: tp.constraint,
|
|
647
|
+
};
|
|
648
|
+
typeScope.define(tp.name, t);
|
|
649
|
+
return t;
|
|
650
|
+
});
|
|
651
|
+
const underlying = this.resolveTypeNode(decl.type, typeScope);
|
|
652
|
+
const named = {
|
|
653
|
+
kind: "named",
|
|
654
|
+
name: decl.name,
|
|
655
|
+
underlying,
|
|
656
|
+
_generic: {
|
|
657
|
+
typeParams: typeParamTypes,
|
|
658
|
+
declNode: decl,
|
|
659
|
+
},
|
|
660
|
+
};
|
|
661
|
+
if (underlying.kind === "struct") {
|
|
662
|
+
underlying.name = decl.name;
|
|
663
|
+
underlying.methods = new Map();
|
|
664
|
+
}
|
|
665
|
+
this.types.set(decl.name, named);
|
|
666
|
+
this.globals.define(decl.name, named);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
545
669
|
const underlying = this.resolveTypeNode(decl.type, this.globals);
|
|
546
670
|
if (decl.isAlias) {
|
|
547
671
|
// type A = B — transparent alias, A and B are identical types
|
|
@@ -562,11 +686,40 @@ export class TypeChecker {
|
|
|
562
686
|
}
|
|
563
687
|
|
|
564
688
|
collectFunc(decl) {
|
|
689
|
+
// For generic functions, create type param scope for resolving param/return types
|
|
690
|
+
let resolveScope = this.globals;
|
|
691
|
+
let typeParamTypes = null;
|
|
692
|
+
if (decl.typeParams) {
|
|
693
|
+
resolveScope = new Scope(this.globals);
|
|
694
|
+
typeParamTypes = decl.typeParams.map((tp) => {
|
|
695
|
+
const t = {
|
|
696
|
+
kind: "typeParam",
|
|
697
|
+
name: tp.name,
|
|
698
|
+
constraint: tp.constraint,
|
|
699
|
+
};
|
|
700
|
+
resolveScope.define(tp.name, t);
|
|
701
|
+
return t;
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
// For methods on generic types, inject the receiver's type params
|
|
705
|
+
if (decl.kind === "MethodDecl") {
|
|
706
|
+
const recvTypeName =
|
|
707
|
+
decl.recvType.kind === "GenericTypeName"
|
|
708
|
+
? decl.recvType.name
|
|
709
|
+
: decl.recvType.name;
|
|
710
|
+
const recvNamedType = this.types.get(recvTypeName);
|
|
711
|
+
if (recvNamedType?._generic) {
|
|
712
|
+
resolveScope = new Scope(this.globals);
|
|
713
|
+
for (const tp of recvNamedType._generic.typeParams) {
|
|
714
|
+
resolveScope.define(tp.name, tp);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
565
718
|
const paramTypes = decl.params.map((p) =>
|
|
566
|
-
this.resolveTypeNode(p.type,
|
|
719
|
+
this.resolveTypeNode(p.type, resolveScope),
|
|
567
720
|
);
|
|
568
721
|
const returnType = decl.returnType
|
|
569
|
-
? this.resolveTypeNode(decl.returnType,
|
|
722
|
+
? this.resolveTypeNode(decl.returnType, resolveScope)
|
|
570
723
|
: VOID;
|
|
571
724
|
const isVariadic =
|
|
572
725
|
decl.params.length > 0 && decl.params[decl.params.length - 1].variadic;
|
|
@@ -581,13 +734,38 @@ export class TypeChecker {
|
|
|
581
734
|
if (this.globals.symbols.has(decl.name) && decl.name !== "init") {
|
|
582
735
|
this.err(`${decl.name} redeclared in this block`, decl);
|
|
583
736
|
}
|
|
584
|
-
|
|
737
|
+
if (typeParamTypes) {
|
|
738
|
+
this.globals.define(decl.name, {
|
|
739
|
+
kind: "generic",
|
|
740
|
+
name: decl.name,
|
|
741
|
+
typeParams: typeParamTypes,
|
|
742
|
+
underlying: funcType,
|
|
743
|
+
});
|
|
744
|
+
} else {
|
|
745
|
+
this.globals.define(decl.name, funcType);
|
|
746
|
+
}
|
|
585
747
|
} else {
|
|
586
748
|
// Method: attach to struct type
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
749
|
+
const recvTypeName =
|
|
750
|
+
decl.recvType.kind === "GenericTypeName"
|
|
751
|
+
? decl.recvType.name
|
|
752
|
+
: decl.recvType.name;
|
|
753
|
+
const recvNamedType = this.types.get(recvTypeName);
|
|
754
|
+
if (recvNamedType?._generic) {
|
|
755
|
+
// Store method on the generic metadata and on the underlying struct
|
|
756
|
+
if (!recvNamedType._generic.methods)
|
|
757
|
+
recvNamedType._generic.methods = new Map();
|
|
758
|
+
recvNamedType._generic.methods.set(decl.name, funcType);
|
|
759
|
+
const base = recvNamedType.underlying;
|
|
760
|
+
if (base?.kind === "struct") {
|
|
761
|
+
base.methods.set(decl.name, funcType);
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
|
|
765
|
+
const base = recvType?.underlying ?? recvType;
|
|
766
|
+
if (base?.kind === "struct") {
|
|
767
|
+
base.methods.set(decl.name, funcType);
|
|
768
|
+
}
|
|
591
769
|
}
|
|
592
770
|
}
|
|
593
771
|
}
|
|
@@ -615,18 +793,29 @@ export class TypeChecker {
|
|
|
615
793
|
|
|
616
794
|
checkFuncDecl(decl, outer) {
|
|
617
795
|
const inner = new Scope(outer);
|
|
796
|
+
// Inject type params into scope for generic functions
|
|
797
|
+
if (decl.typeParams) {
|
|
798
|
+
for (const tp of decl.typeParams) {
|
|
799
|
+
const constraint = tp.constraint
|
|
800
|
+
? this.resolveTypeNode(tp.constraint, outer)
|
|
801
|
+
: ANY;
|
|
802
|
+
inner.define(tp.name, { kind: "typeParam", name: tp.name, constraint });
|
|
803
|
+
}
|
|
804
|
+
}
|
|
618
805
|
for (const p of decl.params) {
|
|
619
|
-
inner.define(p.name, this.resolveTypeNode(p.type,
|
|
806
|
+
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
620
807
|
}
|
|
621
|
-
|
|
622
|
-
? this.resolveTypeNode(decl.returnType,
|
|
808
|
+
let returnType = decl.returnType
|
|
809
|
+
? this.resolveTypeNode(decl.returnType, inner)
|
|
623
810
|
: VOID;
|
|
624
811
|
const hasNamedReturns = this._injectNamedReturns(
|
|
625
812
|
decl.returnType,
|
|
626
813
|
inner,
|
|
627
814
|
outer,
|
|
628
815
|
);
|
|
629
|
-
|
|
816
|
+
// Wrap the return type to avoid mutating the shared singleton
|
|
817
|
+
if (hasNamedReturns && returnType)
|
|
818
|
+
returnType = { ...returnType, _hasNamedReturns: true };
|
|
630
819
|
const savedDefer = this._deferCount;
|
|
631
820
|
this._deferCount = 0;
|
|
632
821
|
this.checkBlock(decl.body, inner, returnType);
|
|
@@ -634,24 +823,42 @@ export class TypeChecker {
|
|
|
634
823
|
if (this._deferCount > 0) decl.body._hasDefer = true;
|
|
635
824
|
this._deferCount = savedDefer;
|
|
636
825
|
decl._returnType = returnType;
|
|
826
|
+
// Missing return check: non-void functions must terminate on all paths
|
|
827
|
+
if (!isVoid(returnType) && !returnType._hasNamedReturns) {
|
|
828
|
+
if (!this._isTerminating(decl.body)) {
|
|
829
|
+
this.err(`missing return at end of function '${decl.name}'`, decl);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
637
832
|
}
|
|
638
833
|
|
|
639
834
|
checkMethodDecl(decl, outer) {
|
|
640
835
|
const inner = new Scope(outer);
|
|
836
|
+
// For generic receiver types (e.g. Stack[T]), inject type params
|
|
837
|
+
const recvTypeName =
|
|
838
|
+
decl.recvType.kind === "GenericTypeName"
|
|
839
|
+
? decl.recvType.name
|
|
840
|
+
: decl.recvType.name;
|
|
841
|
+
const recvNamedType = this.types.get(recvTypeName);
|
|
842
|
+
if (recvNamedType?._generic) {
|
|
843
|
+
for (const tp of recvNamedType._generic.typeParams) {
|
|
844
|
+
inner.define(tp.name, tp);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
641
847
|
const recvType = this.resolveTypeNodeName(decl.recvType, outer);
|
|
642
848
|
inner.define(decl.recvName, recvType);
|
|
643
849
|
for (const p of decl.params) {
|
|
644
|
-
inner.define(p.name, this.resolveTypeNode(p.type,
|
|
850
|
+
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
645
851
|
}
|
|
646
|
-
|
|
647
|
-
? this.resolveTypeNode(decl.returnType,
|
|
852
|
+
let returnType = decl.returnType
|
|
853
|
+
? this.resolveTypeNode(decl.returnType, inner)
|
|
648
854
|
: VOID;
|
|
649
855
|
const hasNamedReturns = this._injectNamedReturns(
|
|
650
856
|
decl.returnType,
|
|
651
857
|
inner,
|
|
652
|
-
|
|
858
|
+
inner,
|
|
653
859
|
);
|
|
654
|
-
if (hasNamedReturns && returnType)
|
|
860
|
+
if (hasNamedReturns && returnType)
|
|
861
|
+
returnType = { ...returnType, _hasNamedReturns: true };
|
|
655
862
|
const savedDefer = this._deferCount;
|
|
656
863
|
this._deferCount = 0;
|
|
657
864
|
this.checkBlock(decl.body, inner, returnType);
|
|
@@ -659,6 +866,92 @@ export class TypeChecker {
|
|
|
659
866
|
if (this._deferCount > 0) decl.body._hasDefer = true;
|
|
660
867
|
this._deferCount = savedDefer;
|
|
661
868
|
decl._returnType = returnType;
|
|
869
|
+
// Missing return check
|
|
870
|
+
if (!isVoid(returnType) && !returnType._hasNamedReturns) {
|
|
871
|
+
if (!this._isTerminating(decl.body)) {
|
|
872
|
+
this.err(`missing return at end of method '${decl.name}'`, decl);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Go spec §Terminating statements — returns true if the statement always terminates.
|
|
878
|
+
_isTerminating(stmt) {
|
|
879
|
+
if (!stmt) return false;
|
|
880
|
+
switch (stmt.kind) {
|
|
881
|
+
case "ReturnStmt":
|
|
882
|
+
return true;
|
|
883
|
+
case "BranchStmt":
|
|
884
|
+
// panic() is in ExprStmt, not BranchStmt — but "goto" could be here
|
|
885
|
+
return false;
|
|
886
|
+
case "ExprStmt":
|
|
887
|
+
// panic(...) is a terminating call
|
|
888
|
+
if (
|
|
889
|
+
stmt.expr?.kind === "CallExpr" &&
|
|
890
|
+
stmt.expr?.func?.kind === "Ident" &&
|
|
891
|
+
stmt.expr?.func?.name === "panic"
|
|
892
|
+
)
|
|
893
|
+
return true;
|
|
894
|
+
return false;
|
|
895
|
+
case "Block":
|
|
896
|
+
return this._isTerminatingBlock(stmt);
|
|
897
|
+
case "IfStmt":
|
|
898
|
+
// Terminating if: has else, and both branches terminate
|
|
899
|
+
if (!stmt.elseBody) return false;
|
|
900
|
+
return (
|
|
901
|
+
this._isTerminatingBlock(stmt.body) &&
|
|
902
|
+
(stmt.elseBody.kind === "Block"
|
|
903
|
+
? this._isTerminatingBlock(stmt.elseBody)
|
|
904
|
+
: this._isTerminating(stmt.elseBody))
|
|
905
|
+
);
|
|
906
|
+
case "ForStmt":
|
|
907
|
+
// Infinite loop (no condition) with no break — terminating
|
|
908
|
+
if (!stmt.cond && !this._blockHasBreak(stmt.body)) return true;
|
|
909
|
+
return false;
|
|
910
|
+
case "SwitchStmt":
|
|
911
|
+
// Terminating switch: has default and every case terminates
|
|
912
|
+
return this._isTerminatingSwitch(stmt);
|
|
913
|
+
case "TypeSwitchStmt":
|
|
914
|
+
return this._isTerminatingTypeSwitch(stmt);
|
|
915
|
+
case "LabeledStmt":
|
|
916
|
+
return this._isTerminating(stmt.body);
|
|
917
|
+
default:
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
_isTerminatingBlock(block) {
|
|
923
|
+
if (!block?.stmts?.length) return false;
|
|
924
|
+
return this._isTerminating(block.stmts[block.stmts.length - 1]);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
_blockHasBreak(block) {
|
|
928
|
+
if (!block?.stmts) return false;
|
|
929
|
+
for (const s of block.stmts) {
|
|
930
|
+
if (s.kind === "BranchStmt" && s.keyword === "break") return true;
|
|
931
|
+
}
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
_isTerminatingSwitch(stmt) {
|
|
936
|
+
let hasDefault = false;
|
|
937
|
+
for (const c of stmt.cases ?? []) {
|
|
938
|
+
if (!c.list) hasDefault = true;
|
|
939
|
+
if (!c.stmts?.length) return false;
|
|
940
|
+
const last = c.stmts[c.stmts.length - 1];
|
|
941
|
+
if (!this._isTerminating(last)) return false;
|
|
942
|
+
}
|
|
943
|
+
return hasDefault;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
_isTerminatingTypeSwitch(stmt) {
|
|
947
|
+
let hasDefault = false;
|
|
948
|
+
for (const c of stmt.cases ?? []) {
|
|
949
|
+
if (!c.types) hasDefault = true;
|
|
950
|
+
if (!c.stmts?.length) return false;
|
|
951
|
+
const last = c.stmts[c.stmts.length - 1];
|
|
952
|
+
if (!this._isTerminating(last)) return false;
|
|
953
|
+
}
|
|
954
|
+
return hasDefault;
|
|
662
955
|
}
|
|
663
956
|
|
|
664
957
|
_injectNamedReturns(returnTypeNode, scope, outer) {
|
|
@@ -705,10 +998,37 @@ export class TypeChecker {
|
|
|
705
998
|
switch (node.kind) {
|
|
706
999
|
case "TypeName": {
|
|
707
1000
|
if (BASIC_TYPES[node.name]) return BASIC_TYPES[node.name];
|
|
1001
|
+
if (node.name === "comparable")
|
|
1002
|
+
return { kind: "basic", name: "comparable" };
|
|
1003
|
+
// Check scope for type params before checking this.types
|
|
1004
|
+
if (scope) {
|
|
1005
|
+
const fromScope = scope.lookup(node.name);
|
|
1006
|
+
if (fromScope?.kind === "typeParam") return fromScope;
|
|
1007
|
+
}
|
|
708
1008
|
const named = this.types.get(node.name);
|
|
709
1009
|
if (named) return named;
|
|
710
1010
|
return this.err(`Unknown type '${node.name}'`, node);
|
|
711
1011
|
}
|
|
1012
|
+
case "GenericTypeName": {
|
|
1013
|
+
const base = this.types.get(node.name);
|
|
1014
|
+
if (!base) return this.err(`Unknown type '${node.name}'`, node);
|
|
1015
|
+
if (base.kind === "named" && base._generic) {
|
|
1016
|
+
return this.instantiateGenericType(
|
|
1017
|
+
base._generic,
|
|
1018
|
+
node.typeArgs,
|
|
1019
|
+
scope,
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
return this.err(`Type '${node.name}' is not generic`, node);
|
|
1023
|
+
}
|
|
1024
|
+
case "TypeParam": {
|
|
1025
|
+
const constraint = node.constraint
|
|
1026
|
+
? this.resolveTypeNode(node.constraint, scope)
|
|
1027
|
+
: ANY;
|
|
1028
|
+
return { kind: "typeParam", name: node.name, constraint };
|
|
1029
|
+
}
|
|
1030
|
+
case "UnionConstraint":
|
|
1031
|
+
return node;
|
|
712
1032
|
case "SliceType":
|
|
713
1033
|
return {
|
|
714
1034
|
kind: "slice",
|
|
@@ -827,11 +1147,207 @@ export class TypeChecker {
|
|
|
827
1147
|
const named = this.types.get(node.name);
|
|
828
1148
|
if (named) return named;
|
|
829
1149
|
}
|
|
1150
|
+
if (node.kind === "GenericTypeName") {
|
|
1151
|
+
const named = this.types.get(node.name);
|
|
1152
|
+
if (named) return named;
|
|
1153
|
+
}
|
|
830
1154
|
return this.resolveTypeNode(node, scope);
|
|
831
1155
|
}
|
|
832
1156
|
|
|
833
1157
|
// ── Helpers ───────────────────────────────────────────────────
|
|
834
1158
|
|
|
1159
|
+
// ── Generics — instantiation, inference, substitution ────────
|
|
1160
|
+
|
|
1161
|
+
substituteType(type, map) {
|
|
1162
|
+
if (!type) return type;
|
|
1163
|
+
if (type.kind === "typeParam") {
|
|
1164
|
+
const bound = map.get(type.name);
|
|
1165
|
+
return bound ?? type;
|
|
1166
|
+
}
|
|
1167
|
+
switch (type.kind) {
|
|
1168
|
+
case "basic":
|
|
1169
|
+
case "untyped":
|
|
1170
|
+
return type;
|
|
1171
|
+
case "slice":
|
|
1172
|
+
return { kind: "slice", elem: this.substituteType(type.elem, map) };
|
|
1173
|
+
case "array":
|
|
1174
|
+
return {
|
|
1175
|
+
kind: "array",
|
|
1176
|
+
size: type.size,
|
|
1177
|
+
elem: this.substituteType(type.elem, map),
|
|
1178
|
+
};
|
|
1179
|
+
case "map":
|
|
1180
|
+
return {
|
|
1181
|
+
kind: "map",
|
|
1182
|
+
key: this.substituteType(type.key, map),
|
|
1183
|
+
value: this.substituteType(type.value, map),
|
|
1184
|
+
};
|
|
1185
|
+
case "func": {
|
|
1186
|
+
return {
|
|
1187
|
+
kind: "func",
|
|
1188
|
+
params: type.params.map((p) => this.substituteType(p, map)),
|
|
1189
|
+
returns: type.returns.map((r) => this.substituteType(r, map)),
|
|
1190
|
+
variadic: type.variadic,
|
|
1191
|
+
async: type.async,
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
case "struct": {
|
|
1195
|
+
const fields = new Map();
|
|
1196
|
+
for (const [k, v] of type.fields)
|
|
1197
|
+
fields.set(k, this.substituteType(v, map));
|
|
1198
|
+
const methods = new Map();
|
|
1199
|
+
for (const [k, v] of type.methods)
|
|
1200
|
+
methods.set(k, this.substituteType(v, map));
|
|
1201
|
+
return {
|
|
1202
|
+
kind: "struct",
|
|
1203
|
+
name: type.name,
|
|
1204
|
+
fields,
|
|
1205
|
+
methods,
|
|
1206
|
+
_embeds: type._embeds,
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
case "interface": {
|
|
1210
|
+
const methods = new Map();
|
|
1211
|
+
for (const [k, v] of type.methods)
|
|
1212
|
+
methods.set(k, this.substituteType(v, map));
|
|
1213
|
+
return { kind: "interface", name: type.name, methods };
|
|
1214
|
+
}
|
|
1215
|
+
case "named":
|
|
1216
|
+
return {
|
|
1217
|
+
kind: "named",
|
|
1218
|
+
name: type.name,
|
|
1219
|
+
underlying: this.substituteType(type.underlying, map),
|
|
1220
|
+
};
|
|
1221
|
+
case "pointer":
|
|
1222
|
+
return { kind: "pointer", base: this.substituteType(type.base, map) };
|
|
1223
|
+
case "tuple":
|
|
1224
|
+
return {
|
|
1225
|
+
kind: "tuple",
|
|
1226
|
+
types: type.types.map((t) => this.substituteType(t, map)),
|
|
1227
|
+
};
|
|
1228
|
+
default:
|
|
1229
|
+
return type;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
instantiateGenericFunc(genericType, typeArgs) {
|
|
1234
|
+
const map = new Map();
|
|
1235
|
+
for (let i = 0; i < genericType.typeParams.length; i++) {
|
|
1236
|
+
map.set(genericType.typeParams[i].name, typeArgs[i] ?? ANY);
|
|
1237
|
+
}
|
|
1238
|
+
return this.substituteType(genericType.underlying, map);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
instantiateGenericType(generic, typeArgNodes, scope) {
|
|
1242
|
+
const map = new Map();
|
|
1243
|
+
const typeArgs = typeArgNodes.map((n) => this.resolveTypeNode(n, scope));
|
|
1244
|
+
for (let i = 0; i < generic.typeParams.length; i++) {
|
|
1245
|
+
map.set(generic.typeParams[i].name, typeArgs[i] ?? ANY);
|
|
1246
|
+
}
|
|
1247
|
+
// Resolve the underlying type from the decl node using a scope with type params bound
|
|
1248
|
+
const typeScope = new Scope(this.globals);
|
|
1249
|
+
for (const [name, type] of map) typeScope.define(name, type);
|
|
1250
|
+
const underlying = this.resolveTypeNode(generic.declNode.type, typeScope);
|
|
1251
|
+
const instantiated = this.substituteType(underlying, map);
|
|
1252
|
+
if (instantiated.kind === "struct") {
|
|
1253
|
+
instantiated.name = generic.declNode.name;
|
|
1254
|
+
if (!instantiated.methods) instantiated.methods = new Map();
|
|
1255
|
+
// Copy and substitute methods from the generic
|
|
1256
|
+
if (generic.methods) {
|
|
1257
|
+
for (const [mName, mType] of generic.methods) {
|
|
1258
|
+
instantiated.methods.set(mName, this.substituteType(mType, map));
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return {
|
|
1263
|
+
kind: "named",
|
|
1264
|
+
name: generic.declNode.name,
|
|
1265
|
+
underlying: instantiated,
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
inferTypeArgs(genericType, argTypes) {
|
|
1270
|
+
const map = new Map();
|
|
1271
|
+
const params = genericType.underlying.params;
|
|
1272
|
+
for (let i = 0; i < params.length && i < argTypes.length; i++) {
|
|
1273
|
+
this._inferFromPair(params[i], argTypes[i], map);
|
|
1274
|
+
}
|
|
1275
|
+
// Check all type params are resolved
|
|
1276
|
+
for (const tp of genericType.typeParams) {
|
|
1277
|
+
if (!map.has(tp.name)) return null;
|
|
1278
|
+
}
|
|
1279
|
+
// Apply defaultType to resolve untyped constants
|
|
1280
|
+
const result = [];
|
|
1281
|
+
for (const tp of genericType.typeParams) {
|
|
1282
|
+
result.push(defaultType(map.get(tp.name)));
|
|
1283
|
+
}
|
|
1284
|
+
return result;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
_inferFromPair(paramType, argType, map) {
|
|
1288
|
+
if (!paramType || !argType) return;
|
|
1289
|
+
if (paramType.kind === "typeParam") {
|
|
1290
|
+
if (!map.has(paramType.name)) {
|
|
1291
|
+
map.set(paramType.name, argType);
|
|
1292
|
+
}
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
if (paramType.kind === "slice" && argType.kind === "slice") {
|
|
1296
|
+
this._inferFromPair(paramType.elem, argType.elem, map);
|
|
1297
|
+
}
|
|
1298
|
+
if (paramType.kind === "map" && argType.kind === "map") {
|
|
1299
|
+
this._inferFromPair(paramType.key, argType.key, map);
|
|
1300
|
+
this._inferFromPair(paramType.value, argType.value, map);
|
|
1301
|
+
}
|
|
1302
|
+
if (paramType.kind === "func" && argType.kind === "func") {
|
|
1303
|
+
for (
|
|
1304
|
+
let i = 0;
|
|
1305
|
+
i < paramType.params.length && i < argType.params.length;
|
|
1306
|
+
i++
|
|
1307
|
+
) {
|
|
1308
|
+
this._inferFromPair(paramType.params[i], argType.params[i], map);
|
|
1309
|
+
}
|
|
1310
|
+
for (
|
|
1311
|
+
let i = 0;
|
|
1312
|
+
i < paramType.returns.length && i < argType.returns.length;
|
|
1313
|
+
i++
|
|
1314
|
+
) {
|
|
1315
|
+
this._inferFromPair(paramType.returns[i], argType.returns[i], map);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
checkConstraint(typeArg, constraint, node) {
|
|
1321
|
+
if (!constraint) return;
|
|
1322
|
+
if (isAny(constraint)) return;
|
|
1323
|
+
if (constraint.kind === "basic" && constraint.name === "comparable") return;
|
|
1324
|
+
if (constraint.kind === "UnionConstraint") {
|
|
1325
|
+
// Check if typeArg matches any term; ~terms always pass (v1)
|
|
1326
|
+
for (const term of constraint.terms) {
|
|
1327
|
+
if (term.approx) return; // ~ means approximate, always passes
|
|
1328
|
+
const termType = this.resolveTypeNode(term.type, this.globals);
|
|
1329
|
+
if (typeStr(typeArg) === typeStr(termType)) return;
|
|
1330
|
+
}
|
|
1331
|
+
this.err(`type ${typeStr(typeArg)} does not satisfy constraint`, node);
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
// Interface constraint — unwrap named types
|
|
1335
|
+
const base =
|
|
1336
|
+
constraint.kind === "named" ? constraint.underlying : constraint;
|
|
1337
|
+
if (base?.kind === "interface") {
|
|
1338
|
+
if (base.methods.size === 0 && !base.unionConstraint) return;
|
|
1339
|
+
if (base.unionConstraint) {
|
|
1340
|
+
return this.checkConstraint(typeArg, base.unionConstraint, node);
|
|
1341
|
+
}
|
|
1342
|
+
if (!this.implements(typeArg, base, node)) {
|
|
1343
|
+
this.err(
|
|
1344
|
+
`type ${typeStr(typeArg)} does not satisfy constraint ${typeStr(constraint)}`,
|
|
1345
|
+
node,
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
835
1351
|
resolveType(t) {
|
|
836
1352
|
if (!t) return ANY;
|
|
837
1353
|
if (t.kind === "basic" && t.alias) {
|
|
@@ -851,16 +1367,29 @@ export class TypeChecker {
|
|
|
851
1367
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
852
1368
|
return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
|
|
853
1369
|
}
|
|
1370
|
+
if (base?.kind === "interface") {
|
|
1371
|
+
if (base.methods?.has(field)) return base.methods.get(field);
|
|
1372
|
+
return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
|
|
1373
|
+
}
|
|
854
1374
|
if (base?.kind === "namespace") {
|
|
1375
|
+
// Enforce exported identifier rule for GoFront packages only
|
|
1376
|
+
if (
|
|
1377
|
+
base._gofront &&
|
|
1378
|
+
field.length > 0 &&
|
|
1379
|
+
field[0] >= "a" &&
|
|
1380
|
+
field[0] <= "z"
|
|
1381
|
+
) {
|
|
1382
|
+
return this.err(
|
|
1383
|
+
`cannot refer to unexported name ${base.name}.${field}`,
|
|
1384
|
+
node,
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
855
1387
|
if (field in base.members) return base.members[field];
|
|
856
1388
|
return this.err(`No member '${field}' in namespace ${base.name}`, node);
|
|
857
1389
|
}
|
|
858
1390
|
// For explicitly typed non-any values, report the bad access rather than
|
|
859
1391
|
// silently returning any — this catches typos on structs and primitive misuse.
|
|
860
1392
|
if (base && !isAny(base)) {
|
|
861
|
-
// error.Error() is valid — it returns the error string
|
|
862
|
-
if (isError(base) && field === "Error")
|
|
863
|
-
return { kind: "func", params: [], returns: [STRING], async: false };
|
|
864
1393
|
// pointer.value is the GoFront new(T) pattern
|
|
865
1394
|
if (base.kind === "pointer" && field === "value") return base.base ?? ANY;
|
|
866
1395
|
const badKinds = ["basic", "slice", "array", "map", "func", "tuple"];
|
|
@@ -875,9 +1404,64 @@ export class TypeChecker {
|
|
|
875
1404
|
}
|
|
876
1405
|
|
|
877
1406
|
binaryResultType(op, lt, rt, node) {
|
|
878
|
-
|
|
1407
|
+
// Type parameters are permissive — allow operations and return the typeParam
|
|
1408
|
+
if (lt?.kind === "typeParam" || rt?.kind === "typeParam") {
|
|
1409
|
+
if (CMP_OPS.has(op)) return BOOL;
|
|
1410
|
+
if (LOG_OPS.has(op)) return BOOL;
|
|
1411
|
+
return lt?.kind === "typeParam" ? lt : rt;
|
|
1412
|
+
}
|
|
1413
|
+
if (CMP_OPS.has(op)) {
|
|
1414
|
+
// Complex only supports == and !=
|
|
1415
|
+
if (isComplex(lt) || isComplex(rt)) {
|
|
1416
|
+
if (op !== "==" && op !== "!=") {
|
|
1417
|
+
this.err(
|
|
1418
|
+
`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
|
|
1419
|
+
node,
|
|
1420
|
+
);
|
|
1421
|
+
return ANY;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
// Reject slice/map/func comparisons unless one side is nil (Go spec)
|
|
1425
|
+
if (op === "==" || op === "!=") {
|
|
1426
|
+
const lNil = isNil(lt);
|
|
1427
|
+
const rNil = isNil(rt);
|
|
1428
|
+
if (!lNil && !rNil) {
|
|
1429
|
+
for (const t of [lt, rt]) {
|
|
1430
|
+
const base = t?.kind === "named" ? t.underlying : t;
|
|
1431
|
+
if (
|
|
1432
|
+
base?.kind === "slice" ||
|
|
1433
|
+
base?.kind === "map" ||
|
|
1434
|
+
base?.kind === "func"
|
|
1435
|
+
) {
|
|
1436
|
+
this.err(`operator ${op} not defined on ${typeStr(t)}`, node);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
return BOOL;
|
|
1442
|
+
}
|
|
879
1443
|
if (LOG_OPS.has(op)) return BOOL;
|
|
880
1444
|
if (isAny(lt) || isAny(rt)) return ANY;
|
|
1445
|
+
// Complex arithmetic: +, -, *, /
|
|
1446
|
+
if (isComplex(lt) || isComplex(rt)) {
|
|
1447
|
+
if (!isComplexOrNumeric(lt) || !isComplexOrNumeric(rt)) {
|
|
1448
|
+
this.err(
|
|
1449
|
+
`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
|
|
1450
|
+
node,
|
|
1451
|
+
);
|
|
1452
|
+
return ANY;
|
|
1453
|
+
}
|
|
1454
|
+
if (op !== "+" && op !== "-" && op !== "*" && op !== "/") {
|
|
1455
|
+
this.err(
|
|
1456
|
+
`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
|
|
1457
|
+
node,
|
|
1458
|
+
);
|
|
1459
|
+
return ANY;
|
|
1460
|
+
}
|
|
1461
|
+
if (lt.kind === "untyped" && rt.kind === "untyped")
|
|
1462
|
+
return UNTYPED_COMPLEX;
|
|
1463
|
+
return COMPLEX128;
|
|
1464
|
+
}
|
|
881
1465
|
if (isNumeric(lt) && isNumeric(rt)) {
|
|
882
1466
|
const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
|
|
883
1467
|
const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
|
|
@@ -908,8 +1492,19 @@ export class TypeChecker {
|
|
|
908
1492
|
if (!target || !source) return;
|
|
909
1493
|
target = this.resolveType(target);
|
|
910
1494
|
source = this.resolveType(source);
|
|
1495
|
+
// Type params are permissive — allow any assignment
|
|
1496
|
+
if (target?.kind === "typeParam" || source?.kind === "typeParam") return;
|
|
911
1497
|
if (isAny(target) || isAny(source)) return;
|
|
912
1498
|
if (isNil(source)) return; // nil is assignable to anything nullable
|
|
1499
|
+
// Pointer type: nil → *T is allowed (handled above); *T → *T check base types
|
|
1500
|
+
if (isPointer(target) && isPointer(source)) {
|
|
1501
|
+
this.assertAssignable(
|
|
1502
|
+
target.base ?? target.underlying?.base,
|
|
1503
|
+
source.base ?? source.underlying?.base,
|
|
1504
|
+
node,
|
|
1505
|
+
);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
913
1508
|
|
|
914
1509
|
// Untyped constants coerce to any compatible typed target
|
|
915
1510
|
if (isUntyped(source)) {
|
|
@@ -931,6 +1526,18 @@ export class TypeChecker {
|
|
|
931
1526
|
if (source.base === "string" && target.name === "string") return;
|
|
932
1527
|
// untyped bool → bool
|
|
933
1528
|
if (source.base === "bool" && target.name === "bool") return;
|
|
1529
|
+
// untyped complex → complex128 or complex64
|
|
1530
|
+
if (
|
|
1531
|
+
source.base === "complex128" &&
|
|
1532
|
+
(target.name === "complex128" || target.name === "complex64")
|
|
1533
|
+
)
|
|
1534
|
+
return;
|
|
1535
|
+
// untyped int/float → complex (Go allows: var z complex128 = 5)
|
|
1536
|
+
if (
|
|
1537
|
+
isComplex(target) &&
|
|
1538
|
+
(source.base === "int" || source.base === "float64")
|
|
1539
|
+
)
|
|
1540
|
+
return;
|
|
934
1541
|
}
|
|
935
1542
|
}
|
|
936
1543
|
|
|
@@ -939,6 +1546,33 @@ export class TypeChecker {
|
|
|
939
1546
|
if (target.name === "float64" && source.name === "int") return;
|
|
940
1547
|
if (target.name === "int" && source.name === "float64") return; // truncating
|
|
941
1548
|
}
|
|
1549
|
+
|
|
1550
|
+
// Array-specific checks: size matching and array/slice incompatibility
|
|
1551
|
+
if (target.kind === "array" && source.kind === "array") {
|
|
1552
|
+
if (
|
|
1553
|
+
target.size != null &&
|
|
1554
|
+
source.size != null &&
|
|
1555
|
+
target.size !== source.size
|
|
1556
|
+
) {
|
|
1557
|
+
this.err(
|
|
1558
|
+
`Cannot assign ${typeStr(source)} to ${typeStr(target)} (different array lengths)`,
|
|
1559
|
+
node,
|
|
1560
|
+
);
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
// Check element type compatibility
|
|
1564
|
+
this.assertAssignable(target.elem, source.elem, node);
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (target.kind === "array" && source.kind === "slice") {
|
|
1568
|
+
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
if (target.kind === "slice" && source.kind === "array") {
|
|
1572
|
+
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
942
1576
|
if (typeStr(target) !== typeStr(source)) {
|
|
943
1577
|
// Interface satisfaction check
|
|
944
1578
|
let tBase = target.kind === "named" ? target.underlying : target;
|
|
@@ -946,6 +1580,8 @@ export class TypeChecker {
|
|
|
946
1580
|
tBase = this.resolveType(tBase);
|
|
947
1581
|
sBase = this.resolveType(sBase);
|
|
948
1582
|
if (tBase?.kind === "interface") {
|
|
1583
|
+
// Empty interface (interface{}) accepts any type
|
|
1584
|
+
if (tBase.methods.size === 0) return;
|
|
949
1585
|
if (!this.implements(source, tBase, node)) {
|
|
950
1586
|
this.err(
|
|
951
1587
|
`${typeStr(source)} does not implement ${typeStr(target)}`,
|