gofront 0.0.4 → 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 +104 -126
- package/package.json +2 -3
- package/src/codegen/expressions.js +448 -256
- package/src/codegen/statements.js +193 -6
- package/src/codegen.js +89 -3
- 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 +57 -4
- package/src/typechecker/expressions.js +279 -10
- package/src/typechecker/statements.js +64 -3
- package/src/typechecker/types.js +96 -10
- package/src/typechecker.js +482 -16
package/src/typechecker.js
CHANGED
|
@@ -13,14 +13,17 @@ 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,
|
|
26
29
|
isVoid,
|
|
@@ -29,6 +32,7 @@ import {
|
|
|
29
32
|
STRING,
|
|
30
33
|
TypeCheckError,
|
|
31
34
|
typeStr,
|
|
35
|
+
UNTYPED_COMPLEX,
|
|
32
36
|
UNTYPED_FLOAT,
|
|
33
37
|
UNTYPED_INT,
|
|
34
38
|
VOID,
|
|
@@ -50,6 +54,8 @@ export class TypeChecker {
|
|
|
50
54
|
this._deferCount = 0; // tracks defer usage in current function body
|
|
51
55
|
this._imports = []; // tracked imports for unused-import detection
|
|
52
56
|
this._setupGlobals();
|
|
57
|
+
// Register error as a named type (interface)
|
|
58
|
+
this.types.set("error", ERROR);
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
_setupGlobals() {
|
|
@@ -194,6 +200,47 @@ export class TypeChecker {
|
|
|
194
200
|
},
|
|
195
201
|
});
|
|
196
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
|
+
|
|
197
244
|
// strconv package
|
|
198
245
|
this.globals.define("strconv", {
|
|
199
246
|
kind: "namespace",
|
|
@@ -329,6 +376,8 @@ export class TypeChecker {
|
|
|
329
376
|
name: "errors",
|
|
330
377
|
members: {
|
|
331
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] },
|
|
332
381
|
},
|
|
333
382
|
});
|
|
334
383
|
|
|
@@ -397,6 +446,9 @@ export class TypeChecker {
|
|
|
397
446
|
this.globals.define("min", { kind: "builtin", name: "min" });
|
|
398
447
|
this.globals.define("max", { kind: "builtin", name: "max" });
|
|
399
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" });
|
|
400
452
|
}
|
|
401
453
|
|
|
402
454
|
addDefinitions(types, values) {
|
|
@@ -584,6 +636,36 @@ export class TypeChecker {
|
|
|
584
636
|
// ── Type collection ──────────────────────────────────────────
|
|
585
637
|
|
|
586
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
|
+
}
|
|
587
669
|
const underlying = this.resolveTypeNode(decl.type, this.globals);
|
|
588
670
|
if (decl.isAlias) {
|
|
589
671
|
// type A = B — transparent alias, A and B are identical types
|
|
@@ -604,11 +686,40 @@ export class TypeChecker {
|
|
|
604
686
|
}
|
|
605
687
|
|
|
606
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
|
+
}
|
|
607
718
|
const paramTypes = decl.params.map((p) =>
|
|
608
|
-
this.resolveTypeNode(p.type,
|
|
719
|
+
this.resolveTypeNode(p.type, resolveScope),
|
|
609
720
|
);
|
|
610
721
|
const returnType = decl.returnType
|
|
611
|
-
? this.resolveTypeNode(decl.returnType,
|
|
722
|
+
? this.resolveTypeNode(decl.returnType, resolveScope)
|
|
612
723
|
: VOID;
|
|
613
724
|
const isVariadic =
|
|
614
725
|
decl.params.length > 0 && decl.params[decl.params.length - 1].variadic;
|
|
@@ -623,13 +734,38 @@ export class TypeChecker {
|
|
|
623
734
|
if (this.globals.symbols.has(decl.name) && decl.name !== "init") {
|
|
624
735
|
this.err(`${decl.name} redeclared in this block`, decl);
|
|
625
736
|
}
|
|
626
|
-
|
|
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
|
+
}
|
|
627
747
|
} else {
|
|
628
748
|
// Method: attach to struct type
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
+
}
|
|
633
769
|
}
|
|
634
770
|
}
|
|
635
771
|
}
|
|
@@ -657,11 +793,20 @@ export class TypeChecker {
|
|
|
657
793
|
|
|
658
794
|
checkFuncDecl(decl, outer) {
|
|
659
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
|
+
}
|
|
660
805
|
for (const p of decl.params) {
|
|
661
|
-
inner.define(p.name, this.resolveTypeNode(p.type,
|
|
806
|
+
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
662
807
|
}
|
|
663
808
|
let returnType = decl.returnType
|
|
664
|
-
? this.resolveTypeNode(decl.returnType,
|
|
809
|
+
? this.resolveTypeNode(decl.returnType, inner)
|
|
665
810
|
: VOID;
|
|
666
811
|
const hasNamedReturns = this._injectNamedReturns(
|
|
667
812
|
decl.returnType,
|
|
@@ -688,18 +833,29 @@ export class TypeChecker {
|
|
|
688
833
|
|
|
689
834
|
checkMethodDecl(decl, outer) {
|
|
690
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
|
+
}
|
|
691
847
|
const recvType = this.resolveTypeNodeName(decl.recvType, outer);
|
|
692
848
|
inner.define(decl.recvName, recvType);
|
|
693
849
|
for (const p of decl.params) {
|
|
694
|
-
inner.define(p.name, this.resolveTypeNode(p.type,
|
|
850
|
+
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
695
851
|
}
|
|
696
852
|
let returnType = decl.returnType
|
|
697
|
-
? this.resolveTypeNode(decl.returnType,
|
|
853
|
+
? this.resolveTypeNode(decl.returnType, inner)
|
|
698
854
|
: VOID;
|
|
699
855
|
const hasNamedReturns = this._injectNamedReturns(
|
|
700
856
|
decl.returnType,
|
|
701
857
|
inner,
|
|
702
|
-
|
|
858
|
+
inner,
|
|
703
859
|
);
|
|
704
860
|
if (hasNamedReturns && returnType)
|
|
705
861
|
returnType = { ...returnType, _hasNamedReturns: true };
|
|
@@ -842,10 +998,37 @@ export class TypeChecker {
|
|
|
842
998
|
switch (node.kind) {
|
|
843
999
|
case "TypeName": {
|
|
844
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
|
+
}
|
|
845
1008
|
const named = this.types.get(node.name);
|
|
846
1009
|
if (named) return named;
|
|
847
1010
|
return this.err(`Unknown type '${node.name}'`, node);
|
|
848
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;
|
|
849
1032
|
case "SliceType":
|
|
850
1033
|
return {
|
|
851
1034
|
kind: "slice",
|
|
@@ -964,11 +1147,207 @@ export class TypeChecker {
|
|
|
964
1147
|
const named = this.types.get(node.name);
|
|
965
1148
|
if (named) return named;
|
|
966
1149
|
}
|
|
1150
|
+
if (node.kind === "GenericTypeName") {
|
|
1151
|
+
const named = this.types.get(node.name);
|
|
1152
|
+
if (named) return named;
|
|
1153
|
+
}
|
|
967
1154
|
return this.resolveTypeNode(node, scope);
|
|
968
1155
|
}
|
|
969
1156
|
|
|
970
1157
|
// ── Helpers ───────────────────────────────────────────────────
|
|
971
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
|
+
|
|
972
1351
|
resolveType(t) {
|
|
973
1352
|
if (!t) return ANY;
|
|
974
1353
|
if (t.kind === "basic" && t.alias) {
|
|
@@ -988,6 +1367,10 @@ export class TypeChecker {
|
|
|
988
1367
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
989
1368
|
return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
|
|
990
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
|
+
}
|
|
991
1374
|
if (base?.kind === "namespace") {
|
|
992
1375
|
// Enforce exported identifier rule for GoFront packages only
|
|
993
1376
|
if (
|
|
@@ -1007,9 +1390,6 @@ export class TypeChecker {
|
|
|
1007
1390
|
// For explicitly typed non-any values, report the bad access rather than
|
|
1008
1391
|
// silently returning any — this catches typos on structs and primitive misuse.
|
|
1009
1392
|
if (base && !isAny(base)) {
|
|
1010
|
-
// error.Error() is valid — it returns the error string
|
|
1011
|
-
if (isError(base) && field === "Error")
|
|
1012
|
-
return { kind: "func", params: [], returns: [STRING], async: false };
|
|
1013
1393
|
// pointer.value is the GoFront new(T) pattern
|
|
1014
1394
|
if (base.kind === "pointer" && field === "value") return base.base ?? ANY;
|
|
1015
1395
|
const badKinds = ["basic", "slice", "array", "map", "func", "tuple"];
|
|
@@ -1024,7 +1404,23 @@ export class TypeChecker {
|
|
|
1024
1404
|
}
|
|
1025
1405
|
|
|
1026
1406
|
binaryResultType(op, lt, rt, node) {
|
|
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
|
+
}
|
|
1027
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
|
+
}
|
|
1028
1424
|
// Reject slice/map/func comparisons unless one side is nil (Go spec)
|
|
1029
1425
|
if (op === "==" || op === "!=") {
|
|
1030
1426
|
const lNil = isNil(lt);
|
|
@@ -1046,6 +1442,26 @@ export class TypeChecker {
|
|
|
1046
1442
|
}
|
|
1047
1443
|
if (LOG_OPS.has(op)) return BOOL;
|
|
1048
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
|
+
}
|
|
1049
1465
|
if (isNumeric(lt) && isNumeric(rt)) {
|
|
1050
1466
|
const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
|
|
1051
1467
|
const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
|
|
@@ -1076,8 +1492,19 @@ export class TypeChecker {
|
|
|
1076
1492
|
if (!target || !source) return;
|
|
1077
1493
|
target = this.resolveType(target);
|
|
1078
1494
|
source = this.resolveType(source);
|
|
1495
|
+
// Type params are permissive — allow any assignment
|
|
1496
|
+
if (target?.kind === "typeParam" || source?.kind === "typeParam") return;
|
|
1079
1497
|
if (isAny(target) || isAny(source)) return;
|
|
1080
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
|
+
}
|
|
1081
1508
|
|
|
1082
1509
|
// Untyped constants coerce to any compatible typed target
|
|
1083
1510
|
if (isUntyped(source)) {
|
|
@@ -1099,6 +1526,18 @@ export class TypeChecker {
|
|
|
1099
1526
|
if (source.base === "string" && target.name === "string") return;
|
|
1100
1527
|
// untyped bool → bool
|
|
1101
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;
|
|
1102
1541
|
}
|
|
1103
1542
|
}
|
|
1104
1543
|
|
|
@@ -1107,6 +1546,33 @@ export class TypeChecker {
|
|
|
1107
1546
|
if (target.name === "float64" && source.name === "int") return;
|
|
1108
1547
|
if (target.name === "int" && source.name === "float64") return; // truncating
|
|
1109
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
|
+
|
|
1110
1576
|
if (typeStr(target) !== typeStr(source)) {
|
|
1111
1577
|
// Interface satisfaction check
|
|
1112
1578
|
let tBase = target.kind === "named" ? target.underlying : target;
|