gofront 0.0.8 → 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.
- package/README.md +88 -12
- package/package.json +8 -4
- package/src/codegen/expressions.js +520 -1717
- package/src/{codegen.js → codegen/index.js} +155 -184
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +305 -296
- package/src/codegen/stdlib/builder.js +36 -0
- package/src/codegen/stdlib/bytes.js +62 -0
- package/src/codegen/stdlib/errors.js +17 -0
- package/src/codegen/stdlib/fmt.js +101 -0
- package/src/codegen/stdlib/gom.js +152 -0
- package/src/codegen/stdlib/html.js +17 -0
- package/src/codegen/stdlib/index.js +95 -0
- package/src/codegen/stdlib/io.js +33 -0
- package/src/codegen/stdlib/maps.js +42 -0
- package/src/codegen/stdlib/math.js +53 -0
- package/src/codegen/stdlib/os.js +10 -0
- package/src/codegen/stdlib/path.js +31 -0
- package/src/codegen/stdlib/rand.js +24 -0
- package/src/codegen/stdlib/regexp.js +51 -0
- package/src/codegen/stdlib/slices.js +48 -0
- package/src/codegen/stdlib/sort.js +28 -0
- package/src/codegen/stdlib/strconv.js +30 -0
- package/src/codegen/stdlib/strings.js +83 -0
- package/src/codegen/stdlib/time.js +54 -0
- package/src/codegen/stdlib/unicode.js +22 -0
- package/src/codegen/stdlib/utf8.js +23 -0
- package/src/codegen/templ.js +281 -0
- package/src/compiler.js +102 -62
- package/src/dts-parser.js +355 -371
- package/src/index.js +22 -54
- package/src/lexer.js +200 -286
- package/src/minifier.js +305 -318
- package/src/parser/expressions.js +54 -36
- package/src/{parser.js → parser/index.js} +12 -5
- package/src/parser/statements.js +29 -35
- package/src/parser/types.js +31 -29
- package/src/templ-lexer.js +737 -0
- package/src/templ-parser.js +235 -0
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +258 -0
- package/src/typechecker/expressions.js +559 -549
- package/src/typechecker/index.js +555 -0
- package/src/typechecker/resolve.js +421 -0
- package/src/typechecker/statements.js +267 -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 +83 -0
- package/src/typechecker/types.js +6 -3
- package/src/typechecker.js +0 -1271
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Templ parser: extends the Go Parser to handle TemplDecl nodes.
|
|
2
|
+
//
|
|
3
|
+
// A .templ file is tokenised by TemplLexer into a mixed stream of standard Go
|
|
4
|
+
// tokens (T.*) and HTML/templ tokens (TT.*). This parser handles TT.TEMPL_KW
|
|
5
|
+
// in parseTopDecl; everything else is delegated to the base Parser.
|
|
6
|
+
//
|
|
7
|
+
// Produced AST additions:
|
|
8
|
+
// TemplDecl { kind, name, params, body: TemplNode[] }
|
|
9
|
+
// TemplElement { kind, tag, attrs, children }
|
|
10
|
+
// TemplText { kind, value }
|
|
11
|
+
// TemplExpr { kind, tokens } ← raw Go token array
|
|
12
|
+
// TemplComponent { kind, tokens } ← raw Go token array for call
|
|
13
|
+
// TemplChildren { kind }
|
|
14
|
+
// TemplIf { kind, condTokens, then: TemplNode[], else_: TemplNode[] | null }
|
|
15
|
+
// TemplFor { kind, stmtTokens, body: TemplNode[] }
|
|
16
|
+
// TemplSwitch { kind, exprTokens, cases: TemplCase[] }
|
|
17
|
+
// TemplCase { kind, caseTokens: tokens | null (null = default), body: TemplNode[] }
|
|
18
|
+
|
|
19
|
+
import { Parser } from "./parser/index.js";
|
|
20
|
+
import { TT } from "./templ-lexer.js";
|
|
21
|
+
import { T } from "./tokens.js";
|
|
22
|
+
|
|
23
|
+
const VOID_ELEMENTS = new Set([
|
|
24
|
+
"area",
|
|
25
|
+
"base",
|
|
26
|
+
"br",
|
|
27
|
+
"col",
|
|
28
|
+
"embed",
|
|
29
|
+
"hr",
|
|
30
|
+
"img",
|
|
31
|
+
"input",
|
|
32
|
+
"link",
|
|
33
|
+
"meta",
|
|
34
|
+
"param",
|
|
35
|
+
"source",
|
|
36
|
+
"track",
|
|
37
|
+
"wbr",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export class TemplParser extends Parser {
|
|
41
|
+
// ── Top-level declaration override ───────────────────────────
|
|
42
|
+
|
|
43
|
+
parseTopDecl() {
|
|
44
|
+
if (this.check(TT.TEMPL_KW)) return this.parseTemplDecl();
|
|
45
|
+
return super.parseTopDecl();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── TemplDecl ─────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
parseTemplDecl() {
|
|
51
|
+
const _line = this.peek().line;
|
|
52
|
+
this.advance(); // consume TEMPL_KW
|
|
53
|
+
|
|
54
|
+
const name = this.expect(T.IDENT).value;
|
|
55
|
+
|
|
56
|
+
// Parse parameter list (standard Go params: LPAREN...RPAREN)
|
|
57
|
+
const params = this.parseParamList();
|
|
58
|
+
|
|
59
|
+
// Optional semicolon between ) and {
|
|
60
|
+
this.semi();
|
|
61
|
+
|
|
62
|
+
// Consume the opening { of the HTML body
|
|
63
|
+
this.expect(T.LBRACE);
|
|
64
|
+
|
|
65
|
+
// Parse the HTML body; parseHtmlChildren consumes the closing RBRACE
|
|
66
|
+
const body = this.parseHtmlChildren();
|
|
67
|
+
|
|
68
|
+
this.semi();
|
|
69
|
+
|
|
70
|
+
return { kind: "TemplDecl", name, params, body, _line };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── HTML node parsing ─────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
// Parse zero or more HTML nodes until RBRACE (end of body) or EOF.
|
|
76
|
+
// Consumes the RBRACE.
|
|
77
|
+
parseHtmlChildren() {
|
|
78
|
+
const nodes = [];
|
|
79
|
+
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
80
|
+
if (this.check(TT.TEMPL_ELSE)) break; // handled by caller (parseTemplIf)
|
|
81
|
+
const node = this.parseHtmlNode();
|
|
82
|
+
if (node) nodes.push(node);
|
|
83
|
+
}
|
|
84
|
+
if (this.check(T.RBRACE)) this.advance(); // consume }
|
|
85
|
+
return nodes;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Parse a single HTML node from the current token.
|
|
89
|
+
parseHtmlNode() {
|
|
90
|
+
const t = this.peek();
|
|
91
|
+
switch (t.type) {
|
|
92
|
+
case TT.HTML_OPEN:
|
|
93
|
+
return this.parseHtmlElement();
|
|
94
|
+
case TT.HTML_SELF: {
|
|
95
|
+
this.advance();
|
|
96
|
+
return {
|
|
97
|
+
kind: "TemplElement",
|
|
98
|
+
tag: t.value.tag,
|
|
99
|
+
attrs: t.value.attrs,
|
|
100
|
+
children: [],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
case TT.HTML_TEXT:
|
|
104
|
+
this.advance();
|
|
105
|
+
return { kind: "TemplText", value: t.value };
|
|
106
|
+
case TT.TEMPL_EXPR:
|
|
107
|
+
this.advance();
|
|
108
|
+
return { kind: "TemplExpr", tokens: t.value };
|
|
109
|
+
case TT.TEMPL_COMP:
|
|
110
|
+
this.advance();
|
|
111
|
+
return { kind: "TemplComponent", tokens: t.value };
|
|
112
|
+
case TT.TEMPL_CHILDREN:
|
|
113
|
+
this.advance();
|
|
114
|
+
return { kind: "TemplChildren" };
|
|
115
|
+
case TT.TEMPL_IF: {
|
|
116
|
+
this.advance();
|
|
117
|
+
return this.parseTemplIf(t);
|
|
118
|
+
}
|
|
119
|
+
case TT.TEMPL_FOR: {
|
|
120
|
+
this.advance();
|
|
121
|
+
return this.parseTemplFor(t);
|
|
122
|
+
}
|
|
123
|
+
case TT.TEMPL_SWITCH: {
|
|
124
|
+
this.advance();
|
|
125
|
+
return this.parseTemplSwitch(t);
|
|
126
|
+
}
|
|
127
|
+
default:
|
|
128
|
+
// Unknown token inside HTML body — skip it to avoid infinite loops
|
|
129
|
+
this.advance();
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Parse children of an element until the matching close tag.
|
|
135
|
+
parseHtmlElement() {
|
|
136
|
+
const openTok = this.advance(); // HTML_OPEN
|
|
137
|
+
const { tag, attrs } = openTok.value;
|
|
138
|
+
|
|
139
|
+
// Void elements have no children and no close tag even without />
|
|
140
|
+
if (VOID_ELEMENTS.has(tag)) {
|
|
141
|
+
return { kind: "TemplElement", tag, attrs, children: [] };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const children = [];
|
|
145
|
+
while (!this.check(T.EOF) && !this.check(T.RBRACE)) {
|
|
146
|
+
if (this.check(TT.HTML_CLOSE)) break;
|
|
147
|
+
// Also break on a close tag for a different ancestor (shouldn't happen in
|
|
148
|
+
// well-formed HTML, but prevents infinite loops on malformed input)
|
|
149
|
+
if (this.check(TT.TEMPL_ELSE)) break;
|
|
150
|
+
const node = this.parseHtmlNode();
|
|
151
|
+
if (node) children.push(node);
|
|
152
|
+
}
|
|
153
|
+
if (this.check(TT.HTML_CLOSE)) {
|
|
154
|
+
const closeTag = this.peek().value;
|
|
155
|
+
if (closeTag !== tag) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`${this.filename ?? "<templ>"}:${this.peek().line}: unclosed tag <${tag}>, got </${closeTag}>`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
this.advance(); // consume HTML_CLOSE
|
|
161
|
+
} else if (!VOID_ELEMENTS.has(tag)) {
|
|
162
|
+
// Non-void element with no close tag — parse error
|
|
163
|
+
throw new Error(
|
|
164
|
+
`${this.filename ?? "<templ>"}:${openTok.line}: unclosed tag <${tag}>`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return { kind: "TemplElement", tag, attrs, children };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Parse a TemplIf node. `tok` is the TEMPL_IF token (already advanced).
|
|
171
|
+
parseTemplIf(tok) {
|
|
172
|
+
const condTokens = tok.value; // array of Go tokens
|
|
173
|
+
const then = this.parseHtmlChildren(); // reads body until RBRACE
|
|
174
|
+
let else_ = null;
|
|
175
|
+
if (this.check(TT.TEMPL_ELSE)) {
|
|
176
|
+
this.advance(); // consume TEMPL_ELSE
|
|
177
|
+
if (this.check(TT.TEMPL_IF)) {
|
|
178
|
+
const t2 = this.advance();
|
|
179
|
+
else_ = [this.parseTemplIf(t2)]; // else if → wrap in array
|
|
180
|
+
} else {
|
|
181
|
+
else_ = this.parseHtmlChildren(); // else body until RBRACE
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { kind: "TemplIf", condTokens, then, else_ };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Parse a TemplFor node. `tok` is the TEMPL_FOR token (already advanced).
|
|
188
|
+
parseTemplFor(tok) {
|
|
189
|
+
const stmtTokens = tok.value;
|
|
190
|
+
const body = this.parseHtmlChildren();
|
|
191
|
+
return { kind: "TemplFor", stmtTokens, body };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Parse a TemplSwitch node. `tok` is the TEMPL_SWITCH token (already advanced).
|
|
195
|
+
parseTemplSwitch(tok) {
|
|
196
|
+
const exprTokens = tok.value;
|
|
197
|
+
const cases = [];
|
|
198
|
+
while (
|
|
199
|
+
!this.check(TT.TEMPL_END) &&
|
|
200
|
+
!this.check(T.RBRACE) &&
|
|
201
|
+
!this.check(T.EOF)
|
|
202
|
+
) {
|
|
203
|
+
if (this.check(TT.TEMPL_CASE)) {
|
|
204
|
+
const caseTok = this.advance();
|
|
205
|
+
const body = this.parseTemplCaseBody();
|
|
206
|
+
cases.push({ kind: "TemplCase", caseTokens: caseTok.value, body });
|
|
207
|
+
} else if (this.check(TT.TEMPL_DEFAULT)) {
|
|
208
|
+
this.advance();
|
|
209
|
+
const body = this.parseTemplCaseBody();
|
|
210
|
+
cases.push({ kind: "TemplCase", caseTokens: null, body });
|
|
211
|
+
} else {
|
|
212
|
+
this.advance(); // skip unexpected
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (this.check(TT.TEMPL_END)) this.advance(); // consume closing }
|
|
216
|
+
return { kind: "TemplSwitch", exprTokens, cases };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Parse HTML nodes for a switch case body — stops at CASE/DEFAULT/END/RBRACE.
|
|
220
|
+
parseTemplCaseBody() {
|
|
221
|
+
const nodes = [];
|
|
222
|
+
while (
|
|
223
|
+
!this.check(TT.TEMPL_CASE) &&
|
|
224
|
+
!this.check(TT.TEMPL_DEFAULT) &&
|
|
225
|
+
!this.check(TT.TEMPL_END) &&
|
|
226
|
+
!this.check(T.RBRACE) &&
|
|
227
|
+
!this.check(T.EOF)
|
|
228
|
+
) {
|
|
229
|
+
if (this.check(TT.TEMPL_ELSE)) break;
|
|
230
|
+
const node = this.parseHtmlNode();
|
|
231
|
+
if (node) nodes.push(node);
|
|
232
|
+
}
|
|
233
|
+
return nodes;
|
|
234
|
+
}
|
|
235
|
+
}
|
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
|
+
};
|