gofront 0.0.9 → 1.0.0

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.
Files changed (50) hide show
  1. package/README.md +6 -6
  2. package/package.json +7 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/codegen/index.js +576 -0
  5. package/src/codegen/runtime.js +151 -0
  6. package/src/codegen/statements.js +305 -296
  7. package/src/codegen/stdlib/builder.js +36 -0
  8. package/src/codegen/stdlib/bytes.js +62 -0
  9. package/src/codegen/stdlib/errors.js +17 -0
  10. package/src/codegen/stdlib/fmt.js +101 -0
  11. package/src/codegen/stdlib/gom.js +152 -0
  12. package/src/codegen/stdlib/html.js +17 -0
  13. package/src/codegen/stdlib/index.js +95 -0
  14. package/src/codegen/stdlib/io.js +33 -0
  15. package/src/codegen/stdlib/maps.js +42 -0
  16. package/src/codegen/stdlib/math.js +53 -0
  17. package/src/codegen/stdlib/os.js +10 -0
  18. package/src/codegen/stdlib/path.js +31 -0
  19. package/src/codegen/stdlib/rand.js +24 -0
  20. package/src/codegen/stdlib/regexp.js +51 -0
  21. package/src/codegen/stdlib/slices.js +48 -0
  22. package/src/codegen/stdlib/sort.js +28 -0
  23. package/src/codegen/stdlib/strconv.js +30 -0
  24. package/src/codegen/stdlib/strings.js +83 -0
  25. package/src/codegen/stdlib/time.js +54 -0
  26. package/src/codegen/stdlib/unicode.js +22 -0
  27. package/src/codegen/stdlib/utf8.js +23 -0
  28. package/src/codegen/templ.js +281 -0
  29. package/src/compiler.js +80 -74
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +13 -49
  32. package/src/lexer.js +186 -272
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +51 -34
  35. package/src/{parser.js → parser/index.js} +4 -4
  36. package/src/parser/statements.js +24 -34
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-parser.js +2 -2
  39. package/src/tokens.js +109 -0
  40. package/src/typechecker/assignability.js +258 -0
  41. package/src/typechecker/expressions.js +559 -549
  42. package/src/typechecker/index.js +555 -0
  43. package/src/typechecker/resolve.js +421 -0
  44. package/src/typechecker/statements.js +267 -280
  45. package/src/typechecker/stdlib/core.js +592 -0
  46. package/src/typechecker/stdlib/extended.js +332 -0
  47. package/src/typechecker/stdlib.js +9 -1060
  48. package/src/typechecker/termination.js +83 -0
  49. package/src/codegen.js +0 -882
  50. package/src/typechecker.js +0 -1299
@@ -1,6 +1,6 @@
1
1
  // Parser expression methods — installed as a mixin on Parser.prototype.
2
2
 
3
- import { T } from "../lexer.js";
3
+ import { T } from "../tokens.js";
4
4
 
5
5
  // Binary operator precedence (higher = binds tighter, matching Go spec)
6
6
  const PREC = {
@@ -55,6 +55,45 @@ const BUILTIN_KEYWORDS = new Set([
55
55
  "imag",
56
56
  ]);
57
57
 
58
+ // Tokens that cannot legally appear inside a type argument list.
59
+ // Each || in an inline condition counts as CC; extracting here keeps looksLikeTypeArgList lean.
60
+ const DISALLOWED_IN_TYPE_ARGS = new Set([
61
+ T.PLUS,
62
+ T.MINUS,
63
+ T.PERCENT,
64
+ T.SLASH,
65
+ T.AND,
66
+ T.OR,
67
+ T.CARET,
68
+ T.NOT,
69
+ T.LT,
70
+ T.GT,
71
+ T.LTE,
72
+ T.GTE,
73
+ T.EQ,
74
+ T.NEQ,
75
+ T.LSHIFT,
76
+ T.RSHIFT,
77
+ T.AND_NOT,
78
+ T.COLON,
79
+ T.DEFINE,
80
+ T.ASSIGN,
81
+ ]);
82
+
83
+ function _isDisallowedInTypeArgs(tt) {
84
+ return DISALLOWED_IN_TYPE_ARGS.has(tt);
85
+ }
86
+
87
+ function _isTypeIntroducerToken(tt) {
88
+ return (
89
+ tt === T.STAR ||
90
+ tt === T.MAP ||
91
+ tt === T.FUNC ||
92
+ tt === T.INTERFACE ||
93
+ tt === T.STRUCT
94
+ );
95
+ }
96
+
58
97
  export const expressionParserMethods = {
59
98
  parseExprList() {
60
99
  const exprs = [this.parseExpr()];
@@ -185,6 +224,7 @@ export const expressionParserMethods = {
185
224
  let depth = 1;
186
225
  let hasComma = false;
187
226
  let hasTypeKeyword = false;
227
+ let hasUpperIdent = false;
188
228
  while (i < src.length && depth > 0) {
189
229
  const tt = src[i].type;
190
230
  if (tt === T.LBRACKET) {
@@ -201,42 +241,19 @@ export const expressionParserMethods = {
201
241
  // Reject literals — it's an index expression
202
242
  if (tt === T.INT || tt === T.FLOAT || tt === T.STRING) return false;
203
243
  // Reject binary operators that wouldn't appear in type args
204
- if (
205
- tt === T.PLUS ||
206
- tt === T.MINUS ||
207
- tt === T.PERCENT ||
208
- tt === T.SLASH ||
209
- tt === T.AND ||
210
- tt === T.OR ||
211
- tt === T.CARET ||
212
- tt === T.NOT ||
213
- tt === T.LT ||
214
- tt === T.GT ||
215
- tt === T.LTE ||
216
- tt === T.GTE ||
217
- tt === T.EQ ||
218
- tt === T.NEQ ||
219
- tt === T.LSHIFT ||
220
- tt === T.RSHIFT ||
221
- tt === T.AND_NOT ||
222
- tt === T.COLON ||
223
- tt === T.DEFINE ||
224
- tt === T.ASSIGN
225
- )
226
- return false;
244
+ if (_isDisallowedInTypeArgs(tt)) return false;
227
245
  if (depth === 1) {
228
246
  if (tt === T.COMMA) hasComma = true;
229
- if (
230
- tt === T.STAR ||
231
- tt === T.MAP ||
232
- tt === T.FUNC ||
233
- tt === T.INTERFACE ||
234
- tt === T.STRUCT
235
- )
236
- hasTypeKeyword = true;
237
- // Check if it's a builtin type name
247
+ if (_isTypeIntroducerToken(tt)) hasTypeKeyword = true;
238
248
  if (tt === T.IDENT && TYPE_KEYWORDS.has(src[i].value))
239
249
  hasTypeKeyword = true;
250
+ // User-defined types start with uppercase (GoFront convention)
251
+ if (
252
+ tt === T.IDENT &&
253
+ /^[A-Z]/.test(src[i].value) &&
254
+ !TYPE_KEYWORDS.has(src[i].value)
255
+ )
256
+ hasUpperIdent = true;
240
257
  }
241
258
  i++;
242
259
  }
@@ -248,7 +265,7 @@ export const expressionParserMethods = {
248
265
  if (after.type === T.LPAREN || after.type === T.LBRACE) return true;
249
266
  // Function value in argument list: Identity[int], 99) — only if clearly a type
250
267
  if (after.type === T.COMMA || after.type === T.RPAREN) {
251
- return hasComma || hasTypeKeyword;
268
+ return hasComma || hasTypeKeyword || hasUpperIdent;
252
269
  }
253
270
  return false;
254
271
  },
@@ -16,10 +16,10 @@
16
16
  // CallExpr, SelectorExpr, IndexExpr, SliceExpr
17
17
  // CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
18
18
 
19
- import { T } from "./lexer.js";
20
- import { expressionParserMethods } from "./parser/expressions.js";
21
- import { statementParserMethods } from "./parser/statements.js";
22
- import { typeParserMethods } from "./parser/types.js";
19
+ import { T } from "../tokens.js";
20
+ import { expressionParserMethods } from "./expressions.js";
21
+ import { statementParserMethods } from "./statements.js";
22
+ import { typeParserMethods } from "./types.js";
23
23
 
24
24
  export class ParseError extends Error {
25
25
  constructor(msg, token, filename, sourceCode) {
@@ -1,6 +1,19 @@
1
1
  // Parser statement methods — installed as a mixin on Parser.prototype.
2
2
 
3
- import { T } from "../lexer.js";
3
+ import { T } from "../tokens.js";
4
+
5
+ // Method-name dispatch for pure-delegation cases in _parseStmt.
6
+ const PARSE_STMT_DELEGATE = {
7
+ [T.VAR]: "parseVarDecl",
8
+ [T.CONST]: "parseConstDecl",
9
+ [T.TYPE]: "parseTypeDecl",
10
+ [T.RETURN]: "parseReturn",
11
+ [T.DEFER]: "parseDefer",
12
+ [T.IF]: "parseIf",
13
+ [T.FOR]: "parseFor",
14
+ [T.SWITCH]: "parseSwitch",
15
+ [T.LBRACE]: "parseBlock",
16
+ };
4
17
 
5
18
  export const statementParserMethods = {
6
19
  parseBlock() {
@@ -32,59 +45,36 @@ export const statementParserMethods = {
32
45
  },
33
46
 
34
47
  _parseStmt(t) {
48
+ const delegate = PARSE_STMT_DELEGATE[t.type];
49
+ if (delegate) return this[delegate]();
50
+
35
51
  switch (t.type) {
36
- case T.VAR:
37
- return this.parseVarDecl();
38
- case T.CONST:
39
- return this.parseConstDecl();
40
- case T.TYPE:
41
- return this.parseTypeDecl();
42
- case T.RETURN:
43
- return this.parseReturn();
44
- case T.DEFER:
45
- return this.parseDefer();
46
52
  case T.BREAK:
47
- this.advance();
48
- if (this.check(T.IDENT)) {
49
- const label = this.advance().value;
50
- this.semi();
51
- return { kind: "BranchStmt", keyword: "break", label };
52
- }
53
- this.semi();
54
- return { kind: "BranchStmt", keyword: "break" };
55
- case T.CONTINUE:
56
- this.advance();
53
+ case T.CONTINUE: {
54
+ const keyword = this.advance().value;
57
55
  if (this.check(T.IDENT)) {
58
56
  const label = this.advance().value;
59
57
  this.semi();
60
- return { kind: "BranchStmt", keyword: "continue", label };
58
+ return { kind: "BranchStmt", keyword, label };
61
59
  }
62
60
  this.semi();
63
- return { kind: "BranchStmt", keyword: "continue" };
61
+ return { kind: "BranchStmt", keyword };
62
+ }
64
63
  case T.FALLTHROUGH:
65
64
  this.advance();
66
65
  this.semi();
67
66
  return { kind: "BranchStmt", keyword: "fallthrough" };
68
- case T.IF:
69
- return this.parseIf();
70
- case T.FOR:
71
- return this.parseFor();
72
- case T.SWITCH:
73
- return this.parseSwitch();
74
67
  case T.GO:
75
68
  return this.err("goroutines are not supported in GoFront");
76
69
  case T.SELECT:
77
70
  return this.err(
78
71
  "select statement is not supported in GoFront (no channels)",
79
72
  );
80
- case T.LBRACE:
81
- return this.parseBlock();
82
73
  case T.IDENT:
83
74
  if (this.check2(T.COLON)) {
84
- const label = this.advance().value; // consume IDENT
75
+ const label = this.advance().value;
85
76
  this.advance(); // consume COLON
86
- const body = this.parseStmt();
87
- return { kind: "LabeledStmt", label, body };
77
+ return { kind: "LabeledStmt", label, body: this.parseStmt() };
88
78
  }
89
79
  return this.parseSimpleStmt();
90
80
  default:
@@ -1,39 +1,41 @@
1
1
  // Parser type-expression methods — installed as a mixin on Parser.prototype.
2
2
 
3
- import { T } from "../lexer.js";
3
+ import { T } from "../tokens.js";
4
+
5
+ // Token types that directly map to parseTypeName().
6
+ const TYPE_NAME_TOKENS = new Set([
7
+ T.IDENT,
8
+ "int",
9
+ "float64",
10
+ "string",
11
+ "bool",
12
+ "any",
13
+ "byte",
14
+ "rune",
15
+ "error",
16
+ ]);
17
+ // Method-name dispatch for the simple keyword cases in parseType().
18
+ const PARSE_TYPE_DELEGATE = {
19
+ [T.LBRACKET]: "parseSliceOrArrayType",
20
+ [T.MAP]: "parseMapType",
21
+ [T.FUNC]: "parseFuncType",
22
+ [T.STRUCT]: "parseStructType",
23
+ [T.INTERFACE]: "parseInterfaceType",
24
+ };
4
25
 
5
26
  export const typeParserMethods = {
6
27
  parseType() {
7
28
  const t = this.peek();
8
- switch (t.type) {
9
- case T.LBRACKET:
10
- return this.parseSliceOrArrayType();
11
- case T.MAP:
12
- return this.parseMapType();
13
- case T.FUNC:
14
- return this.parseFuncType();
15
- case T.STRUCT:
16
- return this.parseStructType();
17
- case T.INTERFACE:
18
- return this.parseInterfaceType();
19
- case T.STAR:
20
- this.advance();
21
- return { kind: "PointerType", base: this.parseType() };
22
- case T.CHAN:
23
- return this.err("channels are not supported in GoFront");
24
- case T.IDENT:
25
- case "int":
26
- case "float64":
27
- case "string":
28
- case "bool":
29
- case "any":
30
- case "byte":
31
- case "rune":
32
- case "error":
33
- return this.parseTypeName();
34
- default:
35
- this.err(`Expected type, got '${t.value}'`);
29
+ const delegate = PARSE_TYPE_DELEGATE[t.type];
30
+ if (delegate) return this[delegate]();
31
+ if (TYPE_NAME_TOKENS.has(t.type)) return this.parseTypeName();
32
+ if (t.type === T.STAR) {
33
+ this.advance();
34
+ return { kind: "PointerType", base: this.parseType() };
36
35
  }
36
+ if (t.type === T.CHAN)
37
+ return this.err("channels are not supported in GoFront");
38
+ return this.err(`Expected type, got '${t.value}'`);
37
39
  },
38
40
 
39
41
  parseTypeName() {
@@ -16,9 +16,9 @@
16
16
  // TemplSwitch { kind, exprTokens, cases: TemplCase[] }
17
17
  // TemplCase { kind, caseTokens: tokens | null (null = default), body: TemplNode[] }
18
18
 
19
- import { T } from "./lexer.js";
20
- import { Parser } from "./parser.js";
19
+ import { Parser } from "./parser/index.js";
21
20
  import { TT } from "./templ-lexer.js";
21
+ import { T } from "./tokens.js";
22
22
 
23
23
  const VOID_ELEMENTS = new Set([
24
24
  "area",
package/src/tokens.js ADDED
@@ -0,0 +1,109 @@
1
+ // Token type table and Token class — pure data leaves used by every layer
2
+ // (lexer, parser, codegen). Putting these in their own module lets parser
3
+ // and codegen depend only on `tokens.js` instead of the full `lexer.js`.
4
+
5
+ export const T = {
6
+ // Literals
7
+ INT: "INT",
8
+ FLOAT: "FLOAT",
9
+ STRING: "STRING",
10
+ IDENT: "IDENT",
11
+
12
+ IMAG: "IMAG",
13
+
14
+ // Keywords
15
+ FUNC: "func",
16
+ VAR: "var",
17
+ CONST: "const",
18
+ TYPE: "type",
19
+ STRUCT: "struct",
20
+ INTERFACE: "interface",
21
+ IF: "if",
22
+ ELSE: "else",
23
+ FOR: "for",
24
+ RANGE: "range",
25
+ RETURN: "return",
26
+ PACKAGE: "package",
27
+ IMPORT: "import",
28
+ TRUE: "true",
29
+ FALSE: "false",
30
+ NIL: "nil",
31
+ BREAK: "break",
32
+ CONTINUE: "continue",
33
+ SWITCH: "switch",
34
+ CASE: "case",
35
+ DEFAULT: "default",
36
+ MAP: "map",
37
+ NEW: "new",
38
+ DEFER: "defer",
39
+ ASYNC: "async",
40
+ AWAIT: "await",
41
+ GO: "go",
42
+ CHAN: "chan",
43
+ SELECT: "select",
44
+
45
+ // Operators
46
+ PLUS: "+",
47
+ MINUS: "-",
48
+ STAR: "*",
49
+ SLASH: "/",
50
+ PERCENT: "%",
51
+ EQ: "==",
52
+ NEQ: "!=",
53
+ LT: "<",
54
+ GT: ">",
55
+ LTE: "<=",
56
+ GTE: ">=",
57
+ AND: "&&",
58
+ OR: "||",
59
+ NOT: "!",
60
+ ASSIGN: "=",
61
+ DEFINE: ":=",
62
+ PLUS_ASSIGN: "+=",
63
+ MINUS_ASSIGN: "-=",
64
+ STAR_ASSIGN: "*=",
65
+ SLASH_ASSIGN: "/=",
66
+ PERCENT_ASSIGN: "%=",
67
+ AMP_ASSIGN: "&=",
68
+ PIPE_ASSIGN: "|=",
69
+ CARET_ASSIGN: "^=",
70
+ LSHIFT_ASSIGN: "<<=",
71
+ RSHIFT_ASSIGN: ">>=",
72
+ INC: "++",
73
+ DEC: "--",
74
+ AMP: "&",
75
+ PIPE: "|",
76
+ CARET: "^",
77
+ AND_NOT: "&^",
78
+ TILDE: "~",
79
+ LSHIFT: "<<",
80
+ RSHIFT: ">>",
81
+ ELLIPSIS: "...",
82
+ FALLTHROUGH: "fallthrough",
83
+
84
+ // Delimiters
85
+ LPAREN: "(",
86
+ RPAREN: ")",
87
+ LBRACE: "{",
88
+ RBRACE: "}",
89
+ LBRACKET: "[",
90
+ RBRACKET: "]",
91
+ COMMA: ",",
92
+ DOT: ".",
93
+ COLON: ":",
94
+ SEMICOLON: ";",
95
+
96
+ EOF: "EOF",
97
+ };
98
+
99
+ export class Token {
100
+ constructor(type, value, line, col) {
101
+ this.type = type;
102
+ this.value = value;
103
+ this.line = line;
104
+ this.col = col;
105
+ }
106
+ toString() {
107
+ return `Token(${this.type}, ${JSON.stringify(this.value)}, ${this.line}:${this.col})`;
108
+ }
109
+ }
@@ -0,0 +1,258 @@
1
+ // Assignability, binary result types, and interface satisfaction.
2
+ // Installed as a mixin on TypeChecker.prototype.
3
+
4
+ import {
5
+ ANY,
6
+ BOOL,
7
+ CMP_OPS,
8
+ COMPLEX128,
9
+ FLOAT64,
10
+ INT,
11
+ isAny,
12
+ isComplex,
13
+ isComplexOrNumeric,
14
+ isNil,
15
+ isNumeric,
16
+ isPointer,
17
+ isString,
18
+ isUntyped,
19
+ LOG_OPS,
20
+ STRING,
21
+ typeStr,
22
+ UNTYPED_COMPLEX,
23
+ UNTYPED_FLOAT,
24
+ UNTYPED_INT,
25
+ } from "./types.js";
26
+
27
+ // Untyped constant assignability: maps source.base → Set of compatible target.name values
28
+ const UNTYPED_COMPAT = {
29
+ int: new Set(["int", "float64", "complex128", "complex64"]),
30
+ float64: new Set(["float64", "int", "complex128", "complex64"]),
31
+ string: new Set(["string"]),
32
+ bool: new Set(["bool"]),
33
+ complex128: new Set(["complex128", "complex64"]),
34
+ };
35
+
36
+ export const assignabilityMethods = {
37
+ // ── Binary result types ───────────────────────────────────────────
38
+
39
+ binaryResultType(op, lt, rt, node) {
40
+ if (lt?.kind === "typeParam" || rt?.kind === "typeParam")
41
+ return this._binaryResultTypeTypeParam(op, lt, rt);
42
+ if (CMP_OPS.has(op)) return this._binaryResultTypeCmp(op, lt, rt, node);
43
+ if (LOG_OPS.has(op)) return BOOL;
44
+ return this._binaryResultTypeNonComplex(op, lt, rt, node);
45
+ },
46
+
47
+ _binaryResultTypeTypeParam(op, lt, rt) {
48
+ if (CMP_OPS.has(op) || LOG_OPS.has(op)) return BOOL;
49
+ return lt?.kind === "typeParam" ? lt : rt;
50
+ },
51
+
52
+ _binaryResultTypeNonComplex(op, lt, rt, node) {
53
+ if (isAny(lt) || isAny(rt)) return ANY;
54
+ if (isComplex(lt) || isComplex(rt))
55
+ return this._binaryResultTypeComplex(op, lt, rt, node);
56
+ if (isNumeric(lt) && isNumeric(rt))
57
+ return this._binaryResultTypeNumeric(lt, rt);
58
+ if (isString(lt) && isString(rt) && op === "+")
59
+ return this._binaryResultTypeString(lt, rt);
60
+ if (node)
61
+ this.err(`Invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
62
+ return ANY;
63
+ },
64
+
65
+ _binaryResultTypeString(lt, rt) {
66
+ if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
67
+ if (lt.kind === "untyped") return rt;
68
+ if (rt.kind === "untyped") return lt;
69
+ return STRING;
70
+ },
71
+
72
+ _binaryResultTypeCmp(op, lt, rt, node) {
73
+ if (this._checkComplexCmpOp(op, lt, rt, node)) return ANY;
74
+ this._checkEqualityComparable(op, lt, rt, node);
75
+ return BOOL;
76
+ },
77
+
78
+ _binaryResultTypeComplex(op, lt, rt, node) {
79
+ if (!isComplexOrNumeric(lt) || !isComplexOrNumeric(rt)) {
80
+ this.err(`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
81
+ return ANY;
82
+ }
83
+ if (op !== "+" && op !== "-" && op !== "*" && op !== "/") {
84
+ this.err(`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
85
+ return ANY;
86
+ }
87
+ if (lt.kind === "untyped" && rt.kind === "untyped") return UNTYPED_COMPLEX;
88
+ return COMPLEX128;
89
+ },
90
+
91
+ _binaryResultTypeNumeric(lt, rt) {
92
+ const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
93
+ const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
94
+ const isFloat = lFloat || rFloat;
95
+ if (lt.kind === "untyped" && rt.kind === "untyped")
96
+ return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
97
+ if (lt.kind === "untyped")
98
+ return isFloat && rt.name !== "float64" ? FLOAT64 : rt;
99
+ if (rt.kind === "untyped")
100
+ return isFloat && lt.name !== "float64" ? FLOAT64 : lt;
101
+ return isFloat ? FLOAT64 : INT;
102
+ },
103
+
104
+ _checkComplexCmpOp(op, lt, rt, node) {
105
+ if (isComplex(lt) || isComplex(rt)) {
106
+ if (op !== "==" && op !== "!=") {
107
+ this.err(
108
+ `invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
109
+ node,
110
+ );
111
+ return true;
112
+ }
113
+ }
114
+ return false;
115
+ },
116
+
117
+ _isNonComparableKind(t) {
118
+ const base = t?.kind === "named" ? t.underlying : t;
119
+ return (
120
+ base?.kind === "slice" || base?.kind === "map" || base?.kind === "func"
121
+ );
122
+ },
123
+
124
+ _checkEqualityComparable(op, lt, rt, node) {
125
+ if (op !== "==" && op !== "!=") return;
126
+ if (isNil(lt) || isNil(rt)) return;
127
+ for (const t of [lt, rt]) {
128
+ if (this._isNonComparableKind(t))
129
+ this.err(`operator ${op} not defined on ${typeStr(t)}`, node);
130
+ }
131
+ },
132
+
133
+ // ── Assignability ─────────────────────────────────────────────────
134
+
135
+ assertAssignable(target, source, node) {
136
+ if (!target || !source) return;
137
+ target = this.resolveType(target);
138
+ source = this.resolveType(source);
139
+ if (this._assertAssignableEarlyReturn(target, source)) return;
140
+ if (isPointer(target) && isPointer(source)) {
141
+ this.assertAssignable(
142
+ target.base ?? target.underlying?.base,
143
+ source.base ?? source.underlying?.base,
144
+ node,
145
+ );
146
+ return;
147
+ }
148
+ if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
149
+ if (this._isNumericCoercible(target, source)) return;
150
+ if (this._checkArrayAssignable(target, source, node)) return;
151
+ if (typeStr(target) !== typeStr(source))
152
+ this._assertAssignableTypeMismatch(target, source, node);
153
+ },
154
+
155
+ _assertAssignableEarlyReturn(target, source) {
156
+ return (
157
+ target?.kind === "typeParam" ||
158
+ source?.kind === "typeParam" ||
159
+ isAny(target) ||
160
+ isAny(source) ||
161
+ isNil(source)
162
+ );
163
+ },
164
+
165
+ _assertAssignableTypeMismatch(target, source, node) {
166
+ let tBase = target.kind === "named" ? target.underlying : target;
167
+ tBase = this.resolveType(tBase);
168
+ if (tBase?.kind === "interface") {
169
+ if (tBase.methods.size === 0) return;
170
+ if (!this.implements(source, tBase, node))
171
+ this.err(
172
+ `${typeStr(source)} does not implement ${typeStr(target)}`,
173
+ node,
174
+ );
175
+ return;
176
+ }
177
+ this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
178
+ },
179
+
180
+ _isUntypedAssignable(target, source) {
181
+ if (isUntyped(target)) return true;
182
+ if (target.kind !== "basic") return false;
183
+ return (
184
+ (UNTYPED_COMPAT[source.base]?.has(target.name) ?? false) ||
185
+ (isComplex(target) &&
186
+ (source.base === "int" || source.base === "float64"))
187
+ );
188
+ },
189
+
190
+ _isNumericCoercible(target, source) {
191
+ if (target.kind !== "basic" || source.kind !== "basic") return false;
192
+ return (
193
+ (target.name === "float64" && source.name === "int") ||
194
+ (target.name === "int" && source.name === "float64")
195
+ );
196
+ },
197
+
198
+ _checkArrayAssignable(target, source, node) {
199
+ if (target.kind === "array" && source.kind === "array") {
200
+ if (
201
+ target.size != null &&
202
+ source.size != null &&
203
+ target.size !== source.size
204
+ )
205
+ this.err(
206
+ `Cannot assign ${typeStr(source)} to ${typeStr(target)} (different array lengths)`,
207
+ node,
208
+ );
209
+ else this.assertAssignable(target.elem, source.elem, node);
210
+ return true;
211
+ }
212
+ if (
213
+ (target.kind === "array" && source.kind === "slice") ||
214
+ (target.kind === "slice" && source.kind === "array")
215
+ ) {
216
+ this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
217
+ return true;
218
+ }
219
+ return false;
220
+ },
221
+
222
+ // ── Interface satisfaction ────────────────────────────────────────
223
+
224
+ _sigParamsMatch(reqList, actList) {
225
+ if (reqList.length !== actList.length) return false;
226
+ for (let i = 0; i < reqList.length; i++) {
227
+ if (typeStr(reqList[i]) !== typeStr(actList[i])) return false;
228
+ }
229
+ return true;
230
+ },
231
+
232
+ _implementsMethod(required, actual) {
233
+ if (!actual) return false;
234
+ const rp = required.params ?? [],
235
+ ap = actual.params ?? [];
236
+ const rr = required.returns ?? [],
237
+ ar = actual.returns ?? [];
238
+ if (!this._sigParamsMatch(rp, ap)) return false;
239
+ if (!!required.variadic !== !!actual.variadic) return false;
240
+ return this._sigParamsMatch(rr, ar);
241
+ },
242
+
243
+ implements(srcType, iface, _node) {
244
+ let base = srcType.kind === "named" ? srcType.underlying : srcType;
245
+ base = this.resolveType(base);
246
+ const methodMap =
247
+ base?.kind === "struct"
248
+ ? base.methods
249
+ : srcType.kind === "named"
250
+ ? srcType.methods
251
+ : null;
252
+ if (!methodMap) return false;
253
+ for (const [name, required] of iface.methods) {
254
+ if (!this._implementsMethod(required, methodMap.get(name))) return false;
255
+ }
256
+ return true;
257
+ },
258
+ };