gofront 0.0.1 → 0.0.3
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 +357 -33
- package/package.json +7 -3
- package/src/codegen/expressions.js +804 -0
- package/src/codegen/source-map.js +45 -0
- package/src/codegen/statements.js +406 -0
- package/src/codegen.js +50 -851
- package/src/compiler.js +44 -23
- package/src/dts-parser.js +0 -4
- package/src/index.js +11 -20
- package/src/lexer.js +170 -12
- package/src/parser/expressions.js +389 -0
- package/src/parser/statements.js +355 -0
- package/src/parser/types.js +140 -0
- package/src/parser.js +57 -745
- package/src/resolver.js +1 -1
- package/src/typechecker/expressions.js +359 -0
- package/src/typechecker/statements.js +250 -0
- package/src/typechecker/types.js +226 -0
- package/src/typechecker.js +390 -647
package/src/typechecker.js
CHANGED
|
@@ -1,148 +1,40 @@
|
|
|
1
1
|
// Type checker: walks the AST, resolves types for all expressions,
|
|
2
2
|
// and reports type errors at compile time.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const INT = { kind: "basic", name: "int" };
|
|
40
|
-
const FLOAT64 = { kind: "basic", name: "float64" };
|
|
41
|
-
const STRING = { kind: "basic", name: "string" };
|
|
42
|
-
const BOOL = { kind: "basic", name: "bool" };
|
|
43
|
-
const ANY = { kind: "basic", name: "any" };
|
|
44
|
-
const VOID = { kind: "basic", name: "void" };
|
|
45
|
-
const NIL = { kind: "basic", name: "nil" };
|
|
46
|
-
const ERROR = { kind: "basic", name: "error" };
|
|
47
|
-
|
|
48
|
-
const BASIC_TYPES = {
|
|
49
|
-
int: INT,
|
|
50
|
-
float64: FLOAT64,
|
|
51
|
-
string: STRING,
|
|
52
|
-
bool: BOOL,
|
|
53
|
-
any: ANY,
|
|
54
|
-
byte: INT,
|
|
55
|
-
rune: INT,
|
|
56
|
-
error: ERROR,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
function isNumeric(t) {
|
|
60
|
-
return t.kind === "basic" && (t.name === "int" || t.name === "float64");
|
|
61
|
-
}
|
|
62
|
-
function isString(t) {
|
|
63
|
-
return t.kind === "basic" && t.name === "string";
|
|
64
|
-
}
|
|
65
|
-
function isBool(t) {
|
|
66
|
-
return t.kind === "basic" && t.name === "bool";
|
|
67
|
-
}
|
|
68
|
-
function isAny(t) {
|
|
69
|
-
return t.kind === "basic" && t.name === "any";
|
|
70
|
-
}
|
|
71
|
-
function isNil(t) {
|
|
72
|
-
return t.kind === "basic" && t.name === "nil";
|
|
73
|
-
}
|
|
74
|
-
function isVoid(t) {
|
|
75
|
-
return t.kind === "basic" && t.name === "void";
|
|
76
|
-
}
|
|
77
|
-
function isError(t) {
|
|
78
|
-
return t.kind === "basic" && t.name === "error";
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function typeStr(t) {
|
|
82
|
-
if (!t) return "void";
|
|
83
|
-
if (t.kind === "basic" && t.alias) return t.alias;
|
|
84
|
-
switch (t.kind) {
|
|
85
|
-
case "basic":
|
|
86
|
-
return t.name;
|
|
87
|
-
case "slice":
|
|
88
|
-
return `[]${typeStr(t.elem)}`;
|
|
89
|
-
case "array":
|
|
90
|
-
return `[${t.size}]${typeStr(t.elem)}`;
|
|
91
|
-
case "map":
|
|
92
|
-
return `map[${typeStr(t.key)}]${typeStr(t.value)}`;
|
|
93
|
-
case "struct":
|
|
94
|
-
return t.name || "struct{...}";
|
|
95
|
-
case "interface":
|
|
96
|
-
return t.name || "interface{...}";
|
|
97
|
-
case "namespace":
|
|
98
|
-
return t.name || "namespace{...}";
|
|
99
|
-
case "func":
|
|
100
|
-
return `func(${t.params.map(typeStr).join(", ")}) ${typeStr(t.returns[0] ?? VOID)}`;
|
|
101
|
-
case "tuple":
|
|
102
|
-
return `(${t.types.map(typeStr).join(", ")})`;
|
|
103
|
-
case "named":
|
|
104
|
-
return t.name;
|
|
105
|
-
case "pointer":
|
|
106
|
-
return `*${typeStr(t.base)}`;
|
|
107
|
-
default:
|
|
108
|
-
return "?";
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ── Scope / environment ──────────────────────────────────────
|
|
113
|
-
|
|
114
|
-
class Scope {
|
|
115
|
-
constructor(parent = null) {
|
|
116
|
-
this.parent = parent;
|
|
117
|
-
this.symbols = new Map();
|
|
118
|
-
this._consts = new Set(); // names declared as const in this scope
|
|
119
|
-
}
|
|
120
|
-
define(name, type) {
|
|
121
|
-
this.symbols.set(name, type);
|
|
122
|
-
}
|
|
123
|
-
defineConst(name, type) {
|
|
124
|
-
this.symbols.set(name, type);
|
|
125
|
-
this._consts.add(name);
|
|
126
|
-
}
|
|
127
|
-
isConst(name) {
|
|
128
|
-
if (this._consts.has(name)) return true;
|
|
129
|
-
// Don't walk parent — shadowing a const with a var in a child scope is valid
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
lookup(name) {
|
|
133
|
-
if (this.symbols.has(name)) return this.symbols.get(name);
|
|
134
|
-
if (this.parent) return this.parent.lookup(name);
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
// Lookup which scope owns the name (to check const flag)
|
|
138
|
-
lookupScope(name) {
|
|
139
|
-
if (this.symbols.has(name)) return this;
|
|
140
|
-
if (this.parent) return this.parent.lookupScope(name);
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ── Type checker ─────────────────────────────────────────────
|
|
4
|
+
// Split into sub-modules under typechecker/:
|
|
5
|
+
// types.js — shared type constants, predicates, Scope
|
|
6
|
+
// statements.js — checkBlock, checkStmt
|
|
7
|
+
// expressions.js — checkExpr, checkCall, checkBuiltin, checkCompositeLit
|
|
8
|
+
|
|
9
|
+
import { expressionCheckMethods } from "./typechecker/expressions.js";
|
|
10
|
+
import { statementCheckMethods } from "./typechecker/statements.js";
|
|
11
|
+
import {
|
|
12
|
+
ANY,
|
|
13
|
+
BASIC_TYPES,
|
|
14
|
+
BOOL,
|
|
15
|
+
CMP_OPS,
|
|
16
|
+
defaultType,
|
|
17
|
+
ERROR,
|
|
18
|
+
FLOAT64,
|
|
19
|
+
INT,
|
|
20
|
+
isAny,
|
|
21
|
+
isError,
|
|
22
|
+
isNil,
|
|
23
|
+
isNumeric,
|
|
24
|
+
isString,
|
|
25
|
+
isUntyped,
|
|
26
|
+
LOG_OPS,
|
|
27
|
+
Scope,
|
|
28
|
+
STRING,
|
|
29
|
+
TypeCheckError,
|
|
30
|
+
typeStr,
|
|
31
|
+
UNTYPED_FLOAT,
|
|
32
|
+
UNTYPED_INT,
|
|
33
|
+
VOID,
|
|
34
|
+
} from "./typechecker/types.js";
|
|
35
|
+
|
|
36
|
+
// Re-export for consumers that import from typechecker.js
|
|
37
|
+
export { TypeCheckError, typeStr };
|
|
146
38
|
|
|
147
39
|
export class TypeChecker {
|
|
148
40
|
constructor() {
|
|
@@ -153,6 +45,9 @@ export class TypeChecker {
|
|
|
153
45
|
this._currentSource = null;
|
|
154
46
|
this._loopDepth = 0; // for break/continue validation
|
|
155
47
|
this._switchDepth = 0; // for break/fallthrough validation
|
|
48
|
+
this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
|
|
49
|
+
this._deferCount = 0; // tracks defer usage in current function body
|
|
50
|
+
this._imports = []; // tracked imports for unused-import detection
|
|
156
51
|
this._setupGlobals();
|
|
157
52
|
}
|
|
158
53
|
|
|
@@ -188,7 +83,6 @@ export class TypeChecker {
|
|
|
188
83
|
"Boolean",
|
|
189
84
|
"Array",
|
|
190
85
|
"Object",
|
|
191
|
-
"JSON",
|
|
192
86
|
"parseInt",
|
|
193
87
|
"parseFloat",
|
|
194
88
|
"isNaN",
|
|
@@ -219,6 +113,8 @@ export class TypeChecker {
|
|
|
219
113
|
"FileReader",
|
|
220
114
|
"ArrayBuffer",
|
|
221
115
|
"Uint8Array",
|
|
116
|
+
"TextEncoder",
|
|
117
|
+
"TextDecoder",
|
|
222
118
|
"WebGLRenderingContext",
|
|
223
119
|
"WebGL2RenderingContext",
|
|
224
120
|
"GPUDevice",
|
|
@@ -246,6 +142,210 @@ export class TypeChecker {
|
|
|
246
142
|
},
|
|
247
143
|
});
|
|
248
144
|
|
|
145
|
+
// strings package
|
|
146
|
+
const strFn1 = (ret) => ({
|
|
147
|
+
kind: "func",
|
|
148
|
+
params: [STRING],
|
|
149
|
+
returns: [ret],
|
|
150
|
+
});
|
|
151
|
+
const strFn2 = (p2, ret) => ({
|
|
152
|
+
kind: "func",
|
|
153
|
+
params: [STRING, p2],
|
|
154
|
+
returns: [ret],
|
|
155
|
+
});
|
|
156
|
+
const strFn3 = (p2, p3, ret) => ({
|
|
157
|
+
kind: "func",
|
|
158
|
+
params: [STRING, p2, p3],
|
|
159
|
+
returns: [ret],
|
|
160
|
+
});
|
|
161
|
+
this.globals.define("strings", {
|
|
162
|
+
kind: "namespace",
|
|
163
|
+
name: "strings",
|
|
164
|
+
members: {
|
|
165
|
+
Contains: strFn2(STRING, BOOL),
|
|
166
|
+
HasPrefix: strFn2(STRING, BOOL),
|
|
167
|
+
HasSuffix: strFn2(STRING, BOOL),
|
|
168
|
+
Index: strFn2(STRING, INT),
|
|
169
|
+
LastIndex: strFn2(STRING, INT),
|
|
170
|
+
Count: strFn2(STRING, INT),
|
|
171
|
+
Repeat: strFn2(INT, STRING),
|
|
172
|
+
Replace: {
|
|
173
|
+
kind: "func",
|
|
174
|
+
params: [STRING, STRING, STRING, INT],
|
|
175
|
+
returns: [STRING],
|
|
176
|
+
},
|
|
177
|
+
ReplaceAll: strFn3(STRING, STRING, STRING),
|
|
178
|
+
ToUpper: strFn1(STRING),
|
|
179
|
+
ToLower: strFn1(STRING),
|
|
180
|
+
TrimSpace: strFn1(STRING),
|
|
181
|
+
Trim: strFn2(STRING, STRING),
|
|
182
|
+
TrimPrefix: strFn2(STRING, STRING),
|
|
183
|
+
TrimSuffix: strFn2(STRING, STRING),
|
|
184
|
+
TrimLeft: strFn2(STRING, STRING),
|
|
185
|
+
TrimRight: strFn2(STRING, STRING),
|
|
186
|
+
Split: strFn2(STRING, { kind: "slice", elem: STRING }),
|
|
187
|
+
Join: {
|
|
188
|
+
kind: "func",
|
|
189
|
+
params: [{ kind: "slice", elem: STRING }, STRING],
|
|
190
|
+
returns: [STRING],
|
|
191
|
+
},
|
|
192
|
+
EqualFold: strFn2(STRING, BOOL),
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// strconv package
|
|
197
|
+
this.globals.define("strconv", {
|
|
198
|
+
kind: "namespace",
|
|
199
|
+
name: "strconv",
|
|
200
|
+
members: {
|
|
201
|
+
Itoa: { kind: "func", params: [INT], returns: [STRING] },
|
|
202
|
+
Atoi: {
|
|
203
|
+
kind: "func",
|
|
204
|
+
params: [STRING],
|
|
205
|
+
returns: [INT, ERROR],
|
|
206
|
+
},
|
|
207
|
+
FormatFloat: {
|
|
208
|
+
kind: "func",
|
|
209
|
+
params: [FLOAT64, INT, INT, INT],
|
|
210
|
+
returns: [STRING],
|
|
211
|
+
},
|
|
212
|
+
FormatBool: { kind: "func", params: [BOOL], returns: [STRING] },
|
|
213
|
+
FormatInt: { kind: "func", params: [INT, INT], returns: [STRING] },
|
|
214
|
+
ParseFloat: {
|
|
215
|
+
kind: "func",
|
|
216
|
+
params: [STRING, INT],
|
|
217
|
+
returns: [FLOAT64, ERROR],
|
|
218
|
+
},
|
|
219
|
+
ParseInt: {
|
|
220
|
+
kind: "func",
|
|
221
|
+
params: [STRING, INT, INT],
|
|
222
|
+
returns: [INT, ERROR],
|
|
223
|
+
},
|
|
224
|
+
ParseBool: {
|
|
225
|
+
kind: "func",
|
|
226
|
+
params: [STRING],
|
|
227
|
+
returns: [BOOL, ERROR],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// sort package
|
|
233
|
+
this.globals.define("sort", {
|
|
234
|
+
kind: "namespace",
|
|
235
|
+
name: "sort",
|
|
236
|
+
members: {
|
|
237
|
+
Ints: {
|
|
238
|
+
kind: "func",
|
|
239
|
+
params: [{ kind: "slice", elem: INT }],
|
|
240
|
+
returns: [VOID],
|
|
241
|
+
},
|
|
242
|
+
Float64s: {
|
|
243
|
+
kind: "func",
|
|
244
|
+
params: [{ kind: "slice", elem: FLOAT64 }],
|
|
245
|
+
returns: [VOID],
|
|
246
|
+
},
|
|
247
|
+
Strings: {
|
|
248
|
+
kind: "func",
|
|
249
|
+
params: [{ kind: "slice", elem: STRING }],
|
|
250
|
+
returns: [VOID],
|
|
251
|
+
},
|
|
252
|
+
Slice: {
|
|
253
|
+
kind: "func",
|
|
254
|
+
params: [ANY, ANY],
|
|
255
|
+
returns: [VOID],
|
|
256
|
+
},
|
|
257
|
+
SliceStable: {
|
|
258
|
+
kind: "func",
|
|
259
|
+
params: [ANY, ANY],
|
|
260
|
+
returns: [VOID],
|
|
261
|
+
},
|
|
262
|
+
SliceIsSorted: {
|
|
263
|
+
kind: "func",
|
|
264
|
+
params: [ANY, ANY],
|
|
265
|
+
returns: [BOOL],
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// math package
|
|
271
|
+
this.globals.define("math", {
|
|
272
|
+
kind: "namespace",
|
|
273
|
+
name: "math",
|
|
274
|
+
members: {
|
|
275
|
+
Abs: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
276
|
+
Floor: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
277
|
+
Ceil: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
278
|
+
Round: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
279
|
+
Sqrt: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
280
|
+
Cbrt: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
281
|
+
Pow: {
|
|
282
|
+
kind: "func",
|
|
283
|
+
params: [FLOAT64, FLOAT64],
|
|
284
|
+
returns: [FLOAT64],
|
|
285
|
+
},
|
|
286
|
+
Log: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
287
|
+
Log2: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
288
|
+
Log10: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
289
|
+
Sin: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
290
|
+
Cos: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
291
|
+
Tan: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
292
|
+
Min: {
|
|
293
|
+
kind: "func",
|
|
294
|
+
params: [FLOAT64, FLOAT64],
|
|
295
|
+
returns: [FLOAT64],
|
|
296
|
+
},
|
|
297
|
+
Max: {
|
|
298
|
+
kind: "func",
|
|
299
|
+
params: [FLOAT64, FLOAT64],
|
|
300
|
+
returns: [FLOAT64],
|
|
301
|
+
},
|
|
302
|
+
Mod: {
|
|
303
|
+
kind: "func",
|
|
304
|
+
params: [FLOAT64, FLOAT64],
|
|
305
|
+
returns: [FLOAT64],
|
|
306
|
+
},
|
|
307
|
+
Inf: { kind: "func", params: [INT], returns: [FLOAT64] },
|
|
308
|
+
IsNaN: { kind: "func", params: [FLOAT64], returns: [BOOL] },
|
|
309
|
+
IsInf: {
|
|
310
|
+
kind: "func",
|
|
311
|
+
params: [FLOAT64, INT],
|
|
312
|
+
returns: [BOOL],
|
|
313
|
+
},
|
|
314
|
+
NaN: { kind: "func", params: [], returns: [FLOAT64] },
|
|
315
|
+
// Constants (typed as func with no params for namespace member access)
|
|
316
|
+
Pi: FLOAT64,
|
|
317
|
+
E: FLOAT64,
|
|
318
|
+
MaxFloat64: FLOAT64,
|
|
319
|
+
SmallestNonzeroFloat64: FLOAT64,
|
|
320
|
+
MaxInt: INT,
|
|
321
|
+
MinInt: INT,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// errors package
|
|
326
|
+
this.globals.define("errors", {
|
|
327
|
+
kind: "namespace",
|
|
328
|
+
name: "errors",
|
|
329
|
+
members: {
|
|
330
|
+
New: { kind: "func", params: [STRING], returns: [ERROR] },
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// time package (partial — JS-friendly subset)
|
|
335
|
+
this.globals.define("time", {
|
|
336
|
+
kind: "namespace",
|
|
337
|
+
name: "time",
|
|
338
|
+
members: {
|
|
339
|
+
Now: { kind: "func", params: [], returns: [ANY] },
|
|
340
|
+
Since: { kind: "func", params: [ANY], returns: [ANY] },
|
|
341
|
+
Sleep: { kind: "func", params: [ANY], returns: [VOID], async: true },
|
|
342
|
+
Millisecond: INT,
|
|
343
|
+
Second: INT,
|
|
344
|
+
Minute: INT,
|
|
345
|
+
Hour: INT,
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
249
349
|
this.globals.define("append", { kind: "builtin", name: "append" });
|
|
250
350
|
this.globals.define("len", { kind: "builtin", name: "len" });
|
|
251
351
|
this.globals.define("cap", { kind: "builtin", name: "cap" });
|
|
@@ -254,9 +354,13 @@ export class TypeChecker {
|
|
|
254
354
|
this.globals.define("print", { kind: "builtin", name: "print" });
|
|
255
355
|
this.globals.define("println", { kind: "builtin", name: "println" });
|
|
256
356
|
this.globals.define("panic", { kind: "builtin", name: "panic" });
|
|
357
|
+
this.globals.define("recover", { kind: "builtin", name: "recover" });
|
|
257
358
|
this.globals.define("new", { kind: "builtin", name: "new" });
|
|
258
359
|
this.globals.define("copy", { kind: "builtin", name: "copy" });
|
|
259
360
|
this.globals.define("error", { kind: "builtin", name: "error" });
|
|
361
|
+
this.globals.define("min", { kind: "builtin", name: "min" });
|
|
362
|
+
this.globals.define("max", { kind: "builtin", name: "max" });
|
|
363
|
+
this.globals.define("clear", { kind: "builtin", name: "clear" });
|
|
260
364
|
}
|
|
261
365
|
|
|
262
366
|
addDefinitions(types, values) {
|
|
@@ -278,6 +382,27 @@ export class TypeChecker {
|
|
|
278
382
|
for (const [name, type] of types) this.types.set(name, type);
|
|
279
383
|
}
|
|
280
384
|
|
|
385
|
+
// Register an import for unused-import detection.
|
|
386
|
+
// `name` is the package name or alias used in code.
|
|
387
|
+
// `node` carries _line for error reporting.
|
|
388
|
+
// `filename` / `source` identify the file containing the import.
|
|
389
|
+
trackImport(name, node, filename, source) {
|
|
390
|
+
this._imports.push({ name, node, filename, source });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Report any tracked imports whose package name was never referenced.
|
|
394
|
+
reportUnusedImports() {
|
|
395
|
+
for (const { name, node, filename, source } of this._imports) {
|
|
396
|
+
if (!this.globals._used.has(name)) {
|
|
397
|
+
const saved = [this._currentFile, this._currentSource];
|
|
398
|
+
this._currentFile = filename;
|
|
399
|
+
this._currentSource = source;
|
|
400
|
+
this.err(`'${name}' imported and not used`, node);
|
|
401
|
+
[this._currentFile, this._currentSource] = saved;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
281
406
|
// Return a snapshot of all user-defined globals (excludes built-ins).
|
|
282
407
|
// Used by the compiler to expose a package's exports to importers.
|
|
283
408
|
getExportedSymbols() {
|
|
@@ -305,6 +430,12 @@ export class TypeChecker {
|
|
|
305
430
|
return ANY; // recovery type
|
|
306
431
|
}
|
|
307
432
|
|
|
433
|
+
_reportUnused(scope, node) {
|
|
434
|
+
for (const name of scope.unusedLocals()) {
|
|
435
|
+
this.err(`'${name}' declared and not used`, node);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
308
439
|
check(program) {
|
|
309
440
|
this._currentFile = program._filename ?? null;
|
|
310
441
|
this._currentSource = program._source ?? null;
|
|
@@ -412,6 +543,12 @@ export class TypeChecker {
|
|
|
412
543
|
|
|
413
544
|
collectType(decl) {
|
|
414
545
|
const underlying = this.resolveTypeNode(decl.type, this.globals);
|
|
546
|
+
if (decl.isAlias) {
|
|
547
|
+
// type A = B — transparent alias, A and B are identical types
|
|
548
|
+
this.types.set(decl.name, underlying);
|
|
549
|
+
this.globals.define(decl.name, underlying);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
415
552
|
const named = { kind: "named", name: decl.name, underlying };
|
|
416
553
|
if (underlying.kind === "struct") {
|
|
417
554
|
underlying.name = decl.name;
|
|
@@ -490,7 +627,12 @@ export class TypeChecker {
|
|
|
490
627
|
outer,
|
|
491
628
|
);
|
|
492
629
|
if (hasNamedReturns && returnType) returnType._hasNamedReturns = true;
|
|
630
|
+
const savedDefer = this._deferCount;
|
|
631
|
+
this._deferCount = 0;
|
|
493
632
|
this.checkBlock(decl.body, inner, returnType);
|
|
633
|
+
this._reportUnused(inner, decl);
|
|
634
|
+
if (this._deferCount > 0) decl.body._hasDefer = true;
|
|
635
|
+
this._deferCount = savedDefer;
|
|
494
636
|
decl._returnType = returnType;
|
|
495
637
|
}
|
|
496
638
|
|
|
@@ -510,7 +652,12 @@ export class TypeChecker {
|
|
|
510
652
|
outer,
|
|
511
653
|
);
|
|
512
654
|
if (hasNamedReturns && returnType) returnType._hasNamedReturns = true;
|
|
655
|
+
const savedDefer = this._deferCount;
|
|
656
|
+
this._deferCount = 0;
|
|
513
657
|
this.checkBlock(decl.body, inner, returnType);
|
|
658
|
+
this._reportUnused(inner, decl);
|
|
659
|
+
if (this._deferCount > 0) decl.body._hasDefer = true;
|
|
660
|
+
this._deferCount = savedDefer;
|
|
514
661
|
decl._returnType = returnType;
|
|
515
662
|
}
|
|
516
663
|
|
|
@@ -527,20 +674,21 @@ export class TypeChecker {
|
|
|
527
674
|
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
528
675
|
if (spec.value) {
|
|
529
676
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
530
|
-
if (!type) type = valTypes[0] ?? ANY;
|
|
677
|
+
if (!type) type = defaultType(valTypes[0]) ?? ANY;
|
|
531
678
|
for (let i = 0; i < spec.names.length; i++) {
|
|
532
679
|
const vt = valTypes[i] ?? ANY;
|
|
533
680
|
this.assertAssignable(type, vt, spec.value[i]);
|
|
534
681
|
}
|
|
535
682
|
}
|
|
536
683
|
if (!type) type = ANY;
|
|
537
|
-
for (const name of spec.names) scope.
|
|
684
|
+
for (const name of spec.names) scope.defineLocal(name, type);
|
|
538
685
|
}
|
|
539
686
|
}
|
|
540
687
|
|
|
541
688
|
checkConstDecl(decl, scope) {
|
|
542
689
|
for (const spec of decl.decls) {
|
|
543
690
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
691
|
+
// Explicit type annotation → typed constant; otherwise preserve untyped
|
|
544
692
|
const type = spec.type
|
|
545
693
|
? this.resolveTypeNode(spec.type, scope)
|
|
546
694
|
: (valTypes[0] ?? ANY);
|
|
@@ -550,495 +698,6 @@ export class TypeChecker {
|
|
|
550
698
|
|
|
551
699
|
// ── Statement checking ───────────────────────────────────────
|
|
552
700
|
|
|
553
|
-
checkBlock(block, scope, returnType) {
|
|
554
|
-
for (const stmt of block.stmts) {
|
|
555
|
-
this.checkStmt(stmt, scope, returnType);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
checkStmt(stmt, scope, returnType) {
|
|
560
|
-
switch (stmt.kind) {
|
|
561
|
-
case "VarDecl":
|
|
562
|
-
this.checkVarDecl(stmt, scope);
|
|
563
|
-
break;
|
|
564
|
-
case "ConstDecl":
|
|
565
|
-
this.checkConstDecl(stmt, scope);
|
|
566
|
-
break;
|
|
567
|
-
case "TypeDecl":
|
|
568
|
-
this.collectType(stmt);
|
|
569
|
-
break;
|
|
570
|
-
|
|
571
|
-
case "DefineStmt": {
|
|
572
|
-
// comma-ok type assertion: n, ok := x.(T)
|
|
573
|
-
if (
|
|
574
|
-
stmt.lhs.length === 2 &&
|
|
575
|
-
stmt.rhs.length === 1 &&
|
|
576
|
-
stmt.rhs[0].kind === "TypeAssertExpr"
|
|
577
|
-
) {
|
|
578
|
-
stmt.rhs[0]._commaOk = true;
|
|
579
|
-
}
|
|
580
|
-
const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
|
|
581
|
-
// Flatten tuple returns
|
|
582
|
-
const rhsFlat =
|
|
583
|
-
rhs.length === 1 && rhs[0].kind === "tuple" ? rhs[0].types : rhs;
|
|
584
|
-
// comma-ok type assertion returns [assertedType, bool]
|
|
585
|
-
if (stmt.rhs[0]?._commaOk) {
|
|
586
|
-
const lhsNames = stmt.lhs.map((e) => e.name ?? e);
|
|
587
|
-
if (lhsNames[0] !== "_") scope.define(lhsNames[0], rhs[0]);
|
|
588
|
-
if (lhsNames[1] !== "_") scope.define(lhsNames[1], BOOL);
|
|
589
|
-
stmt._rhsTypes = [rhs[0], BOOL];
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
for (let i = 0; i < stmt.lhs.length; i++) {
|
|
593
|
-
const name = stmt.lhs[i].name ?? stmt.lhs[i];
|
|
594
|
-
if (name === "_") continue;
|
|
595
|
-
// Short variable re-declaration: mark vars that already exist
|
|
596
|
-
// in the current (not parent) scope as redeclared.
|
|
597
|
-
if (scope.symbols.has(name)) {
|
|
598
|
-
stmt.lhs[i]._redecl = true;
|
|
599
|
-
}
|
|
600
|
-
scope.define(name, rhsFlat[i] ?? ANY);
|
|
601
|
-
}
|
|
602
|
-
// Go requires at least one new variable in :=
|
|
603
|
-
const allRedecl = stmt.lhs.every(
|
|
604
|
-
(e) => e._redecl || (e.name ?? e) === "_",
|
|
605
|
-
);
|
|
606
|
-
if (allRedecl && stmt.lhs.length > 0) {
|
|
607
|
-
this.err("no new variables on left side of :=", stmt.lhs[0]);
|
|
608
|
-
}
|
|
609
|
-
stmt._rhsTypes = rhsFlat;
|
|
610
|
-
break;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
case "AssignStmt": {
|
|
614
|
-
// Check for const reassignment before evaluating RHS
|
|
615
|
-
for (const lhs of stmt.lhs) {
|
|
616
|
-
if (lhs.kind === "Ident") {
|
|
617
|
-
const ownerScope = scope.lookupScope(lhs.name);
|
|
618
|
-
if (ownerScope?.isConst(lhs.name))
|
|
619
|
-
this.err(`cannot assign to const '${lhs.name}'`, lhs);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
|
|
623
|
-
const rhsFlat =
|
|
624
|
-
rhs.length === 1 && rhs[0]?.kind === "tuple" ? rhs[0].types : rhs;
|
|
625
|
-
const lhsTypes = stmt.lhs.map((e) => this.checkExpr(e, scope));
|
|
626
|
-
for (let i = 0; i < lhsTypes.length; i++) {
|
|
627
|
-
const r = rhsFlat[i] ?? ANY;
|
|
628
|
-
if (!isAny(lhsTypes[i]) && !isAny(r)) {
|
|
629
|
-
this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
break;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
case "IncDecStmt":
|
|
636
|
-
this.checkExpr(stmt.expr, scope);
|
|
637
|
-
break;
|
|
638
|
-
|
|
639
|
-
case "ExprStmt":
|
|
640
|
-
this.checkExpr(stmt.expr, scope);
|
|
641
|
-
break;
|
|
642
|
-
|
|
643
|
-
case "ReturnStmt": {
|
|
644
|
-
const types = stmt.values.map((v) => this.checkExpr(v, scope));
|
|
645
|
-
stmt._types = types;
|
|
646
|
-
if (
|
|
647
|
-
!isVoid(returnType) &&
|
|
648
|
-
types.length === 0 &&
|
|
649
|
-
!returnType?._hasNamedReturns
|
|
650
|
-
) {
|
|
651
|
-
this.err("Missing return value", stmt);
|
|
652
|
-
}
|
|
653
|
-
// Check each returned value is assignable to its declared type
|
|
654
|
-
if (!isVoid(returnType) && types.length > 0) {
|
|
655
|
-
const expected =
|
|
656
|
-
returnType.kind === "tuple" ? returnType.types : [returnType];
|
|
657
|
-
for (let i = 0; i < expected.length; i++) {
|
|
658
|
-
if (types[i])
|
|
659
|
-
this.assertAssignable(
|
|
660
|
-
expected[i],
|
|
661
|
-
types[i],
|
|
662
|
-
stmt.values[i] ?? stmt,
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
break;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
case "IfStmt": {
|
|
670
|
-
const inner = new Scope(scope);
|
|
671
|
-
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
672
|
-
const ct = this.checkExpr(stmt.cond, inner);
|
|
673
|
-
if (!isBool(ct) && !isAny(ct))
|
|
674
|
-
this.err("If condition must be bool", stmt);
|
|
675
|
-
this.checkBlock(stmt.body, new Scope(inner), returnType);
|
|
676
|
-
if (stmt.elseBody) {
|
|
677
|
-
if (stmt.elseBody.kind === "Block")
|
|
678
|
-
this.checkBlock(stmt.elseBody, new Scope(inner), returnType);
|
|
679
|
-
else this.checkStmt(stmt.elseBody, inner, returnType);
|
|
680
|
-
}
|
|
681
|
-
break;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
case "ForStmt": {
|
|
685
|
-
const inner = new Scope(scope);
|
|
686
|
-
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
687
|
-
if (stmt.cond) {
|
|
688
|
-
const ct = this.checkExpr(stmt.cond, inner);
|
|
689
|
-
if (!isBool(ct) && !isAny(ct))
|
|
690
|
-
this.err("For condition must be bool", stmt);
|
|
691
|
-
}
|
|
692
|
-
if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
|
|
693
|
-
this._loopDepth++;
|
|
694
|
-
this.checkBlock(stmt.body, new Scope(inner), returnType);
|
|
695
|
-
this._loopDepth--;
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
case "SwitchStmt": {
|
|
700
|
-
const inner = new Scope(scope);
|
|
701
|
-
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
702
|
-
const _tagType = stmt.tag ? this.checkExpr(stmt.tag, inner) : BOOL;
|
|
703
|
-
this._switchDepth++;
|
|
704
|
-
for (const c of stmt.cases) {
|
|
705
|
-
const caseScope = new Scope(inner);
|
|
706
|
-
if (c.list) for (const e of c.list) this.checkExpr(e, caseScope);
|
|
707
|
-
for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
|
|
708
|
-
}
|
|
709
|
-
this._switchDepth--;
|
|
710
|
-
break;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
case "DeferStmt": {
|
|
714
|
-
if (stmt.call.kind !== "CallExpr")
|
|
715
|
-
this.err("defer requires a function call", stmt.call);
|
|
716
|
-
this.checkExpr(stmt.call, scope);
|
|
717
|
-
break;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
case "BranchStmt": {
|
|
721
|
-
const kw = stmt.keyword;
|
|
722
|
-
if (kw === "continue" && this._loopDepth === 0)
|
|
723
|
-
this.err("continue statement outside for loop", stmt);
|
|
724
|
-
else if (
|
|
725
|
-
kw === "break" &&
|
|
726
|
-
this._loopDepth === 0 &&
|
|
727
|
-
this._switchDepth === 0
|
|
728
|
-
)
|
|
729
|
-
this.err("break statement outside for loop or switch", stmt);
|
|
730
|
-
else if (kw === "fallthrough" && this._switchDepth === 0)
|
|
731
|
-
this.err("fallthrough statement outside switch", stmt);
|
|
732
|
-
break;
|
|
733
|
-
}
|
|
734
|
-
case "Block":
|
|
735
|
-
this.checkBlock(stmt, new Scope(scope), returnType);
|
|
736
|
-
break;
|
|
737
|
-
default:
|
|
738
|
-
break;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// ── Expression checking (returns type) ───────────────────────
|
|
743
|
-
|
|
744
|
-
checkExpr(expr, scope) {
|
|
745
|
-
const t = this._checkExpr(expr, scope);
|
|
746
|
-
expr._type = t;
|
|
747
|
-
return t;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
_checkExpr(expr, scope) {
|
|
751
|
-
switch (expr.kind) {
|
|
752
|
-
case "BasicLit": {
|
|
753
|
-
switch (expr.litKind) {
|
|
754
|
-
case "INT":
|
|
755
|
-
return INT;
|
|
756
|
-
case "FLOAT":
|
|
757
|
-
return FLOAT64;
|
|
758
|
-
case "STRING":
|
|
759
|
-
return STRING;
|
|
760
|
-
case "BOOL":
|
|
761
|
-
return BOOL;
|
|
762
|
-
case "NIL":
|
|
763
|
-
return NIL;
|
|
764
|
-
}
|
|
765
|
-
break;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
case "Ident": {
|
|
769
|
-
const t = scope.lookup(expr.name);
|
|
770
|
-
if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
|
|
771
|
-
return t;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
case "UnaryExpr": {
|
|
775
|
-
const ot = this.checkExpr(expr.operand, scope);
|
|
776
|
-
if (expr.op === "!") {
|
|
777
|
-
if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
|
|
778
|
-
return BOOL;
|
|
779
|
-
}
|
|
780
|
-
if (expr.op === "-" || expr.op === "+") {
|
|
781
|
-
if (!isNumeric(ot) && !isAny(ot))
|
|
782
|
-
this.err(`${expr.op} requires numeric`, expr);
|
|
783
|
-
return ot;
|
|
784
|
-
}
|
|
785
|
-
return ot;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
case "BinaryExpr": {
|
|
789
|
-
const lt = this.checkExpr(expr.left, scope);
|
|
790
|
-
const rt = this.checkExpr(expr.right, scope);
|
|
791
|
-
if (isAny(lt) || isAny(rt)) return this.binaryResultType(expr.op, ANY);
|
|
792
|
-
return this.binaryResultType(expr.op, lt, rt, expr);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
case "CallExpr": {
|
|
796
|
-
return this.checkCall(expr, scope);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
case "SelectorExpr": {
|
|
800
|
-
const baseType = this.checkExpr(expr.expr, scope);
|
|
801
|
-
return this.fieldType(baseType, expr.field, expr);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
case "IndexExpr": {
|
|
805
|
-
const bt = this.checkExpr(expr.expr, scope);
|
|
806
|
-
this.checkExpr(expr.index, scope);
|
|
807
|
-
if (isAny(bt)) return ANY;
|
|
808
|
-
if (bt.kind === "slice" || bt.kind === "array") return bt.elem;
|
|
809
|
-
if (bt.kind === "map") {
|
|
810
|
-
expr._mapValueType = bt.value; // for codegen zero-value fallback
|
|
811
|
-
return bt.value;
|
|
812
|
-
}
|
|
813
|
-
if (isString(bt)) return INT; // byte
|
|
814
|
-
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
case "SliceExpr": {
|
|
818
|
-
const bt = this.checkExpr(expr.expr, scope);
|
|
819
|
-
if (expr.low) this.checkExpr(expr.low, scope);
|
|
820
|
-
if (expr.high) this.checkExpr(expr.high, scope);
|
|
821
|
-
if (isAny(bt)) return ANY;
|
|
822
|
-
if (bt.kind === "slice" || bt.kind === "array")
|
|
823
|
-
return { kind: "slice", elem: bt.elem };
|
|
824
|
-
if (isString(bt)) return STRING;
|
|
825
|
-
return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
case "CompositeLit": {
|
|
829
|
-
return this.checkCompositeLit(expr, scope);
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
case "FuncLit": {
|
|
833
|
-
const inner = new Scope(scope);
|
|
834
|
-
for (const p of expr.params)
|
|
835
|
-
inner.define(
|
|
836
|
-
p.name,
|
|
837
|
-
p.type ? this.resolveTypeNode(p.type, scope) : ANY,
|
|
838
|
-
);
|
|
839
|
-
const ret = expr.returnType
|
|
840
|
-
? this.resolveTypeNode(expr.returnType, scope)
|
|
841
|
-
: VOID;
|
|
842
|
-
this.checkBlock(expr.body, inner, ret);
|
|
843
|
-
const paramTypes = expr.params.map((p) =>
|
|
844
|
-
p.type ? this.resolveTypeNode(p.type, scope) : ANY,
|
|
845
|
-
);
|
|
846
|
-
return {
|
|
847
|
-
kind: "func",
|
|
848
|
-
params: paramTypes,
|
|
849
|
-
returns: [ret],
|
|
850
|
-
async: expr.async,
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
case "AwaitExpr": {
|
|
855
|
-
// await unwraps whatever the expression produces — no Promise type modelling
|
|
856
|
-
return this.checkExpr(expr.expr, scope);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
case "TypeConversion": {
|
|
860
|
-
this.checkExpr(expr.expr, scope);
|
|
861
|
-
return this.resolveTypeNode(expr.targetType, scope);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
case "TypeAssertExpr": {
|
|
865
|
-
this.checkExpr(expr.expr, scope);
|
|
866
|
-
return this.resolveTypeNode(expr.type, scope);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
case "RangeExpr": {
|
|
870
|
-
// Type-check the iterated expression so its _type is annotated
|
|
871
|
-
return this.checkExpr(expr.expr, scope);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
default:
|
|
875
|
-
return ANY;
|
|
876
|
-
}
|
|
877
|
-
return ANY;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
checkCall(expr, scope) {
|
|
881
|
-
const fnType = this.checkExpr(expr.func, scope);
|
|
882
|
-
const argTypes = expr.args.map((a) => this.checkExpr(a, scope));
|
|
883
|
-
|
|
884
|
-
// Built-in handling
|
|
885
|
-
if (fnType.kind === "builtin") {
|
|
886
|
-
return this.checkBuiltin(fnType.name, expr, argTypes, scope);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
if (isAny(fnType)) return ANY;
|
|
890
|
-
|
|
891
|
-
if (fnType.kind !== "func") {
|
|
892
|
-
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
// Check arg count (allow variadic slack)
|
|
896
|
-
const minArgs = fnType.params.filter(
|
|
897
|
-
(_, i) => !fnType.variadic || i < fnType.params.length - 1,
|
|
898
|
-
).length;
|
|
899
|
-
if (argTypes.length < minArgs) {
|
|
900
|
-
this.err(
|
|
901
|
-
`Too few arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
|
|
902
|
-
expr,
|
|
903
|
-
);
|
|
904
|
-
}
|
|
905
|
-
if (!fnType.variadic && argTypes.length > fnType.params.length) {
|
|
906
|
-
this.err(
|
|
907
|
-
`Too many arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
|
|
908
|
-
expr,
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
// Check each argument is assignable to its parameter type
|
|
913
|
-
for (let i = 0; i < fnType.params.length && i < argTypes.length; i++) {
|
|
914
|
-
const paramIdx =
|
|
915
|
-
fnType.variadic && i >= fnType.params.length - 1
|
|
916
|
-
? fnType.params.length - 1
|
|
917
|
-
: i;
|
|
918
|
-
this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
const ret = fnType.returns;
|
|
922
|
-
if (!ret || ret.length === 0) return VOID;
|
|
923
|
-
if (ret.length === 1) return ret[0];
|
|
924
|
-
return { kind: "tuple", types: ret };
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
checkBuiltin(name, expr, argTypes, scope) {
|
|
928
|
-
switch (name) {
|
|
929
|
-
case "len":
|
|
930
|
-
case "cap":
|
|
931
|
-
return INT;
|
|
932
|
-
case "append":
|
|
933
|
-
return argTypes[0] ?? ANY;
|
|
934
|
-
case "copy":
|
|
935
|
-
return INT;
|
|
936
|
-
case "delete":
|
|
937
|
-
return VOID;
|
|
938
|
-
case "make": {
|
|
939
|
-
if (expr.args.length < 1) return ANY;
|
|
940
|
-
const typeArg = expr.args[0];
|
|
941
|
-
const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
|
|
942
|
-
return this.resolveTypeNode(typeNode, scope) ?? ANY;
|
|
943
|
-
}
|
|
944
|
-
case "new": {
|
|
945
|
-
if (expr.args.length < 1) return ANY;
|
|
946
|
-
const inner = this.resolveTypeNode(expr.args[0], scope);
|
|
947
|
-
return { kind: "pointer", base: inner };
|
|
948
|
-
}
|
|
949
|
-
case "print":
|
|
950
|
-
case "println":
|
|
951
|
-
case "panic":
|
|
952
|
-
return VOID;
|
|
953
|
-
case "error":
|
|
954
|
-
return ERROR;
|
|
955
|
-
default:
|
|
956
|
-
return ANY;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
checkCompositeLit(expr, scope, hintType = null) {
|
|
961
|
-
// null typeExpr = implicit lit inside a slice/map: {X:1} in []Point{{X:1}}
|
|
962
|
-
const t =
|
|
963
|
-
expr.typeExpr === null
|
|
964
|
-
? (hintType ?? ANY)
|
|
965
|
-
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
966
|
-
const base = t.kind === "named" ? t.underlying : t;
|
|
967
|
-
|
|
968
|
-
if (base?.kind === "struct") {
|
|
969
|
-
for (const elem of expr.elems) {
|
|
970
|
-
if (elem.kind === "KeyValueExpr") {
|
|
971
|
-
const keyName = elem.key.name;
|
|
972
|
-
const fieldType = base.fields?.get(keyName);
|
|
973
|
-
if (fieldType) {
|
|
974
|
-
const vt = this.checkExpr(elem.value, scope);
|
|
975
|
-
this.assertAssignable(fieldType, vt, elem.value);
|
|
976
|
-
} else {
|
|
977
|
-
// Check if it's an embedded type name (e.g. Dog{Animal: Animal{...}})
|
|
978
|
-
const embed = base._embeds?.find(
|
|
979
|
-
(e) => (e.kind === "named" ? e.name : null) === keyName,
|
|
980
|
-
);
|
|
981
|
-
if (embed) {
|
|
982
|
-
const vt = this.checkExpr(elem.value, scope);
|
|
983
|
-
this.assertAssignable(embed, vt, elem.value);
|
|
984
|
-
elem._isEmbedInit = true;
|
|
985
|
-
} else {
|
|
986
|
-
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
} else {
|
|
990
|
-
this.checkExpr(elem, scope);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
return t;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
if (base?.kind === "slice") {
|
|
997
|
-
for (const elem of expr.elems) {
|
|
998
|
-
if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
|
|
999
|
-
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
1000
|
-
elem._type = et;
|
|
1001
|
-
} else if (elem.kind !== "KeyValueExpr") {
|
|
1002
|
-
const et = this.checkExpr(elem, scope);
|
|
1003
|
-
this.assertAssignable(base.elem, et, elem);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
return t;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
if (base?.kind === "map") {
|
|
1010
|
-
for (const elem of expr.elems) {
|
|
1011
|
-
if (elem.kind === "KeyValueExpr") {
|
|
1012
|
-
const kt = this.checkExpr(elem.key, scope);
|
|
1013
|
-
// value may be an implicit composite lit
|
|
1014
|
-
let vt;
|
|
1015
|
-
if (
|
|
1016
|
-
elem.value.kind === "CompositeLit" &&
|
|
1017
|
-
elem.value.typeExpr === null
|
|
1018
|
-
) {
|
|
1019
|
-
vt = this.checkCompositeLit(elem.value, scope, base.value);
|
|
1020
|
-
elem.value._type = vt;
|
|
1021
|
-
} else {
|
|
1022
|
-
vt = this.checkExpr(elem.value, scope);
|
|
1023
|
-
}
|
|
1024
|
-
this.assertAssignable(base.key, kt, elem.key);
|
|
1025
|
-
this.assertAssignable(base.value, vt, elem.value);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
return t;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
// Unknown type context — still check elements
|
|
1032
|
-
for (const elem of expr.elems) {
|
|
1033
|
-
if (elem.kind === "KeyValueExpr") {
|
|
1034
|
-
this.checkExpr(elem.value, scope);
|
|
1035
|
-
} else {
|
|
1036
|
-
this.checkExpr(elem, scope);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
return t ?? ANY;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
701
|
// ── Type resolution ──────────────────────────────────────────
|
|
1043
702
|
|
|
1044
703
|
resolveTypeNode(node, scope) {
|
|
@@ -1058,7 +717,12 @@ export class TypeChecker {
|
|
|
1058
717
|
case "ArrayType":
|
|
1059
718
|
return {
|
|
1060
719
|
kind: "array",
|
|
1061
|
-
|
|
720
|
+
// inferLen: true means [...]T — size is determined by composite literal
|
|
721
|
+
size: node.inferLen
|
|
722
|
+
? null
|
|
723
|
+
: node.size?.value !== undefined
|
|
724
|
+
? Number(node.size.value)
|
|
725
|
+
: node.size,
|
|
1062
726
|
elem: this.resolveTypeNode(node.elem, scope),
|
|
1063
727
|
};
|
|
1064
728
|
case "MapType":
|
|
@@ -1114,7 +778,29 @@ export class TypeChecker {
|
|
|
1114
778
|
const returns = m.returnType
|
|
1115
779
|
? [this.resolveTypeNode(m.returnType, scope)]
|
|
1116
780
|
: [VOID];
|
|
1117
|
-
|
|
781
|
+
const isVariadic =
|
|
782
|
+
m.params.length > 0 && m.params[m.params.length - 1].variadic;
|
|
783
|
+
const mType = { kind: "func", params, returns };
|
|
784
|
+
if (isVariadic) mType.variadic = true;
|
|
785
|
+
methods.set(m.name, mType);
|
|
786
|
+
}
|
|
787
|
+
// Flatten embedded interface methods
|
|
788
|
+
if (node.embeds) {
|
|
789
|
+
for (const embed of node.embeds) {
|
|
790
|
+
const resolved = this.resolveTypeNode(embed, scope);
|
|
791
|
+
const base =
|
|
792
|
+
resolved?.kind === "named" ? resolved.underlying : resolved;
|
|
793
|
+
if (base?.kind === "interface") {
|
|
794
|
+
for (const [mName, mType] of base.methods) {
|
|
795
|
+
if (!methods.has(mName)) methods.set(mName, mType);
|
|
796
|
+
}
|
|
797
|
+
} else if (!isAny(resolved)) {
|
|
798
|
+
this.err(
|
|
799
|
+
`cannot embed non-interface type ${typeStr(resolved)}`,
|
|
800
|
+
embed,
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
1118
804
|
}
|
|
1119
805
|
return { kind: "interface", methods };
|
|
1120
806
|
}
|
|
@@ -1189,16 +875,30 @@ export class TypeChecker {
|
|
|
1189
875
|
}
|
|
1190
876
|
|
|
1191
877
|
binaryResultType(op, lt, rt, node) {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
if (cmp.includes(op)) return BOOL;
|
|
1195
|
-
if (log.includes(op)) return BOOL;
|
|
878
|
+
if (CMP_OPS.has(op)) return BOOL;
|
|
879
|
+
if (LOG_OPS.has(op)) return BOOL;
|
|
1196
880
|
if (isAny(lt) || isAny(rt)) return ANY;
|
|
1197
881
|
if (isNumeric(lt) && isNumeric(rt)) {
|
|
1198
|
-
|
|
1199
|
-
|
|
882
|
+
const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
|
|
883
|
+
const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
|
|
884
|
+
const isFloat = lFloat || rFloat;
|
|
885
|
+
// Both untyped → result is untyped
|
|
886
|
+
if (lt.kind === "untyped" && rt.kind === "untyped")
|
|
887
|
+
return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
|
|
888
|
+
// One typed, one untyped → typed wins
|
|
889
|
+
if (lt.kind === "untyped")
|
|
890
|
+
return isFloat && rt.name !== "float64" ? FLOAT64 : rt;
|
|
891
|
+
if (rt.kind === "untyped")
|
|
892
|
+
return isFloat && lt.name !== "float64" ? FLOAT64 : lt;
|
|
893
|
+
// Both typed
|
|
894
|
+
return isFloat ? FLOAT64 : INT;
|
|
895
|
+
}
|
|
896
|
+
if (isString(lt) && isString(rt) && op === "+") {
|
|
897
|
+
if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
|
|
898
|
+
if (lt.kind === "untyped") return rt;
|
|
899
|
+
if (rt.kind === "untyped") return lt;
|
|
900
|
+
return STRING;
|
|
1200
901
|
}
|
|
1201
|
-
if (isString(lt) && isString(rt) && op === "+") return STRING;
|
|
1202
902
|
if (node)
|
|
1203
903
|
this.err(`Invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
|
|
1204
904
|
return ANY;
|
|
@@ -1211,6 +911,29 @@ export class TypeChecker {
|
|
|
1211
911
|
if (isAny(target) || isAny(source)) return;
|
|
1212
912
|
if (isNil(source)) return; // nil is assignable to anything nullable
|
|
1213
913
|
|
|
914
|
+
// Untyped constants coerce to any compatible typed target
|
|
915
|
+
if (isUntyped(source)) {
|
|
916
|
+
if (isUntyped(target)) return; // untyped → untyped always OK
|
|
917
|
+
if (target.kind === "basic") {
|
|
918
|
+
// untyped int → int or float64
|
|
919
|
+
if (
|
|
920
|
+
source.base === "int" &&
|
|
921
|
+
(target.name === "int" || target.name === "float64")
|
|
922
|
+
)
|
|
923
|
+
return;
|
|
924
|
+
// untyped float → float64 (and int for compatibility)
|
|
925
|
+
if (
|
|
926
|
+
source.base === "float64" &&
|
|
927
|
+
(target.name === "float64" || target.name === "int")
|
|
928
|
+
)
|
|
929
|
+
return;
|
|
930
|
+
// untyped string → string
|
|
931
|
+
if (source.base === "string" && target.name === "string") return;
|
|
932
|
+
// untyped bool → bool
|
|
933
|
+
if (source.base === "bool" && target.name === "bool") return;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
1214
937
|
// Go untyped constant promotion: int literals are assignable to float64
|
|
1215
938
|
if (target.kind === "basic" && source.kind === "basic") {
|
|
1216
939
|
if (target.name === "float64" && source.name === "int") return;
|
|
@@ -1242,11 +965,31 @@ export class TypeChecker {
|
|
|
1242
965
|
for (const [name, required] of iface.methods) {
|
|
1243
966
|
const actual = base.methods?.get(name);
|
|
1244
967
|
if (!actual) return false;
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const
|
|
1248
|
-
|
|
968
|
+
|
|
969
|
+
// Check parameter count matches
|
|
970
|
+
const reqParams = required.params ?? [];
|
|
971
|
+
const actParams = actual.params ?? [];
|
|
972
|
+
if (reqParams.length !== actParams.length) return false;
|
|
973
|
+
|
|
974
|
+
// Check each parameter type matches
|
|
975
|
+
for (let i = 0; i < reqParams.length; i++) {
|
|
976
|
+
if (typeStr(reqParams[i]) !== typeStr(actParams[i])) return false;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// Check variadic flag matches
|
|
980
|
+
if (!!required.variadic !== !!actual.variadic) return false;
|
|
981
|
+
|
|
982
|
+
// Check return types match (count and each type)
|
|
983
|
+
const reqRets = required.returns ?? [];
|
|
984
|
+
const actRets = actual.returns ?? [];
|
|
985
|
+
if (reqRets.length !== actRets.length) return false;
|
|
986
|
+
for (let i = 0; i < reqRets.length; i++) {
|
|
987
|
+
if (typeStr(reqRets[i]) !== typeStr(actRets[i])) return false;
|
|
988
|
+
}
|
|
1249
989
|
}
|
|
1250
990
|
return true;
|
|
1251
991
|
}
|
|
1252
992
|
}
|
|
993
|
+
|
|
994
|
+
Object.assign(TypeChecker.prototype, statementCheckMethods);
|
|
995
|
+
Object.assign(TypeChecker.prototype, expressionCheckMethods);
|