slige-mirror/compiler/check/checker.ts
2025-02-04 15:06:19 +01:00

210 lines
6.0 KiB
TypeScript

import * as ast from "../ast/mod.ts";
import { Ctx, File } from "../ctx.ts";
import { Span } from "../diagnostics.ts";
import { AstId, IdMap } from "../ids.ts";
import { Resols } from "../resolve/resolver.ts";
import { tyToString } from "../ty/to_string.ts";
import { Ty } from "../ty/ty.ts";
import { exhausted, Res, todo } from "../util.ts";
export class Checker {
private itemTys = new IdMap<AstId, Ty>();
private exprTys = new IdMap<AstId, Ty>();
private tyTys = new IdMap<AstId, Ty>();
private currentFile: File;
public constructor(
private ctx: Ctx,
private entryFileAst: ast.File,
private resols: Resols,
) {
this.currentFile = ctx.entryFile();
}
private checkBlock(block: ast.Block, expected: Ty): Ty {
this.checkStmts(block.stmts);
return block.expr &&
this.checkExpr(block.expr, expected) ||
Ty({ tag: "null" });
}
private checkStmts(stmts: ast.Stmt[]) {
}
public fnItemTy(item: ast.Item, kind: ast.FnItem): Ty {
return this.itemTys.get(item.id) || this.checkFnItem(item, kind);
}
private checkFnItem(item: ast.Item, kind: ast.FnItem): Ty {
const params = kind.params.map((param) => this.tyTy(param.ty));
const returnTy = kind.returnTy && this.tyTy(kind.returnTy) ||
Ty({ tag: "null" });
return Ty({ tag: "fn", item, kind, params, returnTy });
}
public exprTy(expr: ast.Expr): Ty {
return this.exprTys.get(expr.id) ||
this.checkExpr(expr, Ty({ tag: "unknown" }));
}
private checkExpr(expr: ast.Expr, expected: Ty): Ty {
const k = expr.kind;
switch (k.tag) {
case "error":
return Ty({ tag: "error" });
case "path":
return this.checkPathExpr(expr, k, expected);
case "null":
return todo();
case "int":
return todo();
case "bool":
return todo();
case "str":
return todo();
case "group":
return todo();
case "array":
return todo();
case "repeat":
return todo();
case "struct":
return todo();
case "ref":
return todo();
case "deref":
return todo();
case "elem":
return todo();
case "field":
return todo();
case "index":
return todo();
case "call":
return todo();
case "unary":
return todo();
case "binary":
return todo();
case "block":
return todo();
case "if":
return todo();
case "loop":
return todo();
case "while":
return todo();
case "for":
return todo();
case "c_for":
return todo();
}
exhausted(k);
}
private checkPathExpr(
expr: ast.Expr,
kind: ast.PathExpr,
expected: Ty,
): Ty {
const res = this.resols.exprRes(expr.id);
switch (res.kind.tag) {
case "error":
return Ty({ tag: "error" });
case "fn": {
const fn = res.kind.item;
const ty = this.fnItemTy(fn, res.kind.kind);
const resu = this.resolveTys(ty, expected);
if (!resu.ok) {
this.report(resu.val, expr.span);
return Ty({ tag: "error" });
}
return resu.val;
}
case "local": {
const ty = this.exprTy(expr);
const resu = this.resolveTys(ty, expected);
if (!resu.ok) {
this.report(resu.val, expr.span);
return Ty({ tag: "error" });
}
return resu.val;
}
}
exhausted(res.kind);
}
private tyTy(ty: ast.Ty): Ty {
return this.tyTys.get(ty.id) ||
this.checkTy(ty);
}
private checkTy(ty: ast.Ty): Ty {
const k = ty.kind;
switch (k.tag) {
case "error":
return Ty({ tag: "error" });
case "null":
case "int":
return Ty({ tag: "int" });
case "bool":
case "str":
case "path":
case "ref":
case "ptr":
case "slice":
case "array":
case "anon_struct":
return todo(k);
}
exhausted(k);
}
private report(msg: string, span: Span) {
this.ctx.report({
severity: "error",
file: this.currentFile,
span,
msg,
});
}
private resolveTys(a: Ty, b: Ty): Res<Ty, string> {
const as = tyToString(this.ctx, a);
const bs = tyToString(this.ctx, b);
const incompat = () =>
Res.Err(
`type '${as}' not compatible with type '${bs}'`,
);
switch (a.kind.tag) {
case "error":
return Res.Ok(b);
case "unknown":
return Res.Ok(b);
case "null": {
if (b.kind.tag !== "null") {
return incompat();
}
return Res.Ok(a);
}
case "int": {
if (b.kind.tag !== "int") {
return incompat();
}
return Res.Ok(a);
}
case "fn": {
if (b.kind.tag !== "fn") {
return incompat();
}
if (b.kind.item.id === a.kind.item.id) {
return incompat();
}
return Res.Ok(a);
}
}
exhausted(a.kind);
}
}