gofront 0.0.2 → 0.0.4
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 +173 -52
- package/package.json +7 -3
- package/src/codegen/expressions.js +907 -0
- package/src/codegen/source-map.js +45 -0
- package/src/codegen/statements.js +464 -0
- package/src/codegen.js +43 -996
- 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 +72 -863
- package/src/typechecker/expressions.js +447 -0
- package/src/typechecker/statements.js +287 -0
- package/src/typechecker/types.js +208 -0
- package/src/typechecker.js +528 -709
package/src/typechecker.js
CHANGED
|
@@ -1,164 +1,41 @@
|
|
|
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 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
|
+
isVoid,
|
|
27
|
+
LOG_OPS,
|
|
28
|
+
Scope,
|
|
29
|
+
STRING,
|
|
30
|
+
TypeCheckError,
|
|
31
|
+
typeStr,
|
|
32
|
+
UNTYPED_FLOAT,
|
|
33
|
+
UNTYPED_INT,
|
|
34
|
+
VOID,
|
|
35
|
+
} from "./typechecker/types.js";
|
|
36
|
+
|
|
37
|
+
// Re-export for consumers that import from typechecker.js
|
|
38
|
+
export { TypeCheckError, typeStr };
|
|
162
39
|
|
|
163
40
|
export class TypeChecker {
|
|
164
41
|
constructor() {
|
|
@@ -169,6 +46,9 @@ export class TypeChecker {
|
|
|
169
46
|
this._currentSource = null;
|
|
170
47
|
this._loopDepth = 0; // for break/continue validation
|
|
171
48
|
this._switchDepth = 0; // for break/fallthrough validation
|
|
49
|
+
this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
|
|
50
|
+
this._deferCount = 0; // tracks defer usage in current function body
|
|
51
|
+
this._imports = []; // tracked imports for unused-import detection
|
|
172
52
|
this._setupGlobals();
|
|
173
53
|
}
|
|
174
54
|
|
|
@@ -263,6 +143,245 @@ export class TypeChecker {
|
|
|
263
143
|
},
|
|
264
144
|
});
|
|
265
145
|
|
|
146
|
+
// strings package
|
|
147
|
+
const strFn1 = (ret) => ({
|
|
148
|
+
kind: "func",
|
|
149
|
+
params: [STRING],
|
|
150
|
+
returns: [ret],
|
|
151
|
+
});
|
|
152
|
+
const strFn2 = (p2, ret) => ({
|
|
153
|
+
kind: "func",
|
|
154
|
+
params: [STRING, p2],
|
|
155
|
+
returns: [ret],
|
|
156
|
+
});
|
|
157
|
+
const strFn3 = (p2, p3, ret) => ({
|
|
158
|
+
kind: "func",
|
|
159
|
+
params: [STRING, p2, p3],
|
|
160
|
+
returns: [ret],
|
|
161
|
+
});
|
|
162
|
+
this.globals.define("strings", {
|
|
163
|
+
kind: "namespace",
|
|
164
|
+
name: "strings",
|
|
165
|
+
members: {
|
|
166
|
+
Contains: strFn2(STRING, BOOL),
|
|
167
|
+
HasPrefix: strFn2(STRING, BOOL),
|
|
168
|
+
HasSuffix: strFn2(STRING, BOOL),
|
|
169
|
+
Index: strFn2(STRING, INT),
|
|
170
|
+
LastIndex: strFn2(STRING, INT),
|
|
171
|
+
Count: strFn2(STRING, INT),
|
|
172
|
+
Repeat: strFn2(INT, STRING),
|
|
173
|
+
Replace: {
|
|
174
|
+
kind: "func",
|
|
175
|
+
params: [STRING, STRING, STRING, INT],
|
|
176
|
+
returns: [STRING],
|
|
177
|
+
},
|
|
178
|
+
ReplaceAll: strFn3(STRING, STRING, STRING),
|
|
179
|
+
ToUpper: strFn1(STRING),
|
|
180
|
+
ToLower: strFn1(STRING),
|
|
181
|
+
TrimSpace: strFn1(STRING),
|
|
182
|
+
Trim: strFn2(STRING, STRING),
|
|
183
|
+
TrimPrefix: strFn2(STRING, STRING),
|
|
184
|
+
TrimSuffix: strFn2(STRING, STRING),
|
|
185
|
+
TrimLeft: strFn2(STRING, STRING),
|
|
186
|
+
TrimRight: strFn2(STRING, STRING),
|
|
187
|
+
Split: strFn2(STRING, { kind: "slice", elem: STRING }),
|
|
188
|
+
Join: {
|
|
189
|
+
kind: "func",
|
|
190
|
+
params: [{ kind: "slice", elem: STRING }, STRING],
|
|
191
|
+
returns: [STRING],
|
|
192
|
+
},
|
|
193
|
+
EqualFold: strFn2(STRING, BOOL),
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// strconv package
|
|
198
|
+
this.globals.define("strconv", {
|
|
199
|
+
kind: "namespace",
|
|
200
|
+
name: "strconv",
|
|
201
|
+
members: {
|
|
202
|
+
Itoa: { kind: "func", params: [INT], returns: [STRING] },
|
|
203
|
+
Atoi: {
|
|
204
|
+
kind: "func",
|
|
205
|
+
params: [STRING],
|
|
206
|
+
returns: [INT, ERROR],
|
|
207
|
+
},
|
|
208
|
+
FormatFloat: {
|
|
209
|
+
kind: "func",
|
|
210
|
+
params: [FLOAT64, INT, INT, INT],
|
|
211
|
+
returns: [STRING],
|
|
212
|
+
},
|
|
213
|
+
FormatBool: { kind: "func", params: [BOOL], returns: [STRING] },
|
|
214
|
+
FormatInt: { kind: "func", params: [INT, INT], returns: [STRING] },
|
|
215
|
+
ParseFloat: {
|
|
216
|
+
kind: "func",
|
|
217
|
+
params: [STRING, INT],
|
|
218
|
+
returns: [FLOAT64, ERROR],
|
|
219
|
+
},
|
|
220
|
+
ParseInt: {
|
|
221
|
+
kind: "func",
|
|
222
|
+
params: [STRING, INT, INT],
|
|
223
|
+
returns: [INT, ERROR],
|
|
224
|
+
},
|
|
225
|
+
ParseBool: {
|
|
226
|
+
kind: "func",
|
|
227
|
+
params: [STRING],
|
|
228
|
+
returns: [BOOL, ERROR],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// sort package
|
|
234
|
+
this.globals.define("sort", {
|
|
235
|
+
kind: "namespace",
|
|
236
|
+
name: "sort",
|
|
237
|
+
members: {
|
|
238
|
+
Ints: {
|
|
239
|
+
kind: "func",
|
|
240
|
+
params: [{ kind: "slice", elem: INT }],
|
|
241
|
+
returns: [VOID],
|
|
242
|
+
},
|
|
243
|
+
Float64s: {
|
|
244
|
+
kind: "func",
|
|
245
|
+
params: [{ kind: "slice", elem: FLOAT64 }],
|
|
246
|
+
returns: [VOID],
|
|
247
|
+
},
|
|
248
|
+
Strings: {
|
|
249
|
+
kind: "func",
|
|
250
|
+
params: [{ kind: "slice", elem: STRING }],
|
|
251
|
+
returns: [VOID],
|
|
252
|
+
},
|
|
253
|
+
Slice: {
|
|
254
|
+
kind: "func",
|
|
255
|
+
params: [ANY, ANY],
|
|
256
|
+
returns: [VOID],
|
|
257
|
+
},
|
|
258
|
+
SliceStable: {
|
|
259
|
+
kind: "func",
|
|
260
|
+
params: [ANY, ANY],
|
|
261
|
+
returns: [VOID],
|
|
262
|
+
},
|
|
263
|
+
SliceIsSorted: {
|
|
264
|
+
kind: "func",
|
|
265
|
+
params: [ANY, ANY],
|
|
266
|
+
returns: [BOOL],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// math package
|
|
272
|
+
this.globals.define("math", {
|
|
273
|
+
kind: "namespace",
|
|
274
|
+
name: "math",
|
|
275
|
+
members: {
|
|
276
|
+
Abs: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
277
|
+
Floor: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
278
|
+
Ceil: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
279
|
+
Round: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
280
|
+
Sqrt: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
281
|
+
Cbrt: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
282
|
+
Pow: {
|
|
283
|
+
kind: "func",
|
|
284
|
+
params: [FLOAT64, FLOAT64],
|
|
285
|
+
returns: [FLOAT64],
|
|
286
|
+
},
|
|
287
|
+
Log: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
288
|
+
Log2: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
289
|
+
Log10: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
290
|
+
Sin: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
291
|
+
Cos: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
292
|
+
Tan: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
|
|
293
|
+
Min: {
|
|
294
|
+
kind: "func",
|
|
295
|
+
params: [FLOAT64, FLOAT64],
|
|
296
|
+
returns: [FLOAT64],
|
|
297
|
+
},
|
|
298
|
+
Max: {
|
|
299
|
+
kind: "func",
|
|
300
|
+
params: [FLOAT64, FLOAT64],
|
|
301
|
+
returns: [FLOAT64],
|
|
302
|
+
},
|
|
303
|
+
Mod: {
|
|
304
|
+
kind: "func",
|
|
305
|
+
params: [FLOAT64, FLOAT64],
|
|
306
|
+
returns: [FLOAT64],
|
|
307
|
+
},
|
|
308
|
+
Inf: { kind: "func", params: [INT], returns: [FLOAT64] },
|
|
309
|
+
IsNaN: { kind: "func", params: [FLOAT64], returns: [BOOL] },
|
|
310
|
+
IsInf: {
|
|
311
|
+
kind: "func",
|
|
312
|
+
params: [FLOAT64, INT],
|
|
313
|
+
returns: [BOOL],
|
|
314
|
+
},
|
|
315
|
+
NaN: { kind: "func", params: [], returns: [FLOAT64] },
|
|
316
|
+
// Constants (typed as func with no params for namespace member access)
|
|
317
|
+
Pi: FLOAT64,
|
|
318
|
+
E: FLOAT64,
|
|
319
|
+
MaxFloat64: FLOAT64,
|
|
320
|
+
SmallestNonzeroFloat64: FLOAT64,
|
|
321
|
+
MaxInt: INT,
|
|
322
|
+
MinInt: INT,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// errors package
|
|
327
|
+
this.globals.define("errors", {
|
|
328
|
+
kind: "namespace",
|
|
329
|
+
name: "errors",
|
|
330
|
+
members: {
|
|
331
|
+
New: { kind: "func", params: [STRING], returns: [ERROR] },
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// time package (partial — JS-friendly subset)
|
|
336
|
+
this.globals.define("time", {
|
|
337
|
+
kind: "namespace",
|
|
338
|
+
name: "time",
|
|
339
|
+
members: {
|
|
340
|
+
Now: { kind: "func", params: [], returns: [ANY] },
|
|
341
|
+
Since: { kind: "func", params: [ANY], returns: [ANY] },
|
|
342
|
+
Sleep: { kind: "func", params: [ANY], returns: [VOID], async: true },
|
|
343
|
+
Millisecond: INT,
|
|
344
|
+
Second: INT,
|
|
345
|
+
Minute: INT,
|
|
346
|
+
Hour: INT,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// unicode package
|
|
351
|
+
const runeToStr = { kind: "func", params: [INT], returns: [BOOL] };
|
|
352
|
+
const runeTrans = { kind: "func", params: [INT], returns: [INT] };
|
|
353
|
+
this.globals.define("unicode", {
|
|
354
|
+
kind: "namespace",
|
|
355
|
+
name: "unicode",
|
|
356
|
+
members: {
|
|
357
|
+
IsLetter: runeToStr,
|
|
358
|
+
IsDigit: runeToStr,
|
|
359
|
+
IsSpace: runeToStr,
|
|
360
|
+
IsUpper: runeToStr,
|
|
361
|
+
IsLower: runeToStr,
|
|
362
|
+
IsPunct: runeToStr,
|
|
363
|
+
IsControl: runeToStr,
|
|
364
|
+
IsPrint: runeToStr,
|
|
365
|
+
IsGraphic: runeToStr,
|
|
366
|
+
ToUpper: runeTrans,
|
|
367
|
+
ToLower: runeTrans,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// os package (JS-friendly subset)
|
|
372
|
+
this.globals.define("os", {
|
|
373
|
+
kind: "namespace",
|
|
374
|
+
name: "os",
|
|
375
|
+
members: {
|
|
376
|
+
Exit: { kind: "func", params: [INT], returns: [VOID] },
|
|
377
|
+
Args: { kind: "slice", elem: STRING },
|
|
378
|
+
Getenv: { kind: "func", params: [STRING], returns: [STRING] },
|
|
379
|
+
Stdout: ANY,
|
|
380
|
+
Stderr: ANY,
|
|
381
|
+
Stdin: ANY,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
266
385
|
this.globals.define("append", { kind: "builtin", name: "append" });
|
|
267
386
|
this.globals.define("len", { kind: "builtin", name: "len" });
|
|
268
387
|
this.globals.define("cap", { kind: "builtin", name: "cap" });
|
|
@@ -295,10 +414,37 @@ export class TypeChecker {
|
|
|
295
414
|
addPackageNamespace(pkgName, symbols, types) {
|
|
296
415
|
const members = {};
|
|
297
416
|
for (const [name, type] of symbols) members[name] = type;
|
|
298
|
-
this
|
|
417
|
+
// _gofront: true marks this as a GoFront package — exported identifier rules apply
|
|
418
|
+
this.globals.define(pkgName, {
|
|
419
|
+
kind: "namespace",
|
|
420
|
+
name: pkgName,
|
|
421
|
+
members,
|
|
422
|
+
_gofront: true,
|
|
423
|
+
});
|
|
299
424
|
for (const [name, type] of types) this.types.set(name, type);
|
|
300
425
|
}
|
|
301
426
|
|
|
427
|
+
// Register an import for unused-import detection.
|
|
428
|
+
// `name` is the package name or alias used in code.
|
|
429
|
+
// `node` carries _line for error reporting.
|
|
430
|
+
// `filename` / `source` identify the file containing the import.
|
|
431
|
+
trackImport(name, node, filename, source) {
|
|
432
|
+
this._imports.push({ name, node, filename, source });
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Report any tracked imports whose package name was never referenced.
|
|
436
|
+
reportUnusedImports() {
|
|
437
|
+
for (const { name, node, filename, source } of this._imports) {
|
|
438
|
+
if (!this.globals._used.has(name)) {
|
|
439
|
+
const saved = [this._currentFile, this._currentSource];
|
|
440
|
+
this._currentFile = filename;
|
|
441
|
+
this._currentSource = source;
|
|
442
|
+
this.err(`'${name}' imported and not used`, node);
|
|
443
|
+
[this._currentFile, this._currentSource] = saved;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
302
448
|
// Return a snapshot of all user-defined globals (excludes built-ins).
|
|
303
449
|
// Used by the compiler to expose a package's exports to importers.
|
|
304
450
|
getExportedSymbols() {
|
|
@@ -326,6 +472,12 @@ export class TypeChecker {
|
|
|
326
472
|
return ANY; // recovery type
|
|
327
473
|
}
|
|
328
474
|
|
|
475
|
+
_reportUnused(scope, node) {
|
|
476
|
+
for (const name of scope.unusedLocals()) {
|
|
477
|
+
this.err(`'${name}' declared and not used`, node);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
329
481
|
check(program) {
|
|
330
482
|
this._currentFile = program._filename ?? null;
|
|
331
483
|
this._currentSource = program._source ?? null;
|
|
@@ -508,7 +660,7 @@ export class TypeChecker {
|
|
|
508
660
|
for (const p of decl.params) {
|
|
509
661
|
inner.define(p.name, this.resolveTypeNode(p.type, outer));
|
|
510
662
|
}
|
|
511
|
-
|
|
663
|
+
let returnType = decl.returnType
|
|
512
664
|
? this.resolveTypeNode(decl.returnType, outer)
|
|
513
665
|
: VOID;
|
|
514
666
|
const hasNamedReturns = this._injectNamedReturns(
|
|
@@ -516,9 +668,22 @@ export class TypeChecker {
|
|
|
516
668
|
inner,
|
|
517
669
|
outer,
|
|
518
670
|
);
|
|
519
|
-
|
|
671
|
+
// Wrap the return type to avoid mutating the shared singleton
|
|
672
|
+
if (hasNamedReturns && returnType)
|
|
673
|
+
returnType = { ...returnType, _hasNamedReturns: true };
|
|
674
|
+
const savedDefer = this._deferCount;
|
|
675
|
+
this._deferCount = 0;
|
|
520
676
|
this.checkBlock(decl.body, inner, returnType);
|
|
677
|
+
this._reportUnused(inner, decl);
|
|
678
|
+
if (this._deferCount > 0) decl.body._hasDefer = true;
|
|
679
|
+
this._deferCount = savedDefer;
|
|
521
680
|
decl._returnType = returnType;
|
|
681
|
+
// Missing return check: non-void functions must terminate on all paths
|
|
682
|
+
if (!isVoid(returnType) && !returnType._hasNamedReturns) {
|
|
683
|
+
if (!this._isTerminating(decl.body)) {
|
|
684
|
+
this.err(`missing return at end of function '${decl.name}'`, decl);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
522
687
|
}
|
|
523
688
|
|
|
524
689
|
checkMethodDecl(decl, outer) {
|
|
@@ -528,7 +693,7 @@ export class TypeChecker {
|
|
|
528
693
|
for (const p of decl.params) {
|
|
529
694
|
inner.define(p.name, this.resolveTypeNode(p.type, outer));
|
|
530
695
|
}
|
|
531
|
-
|
|
696
|
+
let returnType = decl.returnType
|
|
532
697
|
? this.resolveTypeNode(decl.returnType, outer)
|
|
533
698
|
: VOID;
|
|
534
699
|
const hasNamedReturns = this._injectNamedReturns(
|
|
@@ -536,9 +701,101 @@ export class TypeChecker {
|
|
|
536
701
|
inner,
|
|
537
702
|
outer,
|
|
538
703
|
);
|
|
539
|
-
if (hasNamedReturns && returnType)
|
|
704
|
+
if (hasNamedReturns && returnType)
|
|
705
|
+
returnType = { ...returnType, _hasNamedReturns: true };
|
|
706
|
+
const savedDefer = this._deferCount;
|
|
707
|
+
this._deferCount = 0;
|
|
540
708
|
this.checkBlock(decl.body, inner, returnType);
|
|
709
|
+
this._reportUnused(inner, decl);
|
|
710
|
+
if (this._deferCount > 0) decl.body._hasDefer = true;
|
|
711
|
+
this._deferCount = savedDefer;
|
|
541
712
|
decl._returnType = returnType;
|
|
713
|
+
// Missing return check
|
|
714
|
+
if (!isVoid(returnType) && !returnType._hasNamedReturns) {
|
|
715
|
+
if (!this._isTerminating(decl.body)) {
|
|
716
|
+
this.err(`missing return at end of method '${decl.name}'`, decl);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Go spec §Terminating statements — returns true if the statement always terminates.
|
|
722
|
+
_isTerminating(stmt) {
|
|
723
|
+
if (!stmt) return false;
|
|
724
|
+
switch (stmt.kind) {
|
|
725
|
+
case "ReturnStmt":
|
|
726
|
+
return true;
|
|
727
|
+
case "BranchStmt":
|
|
728
|
+
// panic() is in ExprStmt, not BranchStmt — but "goto" could be here
|
|
729
|
+
return false;
|
|
730
|
+
case "ExprStmt":
|
|
731
|
+
// panic(...) is a terminating call
|
|
732
|
+
if (
|
|
733
|
+
stmt.expr?.kind === "CallExpr" &&
|
|
734
|
+
stmt.expr?.func?.kind === "Ident" &&
|
|
735
|
+
stmt.expr?.func?.name === "panic"
|
|
736
|
+
)
|
|
737
|
+
return true;
|
|
738
|
+
return false;
|
|
739
|
+
case "Block":
|
|
740
|
+
return this._isTerminatingBlock(stmt);
|
|
741
|
+
case "IfStmt":
|
|
742
|
+
// Terminating if: has else, and both branches terminate
|
|
743
|
+
if (!stmt.elseBody) return false;
|
|
744
|
+
return (
|
|
745
|
+
this._isTerminatingBlock(stmt.body) &&
|
|
746
|
+
(stmt.elseBody.kind === "Block"
|
|
747
|
+
? this._isTerminatingBlock(stmt.elseBody)
|
|
748
|
+
: this._isTerminating(stmt.elseBody))
|
|
749
|
+
);
|
|
750
|
+
case "ForStmt":
|
|
751
|
+
// Infinite loop (no condition) with no break — terminating
|
|
752
|
+
if (!stmt.cond && !this._blockHasBreak(stmt.body)) return true;
|
|
753
|
+
return false;
|
|
754
|
+
case "SwitchStmt":
|
|
755
|
+
// Terminating switch: has default and every case terminates
|
|
756
|
+
return this._isTerminatingSwitch(stmt);
|
|
757
|
+
case "TypeSwitchStmt":
|
|
758
|
+
return this._isTerminatingTypeSwitch(stmt);
|
|
759
|
+
case "LabeledStmt":
|
|
760
|
+
return this._isTerminating(stmt.body);
|
|
761
|
+
default:
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
_isTerminatingBlock(block) {
|
|
767
|
+
if (!block?.stmts?.length) return false;
|
|
768
|
+
return this._isTerminating(block.stmts[block.stmts.length - 1]);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
_blockHasBreak(block) {
|
|
772
|
+
if (!block?.stmts) return false;
|
|
773
|
+
for (const s of block.stmts) {
|
|
774
|
+
if (s.kind === "BranchStmt" && s.keyword === "break") return true;
|
|
775
|
+
}
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
_isTerminatingSwitch(stmt) {
|
|
780
|
+
let hasDefault = false;
|
|
781
|
+
for (const c of stmt.cases ?? []) {
|
|
782
|
+
if (!c.list) hasDefault = true;
|
|
783
|
+
if (!c.stmts?.length) return false;
|
|
784
|
+
const last = c.stmts[c.stmts.length - 1];
|
|
785
|
+
if (!this._isTerminating(last)) return false;
|
|
786
|
+
}
|
|
787
|
+
return hasDefault;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
_isTerminatingTypeSwitch(stmt) {
|
|
791
|
+
let hasDefault = false;
|
|
792
|
+
for (const c of stmt.cases ?? []) {
|
|
793
|
+
if (!c.types) hasDefault = true;
|
|
794
|
+
if (!c.stmts?.length) return false;
|
|
795
|
+
const last = c.stmts[c.stmts.length - 1];
|
|
796
|
+
if (!this._isTerminating(last)) return false;
|
|
797
|
+
}
|
|
798
|
+
return hasDefault;
|
|
542
799
|
}
|
|
543
800
|
|
|
544
801
|
_injectNamedReturns(returnTypeNode, scope, outer) {
|
|
@@ -554,20 +811,21 @@ export class TypeChecker {
|
|
|
554
811
|
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
555
812
|
if (spec.value) {
|
|
556
813
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
557
|
-
if (!type) type = valTypes[0] ?? ANY;
|
|
814
|
+
if (!type) type = defaultType(valTypes[0]) ?? ANY;
|
|
558
815
|
for (let i = 0; i < spec.names.length; i++) {
|
|
559
816
|
const vt = valTypes[i] ?? ANY;
|
|
560
817
|
this.assertAssignable(type, vt, spec.value[i]);
|
|
561
818
|
}
|
|
562
819
|
}
|
|
563
820
|
if (!type) type = ANY;
|
|
564
|
-
for (const name of spec.names) scope.
|
|
821
|
+
for (const name of spec.names) scope.defineLocal(name, type);
|
|
565
822
|
}
|
|
566
823
|
}
|
|
567
824
|
|
|
568
825
|
checkConstDecl(decl, scope) {
|
|
569
826
|
for (const spec of decl.decls) {
|
|
570
827
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
828
|
+
// Explicit type annotation → typed constant; otherwise preserve untyped
|
|
571
829
|
const type = spec.type
|
|
572
830
|
? this.resolveTypeNode(spec.type, scope)
|
|
573
831
|
: (valTypes[0] ?? ANY);
|
|
@@ -577,541 +835,6 @@ export class TypeChecker {
|
|
|
577
835
|
|
|
578
836
|
// ── Statement checking ───────────────────────────────────────
|
|
579
837
|
|
|
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
838
|
// ── Type resolution ──────────────────────────────────────────
|
|
1116
839
|
|
|
1117
840
|
resolveTypeNode(node, scope) {
|
|
@@ -1192,7 +915,11 @@ export class TypeChecker {
|
|
|
1192
915
|
const returns = m.returnType
|
|
1193
916
|
? [this.resolveTypeNode(m.returnType, scope)]
|
|
1194
917
|
: [VOID];
|
|
1195
|
-
|
|
918
|
+
const isVariadic =
|
|
919
|
+
m.params.length > 0 && m.params[m.params.length - 1].variadic;
|
|
920
|
+
const mType = { kind: "func", params, returns };
|
|
921
|
+
if (isVariadic) mType.variadic = true;
|
|
922
|
+
methods.set(m.name, mType);
|
|
1196
923
|
}
|
|
1197
924
|
// Flatten embedded interface methods
|
|
1198
925
|
if (node.embeds) {
|
|
@@ -1262,6 +989,18 @@ export class TypeChecker {
|
|
|
1262
989
|
return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
|
|
1263
990
|
}
|
|
1264
991
|
if (base?.kind === "namespace") {
|
|
992
|
+
// Enforce exported identifier rule for GoFront packages only
|
|
993
|
+
if (
|
|
994
|
+
base._gofront &&
|
|
995
|
+
field.length > 0 &&
|
|
996
|
+
field[0] >= "a" &&
|
|
997
|
+
field[0] <= "z"
|
|
998
|
+
) {
|
|
999
|
+
return this.err(
|
|
1000
|
+
`cannot refer to unexported name ${base.name}.${field}`,
|
|
1001
|
+
node,
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1265
1004
|
if (field in base.members) return base.members[field];
|
|
1266
1005
|
return this.err(`No member '${field}' in namespace ${base.name}`, node);
|
|
1267
1006
|
}
|
|
@@ -1285,14 +1024,49 @@ export class TypeChecker {
|
|
|
1285
1024
|
}
|
|
1286
1025
|
|
|
1287
1026
|
binaryResultType(op, lt, rt, node) {
|
|
1288
|
-
if (CMP_OPS.has(op))
|
|
1027
|
+
if (CMP_OPS.has(op)) {
|
|
1028
|
+
// Reject slice/map/func comparisons unless one side is nil (Go spec)
|
|
1029
|
+
if (op === "==" || op === "!=") {
|
|
1030
|
+
const lNil = isNil(lt);
|
|
1031
|
+
const rNil = isNil(rt);
|
|
1032
|
+
if (!lNil && !rNil) {
|
|
1033
|
+
for (const t of [lt, rt]) {
|
|
1034
|
+
const base = t?.kind === "named" ? t.underlying : t;
|
|
1035
|
+
if (
|
|
1036
|
+
base?.kind === "slice" ||
|
|
1037
|
+
base?.kind === "map" ||
|
|
1038
|
+
base?.kind === "func"
|
|
1039
|
+
) {
|
|
1040
|
+
this.err(`operator ${op} not defined on ${typeStr(t)}`, node);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return BOOL;
|
|
1046
|
+
}
|
|
1289
1047
|
if (LOG_OPS.has(op)) return BOOL;
|
|
1290
1048
|
if (isAny(lt) || isAny(rt)) return ANY;
|
|
1291
1049
|
if (isNumeric(lt) && isNumeric(rt)) {
|
|
1292
|
-
|
|
1293
|
-
|
|
1050
|
+
const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
|
|
1051
|
+
const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
|
|
1052
|
+
const isFloat = lFloat || rFloat;
|
|
1053
|
+
// Both untyped → result is untyped
|
|
1054
|
+
if (lt.kind === "untyped" && rt.kind === "untyped")
|
|
1055
|
+
return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
|
|
1056
|
+
// One typed, one untyped → typed wins
|
|
1057
|
+
if (lt.kind === "untyped")
|
|
1058
|
+
return isFloat && rt.name !== "float64" ? FLOAT64 : rt;
|
|
1059
|
+
if (rt.kind === "untyped")
|
|
1060
|
+
return isFloat && lt.name !== "float64" ? FLOAT64 : lt;
|
|
1061
|
+
// Both typed
|
|
1062
|
+
return isFloat ? FLOAT64 : INT;
|
|
1063
|
+
}
|
|
1064
|
+
if (isString(lt) && isString(rt) && op === "+") {
|
|
1065
|
+
if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
|
|
1066
|
+
if (lt.kind === "untyped") return rt;
|
|
1067
|
+
if (rt.kind === "untyped") return lt;
|
|
1068
|
+
return STRING;
|
|
1294
1069
|
}
|
|
1295
|
-
if (isString(lt) && isString(rt) && op === "+") return STRING;
|
|
1296
1070
|
if (node)
|
|
1297
1071
|
this.err(`Invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
|
|
1298
1072
|
return ANY;
|
|
@@ -1305,6 +1079,29 @@ export class TypeChecker {
|
|
|
1305
1079
|
if (isAny(target) || isAny(source)) return;
|
|
1306
1080
|
if (isNil(source)) return; // nil is assignable to anything nullable
|
|
1307
1081
|
|
|
1082
|
+
// Untyped constants coerce to any compatible typed target
|
|
1083
|
+
if (isUntyped(source)) {
|
|
1084
|
+
if (isUntyped(target)) return; // untyped → untyped always OK
|
|
1085
|
+
if (target.kind === "basic") {
|
|
1086
|
+
// untyped int → int or float64
|
|
1087
|
+
if (
|
|
1088
|
+
source.base === "int" &&
|
|
1089
|
+
(target.name === "int" || target.name === "float64")
|
|
1090
|
+
)
|
|
1091
|
+
return;
|
|
1092
|
+
// untyped float → float64 (and int for compatibility)
|
|
1093
|
+
if (
|
|
1094
|
+
source.base === "float64" &&
|
|
1095
|
+
(target.name === "float64" || target.name === "int")
|
|
1096
|
+
)
|
|
1097
|
+
return;
|
|
1098
|
+
// untyped string → string
|
|
1099
|
+
if (source.base === "string" && target.name === "string") return;
|
|
1100
|
+
// untyped bool → bool
|
|
1101
|
+
if (source.base === "bool" && target.name === "bool") return;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1308
1105
|
// Go untyped constant promotion: int literals are assignable to float64
|
|
1309
1106
|
if (target.kind === "basic" && source.kind === "basic") {
|
|
1310
1107
|
if (target.name === "float64" && source.name === "int") return;
|
|
@@ -1317,6 +1114,8 @@ export class TypeChecker {
|
|
|
1317
1114
|
tBase = this.resolveType(tBase);
|
|
1318
1115
|
sBase = this.resolveType(sBase);
|
|
1319
1116
|
if (tBase?.kind === "interface") {
|
|
1117
|
+
// Empty interface (interface{}) accepts any type
|
|
1118
|
+
if (tBase.methods.size === 0) return;
|
|
1320
1119
|
if (!this.implements(source, tBase, node)) {
|
|
1321
1120
|
this.err(
|
|
1322
1121
|
`${typeStr(source)} does not implement ${typeStr(target)}`,
|
|
@@ -1336,11 +1135,31 @@ export class TypeChecker {
|
|
|
1336
1135
|
for (const [name, required] of iface.methods) {
|
|
1337
1136
|
const actual = base.methods?.get(name);
|
|
1338
1137
|
if (!actual) return false;
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1138
|
+
|
|
1139
|
+
// Check parameter count matches
|
|
1140
|
+
const reqParams = required.params ?? [];
|
|
1141
|
+
const actParams = actual.params ?? [];
|
|
1142
|
+
if (reqParams.length !== actParams.length) return false;
|
|
1143
|
+
|
|
1144
|
+
// Check each parameter type matches
|
|
1145
|
+
for (let i = 0; i < reqParams.length; i++) {
|
|
1146
|
+
if (typeStr(reqParams[i]) !== typeStr(actParams[i])) return false;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Check variadic flag matches
|
|
1150
|
+
if (!!required.variadic !== !!actual.variadic) return false;
|
|
1151
|
+
|
|
1152
|
+
// Check return types match (count and each type)
|
|
1153
|
+
const reqRets = required.returns ?? [];
|
|
1154
|
+
const actRets = actual.returns ?? [];
|
|
1155
|
+
if (reqRets.length !== actRets.length) return false;
|
|
1156
|
+
for (let i = 0; i < reqRets.length; i++) {
|
|
1157
|
+
if (typeStr(reqRets[i]) !== typeStr(actRets[i])) return false;
|
|
1158
|
+
}
|
|
1343
1159
|
}
|
|
1344
1160
|
return true;
|
|
1345
1161
|
}
|
|
1346
1162
|
}
|
|
1163
|
+
|
|
1164
|
+
Object.assign(TypeChecker.prototype, statementCheckMethods);
|
|
1165
|
+
Object.assign(TypeChecker.prototype, expressionCheckMethods);
|