gofront 0.0.2 → 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 +175 -51
- 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 +34 -990
- package/src/compiler.js +36 -22
- package/src/index.js +3 -3
- package/src/lexer.js +94 -6
- 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 +40 -853
- 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 +352 -703
package/src/typechecker.js
CHANGED
|
@@ -1,164 +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
|
-
const CMP_OPS = new Set(["==", "!=", "<", ">", "<=", ">="]);
|
|
39
|
-
const LOG_OPS = new Set(["&&", "||"]);
|
|
40
|
-
|
|
41
|
-
// ── Built-in types ───────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
const INT = { kind: "basic", name: "int" };
|
|
44
|
-
const FLOAT64 = { kind: "basic", name: "float64" };
|
|
45
|
-
const STRING = { kind: "basic", name: "string" };
|
|
46
|
-
const BOOL = { kind: "basic", name: "bool" };
|
|
47
|
-
const ANY = { kind: "basic", name: "any" };
|
|
48
|
-
const VOID = { kind: "basic", name: "void" };
|
|
49
|
-
const NIL = { kind: "basic", name: "nil" };
|
|
50
|
-
const ERROR = { kind: "basic", name: "error" };
|
|
51
|
-
|
|
52
|
-
const BASIC_TYPES = {
|
|
53
|
-
int: INT,
|
|
54
|
-
float64: FLOAT64,
|
|
55
|
-
string: STRING,
|
|
56
|
-
bool: BOOL,
|
|
57
|
-
any: ANY,
|
|
58
|
-
byte: INT,
|
|
59
|
-
rune: INT,
|
|
60
|
-
error: ERROR,
|
|
61
|
-
// Sized integer / float aliases — all map to int or float64 at runtime
|
|
62
|
-
uint: INT,
|
|
63
|
-
int8: INT,
|
|
64
|
-
int16: INT,
|
|
65
|
-
int32: INT,
|
|
66
|
-
int64: INT,
|
|
67
|
-
uint8: INT,
|
|
68
|
-
uint16: INT,
|
|
69
|
-
uint32: INT,
|
|
70
|
-
uint64: INT,
|
|
71
|
-
uintptr: INT,
|
|
72
|
-
float32: FLOAT64,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
function isNumeric(t) {
|
|
76
|
-
return t.kind === "basic" && (t.name === "int" || t.name === "float64");
|
|
77
|
-
}
|
|
78
|
-
function isString(t) {
|
|
79
|
-
return t.kind === "basic" && t.name === "string";
|
|
80
|
-
}
|
|
81
|
-
function isBool(t) {
|
|
82
|
-
return t.kind === "basic" && t.name === "bool";
|
|
83
|
-
}
|
|
84
|
-
function isAny(t) {
|
|
85
|
-
return t.kind === "basic" && t.name === "any";
|
|
86
|
-
}
|
|
87
|
-
function isNil(t) {
|
|
88
|
-
return t.kind === "basic" && t.name === "nil";
|
|
89
|
-
}
|
|
90
|
-
function isVoid(t) {
|
|
91
|
-
return t.kind === "basic" && t.name === "void";
|
|
92
|
-
}
|
|
93
|
-
function isError(t) {
|
|
94
|
-
return t.kind === "basic" && t.name === "error";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function typeStr(t) {
|
|
98
|
-
if (!t) return "void";
|
|
99
|
-
if (t.kind === "basic" && t.alias) return t.alias;
|
|
100
|
-
switch (t.kind) {
|
|
101
|
-
case "basic":
|
|
102
|
-
return t.name;
|
|
103
|
-
case "slice":
|
|
104
|
-
return `[]${typeStr(t.elem)}`;
|
|
105
|
-
case "array":
|
|
106
|
-
return `[${t.size}]${typeStr(t.elem)}`;
|
|
107
|
-
case "map":
|
|
108
|
-
return `map[${typeStr(t.key)}]${typeStr(t.value)}`;
|
|
109
|
-
case "struct":
|
|
110
|
-
return t.name || "struct{...}";
|
|
111
|
-
case "interface":
|
|
112
|
-
return t.name || "interface{...}";
|
|
113
|
-
case "namespace":
|
|
114
|
-
return t.name || "namespace{...}";
|
|
115
|
-
case "func":
|
|
116
|
-
return `func(${t.params.map(typeStr).join(", ")}) ${typeStr(t.returns[0] ?? VOID)}`;
|
|
117
|
-
case "tuple":
|
|
118
|
-
return `(${t.types.map(typeStr).join(", ")})`;
|
|
119
|
-
case "named":
|
|
120
|
-
return t.name;
|
|
121
|
-
case "pointer":
|
|
122
|
-
return `*${typeStr(t.base)}`;
|
|
123
|
-
default:
|
|
124
|
-
return "?";
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ── Scope / environment ──────────────────────────────────────
|
|
129
|
-
|
|
130
|
-
class Scope {
|
|
131
|
-
constructor(parent = null) {
|
|
132
|
-
this.parent = parent;
|
|
133
|
-
this.symbols = new Map();
|
|
134
|
-
this._consts = new Set(); // names declared as const in this scope
|
|
135
|
-
}
|
|
136
|
-
define(name, type) {
|
|
137
|
-
this.symbols.set(name, type);
|
|
138
|
-
}
|
|
139
|
-
defineConst(name, type) {
|
|
140
|
-
this.symbols.set(name, type);
|
|
141
|
-
this._consts.add(name);
|
|
142
|
-
}
|
|
143
|
-
isConst(name) {
|
|
144
|
-
if (this._consts.has(name)) return true;
|
|
145
|
-
// Don't walk parent — shadowing a const with a var in a child scope is valid
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
lookup(name) {
|
|
149
|
-
if (this.symbols.has(name)) return this.symbols.get(name);
|
|
150
|
-
if (this.parent) return this.parent.lookup(name);
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
// Lookup which scope owns the name (to check const flag)
|
|
154
|
-
lookupScope(name) {
|
|
155
|
-
if (this.symbols.has(name)) return this;
|
|
156
|
-
if (this.parent) return this.parent.lookupScope(name);
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ── 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 };
|
|
162
38
|
|
|
163
39
|
export class TypeChecker {
|
|
164
40
|
constructor() {
|
|
@@ -169,6 +45,9 @@ export class TypeChecker {
|
|
|
169
45
|
this._currentSource = null;
|
|
170
46
|
this._loopDepth = 0; // for break/continue validation
|
|
171
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
|
|
172
51
|
this._setupGlobals();
|
|
173
52
|
}
|
|
174
53
|
|
|
@@ -263,6 +142,210 @@ export class TypeChecker {
|
|
|
263
142
|
},
|
|
264
143
|
});
|
|
265
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
|
+
|
|
266
349
|
this.globals.define("append", { kind: "builtin", name: "append" });
|
|
267
350
|
this.globals.define("len", { kind: "builtin", name: "len" });
|
|
268
351
|
this.globals.define("cap", { kind: "builtin", name: "cap" });
|
|
@@ -299,6 +382,27 @@ export class TypeChecker {
|
|
|
299
382
|
for (const [name, type] of types) this.types.set(name, type);
|
|
300
383
|
}
|
|
301
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
|
+
|
|
302
406
|
// Return a snapshot of all user-defined globals (excludes built-ins).
|
|
303
407
|
// Used by the compiler to expose a package's exports to importers.
|
|
304
408
|
getExportedSymbols() {
|
|
@@ -326,6 +430,12 @@ export class TypeChecker {
|
|
|
326
430
|
return ANY; // recovery type
|
|
327
431
|
}
|
|
328
432
|
|
|
433
|
+
_reportUnused(scope, node) {
|
|
434
|
+
for (const name of scope.unusedLocals()) {
|
|
435
|
+
this.err(`'${name}' declared and not used`, node);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
329
439
|
check(program) {
|
|
330
440
|
this._currentFile = program._filename ?? null;
|
|
331
441
|
this._currentSource = program._source ?? null;
|
|
@@ -517,7 +627,12 @@ export class TypeChecker {
|
|
|
517
627
|
outer,
|
|
518
628
|
);
|
|
519
629
|
if (hasNamedReturns && returnType) returnType._hasNamedReturns = true;
|
|
630
|
+
const savedDefer = this._deferCount;
|
|
631
|
+
this._deferCount = 0;
|
|
520
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;
|
|
521
636
|
decl._returnType = returnType;
|
|
522
637
|
}
|
|
523
638
|
|
|
@@ -537,7 +652,12 @@ export class TypeChecker {
|
|
|
537
652
|
outer,
|
|
538
653
|
);
|
|
539
654
|
if (hasNamedReturns && returnType) returnType._hasNamedReturns = true;
|
|
655
|
+
const savedDefer = this._deferCount;
|
|
656
|
+
this._deferCount = 0;
|
|
540
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;
|
|
541
661
|
decl._returnType = returnType;
|
|
542
662
|
}
|
|
543
663
|
|
|
@@ -554,20 +674,21 @@ export class TypeChecker {
|
|
|
554
674
|
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
555
675
|
if (spec.value) {
|
|
556
676
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
557
|
-
if (!type) type = valTypes[0] ?? ANY;
|
|
677
|
+
if (!type) type = defaultType(valTypes[0]) ?? ANY;
|
|
558
678
|
for (let i = 0; i < spec.names.length; i++) {
|
|
559
679
|
const vt = valTypes[i] ?? ANY;
|
|
560
680
|
this.assertAssignable(type, vt, spec.value[i]);
|
|
561
681
|
}
|
|
562
682
|
}
|
|
563
683
|
if (!type) type = ANY;
|
|
564
|
-
for (const name of spec.names) scope.
|
|
684
|
+
for (const name of spec.names) scope.defineLocal(name, type);
|
|
565
685
|
}
|
|
566
686
|
}
|
|
567
687
|
|
|
568
688
|
checkConstDecl(decl, scope) {
|
|
569
689
|
for (const spec of decl.decls) {
|
|
570
690
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
691
|
+
// Explicit type annotation → typed constant; otherwise preserve untyped
|
|
571
692
|
const type = spec.type
|
|
572
693
|
? this.resolveTypeNode(spec.type, scope)
|
|
573
694
|
: (valTypes[0] ?? ANY);
|
|
@@ -577,541 +698,6 @@ export class TypeChecker {
|
|
|
577
698
|
|
|
578
699
|
// ── Statement checking ───────────────────────────────────────
|
|
579
700
|
|
|
580
|
-
checkBlock(block, scope, returnType) {
|
|
581
|
-
for (const stmt of block.stmts) {
|
|
582
|
-
this.checkStmt(stmt, scope, returnType);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
checkStmt(stmt, scope, returnType) {
|
|
587
|
-
switch (stmt.kind) {
|
|
588
|
-
case "VarDecl":
|
|
589
|
-
this.checkVarDecl(stmt, scope);
|
|
590
|
-
break;
|
|
591
|
-
case "ConstDecl":
|
|
592
|
-
this.checkConstDecl(stmt, scope);
|
|
593
|
-
break;
|
|
594
|
-
case "TypeDecl":
|
|
595
|
-
this.collectType(stmt);
|
|
596
|
-
break;
|
|
597
|
-
|
|
598
|
-
case "DefineStmt": {
|
|
599
|
-
// comma-ok type assertion: n, ok := x.(T)
|
|
600
|
-
if (
|
|
601
|
-
stmt.lhs.length === 2 &&
|
|
602
|
-
stmt.rhs.length === 1 &&
|
|
603
|
-
stmt.rhs[0].kind === "TypeAssertExpr"
|
|
604
|
-
) {
|
|
605
|
-
stmt.rhs[0]._commaOk = true;
|
|
606
|
-
}
|
|
607
|
-
const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
|
|
608
|
-
// Flatten tuple returns
|
|
609
|
-
const rhsFlat =
|
|
610
|
-
rhs.length === 1 && rhs[0].kind === "tuple" ? rhs[0].types : rhs;
|
|
611
|
-
// comma-ok type assertion returns [assertedType, bool]
|
|
612
|
-
if (stmt.rhs[0]?._commaOk) {
|
|
613
|
-
const lhsNames = stmt.lhs.map((e) => e.name ?? e);
|
|
614
|
-
if (lhsNames[0] !== "_") scope.define(lhsNames[0], rhs[0]);
|
|
615
|
-
if (lhsNames[1] !== "_") scope.define(lhsNames[1], BOOL);
|
|
616
|
-
stmt._rhsTypes = [rhs[0], BOOL];
|
|
617
|
-
break;
|
|
618
|
-
}
|
|
619
|
-
for (let i = 0; i < stmt.lhs.length; i++) {
|
|
620
|
-
const name = stmt.lhs[i].name ?? stmt.lhs[i];
|
|
621
|
-
if (name === "_") continue;
|
|
622
|
-
// Short variable re-declaration: mark vars that already exist
|
|
623
|
-
// in the current (not parent) scope as redeclared.
|
|
624
|
-
if (scope.symbols.has(name)) {
|
|
625
|
-
stmt.lhs[i]._redecl = true;
|
|
626
|
-
}
|
|
627
|
-
scope.define(name, rhsFlat[i] ?? ANY);
|
|
628
|
-
}
|
|
629
|
-
// Go requires at least one new variable in :=
|
|
630
|
-
const allRedecl = stmt.lhs.every(
|
|
631
|
-
(e) => e._redecl || (e.name ?? e) === "_",
|
|
632
|
-
);
|
|
633
|
-
if (allRedecl && stmt.lhs.length > 0) {
|
|
634
|
-
this.err("no new variables on left side of :=", stmt.lhs[0]);
|
|
635
|
-
}
|
|
636
|
-
stmt._rhsTypes = rhsFlat;
|
|
637
|
-
break;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
case "AssignStmt": {
|
|
641
|
-
// Check for const reassignment before evaluating RHS
|
|
642
|
-
for (const lhs of stmt.lhs) {
|
|
643
|
-
if (lhs.kind === "Ident") {
|
|
644
|
-
const ownerScope = scope.lookupScope(lhs.name);
|
|
645
|
-
if (ownerScope?.isConst(lhs.name))
|
|
646
|
-
this.err(`cannot assign to const '${lhs.name}'`, lhs);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
|
|
650
|
-
const rhsFlat =
|
|
651
|
-
rhs.length === 1 && rhs[0]?.kind === "tuple" ? rhs[0].types : rhs;
|
|
652
|
-
const lhsTypes = stmt.lhs.map((e) => this.checkExpr(e, scope));
|
|
653
|
-
for (let i = 0; i < lhsTypes.length; i++) {
|
|
654
|
-
const r = rhsFlat[i] ?? ANY;
|
|
655
|
-
if (!isAny(lhsTypes[i]) && !isAny(r)) {
|
|
656
|
-
this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
break;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
case "IncDecStmt":
|
|
663
|
-
this.checkExpr(stmt.expr, scope);
|
|
664
|
-
break;
|
|
665
|
-
|
|
666
|
-
case "ExprStmt":
|
|
667
|
-
this.checkExpr(stmt.expr, scope);
|
|
668
|
-
break;
|
|
669
|
-
|
|
670
|
-
case "ReturnStmt": {
|
|
671
|
-
const types = stmt.values.map((v) => this.checkExpr(v, scope));
|
|
672
|
-
stmt._types = types;
|
|
673
|
-
if (
|
|
674
|
-
!isVoid(returnType) &&
|
|
675
|
-
types.length === 0 &&
|
|
676
|
-
!returnType?._hasNamedReturns
|
|
677
|
-
) {
|
|
678
|
-
this.err("Missing return value", stmt);
|
|
679
|
-
}
|
|
680
|
-
// Check each returned value is assignable to its declared type
|
|
681
|
-
if (!isVoid(returnType) && types.length > 0) {
|
|
682
|
-
const expected =
|
|
683
|
-
returnType.kind === "tuple" ? returnType.types : [returnType];
|
|
684
|
-
for (let i = 0; i < expected.length; i++) {
|
|
685
|
-
if (types[i])
|
|
686
|
-
this.assertAssignable(
|
|
687
|
-
expected[i],
|
|
688
|
-
types[i],
|
|
689
|
-
stmt.values[i] ?? stmt,
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
break;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
case "IfStmt": {
|
|
697
|
-
const inner = new Scope(scope);
|
|
698
|
-
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
699
|
-
const ct = this.checkExpr(stmt.cond, inner);
|
|
700
|
-
if (!isBool(ct) && !isAny(ct))
|
|
701
|
-
this.err("If condition must be bool", stmt);
|
|
702
|
-
this.checkBlock(stmt.body, new Scope(inner), returnType);
|
|
703
|
-
if (stmt.elseBody) {
|
|
704
|
-
if (stmt.elseBody.kind === "Block")
|
|
705
|
-
this.checkBlock(stmt.elseBody, new Scope(inner), returnType);
|
|
706
|
-
else this.checkStmt(stmt.elseBody, inner, returnType);
|
|
707
|
-
}
|
|
708
|
-
break;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
case "ForStmt": {
|
|
712
|
-
const inner = new Scope(scope);
|
|
713
|
-
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
714
|
-
if (stmt.cond) {
|
|
715
|
-
// for range N { } — RangeExpr as condition is valid (Go 1.22 int range)
|
|
716
|
-
if (stmt.cond.kind !== "RangeExpr") {
|
|
717
|
-
const ct = this.checkExpr(stmt.cond, inner);
|
|
718
|
-
if (!isBool(ct) && !isAny(ct))
|
|
719
|
-
this.err("For condition must be bool", stmt);
|
|
720
|
-
} else {
|
|
721
|
-
this.checkExpr(stmt.cond.expr, inner);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
|
|
725
|
-
this._loopDepth++;
|
|
726
|
-
this.checkBlock(stmt.body, new Scope(inner), returnType);
|
|
727
|
-
this._loopDepth--;
|
|
728
|
-
break;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
case "SwitchStmt": {
|
|
732
|
-
const inner = new Scope(scope);
|
|
733
|
-
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
734
|
-
if (stmt.tag) this.checkExpr(stmt.tag, inner);
|
|
735
|
-
this._switchDepth++;
|
|
736
|
-
for (const c of stmt.cases) {
|
|
737
|
-
const caseScope = new Scope(inner);
|
|
738
|
-
if (c.list) for (const e of c.list) this.checkExpr(e, caseScope);
|
|
739
|
-
for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
|
|
740
|
-
}
|
|
741
|
-
this._switchDepth--;
|
|
742
|
-
break;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
case "TypeSwitchStmt": {
|
|
746
|
-
const inner = new Scope(scope);
|
|
747
|
-
this.checkExpr(stmt.expr, inner);
|
|
748
|
-
this._switchDepth++;
|
|
749
|
-
for (const c of stmt.cases) {
|
|
750
|
-
const caseScope = new Scope(inner);
|
|
751
|
-
if (stmt.assign) {
|
|
752
|
-
// Bind the variable to the single case type, or any for multi/default
|
|
753
|
-
const bindType =
|
|
754
|
-
c.types?.length === 1
|
|
755
|
-
? this.resolveTypeNode(c.types[0], inner)
|
|
756
|
-
: ANY;
|
|
757
|
-
caseScope.define(stmt.assign, bindType);
|
|
758
|
-
}
|
|
759
|
-
for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
|
|
760
|
-
}
|
|
761
|
-
this._switchDepth--;
|
|
762
|
-
break;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
case "DeferStmt": {
|
|
766
|
-
if (stmt.call.kind !== "CallExpr")
|
|
767
|
-
this.err("defer requires a function call", stmt.call);
|
|
768
|
-
this.checkExpr(stmt.call, scope);
|
|
769
|
-
break;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
case "LabeledStmt":
|
|
773
|
-
this.checkStmt(stmt.body, scope, returnType);
|
|
774
|
-
break;
|
|
775
|
-
|
|
776
|
-
case "BranchStmt": {
|
|
777
|
-
const kw = stmt.keyword;
|
|
778
|
-
if (stmt.label) break; // labeled branch — target checked by JS
|
|
779
|
-
if (kw === "continue" && this._loopDepth === 0)
|
|
780
|
-
this.err("continue statement outside for loop", stmt);
|
|
781
|
-
else if (
|
|
782
|
-
kw === "break" &&
|
|
783
|
-
this._loopDepth === 0 &&
|
|
784
|
-
this._switchDepth === 0
|
|
785
|
-
)
|
|
786
|
-
this.err("break statement outside for loop or switch", stmt);
|
|
787
|
-
else if (kw === "fallthrough" && this._switchDepth === 0)
|
|
788
|
-
this.err("fallthrough statement outside switch", stmt);
|
|
789
|
-
break;
|
|
790
|
-
}
|
|
791
|
-
case "Block":
|
|
792
|
-
this.checkBlock(stmt, new Scope(scope), returnType);
|
|
793
|
-
break;
|
|
794
|
-
default:
|
|
795
|
-
break;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// ── Expression checking (returns type) ───────────────────────
|
|
800
|
-
|
|
801
|
-
checkExpr(expr, scope) {
|
|
802
|
-
const t = this._checkExpr(expr, scope);
|
|
803
|
-
expr._type = t;
|
|
804
|
-
return t;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
_checkExpr(expr, scope) {
|
|
808
|
-
switch (expr.kind) {
|
|
809
|
-
case "BasicLit": {
|
|
810
|
-
switch (expr.litKind) {
|
|
811
|
-
case "INT":
|
|
812
|
-
return INT;
|
|
813
|
-
case "FLOAT":
|
|
814
|
-
return FLOAT64;
|
|
815
|
-
case "STRING":
|
|
816
|
-
return STRING;
|
|
817
|
-
case "BOOL":
|
|
818
|
-
return BOOL;
|
|
819
|
-
case "NIL":
|
|
820
|
-
return NIL;
|
|
821
|
-
}
|
|
822
|
-
break;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
case "Ident": {
|
|
826
|
-
const t = scope.lookup(expr.name);
|
|
827
|
-
if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
|
|
828
|
-
return t;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
case "UnaryExpr": {
|
|
832
|
-
const ot = this.checkExpr(expr.operand, scope);
|
|
833
|
-
if (expr.op === "!") {
|
|
834
|
-
if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
|
|
835
|
-
return BOOL;
|
|
836
|
-
}
|
|
837
|
-
if (expr.op === "-" || expr.op === "+") {
|
|
838
|
-
if (!isNumeric(ot) && !isAny(ot))
|
|
839
|
-
this.err(`${expr.op} requires numeric`, expr);
|
|
840
|
-
return ot;
|
|
841
|
-
}
|
|
842
|
-
return ot;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
case "BinaryExpr": {
|
|
846
|
-
const lt = this.checkExpr(expr.left, scope);
|
|
847
|
-
const rt = this.checkExpr(expr.right, scope);
|
|
848
|
-
if (isAny(lt) || isAny(rt)) return this.binaryResultType(expr.op, ANY);
|
|
849
|
-
return this.binaryResultType(expr.op, lt, rt, expr);
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
case "CallExpr": {
|
|
853
|
-
return this.checkCall(expr, scope);
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
case "SelectorExpr": {
|
|
857
|
-
const baseType = this.checkExpr(expr.expr, scope);
|
|
858
|
-
return this.fieldType(baseType, expr.field, expr);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
case "IndexExpr": {
|
|
862
|
-
const bt = this.checkExpr(expr.expr, scope);
|
|
863
|
-
this.checkExpr(expr.index, scope);
|
|
864
|
-
if (isAny(bt)) return ANY;
|
|
865
|
-
if (bt.kind === "slice" || bt.kind === "array") return bt.elem;
|
|
866
|
-
if (bt.kind === "map") {
|
|
867
|
-
expr._mapValueType = bt.value; // for codegen zero-value fallback
|
|
868
|
-
return bt.value;
|
|
869
|
-
}
|
|
870
|
-
if (isString(bt)) return INT; // byte
|
|
871
|
-
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
case "SliceExpr": {
|
|
875
|
-
const bt = this.checkExpr(expr.expr, scope);
|
|
876
|
-
if (expr.low) this.checkExpr(expr.low, scope);
|
|
877
|
-
if (expr.high) this.checkExpr(expr.high, scope);
|
|
878
|
-
if (isAny(bt)) return ANY;
|
|
879
|
-
if (bt.kind === "slice" || bt.kind === "array")
|
|
880
|
-
return { kind: "slice", elem: bt.elem };
|
|
881
|
-
if (isString(bt)) return STRING;
|
|
882
|
-
return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
case "CompositeLit": {
|
|
886
|
-
return this.checkCompositeLit(expr, scope);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
case "FuncLit": {
|
|
890
|
-
const inner = new Scope(scope);
|
|
891
|
-
for (const p of expr.params)
|
|
892
|
-
inner.define(
|
|
893
|
-
p.name,
|
|
894
|
-
p.type ? this.resolveTypeNode(p.type, scope) : ANY,
|
|
895
|
-
);
|
|
896
|
-
const ret = expr.returnType
|
|
897
|
-
? this.resolveTypeNode(expr.returnType, scope)
|
|
898
|
-
: VOID;
|
|
899
|
-
this.checkBlock(expr.body, inner, ret);
|
|
900
|
-
const paramTypes = expr.params.map((p) =>
|
|
901
|
-
p.type ? this.resolveTypeNode(p.type, scope) : ANY,
|
|
902
|
-
);
|
|
903
|
-
return {
|
|
904
|
-
kind: "func",
|
|
905
|
-
params: paramTypes,
|
|
906
|
-
returns: [ret],
|
|
907
|
-
async: expr.async,
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
case "AwaitExpr": {
|
|
912
|
-
// await unwraps whatever the expression produces — no Promise type modelling
|
|
913
|
-
return this.checkExpr(expr.expr, scope);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
case "TypeConversion": {
|
|
917
|
-
this.checkExpr(expr.expr, scope);
|
|
918
|
-
return this.resolveTypeNode(expr.targetType, scope);
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
case "TypeAssertExpr": {
|
|
922
|
-
this.checkExpr(expr.expr, scope);
|
|
923
|
-
return this.resolveTypeNode(expr.type, scope);
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
case "RangeExpr": {
|
|
927
|
-
// Type-check the iterated expression so its _type is annotated
|
|
928
|
-
return this.checkExpr(expr.expr, scope);
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
default:
|
|
932
|
-
return ANY;
|
|
933
|
-
}
|
|
934
|
-
return ANY;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
checkCall(expr, scope) {
|
|
938
|
-
const fnType = this.checkExpr(expr.func, scope);
|
|
939
|
-
|
|
940
|
-
// Handle built-ins before evaluating args; some (e.g. new) take type names as
|
|
941
|
-
// arguments, which would otherwise produce false "Undefined" errors.
|
|
942
|
-
if (fnType.kind === "builtin") {
|
|
943
|
-
const argTypes = expr.args.map((a, i) => {
|
|
944
|
-
// new(T) — first arg is a type name, not a value expression
|
|
945
|
-
if (fnType.name === "new" && i === 0) return ANY;
|
|
946
|
-
return this.checkExpr(a, scope);
|
|
947
|
-
});
|
|
948
|
-
return this.checkBuiltin(fnType.name, expr, argTypes, scope);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
const argTypes = expr.args.map((a) => this.checkExpr(a, scope));
|
|
952
|
-
|
|
953
|
-
if (isAny(fnType)) return ANY;
|
|
954
|
-
|
|
955
|
-
if (fnType.kind !== "func") {
|
|
956
|
-
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// Check arg count (allow variadic slack)
|
|
960
|
-
const minArgs = fnType.params.filter(
|
|
961
|
-
(_, i) => !fnType.variadic || i < fnType.params.length - 1,
|
|
962
|
-
).length;
|
|
963
|
-
if (argTypes.length < minArgs) {
|
|
964
|
-
this.err(
|
|
965
|
-
`Too few arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
|
|
966
|
-
expr,
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
if (!fnType.variadic && argTypes.length > fnType.params.length) {
|
|
970
|
-
this.err(
|
|
971
|
-
`Too many arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
|
|
972
|
-
expr,
|
|
973
|
-
);
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Check each argument is assignable to its parameter type
|
|
977
|
-
for (let i = 0; i < fnType.params.length && i < argTypes.length; i++) {
|
|
978
|
-
const paramIdx =
|
|
979
|
-
fnType.variadic && i >= fnType.params.length - 1
|
|
980
|
-
? fnType.params.length - 1
|
|
981
|
-
: i;
|
|
982
|
-
// Spread arg (f(slice...)) passes a whole slice into a variadic — skip per-element check
|
|
983
|
-
if (expr.args[i]?._spread) continue;
|
|
984
|
-
this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
const ret = fnType.returns;
|
|
988
|
-
if (!ret || ret.length === 0) return VOID;
|
|
989
|
-
if (ret.length === 1) return ret[0];
|
|
990
|
-
return { kind: "tuple", types: ret };
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
checkBuiltin(name, expr, argTypes, scope) {
|
|
994
|
-
switch (name) {
|
|
995
|
-
case "len":
|
|
996
|
-
case "cap":
|
|
997
|
-
return INT;
|
|
998
|
-
case "append":
|
|
999
|
-
return argTypes[0] ?? ANY;
|
|
1000
|
-
case "copy":
|
|
1001
|
-
return INT;
|
|
1002
|
-
case "delete":
|
|
1003
|
-
return VOID;
|
|
1004
|
-
case "make": {
|
|
1005
|
-
if (expr.args.length < 1) return ANY;
|
|
1006
|
-
const typeArg = expr.args[0];
|
|
1007
|
-
const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
|
|
1008
|
-
return this.resolveTypeNode(typeNode, scope) ?? ANY;
|
|
1009
|
-
}
|
|
1010
|
-
case "new": {
|
|
1011
|
-
if (expr.args.length < 1) return ANY;
|
|
1012
|
-
const inner = this.resolveTypeNode(expr.args[0], scope);
|
|
1013
|
-
return { kind: "pointer", base: inner };
|
|
1014
|
-
}
|
|
1015
|
-
case "print":
|
|
1016
|
-
case "println":
|
|
1017
|
-
case "panic":
|
|
1018
|
-
return VOID;
|
|
1019
|
-
case "recover":
|
|
1020
|
-
return ANY;
|
|
1021
|
-
case "error":
|
|
1022
|
-
return ERROR;
|
|
1023
|
-
case "min":
|
|
1024
|
-
case "max":
|
|
1025
|
-
return argTypes[0] ?? ANY;
|
|
1026
|
-
case "clear":
|
|
1027
|
-
return VOID;
|
|
1028
|
-
default:
|
|
1029
|
-
return ANY;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
checkCompositeLit(expr, scope, hintType = null) {
|
|
1034
|
-
// null typeExpr = implicit lit inside a slice/map: {X:1} in []Point{{X:1}}
|
|
1035
|
-
const t =
|
|
1036
|
-
expr.typeExpr === null
|
|
1037
|
-
? (hintType ?? ANY)
|
|
1038
|
-
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
1039
|
-
const base = t.kind === "named" ? t.underlying : t;
|
|
1040
|
-
|
|
1041
|
-
if (base?.kind === "struct") {
|
|
1042
|
-
for (const elem of expr.elems) {
|
|
1043
|
-
if (elem.kind === "KeyValueExpr") {
|
|
1044
|
-
const keyName = elem.key.name;
|
|
1045
|
-
const fieldType = base.fields?.get(keyName);
|
|
1046
|
-
if (fieldType) {
|
|
1047
|
-
const vt = this.checkExpr(elem.value, scope);
|
|
1048
|
-
this.assertAssignable(fieldType, vt, elem.value);
|
|
1049
|
-
} else {
|
|
1050
|
-
// Check if it's an embedded type name (e.g. Dog{Animal: Animal{...}})
|
|
1051
|
-
const embed = base._embeds?.find(
|
|
1052
|
-
(e) => (e.kind === "named" ? e.name : null) === keyName,
|
|
1053
|
-
);
|
|
1054
|
-
if (embed) {
|
|
1055
|
-
const vt = this.checkExpr(elem.value, scope);
|
|
1056
|
-
this.assertAssignable(embed, vt, elem.value);
|
|
1057
|
-
elem._isEmbedInit = true;
|
|
1058
|
-
} else {
|
|
1059
|
-
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
} else {
|
|
1063
|
-
this.checkExpr(elem, scope);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
return t;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
if (base?.kind === "slice" || base?.kind === "array") {
|
|
1070
|
-
for (const elem of expr.elems) {
|
|
1071
|
-
if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
|
|
1072
|
-
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
1073
|
-
elem._type = et;
|
|
1074
|
-
} else if (elem.kind !== "KeyValueExpr") {
|
|
1075
|
-
const et = this.checkExpr(elem, scope);
|
|
1076
|
-
this.assertAssignable(base.elem, et, elem);
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
return t;
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
if (base?.kind === "map") {
|
|
1083
|
-
for (const elem of expr.elems) {
|
|
1084
|
-
if (elem.kind === "KeyValueExpr") {
|
|
1085
|
-
const kt = this.checkExpr(elem.key, scope);
|
|
1086
|
-
// value may be an implicit composite lit
|
|
1087
|
-
let vt;
|
|
1088
|
-
if (
|
|
1089
|
-
elem.value.kind === "CompositeLit" &&
|
|
1090
|
-
elem.value.typeExpr === null
|
|
1091
|
-
) {
|
|
1092
|
-
vt = this.checkCompositeLit(elem.value, scope, base.value);
|
|
1093
|
-
elem.value._type = vt;
|
|
1094
|
-
} else {
|
|
1095
|
-
vt = this.checkExpr(elem.value, scope);
|
|
1096
|
-
}
|
|
1097
|
-
this.assertAssignable(base.key, kt, elem.key);
|
|
1098
|
-
this.assertAssignable(base.value, vt, elem.value);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
return t;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// Unknown type context — still check elements
|
|
1105
|
-
for (const elem of expr.elems) {
|
|
1106
|
-
if (elem.kind === "KeyValueExpr") {
|
|
1107
|
-
this.checkExpr(elem.value, scope);
|
|
1108
|
-
} else {
|
|
1109
|
-
this.checkExpr(elem, scope);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
return t ?? ANY;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
701
|
// ── Type resolution ──────────────────────────────────────────
|
|
1116
702
|
|
|
1117
703
|
resolveTypeNode(node, scope) {
|
|
@@ -1192,7 +778,11 @@ export class TypeChecker {
|
|
|
1192
778
|
const returns = m.returnType
|
|
1193
779
|
? [this.resolveTypeNode(m.returnType, scope)]
|
|
1194
780
|
: [VOID];
|
|
1195
|
-
|
|
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);
|
|
1196
786
|
}
|
|
1197
787
|
// Flatten embedded interface methods
|
|
1198
788
|
if (node.embeds) {
|
|
@@ -1289,10 +879,26 @@ export class TypeChecker {
|
|
|
1289
879
|
if (LOG_OPS.has(op)) return BOOL;
|
|
1290
880
|
if (isAny(lt) || isAny(rt)) return ANY;
|
|
1291
881
|
if (isNumeric(lt) && isNumeric(rt)) {
|
|
1292
|
-
|
|
1293
|
-
|
|
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;
|
|
1294
901
|
}
|
|
1295
|
-
if (isString(lt) && isString(rt) && op === "+") return STRING;
|
|
1296
902
|
if (node)
|
|
1297
903
|
this.err(`Invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
|
|
1298
904
|
return ANY;
|
|
@@ -1305,6 +911,29 @@ export class TypeChecker {
|
|
|
1305
911
|
if (isAny(target) || isAny(source)) return;
|
|
1306
912
|
if (isNil(source)) return; // nil is assignable to anything nullable
|
|
1307
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
|
+
|
|
1308
937
|
// Go untyped constant promotion: int literals are assignable to float64
|
|
1309
938
|
if (target.kind === "basic" && source.kind === "basic") {
|
|
1310
939
|
if (target.name === "float64" && source.name === "int") return;
|
|
@@ -1336,11 +965,31 @@ export class TypeChecker {
|
|
|
1336
965
|
for (const [name, required] of iface.methods) {
|
|
1337
966
|
const actual = base.methods?.get(name);
|
|
1338
967
|
if (!actual) return false;
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
|
|
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
|
+
}
|
|
1343
989
|
}
|
|
1344
990
|
return true;
|
|
1345
991
|
}
|
|
1346
992
|
}
|
|
993
|
+
|
|
994
|
+
Object.assign(TypeChecker.prototype, statementCheckMethods);
|
|
995
|
+
Object.assign(TypeChecker.prototype, expressionCheckMethods);
|