gofront 0.0.4 → 0.0.6

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,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
- isError,
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() {
@@ -140,6 +146,24 @@ export class TypeChecker {
140
146
  Printf: fmtVariadic(VOID),
141
147
  Println: fmtVariadic(VOID),
142
148
  Print: fmtVariadic(VOID),
149
+ Fprintf: {
150
+ kind: "func",
151
+ params: [ANY, STRING],
152
+ returns: [INT, ERROR],
153
+ variadic: true,
154
+ },
155
+ Fprintln: {
156
+ kind: "func",
157
+ params: [ANY],
158
+ returns: [INT, ERROR],
159
+ variadic: true,
160
+ },
161
+ Fprint: {
162
+ kind: "func",
163
+ params: [ANY],
164
+ returns: [INT, ERROR],
165
+ variadic: true,
166
+ },
143
167
  },
144
168
  });
145
169
 
@@ -194,6 +218,47 @@ export class TypeChecker {
194
218
  },
195
219
  });
196
220
 
221
+ // bytes package — operates on []byte (JS arrays of numbers)
222
+ const BYTE_SLICE = { kind: "slice", elem: INT };
223
+ const byFn1 = (ret) => ({
224
+ kind: "func",
225
+ params: [BYTE_SLICE],
226
+ returns: [ret],
227
+ });
228
+ const byFn2 = (p2, ret) => ({
229
+ kind: "func",
230
+ params: [BYTE_SLICE, p2],
231
+ returns: [ret],
232
+ });
233
+ this.globals.define("bytes", {
234
+ kind: "namespace",
235
+ name: "bytes",
236
+ members: {
237
+ Contains: byFn2(BYTE_SLICE, BOOL),
238
+ HasPrefix: byFn2(BYTE_SLICE, BOOL),
239
+ HasSuffix: byFn2(BYTE_SLICE, BOOL),
240
+ Index: byFn2(BYTE_SLICE, INT),
241
+ Count: byFn2(BYTE_SLICE, INT),
242
+ Repeat: byFn2(INT, BYTE_SLICE),
243
+ Replace: {
244
+ kind: "func",
245
+ params: [BYTE_SLICE, BYTE_SLICE, BYTE_SLICE, INT],
246
+ returns: [BYTE_SLICE],
247
+ },
248
+ ToUpper: byFn1(BYTE_SLICE),
249
+ ToLower: byFn1(BYTE_SLICE),
250
+ TrimSpace: byFn1(BYTE_SLICE),
251
+ Trim: byFn2(STRING, BYTE_SLICE),
252
+ Equal: byFn2(BYTE_SLICE, BOOL),
253
+ Split: byFn2(BYTE_SLICE, { kind: "slice", elem: BYTE_SLICE }),
254
+ Join: {
255
+ kind: "func",
256
+ params: [{ kind: "slice", elem: BYTE_SLICE }, BYTE_SLICE],
257
+ returns: [BYTE_SLICE],
258
+ },
259
+ },
260
+ });
261
+
197
262
  // strconv package
198
263
  this.globals.define("strconv", {
199
264
  kind: "namespace",
@@ -329,6 +394,8 @@ export class TypeChecker {
329
394
  name: "errors",
330
395
  members: {
331
396
  New: { kind: "func", params: [STRING], returns: [ERROR] },
397
+ Is: { kind: "func", params: [ERROR, ERROR], returns: [BOOL] },
398
+ Unwrap: { kind: "func", params: [ERROR], returns: [ERROR] },
332
399
  },
333
400
  });
334
401
 
@@ -382,6 +449,195 @@ export class TypeChecker {
382
449
  },
383
450
  });
384
451
 
452
+ // slices package (Go 1.21)
453
+ // Functions are generic — use ANY for slice params so any concrete slice type
454
+ // is accepted without an assignability error (same pattern as sort.Slice).
455
+ this.globals.define("slices", {
456
+ kind: "namespace",
457
+ name: "slices",
458
+ members: {
459
+ Contains: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
460
+ Index: { kind: "func", params: [ANY, ANY], returns: [INT] },
461
+ Equal: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
462
+ Compare: { kind: "func", params: [ANY, ANY], returns: [INT] },
463
+ Sort: { kind: "func", params: [ANY], returns: [VOID] },
464
+ SortFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
465
+ SortStableFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
466
+ IsSorted: { kind: "func", params: [ANY], returns: [BOOL] },
467
+ IsSortedFunc: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
468
+ Reverse: { kind: "func", params: [ANY], returns: [VOID] },
469
+ Max: { kind: "func", params: [ANY], returns: [ANY] },
470
+ Min: { kind: "func", params: [ANY], returns: [ANY] },
471
+ MaxFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
472
+ MinFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
473
+ Clone: { kind: "func", params: [ANY], returns: [ANY] },
474
+ Compact: { kind: "func", params: [ANY], returns: [ANY] },
475
+ CompactFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
476
+ Concat: { kind: "func", params: [ANY], returns: [ANY], variadic: true },
477
+ Delete: { kind: "func", params: [ANY, INT, INT], returns: [ANY] },
478
+ DeleteFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
479
+ Insert: {
480
+ kind: "func",
481
+ params: [ANY, INT, ANY],
482
+ returns: [ANY],
483
+ variadic: true,
484
+ },
485
+ Replace: {
486
+ kind: "func",
487
+ params: [ANY, INT, INT, ANY],
488
+ returns: [ANY],
489
+ variadic: true,
490
+ },
491
+ Grow: { kind: "func", params: [ANY, INT], returns: [ANY] },
492
+ Clip: { kind: "func", params: [ANY], returns: [ANY] },
493
+ },
494
+ });
495
+
496
+ // html package
497
+ this.globals.define("html", {
498
+ kind: "namespace",
499
+ name: "html",
500
+ members: {
501
+ EscapeString: { kind: "func", params: [STRING], returns: [STRING] },
502
+ UnescapeString: { kind: "func", params: [STRING], returns: [STRING] },
503
+ },
504
+ });
505
+
506
+ // maps package (Go 1.21)
507
+ this.globals.define("maps", {
508
+ kind: "namespace",
509
+ name: "maps",
510
+ members: {
511
+ Keys: { kind: "func", params: [ANY], returns: [ANY] },
512
+ Values: { kind: "func", params: [ANY], returns: [ANY] },
513
+ Clone: { kind: "func", params: [ANY], returns: [ANY] },
514
+ Copy: { kind: "func", params: [ANY, ANY], returns: [VOID] },
515
+ Equal: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
516
+ EqualFunc: { kind: "func", params: [ANY, ANY, ANY], returns: [BOOL] },
517
+ Delete: { kind: "func", params: [ANY, ANY], returns: [VOID] },
518
+ DeleteFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
519
+ },
520
+ });
521
+
522
+ // regexp package
523
+ const REGEXP_T = {
524
+ kind: "named",
525
+ name: "regexp.Regexp",
526
+ underlying: { kind: "struct", fields: new Map(), methods: new Map() },
527
+ };
528
+ const REGEXP_PTR = { kind: "pointer", base: REGEXP_T };
529
+ const STR_SLICE = { kind: "slice", elem: STRING };
530
+ const INT_SLICE = { kind: "slice", elem: INT };
531
+ const regexpMethods = new Map([
532
+ ["MatchString", { kind: "func", params: [STRING], returns: [BOOL] }],
533
+ ["FindString", { kind: "func", params: [STRING], returns: [STRING] }],
534
+ [
535
+ "FindStringIndex",
536
+ { kind: "func", params: [STRING], returns: [INT_SLICE] },
537
+ ],
538
+ [
539
+ "FindAllString",
540
+ { kind: "func", params: [STRING, INT], returns: [STR_SLICE] },
541
+ ],
542
+ [
543
+ "FindStringSubmatch",
544
+ { kind: "func", params: [STRING], returns: [STR_SLICE] },
545
+ ],
546
+ [
547
+ "FindAllStringSubmatch",
548
+ {
549
+ kind: "func",
550
+ params: [STRING, INT],
551
+ returns: [{ kind: "slice", elem: STR_SLICE }],
552
+ },
553
+ ],
554
+ [
555
+ "ReplaceAllString",
556
+ { kind: "func", params: [STRING, STRING], returns: [STRING] },
557
+ ],
558
+ [
559
+ "ReplaceAllLiteralString",
560
+ { kind: "func", params: [STRING, STRING], returns: [STRING] },
561
+ ],
562
+ ["Split", { kind: "func", params: [STRING, INT], returns: [STR_SLICE] }],
563
+ ["String", { kind: "func", params: [], returns: [STRING] }],
564
+ ]);
565
+ REGEXP_T.underlying.methods = regexpMethods;
566
+ this.types.set("regexp.Regexp", REGEXP_T);
567
+ this.globals.define("regexp", {
568
+ kind: "namespace",
569
+ name: "regexp",
570
+ members: {
571
+ MustCompile: { kind: "func", params: [STRING], returns: [REGEXP_PTR] },
572
+ Compile: {
573
+ kind: "func",
574
+ params: [STRING],
575
+ returns: [REGEXP_PTR, ERROR],
576
+ },
577
+ MatchString: {
578
+ kind: "func",
579
+ params: [STRING, STRING],
580
+ returns: [BOOL, ERROR],
581
+ },
582
+ QuoteMeta: { kind: "func", params: [STRING], returns: [STRING] },
583
+ },
584
+ });
585
+
586
+ // strings.Builder
587
+ const BYTE_SLICE_T = { kind: "slice", elem: INT };
588
+ const strBuilderMethods = new Map([
589
+ [
590
+ "WriteString",
591
+ { kind: "func", params: [STRING], returns: [INT, ERROR] },
592
+ ],
593
+ ["WriteByte", { kind: "func", params: [INT], returns: [ERROR] }],
594
+ ["WriteRune", { kind: "func", params: [INT], returns: [INT, ERROR] }],
595
+ [
596
+ "Write",
597
+ { kind: "func", params: [BYTE_SLICE_T], returns: [INT, ERROR] },
598
+ ],
599
+ ["String", { kind: "func", params: [], returns: [STRING] }],
600
+ ["Len", { kind: "func", params: [], returns: [INT] }],
601
+ ["Reset", { kind: "func", params: [], returns: [VOID] }],
602
+ ["Grow", { kind: "func", params: [INT], returns: [VOID] }],
603
+ ]);
604
+ this.types.set("strings.Builder", {
605
+ kind: "named",
606
+ name: "strings.Builder",
607
+ underlying: {
608
+ kind: "struct",
609
+ fields: new Map(),
610
+ methods: strBuilderMethods,
611
+ },
612
+ });
613
+
614
+ // bytes.Buffer
615
+ const bytesBufferMethods = new Map([
616
+ [
617
+ "WriteString",
618
+ { kind: "func", params: [STRING], returns: [INT, ERROR] },
619
+ ],
620
+ ["WriteByte", { kind: "func", params: [INT], returns: [ERROR] }],
621
+ [
622
+ "Write",
623
+ { kind: "func", params: [BYTE_SLICE_T], returns: [INT, ERROR] },
624
+ ],
625
+ ["String", { kind: "func", params: [], returns: [STRING] }],
626
+ ["Bytes", { kind: "func", params: [], returns: [BYTE_SLICE_T] }],
627
+ ["Len", { kind: "func", params: [], returns: [INT] }],
628
+ ["Reset", { kind: "func", params: [], returns: [VOID] }],
629
+ ["Grow", { kind: "func", params: [INT], returns: [VOID] }],
630
+ ]);
631
+ this.types.set("bytes.Buffer", {
632
+ kind: "named",
633
+ name: "bytes.Buffer",
634
+ underlying: {
635
+ kind: "struct",
636
+ fields: new Map(),
637
+ methods: bytesBufferMethods,
638
+ },
639
+ });
640
+
385
641
  this.globals.define("append", { kind: "builtin", name: "append" });
386
642
  this.globals.define("len", { kind: "builtin", name: "len" });
387
643
  this.globals.define("cap", { kind: "builtin", name: "cap" });
@@ -397,6 +653,9 @@ export class TypeChecker {
397
653
  this.globals.define("min", { kind: "builtin", name: "min" });
398
654
  this.globals.define("max", { kind: "builtin", name: "max" });
399
655
  this.globals.define("clear", { kind: "builtin", name: "clear" });
656
+ this.globals.define("complex", { kind: "builtin", name: "complex" });
657
+ this.globals.define("real", { kind: "builtin", name: "real" });
658
+ this.globals.define("imag", { kind: "builtin", name: "imag" });
400
659
  }
401
660
 
402
661
  addDefinitions(types, values) {
@@ -584,6 +843,36 @@ export class TypeChecker {
584
843
  // ── Type collection ──────────────────────────────────────────
585
844
 
586
845
  collectType(decl) {
846
+ if (decl.typeParams) {
847
+ // Generic type — resolve underlying with type params as ANY for struct fields
848
+ const typeScope = new Scope(this.globals);
849
+ const typeParamTypes = decl.typeParams.map((tp) => {
850
+ const t = {
851
+ kind: "typeParam",
852
+ name: tp.name,
853
+ constraint: tp.constraint,
854
+ };
855
+ typeScope.define(tp.name, t);
856
+ return t;
857
+ });
858
+ const underlying = this.resolveTypeNode(decl.type, typeScope);
859
+ const named = {
860
+ kind: "named",
861
+ name: decl.name,
862
+ underlying,
863
+ _generic: {
864
+ typeParams: typeParamTypes,
865
+ declNode: decl,
866
+ },
867
+ };
868
+ if (underlying.kind === "struct") {
869
+ underlying.name = decl.name;
870
+ underlying.methods = new Map();
871
+ }
872
+ this.types.set(decl.name, named);
873
+ this.globals.define(decl.name, named);
874
+ return;
875
+ }
587
876
  const underlying = this.resolveTypeNode(decl.type, this.globals);
588
877
  if (decl.isAlias) {
589
878
  // type A = B — transparent alias, A and B are identical types
@@ -604,11 +893,40 @@ export class TypeChecker {
604
893
  }
605
894
 
606
895
  collectFunc(decl) {
896
+ // For generic functions, create type param scope for resolving param/return types
897
+ let resolveScope = this.globals;
898
+ let typeParamTypes = null;
899
+ if (decl.typeParams) {
900
+ resolveScope = new Scope(this.globals);
901
+ typeParamTypes = decl.typeParams.map((tp) => {
902
+ const t = {
903
+ kind: "typeParam",
904
+ name: tp.name,
905
+ constraint: tp.constraint,
906
+ };
907
+ resolveScope.define(tp.name, t);
908
+ return t;
909
+ });
910
+ }
911
+ // For methods on generic types, inject the receiver's type params
912
+ if (decl.kind === "MethodDecl") {
913
+ const recvTypeName =
914
+ decl.recvType.kind === "GenericTypeName"
915
+ ? decl.recvType.name
916
+ : decl.recvType.name;
917
+ const recvNamedType = this.types.get(recvTypeName);
918
+ if (recvNamedType?._generic) {
919
+ resolveScope = new Scope(this.globals);
920
+ for (const tp of recvNamedType._generic.typeParams) {
921
+ resolveScope.define(tp.name, tp);
922
+ }
923
+ }
924
+ }
607
925
  const paramTypes = decl.params.map((p) =>
608
- this.resolveTypeNode(p.type, this.globals),
926
+ this.resolveTypeNode(p.type, resolveScope),
609
927
  );
610
928
  const returnType = decl.returnType
611
- ? this.resolveTypeNode(decl.returnType, this.globals)
929
+ ? this.resolveTypeNode(decl.returnType, resolveScope)
612
930
  : VOID;
613
931
  const isVariadic =
614
932
  decl.params.length > 0 && decl.params[decl.params.length - 1].variadic;
@@ -623,13 +941,38 @@ export class TypeChecker {
623
941
  if (this.globals.symbols.has(decl.name) && decl.name !== "init") {
624
942
  this.err(`${decl.name} redeclared in this block`, decl);
625
943
  }
626
- this.globals.define(decl.name, funcType);
944
+ if (typeParamTypes) {
945
+ this.globals.define(decl.name, {
946
+ kind: "generic",
947
+ name: decl.name,
948
+ typeParams: typeParamTypes,
949
+ underlying: funcType,
950
+ });
951
+ } else {
952
+ this.globals.define(decl.name, funcType);
953
+ }
627
954
  } else {
628
955
  // Method: attach to struct type
629
- const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
630
- const base = recvType?.underlying ?? recvType;
631
- if (base?.kind === "struct") {
632
- base.methods.set(decl.name, funcType);
956
+ const recvTypeName =
957
+ decl.recvType.kind === "GenericTypeName"
958
+ ? decl.recvType.name
959
+ : decl.recvType.name;
960
+ const recvNamedType = this.types.get(recvTypeName);
961
+ if (recvNamedType?._generic) {
962
+ // Store method on the generic metadata and on the underlying struct
963
+ if (!recvNamedType._generic.methods)
964
+ recvNamedType._generic.methods = new Map();
965
+ recvNamedType._generic.methods.set(decl.name, funcType);
966
+ const base = recvNamedType.underlying;
967
+ if (base?.kind === "struct") {
968
+ base.methods.set(decl.name, funcType);
969
+ }
970
+ } else {
971
+ const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
972
+ const base = recvType?.underlying ?? recvType;
973
+ if (base?.kind === "struct") {
974
+ base.methods.set(decl.name, funcType);
975
+ }
633
976
  }
634
977
  }
635
978
  }
@@ -657,11 +1000,20 @@ export class TypeChecker {
657
1000
 
658
1001
  checkFuncDecl(decl, outer) {
659
1002
  const inner = new Scope(outer);
1003
+ // Inject type params into scope for generic functions
1004
+ if (decl.typeParams) {
1005
+ for (const tp of decl.typeParams) {
1006
+ const constraint = tp.constraint
1007
+ ? this.resolveTypeNode(tp.constraint, outer)
1008
+ : ANY;
1009
+ inner.define(tp.name, { kind: "typeParam", name: tp.name, constraint });
1010
+ }
1011
+ }
660
1012
  for (const p of decl.params) {
661
- inner.define(p.name, this.resolveTypeNode(p.type, outer));
1013
+ inner.define(p.name, this.resolveTypeNode(p.type, inner));
662
1014
  }
663
1015
  let returnType = decl.returnType
664
- ? this.resolveTypeNode(decl.returnType, outer)
1016
+ ? this.resolveTypeNode(decl.returnType, inner)
665
1017
  : VOID;
666
1018
  const hasNamedReturns = this._injectNamedReturns(
667
1019
  decl.returnType,
@@ -688,18 +1040,29 @@ export class TypeChecker {
688
1040
 
689
1041
  checkMethodDecl(decl, outer) {
690
1042
  const inner = new Scope(outer);
1043
+ // For generic receiver types (e.g. Stack[T]), inject type params
1044
+ const recvTypeName =
1045
+ decl.recvType.kind === "GenericTypeName"
1046
+ ? decl.recvType.name
1047
+ : decl.recvType.name;
1048
+ const recvNamedType = this.types.get(recvTypeName);
1049
+ if (recvNamedType?._generic) {
1050
+ for (const tp of recvNamedType._generic.typeParams) {
1051
+ inner.define(tp.name, tp);
1052
+ }
1053
+ }
691
1054
  const recvType = this.resolveTypeNodeName(decl.recvType, outer);
692
1055
  inner.define(decl.recvName, recvType);
693
1056
  for (const p of decl.params) {
694
- inner.define(p.name, this.resolveTypeNode(p.type, outer));
1057
+ inner.define(p.name, this.resolveTypeNode(p.type, inner));
695
1058
  }
696
1059
  let returnType = decl.returnType
697
- ? this.resolveTypeNode(decl.returnType, outer)
1060
+ ? this.resolveTypeNode(decl.returnType, inner)
698
1061
  : VOID;
699
1062
  const hasNamedReturns = this._injectNamedReturns(
700
1063
  decl.returnType,
701
1064
  inner,
702
- outer,
1065
+ inner,
703
1066
  );
704
1067
  if (hasNamedReturns && returnType)
705
1068
  returnType = { ...returnType, _hasNamedReturns: true };
@@ -842,10 +1205,37 @@ export class TypeChecker {
842
1205
  switch (node.kind) {
843
1206
  case "TypeName": {
844
1207
  if (BASIC_TYPES[node.name]) return BASIC_TYPES[node.name];
1208
+ if (node.name === "comparable")
1209
+ return { kind: "basic", name: "comparable" };
1210
+ // Check scope for type params before checking this.types
1211
+ if (scope) {
1212
+ const fromScope = scope.lookup(node.name);
1213
+ if (fromScope?.kind === "typeParam") return fromScope;
1214
+ }
845
1215
  const named = this.types.get(node.name);
846
1216
  if (named) return named;
847
1217
  return this.err(`Unknown type '${node.name}'`, node);
848
1218
  }
1219
+ case "GenericTypeName": {
1220
+ const base = this.types.get(node.name);
1221
+ if (!base) return this.err(`Unknown type '${node.name}'`, node);
1222
+ if (base.kind === "named" && base._generic) {
1223
+ return this.instantiateGenericType(
1224
+ base._generic,
1225
+ node.typeArgs,
1226
+ scope,
1227
+ );
1228
+ }
1229
+ return this.err(`Type '${node.name}' is not generic`, node);
1230
+ }
1231
+ case "TypeParam": {
1232
+ const constraint = node.constraint
1233
+ ? this.resolveTypeNode(node.constraint, scope)
1234
+ : ANY;
1235
+ return { kind: "typeParam", name: node.name, constraint };
1236
+ }
1237
+ case "UnionConstraint":
1238
+ return node;
849
1239
  case "SliceType":
850
1240
  return {
851
1241
  kind: "slice",
@@ -964,11 +1354,207 @@ export class TypeChecker {
964
1354
  const named = this.types.get(node.name);
965
1355
  if (named) return named;
966
1356
  }
1357
+ if (node.kind === "GenericTypeName") {
1358
+ const named = this.types.get(node.name);
1359
+ if (named) return named;
1360
+ }
967
1361
  return this.resolveTypeNode(node, scope);
968
1362
  }
969
1363
 
970
1364
  // ── Helpers ───────────────────────────────────────────────────
971
1365
 
1366
+ // ── Generics — instantiation, inference, substitution ────────
1367
+
1368
+ substituteType(type, map) {
1369
+ if (!type) return type;
1370
+ if (type.kind === "typeParam") {
1371
+ const bound = map.get(type.name);
1372
+ return bound ?? type;
1373
+ }
1374
+ switch (type.kind) {
1375
+ case "basic":
1376
+ case "untyped":
1377
+ return type;
1378
+ case "slice":
1379
+ return { kind: "slice", elem: this.substituteType(type.elem, map) };
1380
+ case "array":
1381
+ return {
1382
+ kind: "array",
1383
+ size: type.size,
1384
+ elem: this.substituteType(type.elem, map),
1385
+ };
1386
+ case "map":
1387
+ return {
1388
+ kind: "map",
1389
+ key: this.substituteType(type.key, map),
1390
+ value: this.substituteType(type.value, map),
1391
+ };
1392
+ case "func": {
1393
+ return {
1394
+ kind: "func",
1395
+ params: type.params.map((p) => this.substituteType(p, map)),
1396
+ returns: type.returns.map((r) => this.substituteType(r, map)),
1397
+ variadic: type.variadic,
1398
+ async: type.async,
1399
+ };
1400
+ }
1401
+ case "struct": {
1402
+ const fields = new Map();
1403
+ for (const [k, v] of type.fields)
1404
+ fields.set(k, this.substituteType(v, map));
1405
+ const methods = new Map();
1406
+ for (const [k, v] of type.methods)
1407
+ methods.set(k, this.substituteType(v, map));
1408
+ return {
1409
+ kind: "struct",
1410
+ name: type.name,
1411
+ fields,
1412
+ methods,
1413
+ _embeds: type._embeds,
1414
+ };
1415
+ }
1416
+ case "interface": {
1417
+ const methods = new Map();
1418
+ for (const [k, v] of type.methods)
1419
+ methods.set(k, this.substituteType(v, map));
1420
+ return { kind: "interface", name: type.name, methods };
1421
+ }
1422
+ case "named":
1423
+ return {
1424
+ kind: "named",
1425
+ name: type.name,
1426
+ underlying: this.substituteType(type.underlying, map),
1427
+ };
1428
+ case "pointer":
1429
+ return { kind: "pointer", base: this.substituteType(type.base, map) };
1430
+ case "tuple":
1431
+ return {
1432
+ kind: "tuple",
1433
+ types: type.types.map((t) => this.substituteType(t, map)),
1434
+ };
1435
+ default:
1436
+ return type;
1437
+ }
1438
+ }
1439
+
1440
+ instantiateGenericFunc(genericType, typeArgs) {
1441
+ const map = new Map();
1442
+ for (let i = 0; i < genericType.typeParams.length; i++) {
1443
+ map.set(genericType.typeParams[i].name, typeArgs[i] ?? ANY);
1444
+ }
1445
+ return this.substituteType(genericType.underlying, map);
1446
+ }
1447
+
1448
+ instantiateGenericType(generic, typeArgNodes, scope) {
1449
+ const map = new Map();
1450
+ const typeArgs = typeArgNodes.map((n) => this.resolveTypeNode(n, scope));
1451
+ for (let i = 0; i < generic.typeParams.length; i++) {
1452
+ map.set(generic.typeParams[i].name, typeArgs[i] ?? ANY);
1453
+ }
1454
+ // Resolve the underlying type from the decl node using a scope with type params bound
1455
+ const typeScope = new Scope(this.globals);
1456
+ for (const [name, type] of map) typeScope.define(name, type);
1457
+ const underlying = this.resolveTypeNode(generic.declNode.type, typeScope);
1458
+ const instantiated = this.substituteType(underlying, map);
1459
+ if (instantiated.kind === "struct") {
1460
+ instantiated.name = generic.declNode.name;
1461
+ if (!instantiated.methods) instantiated.methods = new Map();
1462
+ // Copy and substitute methods from the generic
1463
+ if (generic.methods) {
1464
+ for (const [mName, mType] of generic.methods) {
1465
+ instantiated.methods.set(mName, this.substituteType(mType, map));
1466
+ }
1467
+ }
1468
+ }
1469
+ return {
1470
+ kind: "named",
1471
+ name: generic.declNode.name,
1472
+ underlying: instantiated,
1473
+ };
1474
+ }
1475
+
1476
+ inferTypeArgs(genericType, argTypes) {
1477
+ const map = new Map();
1478
+ const params = genericType.underlying.params;
1479
+ for (let i = 0; i < params.length && i < argTypes.length; i++) {
1480
+ this._inferFromPair(params[i], argTypes[i], map);
1481
+ }
1482
+ // Check all type params are resolved
1483
+ for (const tp of genericType.typeParams) {
1484
+ if (!map.has(tp.name)) return null;
1485
+ }
1486
+ // Apply defaultType to resolve untyped constants
1487
+ const result = [];
1488
+ for (const tp of genericType.typeParams) {
1489
+ result.push(defaultType(map.get(tp.name)));
1490
+ }
1491
+ return result;
1492
+ }
1493
+
1494
+ _inferFromPair(paramType, argType, map) {
1495
+ if (!paramType || !argType) return;
1496
+ if (paramType.kind === "typeParam") {
1497
+ if (!map.has(paramType.name)) {
1498
+ map.set(paramType.name, argType);
1499
+ }
1500
+ return;
1501
+ }
1502
+ if (paramType.kind === "slice" && argType.kind === "slice") {
1503
+ this._inferFromPair(paramType.elem, argType.elem, map);
1504
+ }
1505
+ if (paramType.kind === "map" && argType.kind === "map") {
1506
+ this._inferFromPair(paramType.key, argType.key, map);
1507
+ this._inferFromPair(paramType.value, argType.value, map);
1508
+ }
1509
+ if (paramType.kind === "func" && argType.kind === "func") {
1510
+ for (
1511
+ let i = 0;
1512
+ i < paramType.params.length && i < argType.params.length;
1513
+ i++
1514
+ ) {
1515
+ this._inferFromPair(paramType.params[i], argType.params[i], map);
1516
+ }
1517
+ for (
1518
+ let i = 0;
1519
+ i < paramType.returns.length && i < argType.returns.length;
1520
+ i++
1521
+ ) {
1522
+ this._inferFromPair(paramType.returns[i], argType.returns[i], map);
1523
+ }
1524
+ }
1525
+ }
1526
+
1527
+ checkConstraint(typeArg, constraint, node) {
1528
+ if (!constraint) return;
1529
+ if (isAny(constraint)) return;
1530
+ if (constraint.kind === "basic" && constraint.name === "comparable") return;
1531
+ if (constraint.kind === "UnionConstraint") {
1532
+ // Check if typeArg matches any term; ~terms always pass (v1)
1533
+ for (const term of constraint.terms) {
1534
+ if (term.approx) return; // ~ means approximate, always passes
1535
+ const termType = this.resolveTypeNode(term.type, this.globals);
1536
+ if (typeStr(typeArg) === typeStr(termType)) return;
1537
+ }
1538
+ this.err(`type ${typeStr(typeArg)} does not satisfy constraint`, node);
1539
+ return;
1540
+ }
1541
+ // Interface constraint — unwrap named types
1542
+ const base =
1543
+ constraint.kind === "named" ? constraint.underlying : constraint;
1544
+ if (base?.kind === "interface") {
1545
+ if (base.methods.size === 0 && !base.unionConstraint) return;
1546
+ if (base.unionConstraint) {
1547
+ return this.checkConstraint(typeArg, base.unionConstraint, node);
1548
+ }
1549
+ if (!this.implements(typeArg, base, node)) {
1550
+ this.err(
1551
+ `type ${typeStr(typeArg)} does not satisfy constraint ${typeStr(constraint)}`,
1552
+ node,
1553
+ );
1554
+ }
1555
+ }
1556
+ }
1557
+
972
1558
  resolveType(t) {
973
1559
  if (!t) return ANY;
974
1560
  if (t.kind === "basic" && t.alias) {
@@ -981,6 +1567,15 @@ export class TypeChecker {
981
1567
  fieldType(baseType, field, node) {
982
1568
  baseType = this.resolveType(baseType);
983
1569
  if (!baseType || isAny(baseType)) return ANY;
1570
+ // Transparent pointer dereference for named-type method calls: (*T).Method works like T.Method
1571
+ // (only unwrap when the pointer's base is a named type that has methods)
1572
+ if (
1573
+ baseType.kind === "pointer" &&
1574
+ field !== "value" &&
1575
+ baseType.base?.kind === "named"
1576
+ ) {
1577
+ baseType = this.resolveType(baseType.base);
1578
+ }
984
1579
  let base = baseType.kind === "named" ? baseType.underlying : baseType;
985
1580
  base = this.resolveType(base);
986
1581
  if (base?.kind === "struct") {
@@ -988,6 +1583,10 @@ export class TypeChecker {
988
1583
  if (base.methods?.has(field)) return base.methods.get(field);
989
1584
  return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
990
1585
  }
1586
+ if (base?.kind === "interface") {
1587
+ if (base.methods?.has(field)) return base.methods.get(field);
1588
+ return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
1589
+ }
991
1590
  if (base?.kind === "namespace") {
992
1591
  // Enforce exported identifier rule for GoFront packages only
993
1592
  if (
@@ -1007,9 +1606,6 @@ export class TypeChecker {
1007
1606
  // For explicitly typed non-any values, report the bad access rather than
1008
1607
  // silently returning any — this catches typos on structs and primitive misuse.
1009
1608
  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
1609
  // pointer.value is the GoFront new(T) pattern
1014
1610
  if (base.kind === "pointer" && field === "value") return base.base ?? ANY;
1015
1611
  const badKinds = ["basic", "slice", "array", "map", "func", "tuple"];
@@ -1024,7 +1620,23 @@ export class TypeChecker {
1024
1620
  }
1025
1621
 
1026
1622
  binaryResultType(op, lt, rt, node) {
1623
+ // Type parameters are permissive — allow operations and return the typeParam
1624
+ if (lt?.kind === "typeParam" || rt?.kind === "typeParam") {
1625
+ if (CMP_OPS.has(op)) return BOOL;
1626
+ if (LOG_OPS.has(op)) return BOOL;
1627
+ return lt?.kind === "typeParam" ? lt : rt;
1628
+ }
1027
1629
  if (CMP_OPS.has(op)) {
1630
+ // Complex only supports == and !=
1631
+ if (isComplex(lt) || isComplex(rt)) {
1632
+ if (op !== "==" && op !== "!=") {
1633
+ this.err(
1634
+ `invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
1635
+ node,
1636
+ );
1637
+ return ANY;
1638
+ }
1639
+ }
1028
1640
  // Reject slice/map/func comparisons unless one side is nil (Go spec)
1029
1641
  if (op === "==" || op === "!=") {
1030
1642
  const lNil = isNil(lt);
@@ -1046,6 +1658,26 @@ export class TypeChecker {
1046
1658
  }
1047
1659
  if (LOG_OPS.has(op)) return BOOL;
1048
1660
  if (isAny(lt) || isAny(rt)) return ANY;
1661
+ // Complex arithmetic: +, -, *, /
1662
+ if (isComplex(lt) || isComplex(rt)) {
1663
+ if (!isComplexOrNumeric(lt) || !isComplexOrNumeric(rt)) {
1664
+ this.err(
1665
+ `invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
1666
+ node,
1667
+ );
1668
+ return ANY;
1669
+ }
1670
+ if (op !== "+" && op !== "-" && op !== "*" && op !== "/") {
1671
+ this.err(
1672
+ `invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
1673
+ node,
1674
+ );
1675
+ return ANY;
1676
+ }
1677
+ if (lt.kind === "untyped" && rt.kind === "untyped")
1678
+ return UNTYPED_COMPLEX;
1679
+ return COMPLEX128;
1680
+ }
1049
1681
  if (isNumeric(lt) && isNumeric(rt)) {
1050
1682
  const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
1051
1683
  const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
@@ -1076,8 +1708,19 @@ export class TypeChecker {
1076
1708
  if (!target || !source) return;
1077
1709
  target = this.resolveType(target);
1078
1710
  source = this.resolveType(source);
1711
+ // Type params are permissive — allow any assignment
1712
+ if (target?.kind === "typeParam" || source?.kind === "typeParam") return;
1079
1713
  if (isAny(target) || isAny(source)) return;
1080
1714
  if (isNil(source)) return; // nil is assignable to anything nullable
1715
+ // Pointer type: nil → *T is allowed (handled above); *T → *T check base types
1716
+ if (isPointer(target) && isPointer(source)) {
1717
+ this.assertAssignable(
1718
+ target.base ?? target.underlying?.base,
1719
+ source.base ?? source.underlying?.base,
1720
+ node,
1721
+ );
1722
+ return;
1723
+ }
1081
1724
 
1082
1725
  // Untyped constants coerce to any compatible typed target
1083
1726
  if (isUntyped(source)) {
@@ -1099,6 +1742,18 @@ export class TypeChecker {
1099
1742
  if (source.base === "string" && target.name === "string") return;
1100
1743
  // untyped bool → bool
1101
1744
  if (source.base === "bool" && target.name === "bool") return;
1745
+ // untyped complex → complex128 or complex64
1746
+ if (
1747
+ source.base === "complex128" &&
1748
+ (target.name === "complex128" || target.name === "complex64")
1749
+ )
1750
+ return;
1751
+ // untyped int/float → complex (Go allows: var z complex128 = 5)
1752
+ if (
1753
+ isComplex(target) &&
1754
+ (source.base === "int" || source.base === "float64")
1755
+ )
1756
+ return;
1102
1757
  }
1103
1758
  }
1104
1759
 
@@ -1107,6 +1762,33 @@ export class TypeChecker {
1107
1762
  if (target.name === "float64" && source.name === "int") return;
1108
1763
  if (target.name === "int" && source.name === "float64") return; // truncating
1109
1764
  }
1765
+
1766
+ // Array-specific checks: size matching and array/slice incompatibility
1767
+ if (target.kind === "array" && source.kind === "array") {
1768
+ if (
1769
+ target.size != null &&
1770
+ source.size != null &&
1771
+ target.size !== source.size
1772
+ ) {
1773
+ this.err(
1774
+ `Cannot assign ${typeStr(source)} to ${typeStr(target)} (different array lengths)`,
1775
+ node,
1776
+ );
1777
+ return;
1778
+ }
1779
+ // Check element type compatibility
1780
+ this.assertAssignable(target.elem, source.elem, node);
1781
+ return;
1782
+ }
1783
+ if (target.kind === "array" && source.kind === "slice") {
1784
+ this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
1785
+ return;
1786
+ }
1787
+ if (target.kind === "slice" && source.kind === "array") {
1788
+ this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
1789
+ return;
1790
+ }
1791
+
1110
1792
  if (typeStr(target) !== typeStr(source)) {
1111
1793
  // Interface satisfaction check
1112
1794
  let tBase = target.kind === "named" ? target.underlying : target;