gofront 0.0.9 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/package.json +7 -4
- package/src/cli-core.js +65 -0
- package/src/codegen/expressions.js +523 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +308 -296
- package/src/codegen/stdlib/builder.js +39 -0
- package/src/codegen/stdlib/bytes.js +65 -0
- package/src/codegen/stdlib/errors.js +20 -0
- package/src/codegen/stdlib/fmt.js +104 -0
- package/src/codegen/stdlib/gom.js +155 -0
- package/src/codegen/stdlib/html.js +20 -0
- package/src/codegen/stdlib/index.js +98 -0
- package/src/codegen/stdlib/io.js +36 -0
- package/src/codegen/stdlib/maps.js +45 -0
- package/src/codegen/stdlib/math.js +56 -0
- package/src/codegen/stdlib/os.js +13 -0
- package/src/codegen/stdlib/path.js +34 -0
- package/src/codegen/stdlib/rand.js +27 -0
- package/src/codegen/stdlib/regexp.js +54 -0
- package/src/codegen/stdlib/slices.js +51 -0
- package/src/codegen/stdlib/sort.js +31 -0
- package/src/codegen/stdlib/strconv.js +33 -0
- package/src/codegen/stdlib/strings.js +86 -0
- package/src/codegen/stdlib/time.js +57 -0
- package/src/codegen/stdlib/unicode.js +25 -0
- package/src/codegen/stdlib/utf8.js +26 -0
- package/src/codegen/templ.js +284 -0
- package/src/compiler.js +142 -77
- package/src/dts-parser.js +355 -371
- package/src/index.js +32 -162
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/{parser.js → parser/declarations.js} +18 -175
- package/src/parser/expressions.js +51 -34
- package/src/parser/index.js +161 -0
- package/src/parser/statements.js +24 -34
- package/src/parser/types.js +31 -29
- package/src/templ-parser.js +2 -2
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +261 -0
- package/src/typechecker/expressions.js +568 -549
- package/src/typechecker/index.js +556 -0
- package/src/typechecker/resolve.js +428 -0
- package/src/typechecker/statements.js +270 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +86 -0
- package/src/typechecker/types.js +1 -0
- package/src/codegen.js +0 -882
- package/src/typechecker.js +0 -1299
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
// Type checker: walks the AST, resolves types for all expressions,
|
|
2
|
+
// and reports type errors at compile time.
|
|
3
|
+
//
|
|
4
|
+
// Split into sub-modules under typechecker/:
|
|
5
|
+
// types.js — shared type constants, predicates, Scope
|
|
6
|
+
// stdlib.js — browser globals + all built-in package registrations
|
|
7
|
+
// statements.js — checkBlock, checkStmt
|
|
8
|
+
// expressions.js — checkExpr, checkCall, checkBuiltin, checkCompositeLit
|
|
9
|
+
// termination.js — _isTerminating* family
|
|
10
|
+
// resolve.js — resolveTypeNode, fieldType, generics
|
|
11
|
+
// assignability.js — assertAssignable, binaryResultType, implements
|
|
12
|
+
|
|
13
|
+
import { assignabilityMethods } from "./assignability.js";
|
|
14
|
+
import { expressionCheckMethods } from "./expressions.js";
|
|
15
|
+
import { resolveMethods } from "./resolve.js";
|
|
16
|
+
import { statementCheckMethods } from "./statements.js";
|
|
17
|
+
import { setupGlobals } from "./stdlib.js";
|
|
18
|
+
import { terminationMethods } from "./termination.js";
|
|
19
|
+
import {
|
|
20
|
+
ANY,
|
|
21
|
+
defaultType,
|
|
22
|
+
ERROR,
|
|
23
|
+
isVoid,
|
|
24
|
+
Scope,
|
|
25
|
+
TAINTED_ANY,
|
|
26
|
+
TypeCheckError,
|
|
27
|
+
typeStr,
|
|
28
|
+
VOID,
|
|
29
|
+
} from "./types.js";
|
|
30
|
+
|
|
31
|
+
// Re-export for consumers that import from typechecker.js
|
|
32
|
+
export { TypeCheckError, typeStr };
|
|
33
|
+
|
|
34
|
+
export class TypeChecker {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.types = new Map(); // named types
|
|
37
|
+
this.globals = new Scope();
|
|
38
|
+
this.errors = [];
|
|
39
|
+
this._currentFile = null; // filename of file currently being checked
|
|
40
|
+
this._currentSource = null;
|
|
41
|
+
this._loopDepth = 0; // for break/continue validation
|
|
42
|
+
this._switchDepth = 0; // for break/fallthrough validation
|
|
43
|
+
this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
|
|
44
|
+
this._deferCount = 0; // tracks defer usage in current function body
|
|
45
|
+
this._imports = []; // tracked imports for unused-import detection
|
|
46
|
+
this._setupGlobals();
|
|
47
|
+
// Register error as a named type (interface)
|
|
48
|
+
this.types.set("error", ERROR);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_setupGlobals() {
|
|
52
|
+
setupGlobals(this.globals, this.types);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
addDefinitions(types, values) {
|
|
56
|
+
for (const [name, type] of types) {
|
|
57
|
+
this.types.set(name, type);
|
|
58
|
+
}
|
|
59
|
+
for (const [name, type] of values) {
|
|
60
|
+
this.globals.define(name, type);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Add an imported GoFront package as a qualified namespace.
|
|
65
|
+
// e.g. addPackageNamespace('utils', symbolsMap, typesMap)
|
|
66
|
+
// lets callers type-check `utils.Foo` via SelectorExpr.
|
|
67
|
+
addPackageNamespace(pkgName, symbols, types) {
|
|
68
|
+
const members = {};
|
|
69
|
+
for (const [name, type] of symbols) members[name] = type;
|
|
70
|
+
// _gofront: true marks this as a GoFront package — exported identifier rules apply
|
|
71
|
+
this.globals.define(pkgName, {
|
|
72
|
+
kind: "namespace",
|
|
73
|
+
name: pkgName,
|
|
74
|
+
members,
|
|
75
|
+
_gofront: true,
|
|
76
|
+
});
|
|
77
|
+
for (const [name, type] of types) {
|
|
78
|
+
this.types.set(name, type);
|
|
79
|
+
this.types.set(`${pkgName}.${name}`, type);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Register an import for unused-import detection.
|
|
84
|
+
// `name` is the package name or alias used in code.
|
|
85
|
+
// `node` carries _line for error reporting.
|
|
86
|
+
// `filename` / `source` identify the file containing the import.
|
|
87
|
+
trackImport(name, node, filename, source) {
|
|
88
|
+
this._imports.push({ name, node, filename, source });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Report any tracked imports whose package name was never referenced.
|
|
92
|
+
reportUnusedImports() {
|
|
93
|
+
for (const { name, node, filename, source } of this._imports) {
|
|
94
|
+
if (!this.globals._used.has(name)) {
|
|
95
|
+
const saved = [this._currentFile, this._currentSource];
|
|
96
|
+
this._currentFile = filename;
|
|
97
|
+
this._currentSource = source;
|
|
98
|
+
this.err(`'${name}' imported and not used`, node);
|
|
99
|
+
[this._currentFile, this._currentSource] = saved;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Return a snapshot of all user-defined globals (excludes built-ins).
|
|
105
|
+
// Used by the compiler to expose a package's exports to importers.
|
|
106
|
+
getExportedSymbols() {
|
|
107
|
+
const out = new Map();
|
|
108
|
+
for (const [name, type] of this.globals.symbols) {
|
|
109
|
+
if (type?.kind === "builtin") continue; // skip built-ins
|
|
110
|
+
if (type === ANY && !name[0].match(/[A-Z]/)) continue; // skip browser globals
|
|
111
|
+
out.set(name, type);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getExportedTypes() {
|
|
117
|
+
return new Map(this.types);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
err(msg, node) {
|
|
121
|
+
const e = new TypeCheckError(
|
|
122
|
+
msg,
|
|
123
|
+
node,
|
|
124
|
+
this._currentFile,
|
|
125
|
+
this._currentSource,
|
|
126
|
+
);
|
|
127
|
+
this.errors.push(e);
|
|
128
|
+
return TAINTED_ANY; // tainted recovery type — suppresses downstream cascade errors
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_reportUnused(scope, node) {
|
|
132
|
+
for (const name of scope.unusedLocals()) {
|
|
133
|
+
this.err(`'${name}' declared and not used`, node);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_setCurrentFile(p) {
|
|
138
|
+
this._currentFile = p._filename ?? null;
|
|
139
|
+
this._currentSource = p._source ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
check(program) {
|
|
143
|
+
this._setCurrentFile(program);
|
|
144
|
+
// Pass 1: collect type declarations
|
|
145
|
+
for (const decl of program.decls) {
|
|
146
|
+
if (decl.kind === "TypeDecl") this.collectType(decl);
|
|
147
|
+
}
|
|
148
|
+
// Pass 2: collect function / method signatures
|
|
149
|
+
for (const decl of program.decls) {
|
|
150
|
+
if (decl.kind === "FuncDecl" || decl.kind === "MethodDecl") {
|
|
151
|
+
this.collectFunc(decl);
|
|
152
|
+
} else if (decl.kind === "TemplDecl") {
|
|
153
|
+
this._collectTemplDecl(decl);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Pass 2.1: Promote embedded methods
|
|
157
|
+
this._promoteEmbeddedMethods();
|
|
158
|
+
// Pass 3: check bodies
|
|
159
|
+
for (const decl of program.decls) {
|
|
160
|
+
this.checkTopDecl(decl, this.globals);
|
|
161
|
+
}
|
|
162
|
+
return this.errors;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Pre-declare a package-level var so other files can reference its name
|
|
166
|
+
// before we've type-checked its initializer. Called in pass 2.5.
|
|
167
|
+
collectVar(decl) {
|
|
168
|
+
for (const spec of decl.decls) {
|
|
169
|
+
const type = spec.type
|
|
170
|
+
? this.resolveTypeNode(spec.type, this.globals)
|
|
171
|
+
: ANY;
|
|
172
|
+
for (const name of spec.names) this.globals.define(name, type);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
collectConst(decl) {
|
|
177
|
+
for (const spec of decl.decls) {
|
|
178
|
+
const type = spec.type
|
|
179
|
+
? this.resolveTypeNode(spec.type, this.globals)
|
|
180
|
+
: ANY;
|
|
181
|
+
for (const name of spec.names) this.globals.defineConst(name, type);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Like check() but operates over multiple programs (same-package multi-file).
|
|
186
|
+
// All passes run across all files before moving to the next pass,
|
|
187
|
+
// so every file sees every other file's declarations.
|
|
188
|
+
checkAll(programs) {
|
|
189
|
+
this._collectTypesPass(programs);
|
|
190
|
+
this._collectFuncsPass(programs);
|
|
191
|
+
this._promoteEmbeddedMethods();
|
|
192
|
+
this._collectVarsConstsPass(programs);
|
|
193
|
+
this._checkTopDeclsPass(programs);
|
|
194
|
+
return this.errors;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_collectTypesPass(programs) {
|
|
198
|
+
for (const p of programs) {
|
|
199
|
+
this._setCurrentFile(p);
|
|
200
|
+
for (const d of p.decls) if (d.kind === "TypeDecl") this.collectType(d);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
_collectFuncsPass(programs) {
|
|
205
|
+
for (const p of programs) {
|
|
206
|
+
this._setCurrentFile(p);
|
|
207
|
+
for (const d of p.decls) {
|
|
208
|
+
if (d.kind === "FuncDecl" || d.kind === "MethodDecl")
|
|
209
|
+
this.collectFunc(d);
|
|
210
|
+
else if (d.kind === "TemplDecl") this._collectTemplDecl(d);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
_collectVarsConstsPass(programs) {
|
|
216
|
+
for (const p of programs) {
|
|
217
|
+
this._setCurrentFile(p);
|
|
218
|
+
for (const d of p.decls) {
|
|
219
|
+
if (d.kind === "VarDecl") this.collectVar(d);
|
|
220
|
+
if (d.kind === "ConstDecl") this.collectConst(d);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_checkTopDeclsPass(programs) {
|
|
226
|
+
for (const p of programs) {
|
|
227
|
+
this._setCurrentFile(p);
|
|
228
|
+
for (const d of p.decls) this.checkTopDecl(d, this.globals);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── Shared pass helpers ──────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
_promoteEmbeddedMethods() {
|
|
235
|
+
for (const type of this.types.values()) {
|
|
236
|
+
const struct = type.underlying;
|
|
237
|
+
if (struct?.kind === "struct" && struct._embeds) {
|
|
238
|
+
for (const embed of struct._embeds) {
|
|
239
|
+
const base = embed.kind === "named" ? embed.underlying : embed;
|
|
240
|
+
if (base?.kind !== "struct" || !base.methods) continue;
|
|
241
|
+
for (const [mName, mType] of base.methods.entries()) {
|
|
242
|
+
if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── Type collection ──────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
collectType(decl) {
|
|
252
|
+
if (decl.typeParams) {
|
|
253
|
+
// Generic type — resolve underlying with type params as ANY for struct fields
|
|
254
|
+
const typeScope = new Scope(this.globals);
|
|
255
|
+
const typeParamTypes = decl.typeParams.map((tp) => {
|
|
256
|
+
const t = {
|
|
257
|
+
kind: "typeParam",
|
|
258
|
+
name: tp.name,
|
|
259
|
+
constraint: tp.constraint,
|
|
260
|
+
};
|
|
261
|
+
typeScope.define(tp.name, t);
|
|
262
|
+
return t;
|
|
263
|
+
});
|
|
264
|
+
const underlying = this.resolveTypeNode(decl.type, typeScope);
|
|
265
|
+
const named = {
|
|
266
|
+
kind: "named",
|
|
267
|
+
name: decl.name,
|
|
268
|
+
underlying,
|
|
269
|
+
_generic: {
|
|
270
|
+
typeParams: typeParamTypes,
|
|
271
|
+
declNode: decl,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
if (underlying.kind === "struct") {
|
|
275
|
+
underlying.name = decl.name;
|
|
276
|
+
underlying.methods = new Map();
|
|
277
|
+
}
|
|
278
|
+
this.types.set(decl.name, named);
|
|
279
|
+
this.globals.define(decl.name, named);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const underlying = this.resolveTypeNode(decl.type, this.globals);
|
|
283
|
+
if (decl.isAlias) {
|
|
284
|
+
// type A = B — transparent alias, A and B are identical types
|
|
285
|
+
this.types.set(decl.name, underlying);
|
|
286
|
+
this.globals.define(decl.name, underlying);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const named = { kind: "named", name: decl.name, underlying };
|
|
290
|
+
if (underlying.kind === "struct") {
|
|
291
|
+
underlying.name = decl.name;
|
|
292
|
+
underlying.methods = new Map();
|
|
293
|
+
} else if (underlying.kind !== "interface") {
|
|
294
|
+
named.methods = new Map();
|
|
295
|
+
}
|
|
296
|
+
if (underlying.kind === "interface") {
|
|
297
|
+
underlying.name = decl.name;
|
|
298
|
+
}
|
|
299
|
+
this.types.set(decl.name, named);
|
|
300
|
+
this.globals.define(decl.name, named);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
_buildCollectFuncScope(decl) {
|
|
304
|
+
let resolveScope = this.globals;
|
|
305
|
+
let typeParamTypes = null;
|
|
306
|
+
if (decl.typeParams) {
|
|
307
|
+
resolveScope = new Scope(this.globals);
|
|
308
|
+
typeParamTypes = decl.typeParams.map((tp) => {
|
|
309
|
+
const t = {
|
|
310
|
+
kind: "typeParam",
|
|
311
|
+
name: tp.name,
|
|
312
|
+
constraint: tp.constraint,
|
|
313
|
+
};
|
|
314
|
+
resolveScope.define(tp.name, t);
|
|
315
|
+
return t;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (decl.kind === "MethodDecl") {
|
|
319
|
+
const recvNamedType = this.types.get(decl.recvType.name);
|
|
320
|
+
if (recvNamedType?._generic) {
|
|
321
|
+
resolveScope = new Scope(this.globals);
|
|
322
|
+
for (const tp of recvNamedType._generic.typeParams)
|
|
323
|
+
resolveScope.define(tp.name, tp);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return { resolveScope, typeParamTypes };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
_registerFuncDecl(decl, funcType, typeParamTypes) {
|
|
330
|
+
if (this.globals.symbols.has(decl.name) && decl.name !== "init")
|
|
331
|
+
this.err(`${decl.name} redeclared in this block`, decl);
|
|
332
|
+
if (typeParamTypes) {
|
|
333
|
+
this.globals.define(decl.name, {
|
|
334
|
+
kind: "generic",
|
|
335
|
+
name: decl.name,
|
|
336
|
+
typeParams: typeParamTypes,
|
|
337
|
+
underlying: funcType,
|
|
338
|
+
});
|
|
339
|
+
} else {
|
|
340
|
+
this.globals.define(decl.name, funcType);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
collectFunc(decl) {
|
|
345
|
+
const { resolveScope, typeParamTypes } = this._buildCollectFuncScope(decl);
|
|
346
|
+
const paramTypes = decl.params.map((p) =>
|
|
347
|
+
this.resolveTypeNode(p.type, resolveScope),
|
|
348
|
+
);
|
|
349
|
+
const returnType = decl.returnType
|
|
350
|
+
? this.resolveTypeNode(decl.returnType, resolveScope)
|
|
351
|
+
: VOID;
|
|
352
|
+
const isVariadic =
|
|
353
|
+
decl.params.length > 0 && decl.params[decl.params.length - 1].variadic;
|
|
354
|
+
const funcType = {
|
|
355
|
+
kind: "func",
|
|
356
|
+
params: paramTypes,
|
|
357
|
+
returns: [returnType],
|
|
358
|
+
variadic: isVariadic,
|
|
359
|
+
async: decl.async ?? false,
|
|
360
|
+
};
|
|
361
|
+
if (decl.kind === "FuncDecl")
|
|
362
|
+
this._registerFuncDecl(decl, funcType, typeParamTypes);
|
|
363
|
+
else this._attachMethod(decl, funcType);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
_attachGenericMethod(recvNamedType, decl, funcType) {
|
|
367
|
+
if (!recvNamedType._generic.methods)
|
|
368
|
+
recvNamedType._generic.methods = new Map();
|
|
369
|
+
recvNamedType._generic.methods.set(decl.name, funcType);
|
|
370
|
+
const base = recvNamedType.underlying;
|
|
371
|
+
if (base?.kind === "struct") base.methods.set(decl.name, funcType);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
_attachNonGenericMethod(decl, funcType) {
|
|
375
|
+
const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
|
|
376
|
+
const base = recvType?.underlying ?? recvType;
|
|
377
|
+
if (base?.kind === "struct") {
|
|
378
|
+
base.methods.set(decl.name, funcType);
|
|
379
|
+
} else if (recvType?.kind === "named" && recvType.methods) {
|
|
380
|
+
recvType.methods.set(decl.name, funcType);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
_attachMethod(decl, funcType) {
|
|
385
|
+
const recvNamedType = this.types.get(decl.recvType.name);
|
|
386
|
+
if (recvNamedType?._generic)
|
|
387
|
+
this._attachGenericMethod(recvNamedType, decl, funcType);
|
|
388
|
+
else this._attachNonGenericMethod(decl, funcType);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ── Top-level checker ────────────────────────────────────────
|
|
392
|
+
|
|
393
|
+
checkTopDecl(decl, scope) {
|
|
394
|
+
switch (decl.kind) {
|
|
395
|
+
case "FuncDecl":
|
|
396
|
+
this.checkFuncDecl(decl, scope);
|
|
397
|
+
break;
|
|
398
|
+
case "MethodDecl":
|
|
399
|
+
this.checkMethodDecl(decl, scope);
|
|
400
|
+
break;
|
|
401
|
+
case "VarDecl":
|
|
402
|
+
this.checkVarDecl(decl, scope);
|
|
403
|
+
break;
|
|
404
|
+
case "ConstDecl":
|
|
405
|
+
this.checkConstDecl(decl, scope);
|
|
406
|
+
break;
|
|
407
|
+
case "TypeDecl":
|
|
408
|
+
case "TemplDecl":
|
|
409
|
+
break; // already collected / no body to check
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
_collectTemplDecl(decl) {
|
|
414
|
+
const paramTypes = decl.params.map((p) =>
|
|
415
|
+
this.resolveTypeNode(p.type, this.globals),
|
|
416
|
+
);
|
|
417
|
+
const gomNodeType = this.types.get("gom.Node") ?? {
|
|
418
|
+
kind: "basic",
|
|
419
|
+
name: "any",
|
|
420
|
+
};
|
|
421
|
+
const isVariadic =
|
|
422
|
+
decl.params.length > 0 && decl.params[decl.params.length - 1].variadic;
|
|
423
|
+
const funcType = {
|
|
424
|
+
kind: "func",
|
|
425
|
+
params: paramTypes,
|
|
426
|
+
returns: [gomNodeType],
|
|
427
|
+
variadic: isVariadic,
|
|
428
|
+
async: false,
|
|
429
|
+
};
|
|
430
|
+
if (this.globals.symbols.has(decl.name) && decl.name !== "init") {
|
|
431
|
+
this.err(`${decl.name} redeclared in this block`, decl);
|
|
432
|
+
}
|
|
433
|
+
this.globals.define(decl.name, funcType);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
checkFuncDecl(decl, outer) {
|
|
437
|
+
const inner = new Scope(outer);
|
|
438
|
+
this._injectTypeParams(decl, inner, outer);
|
|
439
|
+
for (const p of decl.params) {
|
|
440
|
+
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
441
|
+
}
|
|
442
|
+
const returnType = this._setupFuncReturnType(decl, inner, outer);
|
|
443
|
+
this._runFuncBody(decl, inner, returnType);
|
|
444
|
+
this._checkMissingReturn(decl, returnType, "function");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
checkMethodDecl(decl, outer) {
|
|
448
|
+
const inner = new Scope(outer);
|
|
449
|
+
const recvTypeName =
|
|
450
|
+
decl.recvType.kind === "GenericTypeName"
|
|
451
|
+
? decl.recvType.name
|
|
452
|
+
: decl.recvType.name;
|
|
453
|
+
const recvNamedType = this.types.get(recvTypeName);
|
|
454
|
+
this._injectGenericReceiverTypeParams(inner, recvNamedType);
|
|
455
|
+
const recvType = this.resolveTypeNodeName(decl.recvType, outer);
|
|
456
|
+
inner.define(decl.recvName, recvType);
|
|
457
|
+
for (const p of decl.params) {
|
|
458
|
+
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
459
|
+
}
|
|
460
|
+
const returnType = this._setupFuncReturnType(decl, inner, inner);
|
|
461
|
+
this._runFuncBody(decl, inner, returnType);
|
|
462
|
+
this._checkMissingReturn(decl, returnType, "method");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
_injectTypeParams(decl, inner, outer) {
|
|
466
|
+
if (!decl.typeParams) return;
|
|
467
|
+
for (const tp of decl.typeParams) {
|
|
468
|
+
const constraint = tp.constraint
|
|
469
|
+
? this.resolveTypeNode(tp.constraint, outer)
|
|
470
|
+
: ANY;
|
|
471
|
+
inner.define(tp.name, { kind: "typeParam", name: tp.name, constraint });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
_injectGenericReceiverTypeParams(inner, recvNamedType) {
|
|
476
|
+
if (!recvNamedType?._generic) return;
|
|
477
|
+
for (const tp of recvNamedType._generic.typeParams) {
|
|
478
|
+
inner.define(tp.name, tp);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
_setupFuncReturnType(decl, inner, outer) {
|
|
483
|
+
let returnType = decl.returnType
|
|
484
|
+
? this.resolveTypeNode(decl.returnType, inner)
|
|
485
|
+
: VOID;
|
|
486
|
+
const hasNamedReturns = this._injectNamedReturns(
|
|
487
|
+
decl.returnType,
|
|
488
|
+
inner,
|
|
489
|
+
outer,
|
|
490
|
+
);
|
|
491
|
+
if (hasNamedReturns && returnType)
|
|
492
|
+
returnType = { ...returnType, _hasNamedReturns: true };
|
|
493
|
+
return returnType;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
_runFuncBody(decl, inner, returnType) {
|
|
497
|
+
const savedDefer = this._deferCount;
|
|
498
|
+
this._deferCount = 0;
|
|
499
|
+
this.checkBlock(decl.body, inner, returnType);
|
|
500
|
+
this._reportUnused(inner, decl);
|
|
501
|
+
if (this._deferCount > 0) decl.body._hasDefer = true;
|
|
502
|
+
this._deferCount = savedDefer;
|
|
503
|
+
decl._returnType = returnType;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
_checkMissingReturn(decl, returnType, kind) {
|
|
507
|
+
if (!isVoid(returnType) && !returnType._hasNamedReturns) {
|
|
508
|
+
if (!this._isTerminating(decl.body)) {
|
|
509
|
+
this.err(`missing return at end of ${kind} '${decl.name}'`, decl);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
_injectNamedReturns(returnTypeNode, scope, outer) {
|
|
515
|
+
if (!returnTypeNode?._namedReturns) return false;
|
|
516
|
+
for (const { name, type } of returnTypeNode._namedReturns) {
|
|
517
|
+
if (name) scope.define(name, this.resolveTypeNode(type, outer));
|
|
518
|
+
}
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
checkVarDecl(decl, scope) {
|
|
523
|
+
for (const spec of decl.decls) {
|
|
524
|
+
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
525
|
+
if (spec.value) {
|
|
526
|
+
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
527
|
+
if (!type) type = defaultType(valTypes[0]) ?? ANY;
|
|
528
|
+
for (let i = 0; i < spec.names.length; i++) {
|
|
529
|
+
const vt = valTypes[i] ?? ANY;
|
|
530
|
+
this.assertAssignable(type, vt, spec.value[i]);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (!type) type = ANY;
|
|
534
|
+
for (const name of spec.names) scope.defineLocal(name, type);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
checkConstDecl(decl, scope) {
|
|
539
|
+
for (const spec of decl.decls) {
|
|
540
|
+
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
541
|
+
// Explicit type annotation → typed constant; otherwise preserve untyped
|
|
542
|
+
const type = spec.type
|
|
543
|
+
? this.resolveTypeNode(spec.type, scope)
|
|
544
|
+
: (valTypes[0] ?? ANY);
|
|
545
|
+
for (const name of spec.names) scope.defineConst(name, type);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ── Statement checking ───────────────────────────────────────
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
Object.assign(TypeChecker.prototype, statementCheckMethods);
|
|
553
|
+
Object.assign(TypeChecker.prototype, expressionCheckMethods);
|
|
554
|
+
Object.assign(TypeChecker.prototype, terminationMethods);
|
|
555
|
+
Object.assign(TypeChecker.prototype, resolveMethods);
|
|
556
|
+
Object.assign(TypeChecker.prototype, assignabilityMethods);
|