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.
@@ -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
- isError,
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.globals.define(pkgName, { kind: "namespace", name: pkgName, members });
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, this.globals),
719
+ this.resolveTypeNode(p.type, resolveScope),
567
720
  );
568
721
  const returnType = decl.returnType
569
- ? this.resolveTypeNode(decl.returnType, this.globals)
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
- this.globals.define(decl.name, funcType);
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 recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
588
- const base = recvType?.underlying ?? recvType;
589
- if (base?.kind === "struct") {
590
- base.methods.set(decl.name, funcType);
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, outer));
806
+ inner.define(p.name, this.resolveTypeNode(p.type, inner));
620
807
  }
621
- const returnType = decl.returnType
622
- ? this.resolveTypeNode(decl.returnType, outer)
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
- if (hasNamedReturns && returnType) returnType._hasNamedReturns = true;
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, outer));
850
+ inner.define(p.name, this.resolveTypeNode(p.type, inner));
645
851
  }
646
- const returnType = decl.returnType
647
- ? this.resolveTypeNode(decl.returnType, outer)
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
- outer,
858
+ inner,
653
859
  );
654
- if (hasNamedReturns && returnType) returnType._hasNamedReturns = true;
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
- if (CMP_OPS.has(op)) return BOOL;
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)}`,