bolt/src/types.ts
2020-05-23 22:23:44 +02:00

193 lines
4 KiB
TypeScript

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<Type> {
return this.elementTypes[Symbol.iterator]();
}
}
export function isVariantType(value: any): value is VariantType {
return value instanceof VariantType;
}
export class RecordType {
kind: TypeKind.RecordType = TypeKind.RecordType;
private fieldTypes = new FastStringMap<string, Type>();
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 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.`);
}