import * as ast from "./ast.ts"; export class Ty { private static idCounter = 0; private static internedTys = new Map(); static create( tag: Tag, kind: Omit, ): Ty { const ty = new Ty( this.idCounter, { tag, ...kind } as TyKind & { tag: Tag }, ); const hash = ty.internHash(); if (this.internedTys.has(hash)) { return this.internedTys.get(hash)!; } this.internedTys.set(hash, ty); this.idCounter += 1; return ty; } static Error = Ty.create("Error", {}); static Void = Ty.create("Void", {}); static Int = Ty.create("Int", {}); static Bool = Ty.create("Bool", {}); private internHash(): string { return JSON.stringify(this.kind); } private constructor( public id: number, public kind: TyKind, ) {} is< Tag extends TyKind["tag"], >(tag: Tag): this is Ty & { kind: { tag: Tag } } { return this.kind.tag === tag; } compatibleWith(other: Ty): boolean { // types are interned--we can just do this if (this === other) { return true; } if (this.is("Error")) { return false; } if (this.is("Void")) { return other.is("Void"); } if (this.is("Int")) { return other.is("Int"); } if (this.is("Bool")) { return other.is("Bool"); } if (this.is("Fn")) { if (!other.is("Fn")) { return false; } for (const i of this.kind.params.keys()) { if (!this.kind.params[i].compatibleWith(other.kind.params[i])) { return false; } } if (!this.kind.retTy.compatibleWith(other.kind.retTy)) { return false; } return true; } if (this.is("FnStmt")) { if (!other.is("FnStmt")) { return false; } if (!this.kind.ty.compatibleWith(other.kind.ty)) { return false; } // redundant; sanity check if (this.kind.stmt.id !== other.kind.stmt.id) { return false; } return true; } throw new Error(`'${this.kind.tag}' not handled`); } pretty(): string { if (this.is("Error")) { return ""; } if (this.is("Void")) { return "void"; } if (this.is("Int")) { return "int"; } if (this.is("Bool")) { return "bool"; } if (this.is("Ptr")) { return `*${this.kind.ty.pretty()}`; } if (this.is("PtrMut")) { return `*mut ${this.kind.ty.pretty()}`; } if (this.is("Fn")) { return `fn (${ this.kind.params.map((param) => param.pretty()).join(", ") }) -> ${this.kind.retTy.pretty()}`; } if (this.is("FnStmt")) { if (!this.kind.ty.is("Fn")) throw new Error(); return `fn ${this.kind.stmt.kind.ident}(${ this.kind.ty.kind.params.map((param) => param.pretty()).join( ", ", ) }) -> ${this.kind.ty.kind.retTy.pretty()}`; } throw new Error("unhandled"); } } export type TyKind = | { tag: "Error" } | { tag: "Void" } | { tag: "Int" } | { tag: "Bool" } | { tag: "Ptr"; ty: Ty } | { tag: "PtrMut"; ty: Ty } | { tag: "Array"; ty: Ty; length: number } | { tag: "Fn"; params: Ty[]; retTy: Ty } | { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> };