Compare commits

..

3 Commits

Author SHA1 Message Date
87f90c6700 check2 -> check
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 9s
2026-04-16 02:27:27 +02:00
cb7c887558 remove old checker 2026-04-16 02:26:55 +02:00
a22ced4c24 use new checker 2026-04-16 02:25:47 +02:00
5 changed files with 750 additions and 1404 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,793 +0,0 @@
import { Syms } from "./resolve.ts";
import * as ast from "../ast.ts";
import { Ty } from "../ty.ts";
import { FileReporter, Loc } from "../diagnostics.ts";
import * as stringify from "../stringify.ts";
export class Checker {
private checkedFns = new Map<ast.FnStmt, CheckedFn>();
constructor(
private syms: Syms,
private reporter: FileReporter,
) {}
checkFn(fn: ast.FnStmt): CheckedFn {
const existing = this.checkedFns.get(fn);
if (existing) {
return existing;
}
const checkedFn = new TypeChecker(this, fn, this.syms, this.reporter)
.check();
checkedFn.checkForCheckerInternalTys(this.reporter);
this.checkedFns.set(fn, checkedFn);
return checkedFn;
}
}
export class CheckedFn {
constructor(
private fnTy: Ty,
private nodeTys: Map<ast.Node, Ty>,
) {}
ty(): Ty {
return this.fnTy;
}
exprTy(expr: ast.Node): Ty {
const ty = this.nodeTys.get(expr);
if (ty === undefined) {
throw new Error(`no type for '${expr.kind.tag}'`);
}
return ty;
}
checkForCheckerInternalTys(reporter: FileReporter) {
for (const [node, ty] of this.nodeTys) {
if (ty.isCheckerInternal()) {
reporter.error(
node.loc,
`concrete type must be known at this point, got temporary type '${ty.pretty()}'`,
);
reporter.abort();
}
}
}
}
class TypeChecker {
private nodeTys = new Map<ast.Node, Ty>();
private params!: Ty[];
constructor(
private cx: Checker,
private fn: ast.FnStmt,
private syms: Syms,
private reporter: FileReporter,
) {}
check(): CheckedFn {
const params = this.fn.kind.params
.map((node) => {
const param = node.as("Param");
if (!param.kind.ty) {
this.reporter
.error(param.loc, `parameter must have a type`);
this.reporter.abort();
}
return this.ty(param.kind.ty);
});
this.params = params;
const retTy = this.fn.kind.retTy
? this.ty(this.fn.kind.retTy)
: Ty.Void;
this.fn.kind.body.visit({
visit: (node) => {
const k = node.kind;
switch (k.tag) {
case "ReturnStmt": {
const ty = k.expr ? this.expr(k.expr, retTy) : Ty.Void;
if (!ty.resolvableWith(retTy)) {
this.reporter.error(
node.loc,
`type '${ty.pretty()}' not compatible with return type '${retTy.pretty()}'`,
);
this.reporter.info(
this.fn.kind.retTy?.loc ?? this.fn.loc,
`return type '${retTy}' defined here`,
);
this.reporter.abort();
}
break;
}
case "LetStmt": {
let ty: Ty;
const paramTy = k.param.as("Param").kind.ty;
if (paramTy) {
const explicitTy = this.ty(paramTy);
const exprTy = this.expr(k.expr, explicitTy);
const res = this.resolve(
exprTy,
explicitTy,
k.expr.loc,
);
if (res.rewriteSubtree) {
this.rewriteTree(k.expr, res.ty);
}
ty = res.ty;
} else {
ty = this.expr(k.expr, Ty.Any);
if (ty.is("AnyInt")) {
ty = Ty.I32;
this.rewriteTree(k.expr, ty);
}
}
this.nodeTys.set(node, ty);
break;
}
case "ExprStmt": {
this.expr(k.expr, Ty.Any);
break;
}
case "AssignStmt": {
const placeTy = this.expr(k.place, Ty.Any);
const exprTy = this.expr(k.expr, placeTy);
if (!placeTy.resolvableWith(exprTy)) {
this.reporter.error(
k.expr.loc,
`type '${exprTy.pretty()}' not assignable to type '${placeTy.pretty()}'`,
);
this.reporter.abort();
}
break;
}
case "IfStmt": {
const condTy = this.expr(k.cond, Ty.Bool);
if (!condTy.is("Bool")) {
this.reporter.error(
k.cond.loc,
`expected condition to be 'bool', got '${condTy.pretty()}'`,
);
this.reporter.abort();
}
break;
}
case "WhileStmt": {
const condTy = this.expr(k.cond, Ty.Bool);
if (!condTy.is("Bool")) {
this.reporter.error(
k.cond.loc,
`expected condition to be 'bool', got '${condTy.pretty()}'`,
);
this.reporter.abort();
}
break;
}
case "BreakStmt": {
break;
}
case "FnStmt": {
break;
}
case "Error":
case "File":
case "Block":
case "Param":
case "IdentExpr":
case "IntExpr":
case "StrExpr":
case "ArrayExpr":
case "IndexExpr":
case "CallExpr":
case "UnaryExpr":
case "BinaryExpr":
case "RangeExpr":
case "IdentTy":
case "PtrTy":
case "PtrMutTy":
case "ArrayTy":
case "SliceTy":
break;
default:
k satisfies never;
}
},
});
this.convertAnyIntToI32();
const ty = Ty.create("FnStmt", {
stmt: this.fn,
ty: Ty.create("Fn", { params, retTy }),
});
return new CheckedFn(ty, this.nodeTys);
}
private convertAnyIntToI32() {
for (const [node, ty] of this.nodeTys) {
if (ty.is("AnyInt")) {
this.rewriteTree(node, Ty.I64);
}
}
}
private place(expr: ast.Node, expected: Ty): Ty {
return this.cachedCheck(expr, () => this.checkPlace(expr, expected));
}
private checkPlace(expr: ast.Node, expected: Ty): Ty {
const k = expr.kind;
switch (k.tag) {
case "UnaryExpr": {
switch (k.op) {
case "Deref": {
const innerTy = this.checkPlace(
k.expr,
Ty.AnyDerefable(expected),
);
if (innerTy.is("Ptr") || innerTy.is("PtrMut")) {
return innerTy.kind.ty;
}
}
}
}
}
return this.expr(expr, expected);
}
private expr(expr: ast.Node, expected: Ty): Ty {
return this.cachedCheck(expr, () => this.checkExpr(expr, expected));
}
private checkExpr(expr: ast.Node, expected: Ty): Ty {
const k = expr.kind;
switch (k.tag) {
case "Error":
case "File":
case "Block":
case "ExprStmt":
case "AssignStmt":
case "FnStmt":
case "ReturnStmt":
case "LetStmt":
case "IfStmt":
case "WhileStmt":
case "BreakStmt":
case "Param":
case "IdentTy":
case "PtrTy":
case "PtrMutTy":
case "ArrayTy":
case "SliceTy":
throw new Error(`node '${k.tag}' not an expression`);
case "IdentExpr": {
const sym = this.syms.get(expr);
if (sym.tag === "Fn") {
const fn = this.cx.checkFn(sym.stmt);
return fn.ty();
}
if (sym.tag === "Bool") {
return Ty.Bool;
}
if (sym.tag === "Builtin") {
this.reporter.error(
expr.loc,
`invalid use of builtin '${sym.id}'`,
);
this.reporter.abort();
}
if (sym.tag === "FnParam") {
return this.params[sym.idx];
}
if (sym.tag === "Let") {
const ty = this.nodeTys.get(sym.stmt);
if (!ty) {
throw new Error();
}
return ty;
}
throw new Error(`'${sym.tag}' not handled`);
}
case "IntExpr": {
const intTy = (() => {
switch (k.intTy) {
case "u8":
return Ty.U8;
case "u16":
return Ty.U16;
case "u32":
return Ty.U32;
case "u64":
return Ty.U64;
case "usize":
return Ty.ISize;
case "i8":
return Ty.U8;
case "i16":
return Ty.U16;
case "i32":
return Ty.I32;
case "i64":
return Ty.U64;
case "isize":
return Ty.ISize;
case "any":
return Ty.AnyInt;
default:
throw new Error(
`intType '${k.intTy}' not handled`,
);
}
})();
return this.resolve(intTy, expected, expr.loc).ty;
}
case "StrExpr": {
return this.resolve(
Ty.Ptr(Ty.Slice(Ty.U8)),
expected,
expr.loc,
).ty;
}
case "ArrayExpr": {
if (k.values.length === 0) {
if (expected.is("Any")) {
return Ty.Array(Ty.Any, 0);
} else {
return this.resolve(
Ty.Array(Ty.Any, 0),
expected,
expr.loc,
).ty;
}
}
const expectedInner = expected.isIndexable()
? expected.indexableTy()!
: Ty.Any;
let res = this.resolve(
this.expr(k.values[0], expectedInner),
expectedInner,
k.values[0].loc,
);
while (true) {
for (const val of k.values.slice(1)) {
res = this.resolve(
this.expr(val, expectedInner),
expectedInner,
k.values[0].loc,
);
if (res.rewriteSubtree) {
break;
}
}
if (!res.rewriteSubtree) {
break;
}
for (const val of k.values) {
this.rewriteTree(val, res.ty);
}
}
return Ty.Array(res.ty, k.values.length);
}
case "IndexExpr": {
const innerTy = this.place(
k.value,
Ty.AnyIndexable(expected),
);
if (!innerTy.isIndexable()) {
this.reporter.error(
expr.loc,
`expected indexable type, got '${expected.pretty()}'`,
);
this.reporter.abort();
}
let argTy = this.expr(k.arg, Ty.Any);
if (argTy.is("AnyInt")) {
argTy = Ty.USize;
this.rewriteTree(k.arg, argTy);
return innerTy.indexableTy()!;
} else if (argTy.is("Range")) {
return Ty.Slice(innerTy.indexableTy()!);
} else {
throw new Error();
}
}
case "CallExpr": {
const checkArgs = (
callableTy: Ty & { kind: { tag: "Fn" } },
) => {
const params = callableTy.kind.params;
if (k.args.length !== params.length) {
this.reporter.error(
expr.loc,
`incorrect amount of arguments. got ${k.args.length} expected ${params.length}`,
);
if (calleeTy?.is("FnStmt")) {
this.reporter.info(
calleeTy.kind.stmt.loc,
"function defined here",
);
}
this.reporter.abort();
}
const args = k.args
.map((arg, i) => this.expr(arg, params[i]));
for (const i of args.keys()) {
this.resolve(args[i], params[i], k.args[i].loc, () => {
if (calleeTy?.is("FnStmt")) {
this.reporter.info(
calleeTy.kind.stmt.kind.params[i].loc,
`parameter '${
calleeTy.kind.stmt.kind.params[i]
.as("Param").kind.ident
}' defined here`,
);
}
this.reporter.error(
k.args[i].loc,
`type '${
args[i].pretty()
}' not compatible with type '${
params[i].pretty()
}', for argument ${i}`,
);
});
}
};
const sym = k.value.is("IdentExpr")
? this.syms.get(k.value)
: null;
if (sym?.tag === "Builtin") {
if (sym.id === "len") {
checkArgs(
Ty.create("Fn", {
params: [Ty.Any],
retTy: Ty.USize,
}).as("Fn"),
);
return Ty.USize;
}
if (sym.id === "print") {
for (const arg of k.args) {
const ty = this.expr(arg, Ty.Any);
if (ty.is("AnyInt")) {
this.rewriteTree(arg, Ty.I32);
}
}
return Ty.Void;
}
throw new Error(`builtin '${sym.id}' not handled`);
}
const calleeTy = this.expr(k.value, Ty.AnyCallable(expected));
if (!calleeTy.isCallable()) {
this.reporter.error(
expr.loc,
`expected callable type, got '${expected.pretty()}'`,
);
this.reporter.abort();
}
const callableTy = calleeTy.callableTy();
checkArgs(callableTy);
return callableTy.kind.retTy;
}
case "UnaryExpr": {
switch (k.op) {
case "Not": {
const ty = this.expr(k.expr, Ty.Bool);
if (!ty.is("Bool")) {
this.reporter.error(
expr.loc,
`expected 'bool', got '${expected.pretty()}'`,
);
this.reporter.abort();
}
return ty;
}
case "Neg": {
const ty = this.expr(k.expr, expected);
if (!ty.is("Int")) {
this.reporter.error(
expr.loc,
`cannot apply ! to '${expected.pretty()}'`,
);
this.reporter.abort();
}
return ty;
}
case "Ref": {
const ty = this.expr(k.expr, expected);
return Ty.Ptr(ty);
}
case "RefMut": {
const ty = this.expr(k.expr, expected);
return Ty.PtrMut(ty);
}
case "Deref": {
const ty = this.expr(k.expr, expected);
if (!ty.is("Ptr") && !ty.is("PtrMut")) {
this.reporter.error(
expr.loc,
`cannot dereference type '${expected.pretty()}'`,
);
this.reporter.abort();
}
if (!ty.kind.ty.isSized()) {
this.reporter.error(
expr.loc,
`cannot dereference unsized type '${ty.kind.ty.pretty()}' in an expression`,
);
this.reporter.abort();
}
return ty.kind.ty;
}
default:
k satisfies never;
throw new Error();
}
}
case "BinaryExpr": {
const op = new BinaryOp(k.op);
const expectedInner = op.isPropagating() ? expected : Ty.Any;
const left = this.expr(k.left, expectedInner);
const right = this.expr(k.right, expectedInner);
const res = this.resolve(left, right, expr.loc);
const result = binaryOpTests
.map((test) => test(k.op, left, right))
.filter((result) => result)
.at(0);
if (!result) {
this.reporter.error(
expr.loc,
`operator '${k.tok}' cannot be applied to types '${left.pretty()}' and '${right.pretty()}'`,
);
this.reporter.abort();
}
this.rewriteTree(k.left, res.ty);
this.rewriteTree(k.right, res.ty);
return result;
}
case "RangeExpr": {
for (const operandExpr of [k.begin, k.end]) {
const operandTy = operandExpr &&
this.expr(operandExpr, Ty.USize);
if (operandTy && !operandTy.resolvableWith(Ty.USize)) {
this.reporter.error(
operandExpr.loc,
`range operand must be '${Ty.USize.pretty()}', not '${operandTy.pretty()}'`,
);
this.reporter.abort();
}
}
return Ty.create("Range", {});
}
default:
k satisfies never;
throw new Error();
}
}
private ty(ty: ast.Node): Ty {
return this.cachedCheck(ty, () => this.checkTy(ty));
}
private checkTy(ty: ast.Node): Ty {
const k = ty.kind;
switch (k.tag) {
case "Error":
case "File":
case "Block":
case "ExprStmt":
case "AssignStmt":
case "FnStmt":
case "ReturnStmt":
case "LetStmt":
case "IfStmt":
case "WhileStmt":
case "BreakStmt":
case "Param":
case "IdentExpr":
case "IntExpr":
case "StrExpr":
case "ArrayExpr":
case "IndexExpr":
case "CallExpr":
case "UnaryExpr":
case "BinaryExpr":
case "RangeExpr":
throw new Error(`node '${k.tag}' not a type`);
case "IdentTy": {
const sym = this.syms.get(ty);
if (sym.tag === "BuiltinTy") {
switch (sym.ident) {
case "void":
return Ty.Void;
case "bool":
return Ty.Bool;
case "i8":
return Ty.I8;
case "i16":
return Ty.I16;
case "i32":
return Ty.I32;
case "i64":
return Ty.I64;
case "isize":
return Ty.ISize;
case "u8":
return Ty.U8;
case "u16":
return Ty.U16;
case "u32":
return Ty.U32;
case "u64":
return Ty.U64;
case "usize":
return Ty.USize;
default:
throw new Error(
`unknown type '${k.ident}'`,
);
}
}
this.reporter.error(ty.loc, `symbol is not a type`);
return this.reporter.abort();
}
case "PtrTy": {
const ty = this.ty(k.ty);
return Ty.create("Ptr", { ty });
}
case "PtrMutTy": {
const ty = this.ty(k.ty);
return Ty.create("PtrMut", { ty });
}
case "ArrayTy": {
const ty = this.ty(k.ty);
const lengthTy = this.expr(k.length, Ty.USize);
if (!lengthTy.resolvableWith(Ty.USize)) {
this.reporter.error(
k.length.loc,
`for array length, expected 'int', got '${lengthTy.pretty()}'`,
);
this.reporter.abort();
}
if (!k.length.is("IntExpr")) {
this.reporter.error(
k.length.loc,
`array length must be an 'int' expression`,
);
this.reporter.abort();
}
const length = k.length.kind.value;
return Ty.create("Array", { ty, length });
}
case "SliceTy": {
const ty = this.ty(k.ty);
return Ty.create("Slice", { ty });
}
}
}
private cachedCheck(node: ast.Node, func: () => Ty): Ty {
const existing = this.nodeTys.get(node);
if (existing !== undefined) {
return existing;
}
const ty = func();
this.nodeTys.set(node, ty);
return ty;
}
private resolve(
ty: Ty,
expected: Ty,
loc: Loc,
inCaseOfError?: () => void,
): TyRes {
if (ty == expected) {
return { ty, rewriteSubtree: false };
}
if (expected.is("Any")) {
return { ty, rewriteSubtree: false };
}
if (expected.is("Int") && ty.is("AnyInt")) {
return { ty: expected, rewriteSubtree: true };
}
if (expected.is("AnyInt") && ty.is("Int")) {
return { ty, rewriteSubtree: true };
}
if (!ty.resolvableWith(expected)) {
if (inCaseOfError) {
inCaseOfError();
} else {
this.reporter.error(
loc,
`expected type '${expected.pretty()}', got '${ty.pretty()}'`,
);
}
this.reporter.abort();
}
throw new Error(
`resolving '${ty.pretty()}' with '${expected.pretty()}' not implemented`,
);
}
private rewriteTree(node: ast.Node, ty: Ty) {
this.nodeTys.set(node, ty);
const k = node.kind;
switch (k.tag) {
case "IdentExpr":
break;
case "IntExpr":
break;
case "BinaryExpr": {
const op = new BinaryOp(k.op);
if (op.isPropagating()) {
this.rewriteTree(k.left, ty);
this.rewriteTree(k.right, ty);
}
break;
}
default:
throw new Error(`not implemented for '${k.tag}'`);
}
}
}
type TyRes = {
ty: Ty;
rewriteSubtree: boolean;
};
type BinaryOpTest = (op: ast.BinaryOp, left: Ty, right: Ty) => Ty | null;
const binaryOpTests: BinaryOpTest[] = [
(op, left, right) => {
const ops: ast.BinaryOp[] = ["Add", "Sub", "Mul", "Div", "Rem"];
if (
ops.includes(op) && (left.is("Int") || left.is("AnyInt")) &&
left.resolvableWith(right)
) {
return left;
}
return null;
},
(op, left, right) => {
const ops: ast.BinaryOp[] = ["Eq", "Ne", "Lt", "Gt", "Lte", "Gte"];
if (
ops.includes(op) && (left.is("Int") || left.is("AnyInt")) &&
left.resolvableWith(right)
) {
return Ty.Bool;
}
return null;
},
];
class BinaryOp {
constructor(
public op: ast.BinaryOp,
) {}
isPropagating(): boolean {
return (["Add", "Sub", "Mul", "Div", "Rem"] as ast.BinaryOp[])
.includes(this.op);
}
}

View File

@ -1,4 +1,3 @@
export * from "./parse.ts"; export * from "./parse.ts";
export * from "./resolve.ts"; export * from "./resolve.ts";
export * from "./check.ts"; export * from "./check.ts";
export * from "./check2.ts";

View File

@ -37,40 +37,10 @@ if (!mainFn) {
Deno.exit(1); Deno.exit(1);
} }
const tys = new front.Tys(syms, fileRep);
const checker = new front.Checker(syms, fileRep); const checker = new front.Checker(syms, fileRep);
const fnTys = checker.checkFn(mainFn); const fnTys = checker.checkFn(mainFn);
// fileAst.visit({ const m = new middle.MiddleLowerer(syms, checker);
// visit(node) {
// switch (node.kind.tag) {
// // case "IdentExpr":
// case "IntExpr": // case "StrExpr":
// // case "ArrayExpr":
// // case "IndexExpr":
// // case "CallExpr":
// // case "UnaryExpr":
// // case "BinaryExpr":
// // case "RangeExpr":
// {
// const oldTy = tys.expr(node);
// const newTy = fnTys.exprTy(node);
// if (oldTy !== newTy) {
// if (newTy.is("AnyInt") && oldTy.is("Int")) {
// break;
// }
// throw new Error(
// `'${newTy.pretty()}' != '${oldTy.pretty()}'`,
// );
// }
// break;
// }
// }
// },
// });
const m = new middle.MiddleLowerer(syms, tys);
const mainMiddleFn = m.lowerFn(mainFn); const mainMiddleFn = m.lowerFn(mainFn);
if (Deno.args.includes("--print-mir")) { if (Deno.args.includes("--print-mir")) {

View File

@ -1,5 +1,5 @@
import * as ast from "./ast.ts"; import * as ast from "./ast.ts";
import { Syms, Tys } from "./front/mod.ts"; import { CheckedFn, Checker, Syms } from "./front/mod.ts";
import { Ty } from "./ty.ts"; import { Ty } from "./ty.ts";
import { BasicBlock, BinaryOp, Fn, Inst, InstKind } from "./mir.ts"; import { BasicBlock, BinaryOp, Fn, Inst, InstKind } from "./mir.ts";
@ -8,14 +8,19 @@ export class MiddleLowerer {
constructor( constructor(
private syms: Syms, private syms: Syms,
private tys: Tys, private checker: Checker,
) {} ) {}
lowerFn(stmt: ast.FnStmt): Fn { lowerFn(stmt: ast.FnStmt): Fn {
if (this.fns.has(stmt.id)) { if (this.fns.has(stmt.id)) {
return this.fns.get(stmt.id)!; return this.fns.get(stmt.id)!;
} }
const fn = new FnLowerer(this, this.syms, this.tys, stmt).lower(); const fn = new FnLowerer(
this,
this.syms,
this.checker.checkFn(stmt),
stmt,
).lower();
this.fns.set(stmt.id, fn); this.fns.set(stmt.id, fn);
return fn; return fn;
} }
@ -34,12 +39,12 @@ class FnLowerer {
constructor( constructor(
private lowerer: MiddleLowerer, private lowerer: MiddleLowerer,
private syms: Syms, private syms: Syms,
private tys: Tys, private tys: CheckedFn,
private stmt: ast.FnStmt, private stmt: ast.FnStmt,
) {} ) {}
lower(): Fn { lower(): Fn {
const ty = this.tys.fnStmt(this.stmt); const ty = this.tys.ty();
this.lowerBlock(this.stmt.kind.body.as("Block")); this.lowerBlock(this.stmt.kind.body.as("Block"));
this.pushInst(Ty.Void, "Return", { source: this.makeVoid() }); this.pushInst(Ty.Void, "Return", { source: this.makeVoid() });
this.bbs[0].insts.unshift(...this.allocs); this.bbs[0].insts.unshift(...this.allocs);
@ -172,7 +177,7 @@ class FnLowerer {
} }
private lowerLetStmt(stmt: ast.NodeWithKind<"LetStmt">) { private lowerLetStmt(stmt: ast.NodeWithKind<"LetStmt">) {
const ty = this.tys.param(stmt.kind.param.as("Param")); const ty = this.tys.exprTy(stmt.kind.expr);
const expr = this.lowerExpr(stmt.kind.expr); const expr = this.lowerExpr(stmt.kind.expr);
const local = new Inst( const local = new Inst(
Ty.create("PtrMut", { ty }), Ty.create("PtrMut", { ty }),
@ -195,7 +200,7 @@ class FnLowerer {
private lowerPlace(place: ast.Node): Inst { private lowerPlace(place: ast.Node): Inst {
// evaluate to most direct pointer // evaluate to most direct pointer
const _ty = this.tys.place(place); const _ty = this.tys.exprTy(place);
if (place.is("IdentExpr")) { if (place.is("IdentExpr")) {
const sym = this.syms.get(place); const sym = this.syms.get(place);
@ -218,9 +223,9 @@ class FnLowerer {
if (place.is("IndexExpr")) { if (place.is("IndexExpr")) {
const value = place.kind.value; const value = place.kind.value;
const valueTy = this.tys.place(value); const valueTy = this.tys.exprTy(value);
const arg = place.kind.arg; const arg = place.kind.arg;
const argTy = this.tys.expr(arg); const argTy = this.tys.exprTy(arg);
if (valueTy.is("Array") || valueTy.is("Slice")) { if (valueTy.is("Array") || valueTy.is("Slice")) {
const valueInst = this.lowerPlace(place.kind.value); const valueInst = this.lowerPlace(place.kind.value);
if (argTy.is("Int")) { if (argTy.is("Int")) {
@ -257,7 +262,7 @@ class FnLowerer {
} }
private lowerExpr(expr: ast.Node): Inst { private lowerExpr(expr: ast.Node): Inst {
const ty = this.tys.expr(expr); const ty = this.tys.exprTy(expr);
if (expr.is("IdentExpr")) { if (expr.is("IdentExpr")) {
const sym = this.syms.get(expr); const sym = this.syms.get(expr);
if (sym.tag === "Fn") { if (sym.tag === "Fn") {
@ -265,7 +270,9 @@ class FnLowerer {
return this.pushInst(fn.ty, "Fn", { fn }); return this.pushInst(fn.ty, "Fn", { fn });
} }
if (sym.tag === "FnParam") { if (sym.tag === "FnParam") {
const ty = this.tys.expr(sym.param); const ty = this.tys.ty().as("FnStmt")
.kind.ty.as("Fn")
.kind.params[sym.idx];
return this.pushInst(ty, "Param", { idx: sym.idx }); return this.pushInst(ty, "Param", { idx: sym.idx });
} }
if (sym.tag === "Builtin") { if (sym.tag === "Builtin") {
@ -290,7 +297,7 @@ class FnLowerer {
return this.pushInst(ty, "Str", { value: expr.kind.value }); return this.pushInst(ty, "Str", { value: expr.kind.value });
} }
if (expr.is("ArrayExpr")) { if (expr.is("ArrayExpr")) {
const ty = this.tys.expr(expr); const ty = this.tys.exprTy(expr);
const values = expr.kind.values const values = expr.kind.values
.map((value) => this.lowerExpr(value)); .map((value) => this.lowerExpr(value));
return this.pushInst(ty, "Array", { values }); return this.pushInst(ty, "Array", { values });
@ -323,8 +330,8 @@ class FnLowerer {
return this.lowerUnaryExpr(expr); return this.lowerUnaryExpr(expr);
} }
if (expr.is("BinaryExpr")) { if (expr.is("BinaryExpr")) {
const leftTy = this.tys.expr(expr.kind.left); const leftTy = this.tys.exprTy(expr.kind.left);
const rightTy = this.tys.expr(expr.kind.right); const rightTy = this.tys.exprTy(expr.kind.right);
const binaryOp = binaryOpTests const binaryOp = binaryOpTests
.map((test) => test(expr.kind.op, leftTy, rightTy, ty)) .map((test) => test(expr.kind.op, leftTy, rightTy, ty))
.filter((tested) => tested) .filter((tested) => tested)
@ -342,8 +349,8 @@ class FnLowerer {
} }
private lowerUnaryExpr(expr: ast.NodeWithKind<"UnaryExpr">) { private lowerUnaryExpr(expr: ast.NodeWithKind<"UnaryExpr">) {
const resultTy = this.tys.expr(expr); const resultTy = this.tys.exprTy(expr);
const operandTy = this.tys.expr(expr.kind.expr); const operandTy = this.tys.exprTy(expr.kind.expr);
if ( if (
expr.kind.op === "Neg" && expr.kind.op === "Neg" &&
operandTy.resolvableWith(Ty.I32) && operandTy.resolvableWith(Ty.I32) &&
@ -376,7 +383,7 @@ class FnLowerer {
); );
} }
if (place.is("IndexExpr")) { if (place.is("IndexExpr")) {
const placeTy = this.tys.expr(place); const placeTy = this.tys.exprTy(place);
const placeInst = this.lowerPlace(place); const placeInst = this.lowerPlace(place);
if (placeTy.is("Slice")) { if (placeTy.is("Slice")) {
return placeInst; return placeInst;