gofront 1.0.0 → 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 +1 -1
- package/package.json +1 -1
- package/src/cli-core.js +65 -0
- package/src/codegen/expressions.js +3 -0
- package/src/codegen/statements.js +3 -0
- package/src/codegen/stdlib/builder.js +3 -0
- package/src/codegen/stdlib/bytes.js +3 -0
- package/src/codegen/stdlib/errors.js +3 -0
- package/src/codegen/stdlib/fmt.js +3 -0
- package/src/codegen/stdlib/gom.js +3 -0
- package/src/codegen/stdlib/html.js +3 -0
- package/src/codegen/stdlib/index.js +3 -0
- package/src/codegen/stdlib/io.js +3 -0
- package/src/codegen/stdlib/maps.js +3 -0
- package/src/codegen/stdlib/math.js +3 -0
- package/src/codegen/stdlib/os.js +3 -0
- package/src/codegen/stdlib/path.js +3 -0
- package/src/codegen/stdlib/rand.js +3 -0
- package/src/codegen/stdlib/regexp.js +3 -0
- package/src/codegen/stdlib/slices.js +3 -0
- package/src/codegen/stdlib/sort.js +3 -0
- package/src/codegen/stdlib/strconv.js +3 -0
- package/src/codegen/stdlib/strings.js +3 -0
- package/src/codegen/stdlib/time.js +3 -0
- package/src/codegen/stdlib/unicode.js +3 -0
- package/src/codegen/stdlib/utf8.js +3 -0
- package/src/codegen/templ.js +3 -0
- package/src/compiler.js +63 -4
- package/src/index.js +32 -126
- package/src/parser/declarations.js +347 -0
- package/src/parser/index.js +3 -346
- package/src/typechecker/assignability.js +3 -0
- package/src/typechecker/expressions.js +9 -0
- package/src/typechecker/index.js +2 -1
- package/src/typechecker/resolve.js +9 -2
- package/src/typechecker/statements.js +3 -0
- package/src/typechecker/termination.js +3 -0
- package/src/typechecker/types.js +1 -0
package/src/parser/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Recursive-descent parser. Produces an AST from a GoFront token stream.
|
|
2
2
|
//
|
|
3
3
|
// Split into sub-modules under parser/:
|
|
4
|
+
// declarations.js — top-level declarations (func, method, type, var, const)
|
|
4
5
|
// types.js — type expression parsing (slice, map, struct, interface, etc.)
|
|
5
6
|
// statements.js — block, control flow, simple statements
|
|
6
7
|
// expressions.js — operator precedence, unary, postfix, primary, literals
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
// CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
|
|
18
19
|
|
|
19
20
|
import { T } from "../tokens.js";
|
|
21
|
+
import { declarationParseMethods } from "./declarations.js";
|
|
20
22
|
import { expressionParserMethods } from "./expressions.js";
|
|
21
23
|
import { statementParserMethods } from "./statements.js";
|
|
22
24
|
import { typeParserMethods } from "./types.js";
|
|
@@ -151,354 +153,9 @@ export class Parser {
|
|
|
151
153
|
this.semi();
|
|
152
154
|
return { kind: "ImportDecl", imports };
|
|
153
155
|
}
|
|
154
|
-
|
|
155
|
-
// ── Top-level declarations ───────────────────────────────────
|
|
156
|
-
|
|
157
|
-
parseTopDecl() {
|
|
158
|
-
const t = this.peek();
|
|
159
|
-
switch (t.type) {
|
|
160
|
-
case T.ASYNC:
|
|
161
|
-
return this.parseAsyncFuncOrMethod();
|
|
162
|
-
case T.FUNC:
|
|
163
|
-
return this.parseFuncOrMethod();
|
|
164
|
-
case T.TYPE:
|
|
165
|
-
return this.parseTypeDecl();
|
|
166
|
-
case T.VAR:
|
|
167
|
-
return this.parseVarDecl();
|
|
168
|
-
case T.CONST:
|
|
169
|
-
return this.parseConstDecl();
|
|
170
|
-
default:
|
|
171
|
-
this.err("Expected top-level declaration");
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
parseFuncOrMethod() {
|
|
176
|
-
const _line = this.peek().line;
|
|
177
|
-
this.expect(T.FUNC);
|
|
178
|
-
// method: func (recv Type) Name(...)
|
|
179
|
-
if (this.check(T.LPAREN)) {
|
|
180
|
-
this.expect(T.LPAREN);
|
|
181
|
-
const recvName =
|
|
182
|
-
this.check(T.IDENT) && !this.isReceiverTerminator(this.peek2())
|
|
183
|
-
? this.advance().value
|
|
184
|
-
: "_";
|
|
185
|
-
this.match(T.STAR); // pointer receiver: (c *Counter) — strip *, treat same as value receiver
|
|
186
|
-
const recvType = this.parseTypeName();
|
|
187
|
-
this.expect(T.RPAREN);
|
|
188
|
-
const name = this.expect(T.IDENT).value;
|
|
189
|
-
const { params, returnType } = this.parseSignature();
|
|
190
|
-
const body = this.parseBlock();
|
|
191
|
-
this.semi();
|
|
192
|
-
return {
|
|
193
|
-
kind: "MethodDecl",
|
|
194
|
-
recvName,
|
|
195
|
-
recvType,
|
|
196
|
-
name,
|
|
197
|
-
params,
|
|
198
|
-
returnType,
|
|
199
|
-
body,
|
|
200
|
-
_line,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
// plain function
|
|
204
|
-
const name = this.expect(T.IDENT).value;
|
|
205
|
-
// generic function: func Name[T any, U Stringer](...) ...
|
|
206
|
-
let typeParams = null;
|
|
207
|
-
if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
|
|
208
|
-
typeParams = this.parseTypeParamList();
|
|
209
|
-
}
|
|
210
|
-
const { params, returnType } = this.parseSignature();
|
|
211
|
-
const body = this.parseBlock();
|
|
212
|
-
this.semi();
|
|
213
|
-
return {
|
|
214
|
-
kind: "FuncDecl",
|
|
215
|
-
name,
|
|
216
|
-
typeParams,
|
|
217
|
-
params,
|
|
218
|
-
returnType,
|
|
219
|
-
body,
|
|
220
|
-
_line,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
parseAsyncFuncOrMethod() {
|
|
225
|
-
this.expect(T.ASYNC);
|
|
226
|
-
const decl = this.parseFuncOrMethod();
|
|
227
|
-
decl.async = true;
|
|
228
|
-
return decl;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Returns true if tok is a receiver-list terminator (i.e. not a type name)
|
|
232
|
-
isReceiverTerminator(tok) {
|
|
233
|
-
return (
|
|
234
|
-
tok &&
|
|
235
|
-
(tok.type === T.RPAREN ||
|
|
236
|
-
tok.type === T.COMMA ||
|
|
237
|
-
tok.type === T.SEMICOLON ||
|
|
238
|
-
tok.type === T.EOF)
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Lookahead: does the [ ... ] after a func name or type name look like a type param list?
|
|
243
|
-
// Type param lists contain IDENT followed by a constraint (another IDENT or keyword),
|
|
244
|
-
// not expressions like integers or operators.
|
|
245
|
-
_looksLikeTypeParamList() {
|
|
246
|
-
const i = this.pos + 1; // skip [
|
|
247
|
-
const src = this.tokens;
|
|
248
|
-
// First token must be an IDENT (the type param name)
|
|
249
|
-
if (!src[i] || src[i].type !== T.IDENT) return false;
|
|
250
|
-
// Second token should be a constraint (IDENT like 'any', 'comparable', another type name)
|
|
251
|
-
// or TILDE for union constraints
|
|
252
|
-
const second = src[i + 1];
|
|
253
|
-
if (!second) return false;
|
|
254
|
-
if (second.type === T.IDENT) return true; // T any, T Stringer
|
|
255
|
-
if (second.type === T.TILDE) return true; // T ~int
|
|
256
|
-
if (second.type === T.INTERFACE) return true; // T interface{...}
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
parseSignature() {
|
|
261
|
-
const params = this.parseParamList();
|
|
262
|
-
const returnType = this.parseReturnType();
|
|
263
|
-
return { params, returnType };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Parses (a, b int, c string) style param list
|
|
267
|
-
parseParamList() {
|
|
268
|
-
this.expect(T.LPAREN);
|
|
269
|
-
const params = [];
|
|
270
|
-
if (!this.check(T.RPAREN)) {
|
|
271
|
-
do {
|
|
272
|
-
if (this.check(T.RPAREN)) break;
|
|
273
|
-
// Collect names, then type
|
|
274
|
-
const names = [];
|
|
275
|
-
if (this.check(T.IDENT) && this._isNamedParam()) {
|
|
276
|
-
// named: a, b int or ns ...int
|
|
277
|
-
names.push(this.advance().value);
|
|
278
|
-
while (
|
|
279
|
-
this.match(T.COMMA) &&
|
|
280
|
-
this.check(T.IDENT) &&
|
|
281
|
-
this._isNamedParam()
|
|
282
|
-
) {
|
|
283
|
-
names.push(this.advance().value);
|
|
284
|
-
}
|
|
285
|
-
// variadic: name ...T
|
|
286
|
-
const variadic = this.match(T.ELLIPSIS);
|
|
287
|
-
const type = this.parseType();
|
|
288
|
-
for (const n of names) params.push({ name: n, type, variadic });
|
|
289
|
-
} else {
|
|
290
|
-
// unnamed: just a type (possibly variadic: ...T)
|
|
291
|
-
const variadic = this.match(T.ELLIPSIS);
|
|
292
|
-
const type = this.parseType();
|
|
293
|
-
params.push({ name: "_", type, variadic });
|
|
294
|
-
}
|
|
295
|
-
} while (this.match(T.COMMA));
|
|
296
|
-
}
|
|
297
|
-
this.expect(T.RPAREN);
|
|
298
|
-
return params;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Lookahead: determine if current IDENT starts a named param (name type)
|
|
302
|
-
// vs an unnamed param (just a type). Scans IDENT (COMMA IDENT)* and checks
|
|
303
|
-
// if a type-start token follows — if so, the IDENTs are names; if ) or ,
|
|
304
|
-
// follows, the IDENTs are themselves types.
|
|
305
|
-
_isNamedParam() {
|
|
306
|
-
let pos = this.pos + 1; // skip current IDENT
|
|
307
|
-
while (
|
|
308
|
-
this.tokens[pos]?.type === T.COMMA &&
|
|
309
|
-
this.tokens[pos + 1]?.type === T.IDENT
|
|
310
|
-
) {
|
|
311
|
-
pos += 2;
|
|
312
|
-
}
|
|
313
|
-
const next = this.tokens[pos];
|
|
314
|
-
if (!next) return false;
|
|
315
|
-
return this.looksLikeType(next) || next.type === T.ELLIPSIS;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
looksLikeType(tok) {
|
|
319
|
-
return (
|
|
320
|
-
tok.type === T.LBRACKET ||
|
|
321
|
-
tok.type === T.STAR ||
|
|
322
|
-
tok.type === T.MAP ||
|
|
323
|
-
tok.type === T.FUNC ||
|
|
324
|
-
tok.type === T.INTERFACE ||
|
|
325
|
-
tok.type === T.STRUCT ||
|
|
326
|
-
(tok.type === T.IDENT && tok.value !== "_")
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
parseReturnType(inTypeExpr = false) {
|
|
331
|
-
// No return type
|
|
332
|
-
if (
|
|
333
|
-
this.check(T.LBRACE) ||
|
|
334
|
-
this.check(T.RBRACE) ||
|
|
335
|
-
this.check(T.SEMICOLON) ||
|
|
336
|
-
this.check(T.EOF)
|
|
337
|
-
)
|
|
338
|
-
return null;
|
|
339
|
-
// Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
|
|
340
|
-
if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
|
|
341
|
-
return null;
|
|
342
|
-
// Multiple return values: (int, string) or named (result int, ok bool)
|
|
343
|
-
if (this.check(T.LPAREN)) {
|
|
344
|
-
this.advance();
|
|
345
|
-
const types = [];
|
|
346
|
-
const names = []; // non-null if named returns
|
|
347
|
-
let isNamed = false;
|
|
348
|
-
do {
|
|
349
|
-
// Named returns: only treat IDENT as a name if peek2 is an actual type token
|
|
350
|
-
if (this.check(T.IDENT) && this.looksLikeType(this.peek2())) {
|
|
351
|
-
names.push(this.advance().value);
|
|
352
|
-
isNamed = true;
|
|
353
|
-
} else {
|
|
354
|
-
names.push(null);
|
|
355
|
-
}
|
|
356
|
-
types.push(this.parseType());
|
|
357
|
-
} while (this.match(T.COMMA));
|
|
358
|
-
this.expect(T.RPAREN);
|
|
359
|
-
const typeNode =
|
|
360
|
-
types.length === 1 ? types[0] : { kind: "TupleType", types };
|
|
361
|
-
// Attach named return info so typechecker can inject them into scope
|
|
362
|
-
if (isNamed)
|
|
363
|
-
typeNode._namedReturns = names.map((n, i) => ({
|
|
364
|
-
name: n,
|
|
365
|
-
type: types[i],
|
|
366
|
-
}));
|
|
367
|
-
return typeNode;
|
|
368
|
-
}
|
|
369
|
-
return this.parseType();
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
parseTypeDecl() {
|
|
373
|
-
this.expect(T.TYPE);
|
|
374
|
-
if (this.match(T.LPAREN)) {
|
|
375
|
-
const decls = [];
|
|
376
|
-
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
377
|
-
const name = this.expect(T.IDENT).value;
|
|
378
|
-
const isAlias = this.match(T.ASSIGN);
|
|
379
|
-
const type = this.parseType();
|
|
380
|
-
decls.push({ kind: "TypeDecl", name, type, isAlias });
|
|
381
|
-
this.semi();
|
|
382
|
-
}
|
|
383
|
-
this.expect(T.RPAREN);
|
|
384
|
-
this.semi();
|
|
385
|
-
return decls;
|
|
386
|
-
}
|
|
387
|
-
const name = this.expect(T.IDENT).value;
|
|
388
|
-
// generic type: type Name[T any] struct { ... }
|
|
389
|
-
let typeParams = null;
|
|
390
|
-
if (this.check(T.LBRACKET) && this._looksLikeTypeParamList()) {
|
|
391
|
-
typeParams = this.parseTypeParamList();
|
|
392
|
-
}
|
|
393
|
-
const isAlias = this.match(T.ASSIGN);
|
|
394
|
-
const type = this.parseType();
|
|
395
|
-
this.semi();
|
|
396
|
-
return { kind: "TypeDecl", name, typeParams, type, isAlias };
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
parseVarDecl() {
|
|
400
|
-
this.expect(T.VAR);
|
|
401
|
-
const decls = [];
|
|
402
|
-
if (this.match(T.LPAREN)) {
|
|
403
|
-
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
404
|
-
decls.push(this.parseVarSpec());
|
|
405
|
-
this.semi();
|
|
406
|
-
}
|
|
407
|
-
this.expect(T.RPAREN);
|
|
408
|
-
} else {
|
|
409
|
-
decls.push(this.parseVarSpec());
|
|
410
|
-
}
|
|
411
|
-
this.semi();
|
|
412
|
-
return { kind: "VarDecl", decls };
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
parseVarSpec() {
|
|
416
|
-
const names = [this.expect(T.IDENT).value];
|
|
417
|
-
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
418
|
-
let type = null;
|
|
419
|
-
let value = null;
|
|
420
|
-
if (
|
|
421
|
-
!this.check(T.ASSIGN) &&
|
|
422
|
-
!this.check(T.SEMICOLON) &&
|
|
423
|
-
!this.check(T.RPAREN)
|
|
424
|
-
) {
|
|
425
|
-
type = this.parseType();
|
|
426
|
-
}
|
|
427
|
-
if (this.match(T.ASSIGN)) value = this.parseExprList();
|
|
428
|
-
return { names, type, value };
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
parseConstDecl() {
|
|
432
|
-
this.expect(T.CONST);
|
|
433
|
-
const decls = [];
|
|
434
|
-
if (this.match(T.LPAREN)) {
|
|
435
|
-
let iotaVal = 0;
|
|
436
|
-
let prevRawExprs = null; // raw AST (with iota Idents) from the previous spec
|
|
437
|
-
let prevType = null;
|
|
438
|
-
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
439
|
-
const names = [this.expect(T.IDENT).value];
|
|
440
|
-
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
441
|
-
let type = null;
|
|
442
|
-
let rawExprs; // before iota substitution
|
|
443
|
-
if (this.check(T.ASSIGN)) {
|
|
444
|
-
this.advance();
|
|
445
|
-
rawExprs = this.parseExprList();
|
|
446
|
-
prevRawExprs = rawExprs;
|
|
447
|
-
prevType = null;
|
|
448
|
-
} else if (!this.check(T.SEMICOLON) && !this.check(T.RPAREN)) {
|
|
449
|
-
// optional type annotation before =
|
|
450
|
-
type = this.parseType();
|
|
451
|
-
prevType = type;
|
|
452
|
-
this.expect(T.ASSIGN);
|
|
453
|
-
rawExprs = this.parseExprList();
|
|
454
|
-
prevRawExprs = rawExprs;
|
|
455
|
-
} else {
|
|
456
|
-
// Omitted expression — repeat previous expression with updated iota
|
|
457
|
-
rawExprs = prevRawExprs ?? [
|
|
458
|
-
{ kind: "BasicLit", litKind: "INT", value: String(iotaVal) },
|
|
459
|
-
];
|
|
460
|
-
type = prevType ?? null;
|
|
461
|
-
}
|
|
462
|
-
// Substitute iota deeply in a cloned expression AST
|
|
463
|
-
const value = rawExprs.map((v) => this._substituteIota(v, iotaVal));
|
|
464
|
-
decls.push({ names, type, value });
|
|
465
|
-
iotaVal++;
|
|
466
|
-
this.semi();
|
|
467
|
-
}
|
|
468
|
-
this.expect(T.RPAREN);
|
|
469
|
-
} else {
|
|
470
|
-
const names = [this.expect(T.IDENT).value];
|
|
471
|
-
while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
|
|
472
|
-
let type = null;
|
|
473
|
-
if (!this.check(T.ASSIGN)) type = this.parseType();
|
|
474
|
-
this.expect(T.ASSIGN);
|
|
475
|
-
const value = this.parseExprList();
|
|
476
|
-
decls.push({ names, type, value });
|
|
477
|
-
}
|
|
478
|
-
this.semi();
|
|
479
|
-
return { kind: "ConstDecl", decls };
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// Deep-clone an expression AST, replacing `iota` identifiers with the given integer.
|
|
483
|
-
_substituteIota(node, val) {
|
|
484
|
-
if (!node || typeof node !== "object") return node;
|
|
485
|
-
if (node.kind === "Ident" && node.name === "iota") {
|
|
486
|
-
return { kind: "BasicLit", litKind: "INT", value: String(val) };
|
|
487
|
-
}
|
|
488
|
-
// Shallow-clone the node, then recursively substitute in all child properties
|
|
489
|
-
const clone = { ...node };
|
|
490
|
-
for (const key of Object.keys(clone)) {
|
|
491
|
-
const v = clone[key];
|
|
492
|
-
if (Array.isArray(v)) {
|
|
493
|
-
clone[key] = v.map((item) => this._substituteIota(item, val));
|
|
494
|
-
} else if (v && typeof v === "object" && v.kind) {
|
|
495
|
-
clone[key] = this._substituteIota(v, val);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return clone;
|
|
499
|
-
}
|
|
500
156
|
}
|
|
501
157
|
|
|
502
158
|
Object.assign(Parser.prototype, typeParserMethods);
|
|
503
159
|
Object.assign(Parser.prototype, statementParserMethods);
|
|
504
160
|
Object.assign(Parser.prototype, expressionParserMethods);
|
|
161
|
+
Object.assign(Parser.prototype, declarationParseMethods);
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
UNTYPED_INT,
|
|
25
25
|
} from "./types.js";
|
|
26
26
|
|
|
27
|
+
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
28
|
+
|
|
27
29
|
// Untyped constant assignability: maps source.base → Set of compatible target.name values
|
|
28
30
|
const UNTYPED_COMPAT = {
|
|
29
31
|
int: new Set(["int", "float64", "complex128", "complex64"]),
|
|
@@ -33,6 +35,7 @@ const UNTYPED_COMPAT = {
|
|
|
33
35
|
complex128: new Set(["complex128", "complex64"]),
|
|
34
36
|
};
|
|
35
37
|
|
|
38
|
+
/** @type {ThisType<TypeChecker>} */
|
|
36
39
|
export const assignabilityMethods = {
|
|
37
40
|
// ── Binary result types ───────────────────────────────────────────
|
|
38
41
|
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
NIL,
|
|
18
18
|
Scope,
|
|
19
19
|
STRING,
|
|
20
|
+
TAINTED_ANY,
|
|
20
21
|
typeStr,
|
|
21
22
|
UNTYPED_BOOL,
|
|
22
23
|
UNTYPED_COMPLEX,
|
|
@@ -26,6 +27,8 @@ import {
|
|
|
26
27
|
VOID,
|
|
27
28
|
} from "./types.js";
|
|
28
29
|
|
|
30
|
+
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
31
|
+
|
|
29
32
|
// Dispatch table for builtin function type checking.
|
|
30
33
|
// Each value is (self, name, expr, argTypes, scope) => returnType.
|
|
31
34
|
const BUILTIN_CHECK = {
|
|
@@ -80,6 +83,7 @@ const CHECK_EXPR_DELEGATE = {
|
|
|
80
83
|
InstantiationExpr: "_checkInstantiationExpr",
|
|
81
84
|
};
|
|
82
85
|
|
|
86
|
+
/** @type {ThisType<TypeChecker>} */
|
|
83
87
|
export const expressionCheckMethods = {
|
|
84
88
|
checkExpr(expr, scope) {
|
|
85
89
|
const t = this._checkExpr(expr, scope);
|
|
@@ -115,6 +119,7 @@ export const expressionCheckMethods = {
|
|
|
115
119
|
case "BinaryExpr": {
|
|
116
120
|
const lt = this.checkExpr(expr.left, scope);
|
|
117
121
|
const rt = this.checkExpr(expr.right, scope);
|
|
122
|
+
if (lt?._tainted || rt?._tainted) return TAINTED_ANY;
|
|
118
123
|
if (isAny(lt) || isAny(rt))
|
|
119
124
|
return this.binaryResultType(expr.op, ANY, ANY, expr);
|
|
120
125
|
return this.binaryResultType(expr.op, lt, rt, expr);
|
|
@@ -185,6 +190,7 @@ export const expressionCheckMethods = {
|
|
|
185
190
|
|
|
186
191
|
_checkSelectorExpr(expr, scope) {
|
|
187
192
|
const baseType = this.checkExpr(expr.expr, scope);
|
|
193
|
+
if (baseType?._tainted) return TAINTED_ANY;
|
|
188
194
|
if (expr.expr._isTypeRef) {
|
|
189
195
|
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
190
196
|
base = this.resolveType(base);
|
|
@@ -230,6 +236,7 @@ export const expressionCheckMethods = {
|
|
|
230
236
|
_checkIndexExpr(expr, scope) {
|
|
231
237
|
const bt = this.checkExpr(expr.expr, scope);
|
|
232
238
|
this.checkExpr(expr.index, scope);
|
|
239
|
+
if (bt?._tainted) return TAINTED_ANY;
|
|
233
240
|
if (isAny(bt)) return ANY;
|
|
234
241
|
const btu =
|
|
235
242
|
bt.kind === "named"
|
|
@@ -252,6 +259,7 @@ export const expressionCheckMethods = {
|
|
|
252
259
|
if (expr.low) this.checkExpr(expr.low, scope);
|
|
253
260
|
if (expr.high) this.checkExpr(expr.high, scope);
|
|
254
261
|
if (expr.max) this.checkExpr(expr.max, scope);
|
|
262
|
+
if (bt?._tainted) return TAINTED_ANY;
|
|
255
263
|
if (isAny(bt)) return ANY;
|
|
256
264
|
if (bt.kind === "slice" || bt.kind === "array")
|
|
257
265
|
return { kind: "slice", elem: bt.elem };
|
|
@@ -468,6 +476,7 @@ export const expressionCheckMethods = {
|
|
|
468
476
|
|
|
469
477
|
const argTypes = this._resolveCallArgTypes(expr, scope);
|
|
470
478
|
|
|
479
|
+
if (fnType?._tainted) return TAINTED_ANY;
|
|
471
480
|
if (isAny(fnType)) return ANY;
|
|
472
481
|
|
|
473
482
|
// Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
|
package/src/typechecker/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ERROR,
|
|
23
23
|
isVoid,
|
|
24
24
|
Scope,
|
|
25
|
+
TAINTED_ANY,
|
|
25
26
|
TypeCheckError,
|
|
26
27
|
typeStr,
|
|
27
28
|
VOID,
|
|
@@ -124,7 +125,7 @@ export class TypeChecker {
|
|
|
124
125
|
this._currentSource,
|
|
125
126
|
);
|
|
126
127
|
this.errors.push(e);
|
|
127
|
-
return
|
|
128
|
+
return TAINTED_ANY; // tainted recovery type — suppresses downstream cascade errors
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
_reportUnused(scope, node) {
|
|
@@ -7,10 +7,13 @@ import {
|
|
|
7
7
|
defaultType,
|
|
8
8
|
isAny,
|
|
9
9
|
Scope,
|
|
10
|
+
TAINTED_ANY,
|
|
10
11
|
typeStr,
|
|
11
12
|
VOID,
|
|
12
13
|
} from "./types.js";
|
|
13
14
|
|
|
15
|
+
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
16
|
+
|
|
14
17
|
// Dispatch table for substituteType — keyed by type.kind.
|
|
15
18
|
const SUBSTITUTE_DISPATCH = {
|
|
16
19
|
basic: (_s, type) => type,
|
|
@@ -69,6 +72,7 @@ const SUBSTITUTE_DISPATCH = {
|
|
|
69
72
|
}),
|
|
70
73
|
};
|
|
71
74
|
|
|
75
|
+
/** @type {ThisType<TypeChecker>} */
|
|
72
76
|
export const resolveMethods = {
|
|
73
77
|
resolveTypeNode(node, scope) {
|
|
74
78
|
if (!node) return ANY;
|
|
@@ -217,7 +221,9 @@ export const resolveMethods = {
|
|
|
217
221
|
}
|
|
218
222
|
}
|
|
219
223
|
}
|
|
220
|
-
|
|
224
|
+
const result = { kind: "interface", methods };
|
|
225
|
+
if (node.unionConstraint) result.unionConstraint = node.unionConstraint;
|
|
226
|
+
return result;
|
|
221
227
|
},
|
|
222
228
|
|
|
223
229
|
// ── Generics — substitution, instantiation, inference ────────────
|
|
@@ -354,7 +360,8 @@ export const resolveMethods = {
|
|
|
354
360
|
|
|
355
361
|
fieldType(baseType, field, node) {
|
|
356
362
|
baseType = this.resolveType(baseType);
|
|
357
|
-
if (!baseType || isAny(baseType))
|
|
363
|
+
if (!baseType || isAny(baseType))
|
|
364
|
+
return baseType?._tainted ? TAINTED_ANY : ANY;
|
|
358
365
|
if (this._isTransparentPointerField(baseType, field))
|
|
359
366
|
baseType = this.resolveType(baseType.base);
|
|
360
367
|
if (baseType.kind === "named" && baseType.methods?.has(field))
|
|
@@ -26,6 +26,9 @@ import {
|
|
|
26
26
|
Scope,
|
|
27
27
|
} from "./types.js";
|
|
28
28
|
|
|
29
|
+
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
30
|
+
|
|
31
|
+
/** @type {ThisType<TypeChecker>} */
|
|
29
32
|
export const statementCheckMethods = {
|
|
30
33
|
checkBlock(block, scope, returnType) {
|
|
31
34
|
for (const stmt of block.stmts) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Termination analysis — determines whether a statement/block always returns.
|
|
2
2
|
// Installed as a mixin on TypeChecker.prototype.
|
|
3
3
|
|
|
4
|
+
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
5
|
+
|
|
6
|
+
/** @type {ThisType<TypeChecker>} */
|
|
4
7
|
export const terminationMethods = {
|
|
5
8
|
_isPanicCall(expr) {
|
|
6
9
|
return (
|
package/src/typechecker/types.js
CHANGED
|
@@ -48,6 +48,7 @@ export const FLOAT64 = { kind: "basic", name: "float64" };
|
|
|
48
48
|
export const STRING = { kind: "basic", name: "string" };
|
|
49
49
|
export const BOOL = { kind: "basic", name: "bool" };
|
|
50
50
|
export const ANY = { kind: "basic", name: "any" };
|
|
51
|
+
export const TAINTED_ANY = { kind: "basic", name: "any", _tainted: true };
|
|
51
52
|
export const VOID = { kind: "basic", name: "void" };
|
|
52
53
|
export const NIL = { kind: "basic", name: "nil" };
|
|
53
54
|
export const ERROR = {
|