gofront 0.0.1 → 0.0.2
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 +209 -9
- package/package.json +1 -1
- package/src/codegen.js +198 -43
- package/src/compiler.js +13 -6
- package/src/dts-parser.js +0 -4
- package/src/index.js +8 -17
- package/src/lexer.js +76 -6
- package/src/parser.js +172 -47
- package/src/resolver.js +1 -1
- package/src/typechecker.js +107 -13
package/src/parser.js
CHANGED
|
@@ -129,18 +129,23 @@ export class Parser {
|
|
|
129
129
|
|
|
130
130
|
parseImport() {
|
|
131
131
|
this.expect(T.IMPORT);
|
|
132
|
-
const
|
|
132
|
+
const imports = [];
|
|
133
|
+
const parseOne = () => {
|
|
134
|
+
const alias = this.check(T.IDENT) ? this.advance().value : null;
|
|
135
|
+
const path = this.expect(T.STRING).value;
|
|
136
|
+
return { path, alias };
|
|
137
|
+
};
|
|
133
138
|
if (this.match(T.LPAREN)) {
|
|
134
139
|
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
135
|
-
|
|
140
|
+
imports.push(parseOne());
|
|
136
141
|
this.semi();
|
|
137
142
|
}
|
|
138
143
|
this.expect(T.RPAREN);
|
|
139
144
|
} else {
|
|
140
|
-
|
|
145
|
+
imports.push(parseOne());
|
|
141
146
|
}
|
|
142
147
|
this.semi();
|
|
143
|
-
return { kind: "ImportDecl",
|
|
148
|
+
return { kind: "ImportDecl", imports };
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
// ── Top-level declarations ───────────────────────────────────
|
|
@@ -155,7 +160,7 @@ export class Parser {
|
|
|
155
160
|
case T.TYPE:
|
|
156
161
|
return this.parseTypeDecl();
|
|
157
162
|
case T.VAR:
|
|
158
|
-
return this.parseVarDecl(
|
|
163
|
+
return this.parseVarDecl();
|
|
159
164
|
case T.CONST:
|
|
160
165
|
return this.parseConstDecl();
|
|
161
166
|
default:
|
|
@@ -292,7 +297,12 @@ export class Parser {
|
|
|
292
297
|
|
|
293
298
|
parseReturnType(inTypeExpr = false) {
|
|
294
299
|
// No return type
|
|
295
|
-
if (
|
|
300
|
+
if (
|
|
301
|
+
this.check(T.LBRACE) ||
|
|
302
|
+
this.check(T.RBRACE) ||
|
|
303
|
+
this.check(T.SEMICOLON) ||
|
|
304
|
+
this.check(T.EOF)
|
|
305
|
+
)
|
|
296
306
|
return null;
|
|
297
307
|
// Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
|
|
298
308
|
if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
|
|
@@ -330,12 +340,13 @@ export class Parser {
|
|
|
330
340
|
parseTypeDecl() {
|
|
331
341
|
this.expect(T.TYPE);
|
|
332
342
|
const name = this.expect(T.IDENT).value;
|
|
343
|
+
const isAlias = this.match(T.ASSIGN);
|
|
333
344
|
const type = this.parseType();
|
|
334
345
|
this.semi();
|
|
335
|
-
return { kind: "TypeDecl", name, type };
|
|
346
|
+
return { kind: "TypeDecl", name, type, isAlias };
|
|
336
347
|
}
|
|
337
348
|
|
|
338
|
-
parseVarDecl(
|
|
349
|
+
parseVarDecl() {
|
|
339
350
|
this.expect(T.VAR);
|
|
340
351
|
const decls = [];
|
|
341
352
|
if (this.match(T.LPAREN)) {
|
|
@@ -466,6 +477,11 @@ export class Parser {
|
|
|
466
477
|
this.advance();
|
|
467
478
|
return { kind: "SliceType", elem: this.parseType() };
|
|
468
479
|
}
|
|
480
|
+
if (this.match(T.ELLIPSIS)) {
|
|
481
|
+
// [...]T — array with inferred length from composite literal
|
|
482
|
+
this.expect(T.RBRACKET);
|
|
483
|
+
return { kind: "ArrayType", inferLen: true, elem: this.parseType() };
|
|
484
|
+
}
|
|
469
485
|
// fixed array: [n]T — treated as slice for JS purposes
|
|
470
486
|
const size = this.parseExpr();
|
|
471
487
|
this.expect(T.RBRACKET);
|
|
@@ -508,6 +524,8 @@ export class Parser {
|
|
|
508
524
|
continue;
|
|
509
525
|
}
|
|
510
526
|
const type = this.parseType();
|
|
527
|
+
// Consume optional struct tag (backtick string), e.g. `json:"name"`
|
|
528
|
+
if (this.check(T.STRING)) this.advance();
|
|
511
529
|
fields.push({ names, type });
|
|
512
530
|
this.semi();
|
|
513
531
|
}
|
|
@@ -519,15 +537,27 @@ export class Parser {
|
|
|
519
537
|
this.expect(T.INTERFACE);
|
|
520
538
|
this.expect(T.LBRACE);
|
|
521
539
|
const methods = [];
|
|
540
|
+
const embeds = [];
|
|
522
541
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
523
542
|
const name = this.expect(T.IDENT).value;
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
543
|
+
if (this.check(T.LPAREN)) {
|
|
544
|
+
// Method signature: Name(params) returnType
|
|
545
|
+
const params = this.parseParamList();
|
|
546
|
+
const returnType = this.parseReturnType();
|
|
547
|
+
methods.push({ name, params, returnType });
|
|
548
|
+
} else {
|
|
549
|
+
// Embedded interface: TypeName or pkg.TypeName
|
|
550
|
+
let typeName = name;
|
|
551
|
+
if (this.check(T.DOT)) {
|
|
552
|
+
this.advance();
|
|
553
|
+
typeName += `.${this.expect(T.IDENT).value}`;
|
|
554
|
+
}
|
|
555
|
+
embeds.push({ kind: "TypeName", name: typeName });
|
|
556
|
+
}
|
|
527
557
|
this.semi();
|
|
528
558
|
}
|
|
529
559
|
this.expect(T.RBRACE);
|
|
530
|
-
return { kind: "InterfaceType", methods };
|
|
560
|
+
return { kind: "InterfaceType", methods, embeds };
|
|
531
561
|
}
|
|
532
562
|
|
|
533
563
|
// ── Statements ───────────────────────────────────────────────
|
|
@@ -564,10 +594,20 @@ export class Parser {
|
|
|
564
594
|
return this.parseDefer();
|
|
565
595
|
case T.BREAK:
|
|
566
596
|
this.advance();
|
|
597
|
+
if (this.check(T.IDENT)) {
|
|
598
|
+
const label = this.advance().value;
|
|
599
|
+
this.semi();
|
|
600
|
+
return { kind: "BranchStmt", keyword: "break", label };
|
|
601
|
+
}
|
|
567
602
|
this.semi();
|
|
568
603
|
return { kind: "BranchStmt", keyword: "break" };
|
|
569
604
|
case T.CONTINUE:
|
|
570
605
|
this.advance();
|
|
606
|
+
if (this.check(T.IDENT)) {
|
|
607
|
+
const label = this.advance().value;
|
|
608
|
+
this.semi();
|
|
609
|
+
return { kind: "BranchStmt", keyword: "continue", label };
|
|
610
|
+
}
|
|
571
611
|
this.semi();
|
|
572
612
|
return { kind: "BranchStmt", keyword: "continue" };
|
|
573
613
|
case T.FALLTHROUGH:
|
|
@@ -582,6 +622,14 @@ export class Parser {
|
|
|
582
622
|
return this.parseSwitch();
|
|
583
623
|
case T.LBRACE:
|
|
584
624
|
return this.parseBlock();
|
|
625
|
+
case T.IDENT:
|
|
626
|
+
if (this.check2(T.COLON)) {
|
|
627
|
+
const label = this.advance().value; // consume IDENT
|
|
628
|
+
this.advance(); // consume COLON
|
|
629
|
+
const body = this.parseStmt();
|
|
630
|
+
return { kind: "LabeledStmt", label, body };
|
|
631
|
+
}
|
|
632
|
+
return this.parseSimpleStmt();
|
|
585
633
|
default:
|
|
586
634
|
return this.parseSimpleStmt();
|
|
587
635
|
}
|
|
@@ -687,14 +735,27 @@ export class Parser {
|
|
|
687
735
|
let init = null;
|
|
688
736
|
let tag = null;
|
|
689
737
|
if (!this.check(T.LBRACE)) {
|
|
690
|
-
const
|
|
738
|
+
const first = this.parseSimpleStmtRaw(true);
|
|
691
739
|
if (this.match(T.SEMICOLON)) {
|
|
692
|
-
init =
|
|
693
|
-
if (!this.check(T.LBRACE))
|
|
740
|
+
init = first;
|
|
741
|
+
if (!this.check(T.LBRACE))
|
|
742
|
+
tag = this.stmtToExpr(this.parseSimpleStmtRaw(true));
|
|
694
743
|
} else {
|
|
695
|
-
tag =
|
|
744
|
+
tag = this.stmtToExpr(first);
|
|
696
745
|
}
|
|
697
746
|
}
|
|
747
|
+
|
|
748
|
+
// Detect type switch: switch x.(type) or switch v := x.(type)
|
|
749
|
+
if (tag?.kind === "TypeSwitchExpr") {
|
|
750
|
+
return this._parseTypeSwitchBody(null, tag.expr);
|
|
751
|
+
}
|
|
752
|
+
if (tag?.kind === "DefineStmt" && tag.rhs[0]?.kind === "TypeSwitchExpr") {
|
|
753
|
+
return this._parseTypeSwitchBody(
|
|
754
|
+
tag.lhs[0]?.name ?? null,
|
|
755
|
+
tag.rhs[0].expr,
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
698
759
|
this.expect(T.LBRACE);
|
|
699
760
|
const cases = [];
|
|
700
761
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
@@ -702,29 +763,15 @@ export class Parser {
|
|
|
702
763
|
this.advance();
|
|
703
764
|
const list = this.parseExprList();
|
|
704
765
|
this.expect(T.COLON);
|
|
705
|
-
|
|
706
|
-
while (
|
|
707
|
-
!this.check(T.CASE) &&
|
|
708
|
-
!this.check(T.DEFAULT) &&
|
|
709
|
-
!this.check(T.RBRACE)
|
|
710
|
-
) {
|
|
711
|
-
stmts.push(this.parseStmt());
|
|
712
|
-
this.semi();
|
|
713
|
-
}
|
|
714
|
-
cases.push({ kind: "CaseClause", list, stmts });
|
|
766
|
+
cases.push({ kind: "CaseClause", list, stmts: this._parseCaseBody() });
|
|
715
767
|
} else if (this.check(T.DEFAULT)) {
|
|
716
768
|
this.advance();
|
|
717
769
|
this.expect(T.COLON);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
) {
|
|
724
|
-
stmts.push(this.parseStmt());
|
|
725
|
-
this.semi();
|
|
726
|
-
}
|
|
727
|
-
cases.push({ kind: "CaseClause", list: null, stmts });
|
|
770
|
+
cases.push({
|
|
771
|
+
kind: "CaseClause",
|
|
772
|
+
list: null,
|
|
773
|
+
stmts: this._parseCaseBody(),
|
|
774
|
+
});
|
|
728
775
|
} else {
|
|
729
776
|
this.err("Expected case or default");
|
|
730
777
|
}
|
|
@@ -733,6 +780,54 @@ export class Parser {
|
|
|
733
780
|
return { kind: "SwitchStmt", init, tag, cases };
|
|
734
781
|
}
|
|
735
782
|
|
|
783
|
+
_parseCaseBody() {
|
|
784
|
+
const stmts = [];
|
|
785
|
+
while (
|
|
786
|
+
!this.check(T.CASE) &&
|
|
787
|
+
!this.check(T.DEFAULT) &&
|
|
788
|
+
!this.check(T.RBRACE)
|
|
789
|
+
) {
|
|
790
|
+
stmts.push(this.parseStmt());
|
|
791
|
+
this.semi();
|
|
792
|
+
}
|
|
793
|
+
return stmts;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
_parseTypeSwitchBody(assign, expr) {
|
|
797
|
+
this.expect(T.LBRACE);
|
|
798
|
+
const cases = [];
|
|
799
|
+
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
800
|
+
if (this.check(T.CASE)) {
|
|
801
|
+
this.advance();
|
|
802
|
+
const types = this._parseTypeSwitchTypeList();
|
|
803
|
+
this.expect(T.COLON);
|
|
804
|
+
cases.push({ types, stmts: this._parseCaseBody() });
|
|
805
|
+
} else if (this.check(T.DEFAULT)) {
|
|
806
|
+
this.advance();
|
|
807
|
+
this.expect(T.COLON);
|
|
808
|
+
cases.push({ types: null, stmts: this._parseCaseBody() });
|
|
809
|
+
} else {
|
|
810
|
+
this.err("Expected case or default");
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
this.expect(T.RBRACE);
|
|
814
|
+
return { kind: "TypeSwitchStmt", assign, expr, cases };
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
_parseTypeSwitchTypeList() {
|
|
818
|
+
const parseOne = () => {
|
|
819
|
+
// nil is a keyword, not a type name, so handle it explicitly
|
|
820
|
+
if (this.check(T.NIL)) {
|
|
821
|
+
this.advance();
|
|
822
|
+
return { kind: "TypeName", name: "nil" };
|
|
823
|
+
}
|
|
824
|
+
return this.parseType();
|
|
825
|
+
};
|
|
826
|
+
const types = [parseOne()];
|
|
827
|
+
while (this.match(T.COMMA)) types.push(parseOne());
|
|
828
|
+
return types;
|
|
829
|
+
}
|
|
830
|
+
|
|
736
831
|
// Parses a "simple statement": assignment, define, inc/dec, or expression.
|
|
737
832
|
// Returns a statement node.
|
|
738
833
|
parseSimpleStmt() {
|
|
@@ -770,6 +865,11 @@ export class Parser {
|
|
|
770
865
|
T.STAR_ASSIGN,
|
|
771
866
|
T.SLASH_ASSIGN,
|
|
772
867
|
T.PERCENT_ASSIGN,
|
|
868
|
+
T.AMP_ASSIGN,
|
|
869
|
+
T.PIPE_ASSIGN,
|
|
870
|
+
T.CARET_ASSIGN,
|
|
871
|
+
T.LSHIFT_ASSIGN,
|
|
872
|
+
T.RSHIFT_ASSIGN,
|
|
773
873
|
];
|
|
774
874
|
if (assignOps.includes(t.type)) {
|
|
775
875
|
const op = this.advance().value;
|
|
@@ -800,10 +900,6 @@ export class Parser {
|
|
|
800
900
|
return stmt;
|
|
801
901
|
}
|
|
802
902
|
|
|
803
|
-
exprToStmt(expr) {
|
|
804
|
-
return { kind: "ExprStmt", expr };
|
|
805
|
-
}
|
|
806
|
-
|
|
807
903
|
// ── Expressions ──────────────────────────────────────────────
|
|
808
904
|
|
|
809
905
|
parseExprList() {
|
|
@@ -855,9 +951,16 @@ export class Parser {
|
|
|
855
951
|
// type assertion: expr.(Type)
|
|
856
952
|
if (this.check(T.LPAREN)) {
|
|
857
953
|
this.advance();
|
|
858
|
-
|
|
859
|
-
this.
|
|
860
|
-
|
|
954
|
+
// x.(type) — type switch guard expression
|
|
955
|
+
if (this.check(T.TYPE)) {
|
|
956
|
+
this.advance();
|
|
957
|
+
this.expect(T.RPAREN);
|
|
958
|
+
expr = { kind: "TypeSwitchExpr", expr };
|
|
959
|
+
} else {
|
|
960
|
+
const type = this.parseType();
|
|
961
|
+
this.expect(T.RPAREN);
|
|
962
|
+
expr = { kind: "TypeAssertExpr", expr, type };
|
|
963
|
+
}
|
|
861
964
|
} else {
|
|
862
965
|
const field = this.advance().value;
|
|
863
966
|
expr = { kind: "SelectorExpr", expr, field };
|
|
@@ -915,11 +1018,25 @@ export class Parser {
|
|
|
915
1018
|
(t.type === T.MAP || isSliceOrArrayType) &&
|
|
916
1019
|
!this.looksLikeCompositeLitArg()
|
|
917
1020
|
) {
|
|
918
|
-
|
|
1021
|
+
const typeNode = this.parseType();
|
|
1022
|
+
// []byte(s) / []rune(s) used as a call argument — it's a type conversion
|
|
1023
|
+
if (this.check(T.LPAREN)) {
|
|
1024
|
+
this.advance();
|
|
1025
|
+
const convExpr = this.parseExpr();
|
|
1026
|
+
this.expect(T.RPAREN);
|
|
1027
|
+
args.push({
|
|
1028
|
+
kind: "TypeConversion",
|
|
1029
|
+
targetType: typeNode,
|
|
1030
|
+
expr: convExpr,
|
|
1031
|
+
});
|
|
1032
|
+
} else {
|
|
1033
|
+
args.push({ kind: "TypeExpr", type: typeNode });
|
|
1034
|
+
}
|
|
919
1035
|
} else {
|
|
920
|
-
|
|
1036
|
+
const arg = this.parseExpr();
|
|
1037
|
+
if (this.match(T.ELLIPSIS)) arg._spread = true;
|
|
1038
|
+
args.push(arg);
|
|
921
1039
|
}
|
|
922
|
-
this.match(T.ELLIPSIS); // variadic spread, ignore for now
|
|
923
1040
|
} while (this.match(T.COMMA));
|
|
924
1041
|
}
|
|
925
1042
|
this.expect(T.RPAREN);
|
|
@@ -1080,9 +1197,17 @@ export class Parser {
|
|
|
1080
1197
|
return { kind: "Ident", name: t.value };
|
|
1081
1198
|
}
|
|
1082
1199
|
|
|
1083
|
-
// Slice/array/map composite literals
|
|
1200
|
+
// Slice/array/map composite literals or type conversions:
|
|
1201
|
+
// []int{1,2} → CompositeLit
|
|
1202
|
+
// []byte(s), []rune(s) → TypeConversion
|
|
1084
1203
|
if (t.type === T.LBRACKET || t.type === T.MAP) {
|
|
1085
1204
|
const typeExpr = this.parseType();
|
|
1205
|
+
if (this.check(T.LPAREN)) {
|
|
1206
|
+
this.advance();
|
|
1207
|
+
const expr = this.parseExpr();
|
|
1208
|
+
this.expect(T.RPAREN);
|
|
1209
|
+
return { kind: "TypeConversion", targetType: typeExpr, expr };
|
|
1210
|
+
}
|
|
1086
1211
|
return this.parseCompositeLit(typeExpr);
|
|
1087
1212
|
}
|
|
1088
1213
|
|
package/src/resolver.js
CHANGED
|
@@ -82,7 +82,7 @@ export function resolveAll(imports, fromFile, parseDts) {
|
|
|
82
82
|
const resolved = new Map();
|
|
83
83
|
|
|
84
84
|
for (const imp of imports) {
|
|
85
|
-
for (const path of imp.
|
|
85
|
+
for (const { path } of imp.imports) {
|
|
86
86
|
if (path.startsWith("js:")) continue; // handled separately
|
|
87
87
|
if (isLocalPath(path)) continue; // local .go packages — handled by compiler
|
|
88
88
|
|
package/src/typechecker.js
CHANGED
|
@@ -34,6 +34,10 @@ export class TypeCheckError extends Error {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// ── Static operator sets (module-level for reuse) ────────────
|
|
38
|
+
const CMP_OPS = new Set(["==", "!=", "<", ">", "<=", ">="]);
|
|
39
|
+
const LOG_OPS = new Set(["&&", "||"]);
|
|
40
|
+
|
|
37
41
|
// ── Built-in types ───────────────────────────────────────────
|
|
38
42
|
|
|
39
43
|
const INT = { kind: "basic", name: "int" };
|
|
@@ -54,6 +58,18 @@ const BASIC_TYPES = {
|
|
|
54
58
|
byte: INT,
|
|
55
59
|
rune: INT,
|
|
56
60
|
error: ERROR,
|
|
61
|
+
// Sized integer / float aliases — all map to int or float64 at runtime
|
|
62
|
+
uint: INT,
|
|
63
|
+
int8: INT,
|
|
64
|
+
int16: INT,
|
|
65
|
+
int32: INT,
|
|
66
|
+
int64: INT,
|
|
67
|
+
uint8: INT,
|
|
68
|
+
uint16: INT,
|
|
69
|
+
uint32: INT,
|
|
70
|
+
uint64: INT,
|
|
71
|
+
uintptr: INT,
|
|
72
|
+
float32: FLOAT64,
|
|
57
73
|
};
|
|
58
74
|
|
|
59
75
|
function isNumeric(t) {
|
|
@@ -188,7 +204,6 @@ export class TypeChecker {
|
|
|
188
204
|
"Boolean",
|
|
189
205
|
"Array",
|
|
190
206
|
"Object",
|
|
191
|
-
"JSON",
|
|
192
207
|
"parseInt",
|
|
193
208
|
"parseFloat",
|
|
194
209
|
"isNaN",
|
|
@@ -219,6 +234,8 @@ export class TypeChecker {
|
|
|
219
234
|
"FileReader",
|
|
220
235
|
"ArrayBuffer",
|
|
221
236
|
"Uint8Array",
|
|
237
|
+
"TextEncoder",
|
|
238
|
+
"TextDecoder",
|
|
222
239
|
"WebGLRenderingContext",
|
|
223
240
|
"WebGL2RenderingContext",
|
|
224
241
|
"GPUDevice",
|
|
@@ -254,9 +271,13 @@ export class TypeChecker {
|
|
|
254
271
|
this.globals.define("print", { kind: "builtin", name: "print" });
|
|
255
272
|
this.globals.define("println", { kind: "builtin", name: "println" });
|
|
256
273
|
this.globals.define("panic", { kind: "builtin", name: "panic" });
|
|
274
|
+
this.globals.define("recover", { kind: "builtin", name: "recover" });
|
|
257
275
|
this.globals.define("new", { kind: "builtin", name: "new" });
|
|
258
276
|
this.globals.define("copy", { kind: "builtin", name: "copy" });
|
|
259
277
|
this.globals.define("error", { kind: "builtin", name: "error" });
|
|
278
|
+
this.globals.define("min", { kind: "builtin", name: "min" });
|
|
279
|
+
this.globals.define("max", { kind: "builtin", name: "max" });
|
|
280
|
+
this.globals.define("clear", { kind: "builtin", name: "clear" });
|
|
260
281
|
}
|
|
261
282
|
|
|
262
283
|
addDefinitions(types, values) {
|
|
@@ -412,6 +433,12 @@ export class TypeChecker {
|
|
|
412
433
|
|
|
413
434
|
collectType(decl) {
|
|
414
435
|
const underlying = this.resolveTypeNode(decl.type, this.globals);
|
|
436
|
+
if (decl.isAlias) {
|
|
437
|
+
// type A = B — transparent alias, A and B are identical types
|
|
438
|
+
this.types.set(decl.name, underlying);
|
|
439
|
+
this.globals.define(decl.name, underlying);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
415
442
|
const named = { kind: "named", name: decl.name, underlying };
|
|
416
443
|
if (underlying.kind === "struct") {
|
|
417
444
|
underlying.name = decl.name;
|
|
@@ -685,9 +712,14 @@ export class TypeChecker {
|
|
|
685
712
|
const inner = new Scope(scope);
|
|
686
713
|
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
687
714
|
if (stmt.cond) {
|
|
688
|
-
|
|
689
|
-
if (
|
|
690
|
-
this.
|
|
715
|
+
// for range N { } — RangeExpr as condition is valid (Go 1.22 int range)
|
|
716
|
+
if (stmt.cond.kind !== "RangeExpr") {
|
|
717
|
+
const ct = this.checkExpr(stmt.cond, inner);
|
|
718
|
+
if (!isBool(ct) && !isAny(ct))
|
|
719
|
+
this.err("For condition must be bool", stmt);
|
|
720
|
+
} else {
|
|
721
|
+
this.checkExpr(stmt.cond.expr, inner);
|
|
722
|
+
}
|
|
691
723
|
}
|
|
692
724
|
if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
|
|
693
725
|
this._loopDepth++;
|
|
@@ -699,7 +731,7 @@ export class TypeChecker {
|
|
|
699
731
|
case "SwitchStmt": {
|
|
700
732
|
const inner = new Scope(scope);
|
|
701
733
|
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
702
|
-
|
|
734
|
+
if (stmt.tag) this.checkExpr(stmt.tag, inner);
|
|
703
735
|
this._switchDepth++;
|
|
704
736
|
for (const c of stmt.cases) {
|
|
705
737
|
const caseScope = new Scope(inner);
|
|
@@ -710,6 +742,26 @@ export class TypeChecker {
|
|
|
710
742
|
break;
|
|
711
743
|
}
|
|
712
744
|
|
|
745
|
+
case "TypeSwitchStmt": {
|
|
746
|
+
const inner = new Scope(scope);
|
|
747
|
+
this.checkExpr(stmt.expr, inner);
|
|
748
|
+
this._switchDepth++;
|
|
749
|
+
for (const c of stmt.cases) {
|
|
750
|
+
const caseScope = new Scope(inner);
|
|
751
|
+
if (stmt.assign) {
|
|
752
|
+
// Bind the variable to the single case type, or any for multi/default
|
|
753
|
+
const bindType =
|
|
754
|
+
c.types?.length === 1
|
|
755
|
+
? this.resolveTypeNode(c.types[0], inner)
|
|
756
|
+
: ANY;
|
|
757
|
+
caseScope.define(stmt.assign, bindType);
|
|
758
|
+
}
|
|
759
|
+
for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
|
|
760
|
+
}
|
|
761
|
+
this._switchDepth--;
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
|
|
713
765
|
case "DeferStmt": {
|
|
714
766
|
if (stmt.call.kind !== "CallExpr")
|
|
715
767
|
this.err("defer requires a function call", stmt.call);
|
|
@@ -717,8 +769,13 @@ export class TypeChecker {
|
|
|
717
769
|
break;
|
|
718
770
|
}
|
|
719
771
|
|
|
772
|
+
case "LabeledStmt":
|
|
773
|
+
this.checkStmt(stmt.body, scope, returnType);
|
|
774
|
+
break;
|
|
775
|
+
|
|
720
776
|
case "BranchStmt": {
|
|
721
777
|
const kw = stmt.keyword;
|
|
778
|
+
if (stmt.label) break; // labeled branch — target checked by JS
|
|
722
779
|
if (kw === "continue" && this._loopDepth === 0)
|
|
723
780
|
this.err("continue statement outside for loop", stmt);
|
|
724
781
|
else if (
|
|
@@ -879,13 +936,20 @@ export class TypeChecker {
|
|
|
879
936
|
|
|
880
937
|
checkCall(expr, scope) {
|
|
881
938
|
const fnType = this.checkExpr(expr.func, scope);
|
|
882
|
-
const argTypes = expr.args.map((a) => this.checkExpr(a, scope));
|
|
883
939
|
|
|
884
|
-
//
|
|
940
|
+
// Handle built-ins before evaluating args; some (e.g. new) take type names as
|
|
941
|
+
// arguments, which would otherwise produce false "Undefined" errors.
|
|
885
942
|
if (fnType.kind === "builtin") {
|
|
943
|
+
const argTypes = expr.args.map((a, i) => {
|
|
944
|
+
// new(T) — first arg is a type name, not a value expression
|
|
945
|
+
if (fnType.name === "new" && i === 0) return ANY;
|
|
946
|
+
return this.checkExpr(a, scope);
|
|
947
|
+
});
|
|
886
948
|
return this.checkBuiltin(fnType.name, expr, argTypes, scope);
|
|
887
949
|
}
|
|
888
950
|
|
|
951
|
+
const argTypes = expr.args.map((a) => this.checkExpr(a, scope));
|
|
952
|
+
|
|
889
953
|
if (isAny(fnType)) return ANY;
|
|
890
954
|
|
|
891
955
|
if (fnType.kind !== "func") {
|
|
@@ -915,6 +979,8 @@ export class TypeChecker {
|
|
|
915
979
|
fnType.variadic && i >= fnType.params.length - 1
|
|
916
980
|
? fnType.params.length - 1
|
|
917
981
|
: i;
|
|
982
|
+
// Spread arg (f(slice...)) passes a whole slice into a variadic — skip per-element check
|
|
983
|
+
if (expr.args[i]?._spread) continue;
|
|
918
984
|
this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
|
|
919
985
|
}
|
|
920
986
|
|
|
@@ -950,8 +1016,15 @@ export class TypeChecker {
|
|
|
950
1016
|
case "println":
|
|
951
1017
|
case "panic":
|
|
952
1018
|
return VOID;
|
|
1019
|
+
case "recover":
|
|
1020
|
+
return ANY;
|
|
953
1021
|
case "error":
|
|
954
1022
|
return ERROR;
|
|
1023
|
+
case "min":
|
|
1024
|
+
case "max":
|
|
1025
|
+
return argTypes[0] ?? ANY;
|
|
1026
|
+
case "clear":
|
|
1027
|
+
return VOID;
|
|
955
1028
|
default:
|
|
956
1029
|
return ANY;
|
|
957
1030
|
}
|
|
@@ -993,7 +1066,7 @@ export class TypeChecker {
|
|
|
993
1066
|
return t;
|
|
994
1067
|
}
|
|
995
1068
|
|
|
996
|
-
if (base?.kind === "slice") {
|
|
1069
|
+
if (base?.kind === "slice" || base?.kind === "array") {
|
|
997
1070
|
for (const elem of expr.elems) {
|
|
998
1071
|
if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
|
|
999
1072
|
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
@@ -1058,7 +1131,12 @@ export class TypeChecker {
|
|
|
1058
1131
|
case "ArrayType":
|
|
1059
1132
|
return {
|
|
1060
1133
|
kind: "array",
|
|
1061
|
-
|
|
1134
|
+
// inferLen: true means [...]T — size is determined by composite literal
|
|
1135
|
+
size: node.inferLen
|
|
1136
|
+
? null
|
|
1137
|
+
: node.size?.value !== undefined
|
|
1138
|
+
? Number(node.size.value)
|
|
1139
|
+
: node.size,
|
|
1062
1140
|
elem: this.resolveTypeNode(node.elem, scope),
|
|
1063
1141
|
};
|
|
1064
1142
|
case "MapType":
|
|
@@ -1116,6 +1194,24 @@ export class TypeChecker {
|
|
|
1116
1194
|
: [VOID];
|
|
1117
1195
|
methods.set(m.name, { kind: "func", params, returns });
|
|
1118
1196
|
}
|
|
1197
|
+
// Flatten embedded interface methods
|
|
1198
|
+
if (node.embeds) {
|
|
1199
|
+
for (const embed of node.embeds) {
|
|
1200
|
+
const resolved = this.resolveTypeNode(embed, scope);
|
|
1201
|
+
const base =
|
|
1202
|
+
resolved?.kind === "named" ? resolved.underlying : resolved;
|
|
1203
|
+
if (base?.kind === "interface") {
|
|
1204
|
+
for (const [mName, mType] of base.methods) {
|
|
1205
|
+
if (!methods.has(mName)) methods.set(mName, mType);
|
|
1206
|
+
}
|
|
1207
|
+
} else if (!isAny(resolved)) {
|
|
1208
|
+
this.err(
|
|
1209
|
+
`cannot embed non-interface type ${typeStr(resolved)}`,
|
|
1210
|
+
embed,
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1119
1215
|
return { kind: "interface", methods };
|
|
1120
1216
|
}
|
|
1121
1217
|
case "TupleType": {
|
|
@@ -1189,10 +1285,8 @@ export class TypeChecker {
|
|
|
1189
1285
|
}
|
|
1190
1286
|
|
|
1191
1287
|
binaryResultType(op, lt, rt, node) {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
if (cmp.includes(op)) return BOOL;
|
|
1195
|
-
if (log.includes(op)) return BOOL;
|
|
1288
|
+
if (CMP_OPS.has(op)) return BOOL;
|
|
1289
|
+
if (LOG_OPS.has(op)) return BOOL;
|
|
1196
1290
|
if (isAny(lt) || isAny(rt)) return ANY;
|
|
1197
1291
|
if (isNumeric(lt) && isNumeric(rt)) {
|
|
1198
1292
|
// float64 is contagious
|