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.
@@ -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() {
@@ -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, this.globals),
719
+ this.resolveTypeNode(p.type, resolveScope),
609
720
  );
610
721
  const returnType = decl.returnType
611
- ? this.resolveTypeNode(decl.returnType, this.globals)
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
- 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
+ }
627
747
  } else {
628
748
  // 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);
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, outer));
806
+ inner.define(p.name, this.resolveTypeNode(p.type, inner));
662
807
  }
663
808
  let returnType = decl.returnType
664
- ? this.resolveTypeNode(decl.returnType, outer)
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, outer));
850
+ inner.define(p.name, this.resolveTypeNode(p.type, inner));
695
851
  }
696
852
  let returnType = decl.returnType
697
- ? this.resolveTypeNode(decl.returnType, outer)
853
+ ? this.resolveTypeNode(decl.returnType, inner)
698
854
  : VOID;
699
855
  const hasNamedReturns = this._injectNamedReturns(
700
856
  decl.returnType,
701
857
  inner,
702
- outer,
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;