267 lines
7.7 KiB
TypeScript
267 lines
7.7 KiB
TypeScript
import * as ast from "./ast.ts";
|
|
import * as stringify from "./stringify.ts";
|
|
|
|
export class Ty {
|
|
private static idCounter = 0;
|
|
private static internedTys = new Map<string, Ty>();
|
|
|
|
static create<Tag extends TyKind["tag"]>(
|
|
tag: Tag,
|
|
kind: Omit<TyKind & { tag: Tag }, "tag">,
|
|
): 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 I8 = Ty.create("Int", { intTy: "i8" });
|
|
static I16 = Ty.create("Int", { intTy: "i16" });
|
|
static I32 = Ty.create("Int", { intTy: "i32" });
|
|
static I64 = Ty.create("Int", { intTy: "i64" });
|
|
static ISize = Ty.create("Int", { intTy: "isize" });
|
|
static U8 = Ty.create("Int", { intTy: "u8" });
|
|
static U16 = Ty.create("Int", { intTy: "u16" });
|
|
static U32 = Ty.create("Int", { intTy: "u32" });
|
|
static U64 = Ty.create("Int", { intTy: "u64" });
|
|
static USize = Ty.create("Int", { intTy: "usize" });
|
|
static Bool = Ty.create("Bool", {});
|
|
|
|
static Ptr(ty: Ty): Ty {
|
|
return this.create("Ptr", { ty });
|
|
}
|
|
static PtrMut(ty: Ty): Ty {
|
|
return this.create("PtrMut", { ty });
|
|
}
|
|
static Slice(ty: Ty): Ty {
|
|
return this.create("Slice", { ty });
|
|
}
|
|
static Array(ty: Ty, length: number): Ty {
|
|
return this.create("Array", { ty, length });
|
|
}
|
|
static Fn(params: Ty[], retTy: Ty, generics: Ty[] | null): Ty {
|
|
return this.create("Fn", { params, retTy, generics });
|
|
}
|
|
static Generic(idx: number): Ty {
|
|
return this.create("Generic", { idx });
|
|
}
|
|
static Instance(ty: Ty, args: Ty[]): Ty {
|
|
return this.create("Instance", { ty, args });
|
|
}
|
|
|
|
/** Only used in type checker. */
|
|
static Any = Ty.create("Any", {});
|
|
/** Only used in type checker. */
|
|
static AnyInt = Ty.create("AnyInt", {});
|
|
/** Only used in type checker. */
|
|
static AnyIndexable(ty: Ty): Ty {
|
|
return this.create("AnyIndexable", { ty });
|
|
}
|
|
/** Only used in type checker. */
|
|
static AnyCallable(ty: Ty): Ty {
|
|
return this.create("AnyCallable", { ty });
|
|
}
|
|
/** Only used in type checker. */
|
|
static AnyDerefable(ty: Ty): Ty {
|
|
return this.create("AnyDerefable", { ty });
|
|
}
|
|
|
|
private internHash(): string {
|
|
if (this.is("FnStmt")) {
|
|
return JSON.stringify({ ...this.kind, stmt: this.kind.stmt.id });
|
|
}
|
|
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;
|
|
}
|
|
|
|
assertIs<
|
|
Tag extends TyKind["tag"],
|
|
>(tag: Tag): asserts this is Ty & { kind: { tag: Tag } } {
|
|
if (this.kind.tag !== tag) {
|
|
throw new Error(`ty is not '${tag}'`);
|
|
}
|
|
}
|
|
|
|
as<
|
|
Tag extends TyKind["tag"],
|
|
>(tag: Tag): Ty & { kind: { tag: Tag } } {
|
|
this.assertIs(tag);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Used for checking and inference in the type checker
|
|
* to say if two types are able to implicitly be converted
|
|
* into each other or to a third type. Past type inference
|
|
* this function is meaningless.
|
|
*/
|
|
resolvableWith(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") && this.kind.intTy == other.kind.intTy ||
|
|
other.is("AnyInt");
|
|
}
|
|
|
|
if (this.is("Bool")) {
|
|
return other.is("Bool");
|
|
}
|
|
if (this.is("Ptr")) {
|
|
if (!other.is("Ptr")) {
|
|
return false;
|
|
}
|
|
if (!this.kind.ty.resolvableWith(other.kind.ty)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (this.is("Array")) {
|
|
if (!other.is("Array")) {
|
|
return false;
|
|
}
|
|
if (!this.kind.ty.resolvableWith(other.kind.ty)) {
|
|
return false;
|
|
}
|
|
if (this.kind.length !== other.kind.length) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (this.is("Slice")) {
|
|
if (!other.is("Slice")) {
|
|
return false;
|
|
}
|
|
if (!this.kind.ty.resolvableWith(other.kind.ty)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (this.is("Range")) {
|
|
return other.is("Range");
|
|
}
|
|
if (this.is("Fn")) {
|
|
if (!other.is("Fn")) {
|
|
return false;
|
|
}
|
|
for (const i of this.kind.params.keys()) {
|
|
if (!this.kind.params[i].resolvableWith(other.kind.params[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!this.kind.retTy.resolvableWith(other.kind.retTy)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (this.is("FnStmt")) {
|
|
// Since FnStmt tys are only compatible with itself,
|
|
// we can count on the ty cache for this check.
|
|
return false;
|
|
}
|
|
if (this.is("Any")) {
|
|
return true;
|
|
}
|
|
if (this.is("AnyInt")) {
|
|
return other.is("Int");
|
|
}
|
|
throw new Error(`'${this.kind.tag}' not handled`);
|
|
}
|
|
|
|
isSized(): boolean {
|
|
if (this.is("Slice")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
isIndexable(): boolean {
|
|
return this.is("Array") || this.is("Slice") || this.is("AnyIndexable");
|
|
}
|
|
|
|
indexableTy(): Ty | null {
|
|
if (this.is("Array") || this.is("Slice") || this.is("AnyIndexable")) {
|
|
return this.kind.ty;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
isCallable(): boolean {
|
|
return this.is("FnStmt") || this.is("Fn");
|
|
}
|
|
|
|
callableTy(): Ty & { kind: { tag: "Fn" } } {
|
|
if (this.is("Fn")) {
|
|
return this;
|
|
}
|
|
this.assertIs("FnStmt");
|
|
return this.kind.ty.as("Fn");
|
|
}
|
|
|
|
isDerefable(): boolean {
|
|
return this.is("Ptr") || this.is("PtrMut") || this.is("AnyDerefable");
|
|
}
|
|
|
|
derefableTy(): Ty | null {
|
|
if (this.is("Ptr") || this.is("PtrMut") || this.is("AnyDerefable")) {
|
|
return this.kind.ty;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
isCheckerInternal(): boolean {
|
|
return this.is("Any") || this.is("AnyInt") || this.is("AnyIndexable") ||
|
|
this.is("AnyCallable") || this.is("AnyDerefable");
|
|
}
|
|
|
|
pretty(colors?: stringify.PrettyColors): string {
|
|
return stringify.tyPretty(this, colors);
|
|
}
|
|
}
|
|
|
|
export type TyKind =
|
|
| { tag: "Error" }
|
|
| { tag: "Void" }
|
|
| { tag: "Int"; intTy: ast.IntTy }
|
|
| { tag: "Bool" }
|
|
| { tag: "Ptr"; ty: Ty }
|
|
| { tag: "PtrMut"; ty: Ty }
|
|
| { tag: "Array"; ty: Ty; length: number }
|
|
| { tag: "Slice"; ty: Ty }
|
|
| { tag: "Range" }
|
|
| { tag: "Fn"; params: Ty[]; retTy: Ty; generics: Ty[] | null }
|
|
| { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> }
|
|
| { tag: "Generic"; idx: number }
|
|
| { tag: "Instance"; ty: Ty; args: Ty[] }
|
|
| { tag: "Any" | "AnyInt" }
|
|
| { tag: "AnyIndexable"; ty: Ty }
|
|
| { tag: "AnyCallable"; ty: Ty }
|
|
| { tag: "AnyDerefable"; ty: Ty };
|