import { FastStringMap } from "./util"; enum TypeKind { OpaqueType, AnyType, NeverType, FunctionType, RecordType, VariantType, TupleType, } export type Type = OpaqueType | AnyType | NeverType | FunctionType | RecordType | VariantType | TupleType abstract class TypeBase { abstract kind: TypeKind; } export class OpaqueType extends TypeBase { kind: TypeKind.OpaqueType = TypeKind.OpaqueType; } export function isOpaqueType(value: any): value is OpaqueType { return value.kind === TypeKind.OpaqueType; } export function createOpaqueType(): OpaqueType { return new OpaqueType(); } export class AnyType extends TypeBase { kind: TypeKind.AnyType = TypeKind.AnyType; } export function createAnyType(): AnyType { return new AnyType(); } export function isAnyType(value: any): value is AnyType { return value.kind === TypeKind.AnyType; } export class NeverType extends TypeBase { kind: TypeKind.NeverType = TypeKind.NeverType; } export function isNeverType(value: any): value is NeverType { return value instanceof NeverType; } export class FunctionType extends TypeBase { kind: TypeKind.FunctionType = TypeKind.FunctionType; constructor( public paramTypes: Type[], public returnType: Type, ) { super(); } public getParameterCount(): number { return this.paramTypes.length; } public getParamTypeAtIndex(index: number) { if (index < 0 || index >= this.paramTypes.length) { throw new Error(`Could not get the parameter type at index ${index} because the index was out of bounds.`); } return this.paramTypes[index]; } } export function isFunctionType(value: any): value is FunctionType { return value instanceof FunctionType; } export class VariantType extends TypeBase { kind: TypeKind.VariantType = TypeKind.VariantType; constructor(public elementTypes: Type[]) { super(); } public getOwnElementTypes(): IterableIterator { return this.elementTypes[Symbol.iterator](); } } export function createVariantType(...elementTypes: Type[]): VariantType { return new VariantType(elementTypes); } export function isVariantType(value: any): value is VariantType { return value instanceof VariantType; } export class RecordType { kind: TypeKind.RecordType = TypeKind.RecordType; private fieldTypes = new FastStringMap(); constructor( iterable: IterableIterator<[string, Type]>, ) { for (const [name, type] of iterable) { this.fieldTypes.set(name, type); } } public hasField(name: string) { return name in this.fieldTypes; } public getTypeOfField(name: string) { return this.fieldTypes.get(name); } } export function isRecordType(value: any): value is RecordType { return value.kind === TypeKind.RecordType; } export class TupleType extends TypeBase { kind: TypeKind.TupleType = TypeKind.TupleType; constructor(public elementTypes: Type[]) { super(); } } export function createTupleType(...elementTypes: Type[]) { return new TupleType(elementTypes); } export function isTupleType(value: any): value is TupleType { return value.kind === TypeKind.TupleType; } export function createVoidType() { return createTupleType(); } export function isVoidType(value: any) { return isTupleType(value) && value.elementTypes.length === 0; } export function narrowType(outer: Type, inner: Type): Type { if (isAnyType(outer) || isNeverType(inner)) { return inner; } // TODO cover the other cases return outer; } export function intersectTypes(a: Type, b: Type): Type { if (isNeverType(a) || isNeverType(b)) { return new NeverType(); } if (isAnyType(b)) { return a } if (isAnyType(a)) { return b; } if (isFunctionType(a) && isFunctionType(b)) { if (a.paramTypes.length !== b.paramTypes.length) { return new NeverType(); } const returnType = intersectTypes(a.returnType, b.returnType); const paramTypes = a.paramTypes .map((_, i) => intersectTypes(a.paramTypes[i], b.paramTypes[i])); return new FunctionType(paramTypes, returnType) } return new NeverType(); } export function isTypeAssignable(a: Type, b: Type): boolean { if (isNeverType(a)) { return false; } if (isAnyType(b)) { return true; } if (isOpaqueType(a) && isOpaqueType(b)) { return a === b; } if (a.kind !== b.kind) { return false; } if (isFunctionType(a) && isFunctionType(b)) { if (a.paramTypes.length !== b.paramTypes.length) { return false; } const paramCount = a.getParameterCount(); for (let i = 0; i < paramCount; i++) { if (!isTypeAssignable(a.getParamTypeAtIndex(i), b.getParamTypeAtIndex(i))) { return false; } } return true; } throw new Error(`Should not get here.`); }