gofront 0.0.9 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/package.json +7 -4
- package/src/cli-core.js +65 -0
- package/src/codegen/expressions.js +523 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +308 -296
- package/src/codegen/stdlib/builder.js +39 -0
- package/src/codegen/stdlib/bytes.js +65 -0
- package/src/codegen/stdlib/errors.js +20 -0
- package/src/codegen/stdlib/fmt.js +104 -0
- package/src/codegen/stdlib/gom.js +155 -0
- package/src/codegen/stdlib/html.js +20 -0
- package/src/codegen/stdlib/index.js +98 -0
- package/src/codegen/stdlib/io.js +36 -0
- package/src/codegen/stdlib/maps.js +45 -0
- package/src/codegen/stdlib/math.js +56 -0
- package/src/codegen/stdlib/os.js +13 -0
- package/src/codegen/stdlib/path.js +34 -0
- package/src/codegen/stdlib/rand.js +27 -0
- package/src/codegen/stdlib/regexp.js +54 -0
- package/src/codegen/stdlib/slices.js +51 -0
- package/src/codegen/stdlib/sort.js +31 -0
- package/src/codegen/stdlib/strconv.js +33 -0
- package/src/codegen/stdlib/strings.js +86 -0
- package/src/codegen/stdlib/time.js +57 -0
- package/src/codegen/stdlib/unicode.js +25 -0
- package/src/codegen/stdlib/utf8.js +26 -0
- package/src/codegen/templ.js +284 -0
- package/src/compiler.js +142 -77
- package/src/dts-parser.js +355 -371
- package/src/index.js +32 -162
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/{parser.js → parser/declarations.js} +18 -175
- package/src/parser/expressions.js +51 -34
- package/src/parser/index.js +161 -0
- package/src/parser/statements.js +24 -34
- package/src/parser/types.js +31 -29
- package/src/templ-parser.js +2 -2
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +261 -0
- package/src/typechecker/expressions.js +568 -549
- package/src/typechecker/index.js +556 -0
- package/src/typechecker/resolve.js +428 -0
- package/src/typechecker/statements.js +270 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +86 -0
- package/src/typechecker/types.js +1 -0
- package/src/codegen.js +0 -882
- package/src/typechecker.js +0 -1299
package/src/parser/types.js
CHANGED
|
@@ -1,39 +1,41 @@
|
|
|
1
1
|
// Parser type-expression methods — installed as a mixin on Parser.prototype.
|
|
2
2
|
|
|
3
|
-
import { T } from "../
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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() {
|
package/src/templ-parser.js
CHANGED
|
@@ -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 {
|
|
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,261 @@
|
|
|
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
|
+
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
28
|
+
|
|
29
|
+
// Untyped constant assignability: maps source.base → Set of compatible target.name values
|
|
30
|
+
const UNTYPED_COMPAT = {
|
|
31
|
+
int: new Set(["int", "float64", "complex128", "complex64"]),
|
|
32
|
+
float64: new Set(["float64", "int", "complex128", "complex64"]),
|
|
33
|
+
string: new Set(["string"]),
|
|
34
|
+
bool: new Set(["bool"]),
|
|
35
|
+
complex128: new Set(["complex128", "complex64"]),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** @type {ThisType<TypeChecker>} */
|
|
39
|
+
export const assignabilityMethods = {
|
|
40
|
+
// ── Binary result types ───────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
binaryResultType(op, lt, rt, node) {
|
|
43
|
+
if (lt?.kind === "typeParam" || rt?.kind === "typeParam")
|
|
44
|
+
return this._binaryResultTypeTypeParam(op, lt, rt);
|
|
45
|
+
if (CMP_OPS.has(op)) return this._binaryResultTypeCmp(op, lt, rt, node);
|
|
46
|
+
if (LOG_OPS.has(op)) return BOOL;
|
|
47
|
+
return this._binaryResultTypeNonComplex(op, lt, rt, node);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
_binaryResultTypeTypeParam(op, lt, rt) {
|
|
51
|
+
if (CMP_OPS.has(op) || LOG_OPS.has(op)) return BOOL;
|
|
52
|
+
return lt?.kind === "typeParam" ? lt : rt;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
_binaryResultTypeNonComplex(op, lt, rt, node) {
|
|
56
|
+
if (isAny(lt) || isAny(rt)) return ANY;
|
|
57
|
+
if (isComplex(lt) || isComplex(rt))
|
|
58
|
+
return this._binaryResultTypeComplex(op, lt, rt, node);
|
|
59
|
+
if (isNumeric(lt) && isNumeric(rt))
|
|
60
|
+
return this._binaryResultTypeNumeric(lt, rt);
|
|
61
|
+
if (isString(lt) && isString(rt) && op === "+")
|
|
62
|
+
return this._binaryResultTypeString(lt, rt);
|
|
63
|
+
if (node)
|
|
64
|
+
this.err(`Invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
|
|
65
|
+
return ANY;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
_binaryResultTypeString(lt, rt) {
|
|
69
|
+
if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
|
|
70
|
+
if (lt.kind === "untyped") return rt;
|
|
71
|
+
if (rt.kind === "untyped") return lt;
|
|
72
|
+
return STRING;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
_binaryResultTypeCmp(op, lt, rt, node) {
|
|
76
|
+
if (this._checkComplexCmpOp(op, lt, rt, node)) return ANY;
|
|
77
|
+
this._checkEqualityComparable(op, lt, rt, node);
|
|
78
|
+
return BOOL;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
_binaryResultTypeComplex(op, lt, rt, node) {
|
|
82
|
+
if (!isComplexOrNumeric(lt) || !isComplexOrNumeric(rt)) {
|
|
83
|
+
this.err(`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
|
|
84
|
+
return ANY;
|
|
85
|
+
}
|
|
86
|
+
if (op !== "+" && op !== "-" && op !== "*" && op !== "/") {
|
|
87
|
+
this.err(`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
|
|
88
|
+
return ANY;
|
|
89
|
+
}
|
|
90
|
+
if (lt.kind === "untyped" && rt.kind === "untyped") return UNTYPED_COMPLEX;
|
|
91
|
+
return COMPLEX128;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
_binaryResultTypeNumeric(lt, rt) {
|
|
95
|
+
const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
|
|
96
|
+
const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
|
|
97
|
+
const isFloat = lFloat || rFloat;
|
|
98
|
+
if (lt.kind === "untyped" && rt.kind === "untyped")
|
|
99
|
+
return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
|
|
100
|
+
if (lt.kind === "untyped")
|
|
101
|
+
return isFloat && rt.name !== "float64" ? FLOAT64 : rt;
|
|
102
|
+
if (rt.kind === "untyped")
|
|
103
|
+
return isFloat && lt.name !== "float64" ? FLOAT64 : lt;
|
|
104
|
+
return isFloat ? FLOAT64 : INT;
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
_checkComplexCmpOp(op, lt, rt, node) {
|
|
108
|
+
if (isComplex(lt) || isComplex(rt)) {
|
|
109
|
+
if (op !== "==" && op !== "!=") {
|
|
110
|
+
this.err(
|
|
111
|
+
`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
|
|
112
|
+
node,
|
|
113
|
+
);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
_isNonComparableKind(t) {
|
|
121
|
+
const base = t?.kind === "named" ? t.underlying : t;
|
|
122
|
+
return (
|
|
123
|
+
base?.kind === "slice" || base?.kind === "map" || base?.kind === "func"
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
_checkEqualityComparable(op, lt, rt, node) {
|
|
128
|
+
if (op !== "==" && op !== "!=") return;
|
|
129
|
+
if (isNil(lt) || isNil(rt)) return;
|
|
130
|
+
for (const t of [lt, rt]) {
|
|
131
|
+
if (this._isNonComparableKind(t))
|
|
132
|
+
this.err(`operator ${op} not defined on ${typeStr(t)}`, node);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
// ── Assignability ─────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
assertAssignable(target, source, node) {
|
|
139
|
+
if (!target || !source) return;
|
|
140
|
+
target = this.resolveType(target);
|
|
141
|
+
source = this.resolveType(source);
|
|
142
|
+
if (this._assertAssignableEarlyReturn(target, source)) return;
|
|
143
|
+
if (isPointer(target) && isPointer(source)) {
|
|
144
|
+
this.assertAssignable(
|
|
145
|
+
target.base ?? target.underlying?.base,
|
|
146
|
+
source.base ?? source.underlying?.base,
|
|
147
|
+
node,
|
|
148
|
+
);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
|
|
152
|
+
if (this._isNumericCoercible(target, source)) return;
|
|
153
|
+
if (this._checkArrayAssignable(target, source, node)) return;
|
|
154
|
+
if (typeStr(target) !== typeStr(source))
|
|
155
|
+
this._assertAssignableTypeMismatch(target, source, node);
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
_assertAssignableEarlyReturn(target, source) {
|
|
159
|
+
return (
|
|
160
|
+
target?.kind === "typeParam" ||
|
|
161
|
+
source?.kind === "typeParam" ||
|
|
162
|
+
isAny(target) ||
|
|
163
|
+
isAny(source) ||
|
|
164
|
+
isNil(source)
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
_assertAssignableTypeMismatch(target, source, node) {
|
|
169
|
+
let tBase = target.kind === "named" ? target.underlying : target;
|
|
170
|
+
tBase = this.resolveType(tBase);
|
|
171
|
+
if (tBase?.kind === "interface") {
|
|
172
|
+
if (tBase.methods.size === 0) return;
|
|
173
|
+
if (!this.implements(source, tBase, node))
|
|
174
|
+
this.err(
|
|
175
|
+
`${typeStr(source)} does not implement ${typeStr(target)}`,
|
|
176
|
+
node,
|
|
177
|
+
);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
_isUntypedAssignable(target, source) {
|
|
184
|
+
if (isUntyped(target)) return true;
|
|
185
|
+
if (target.kind !== "basic") return false;
|
|
186
|
+
return (
|
|
187
|
+
(UNTYPED_COMPAT[source.base]?.has(target.name) ?? false) ||
|
|
188
|
+
(isComplex(target) &&
|
|
189
|
+
(source.base === "int" || source.base === "float64"))
|
|
190
|
+
);
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
_isNumericCoercible(target, source) {
|
|
194
|
+
if (target.kind !== "basic" || source.kind !== "basic") return false;
|
|
195
|
+
return (
|
|
196
|
+
(target.name === "float64" && source.name === "int") ||
|
|
197
|
+
(target.name === "int" && source.name === "float64")
|
|
198
|
+
);
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
_checkArrayAssignable(target, source, node) {
|
|
202
|
+
if (target.kind === "array" && source.kind === "array") {
|
|
203
|
+
if (
|
|
204
|
+
target.size != null &&
|
|
205
|
+
source.size != null &&
|
|
206
|
+
target.size !== source.size
|
|
207
|
+
)
|
|
208
|
+
this.err(
|
|
209
|
+
`Cannot assign ${typeStr(source)} to ${typeStr(target)} (different array lengths)`,
|
|
210
|
+
node,
|
|
211
|
+
);
|
|
212
|
+
else this.assertAssignable(target.elem, source.elem, node);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (
|
|
216
|
+
(target.kind === "array" && source.kind === "slice") ||
|
|
217
|
+
(target.kind === "slice" && source.kind === "array")
|
|
218
|
+
) {
|
|
219
|
+
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
// ── Interface satisfaction ────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
_sigParamsMatch(reqList, actList) {
|
|
228
|
+
if (reqList.length !== actList.length) return false;
|
|
229
|
+
for (let i = 0; i < reqList.length; i++) {
|
|
230
|
+
if (typeStr(reqList[i]) !== typeStr(actList[i])) return false;
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
_implementsMethod(required, actual) {
|
|
236
|
+
if (!actual) return false;
|
|
237
|
+
const rp = required.params ?? [],
|
|
238
|
+
ap = actual.params ?? [];
|
|
239
|
+
const rr = required.returns ?? [],
|
|
240
|
+
ar = actual.returns ?? [];
|
|
241
|
+
if (!this._sigParamsMatch(rp, ap)) return false;
|
|
242
|
+
if (!!required.variadic !== !!actual.variadic) return false;
|
|
243
|
+
return this._sigParamsMatch(rr, ar);
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
implements(srcType, iface, _node) {
|
|
247
|
+
let base = srcType.kind === "named" ? srcType.underlying : srcType;
|
|
248
|
+
base = this.resolveType(base);
|
|
249
|
+
const methodMap =
|
|
250
|
+
base?.kind === "struct"
|
|
251
|
+
? base.methods
|
|
252
|
+
: srcType.kind === "named"
|
|
253
|
+
? srcType.methods
|
|
254
|
+
: null;
|
|
255
|
+
if (!methodMap) return false;
|
|
256
|
+
for (const [name, required] of iface.methods) {
|
|
257
|
+
if (!this._implementsMethod(required, methodMap.get(name))) return false;
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
},
|
|
261
|
+
};
|