Also removes TNominal from the list of types because it is redundant w.r.t. TCon.
660 lines
14 KiB
TypeScript
660 lines
14 KiB
TypeScript
import { InspectOptions } from "util";
|
||
import { ClassDeclaration, EnumDeclaration, StructDeclaration, Syntax } from "./cst";
|
||
import { InspectFn, assert, assertNever, toStringTag } from "./util";
|
||
|
||
export enum TypeKind {
|
||
Arrow,
|
||
UniVar,
|
||
RigidVar,
|
||
Con,
|
||
Tuple,
|
||
TupleIndex,
|
||
App,
|
||
Nominal,
|
||
Field,
|
||
Nil,
|
||
Absent,
|
||
Present,
|
||
}
|
||
|
||
export abstract class TypeBase {
|
||
|
||
public abstract readonly kind: TypeKind;
|
||
|
||
public parent: Type = this as unknown as Type;
|
||
|
||
public next: Type = this as any;
|
||
|
||
public abstract node: Syntax | null;
|
||
|
||
public static join(a: Type, b: Type): void {
|
||
const keep = a.next;
|
||
a.next = b;
|
||
b.next = keep;
|
||
}
|
||
|
||
public abstract getTypeVars(): Iterable<TVar>;
|
||
|
||
public abstract shallowClone(): Type;
|
||
|
||
public abstract substitute(sub: TVSub): Type;
|
||
|
||
public find(): Type {
|
||
let curr = this as unknown as Type;
|
||
while (curr.parent !== curr) {
|
||
curr.parent = curr.parent.parent;
|
||
curr = curr.parent;
|
||
}
|
||
return curr;
|
||
}
|
||
|
||
public set(newType: Type): void {
|
||
this.find().parent = newType;
|
||
}
|
||
|
||
public hasTypeVar(tv: TUniVar): boolean {
|
||
for (const other of this.getTypeVars()) {
|
||
if (tv.id === other.id) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public abstract [toStringTag](depth: number, options: InspectOptions, inspect: InspectFn): string;
|
||
|
||
}
|
||
|
||
export function isType(value: any): value is Type {
|
||
return value !== undefined
|
||
&& value !== null
|
||
&& value instanceof TypeBase;
|
||
}
|
||
|
||
export class TRigidVar extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.RigidVar;
|
||
|
||
public constructor(
|
||
public id: number,
|
||
public displayName: string,
|
||
public node: Syntax | null = null
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public *getTypeVars(): Iterable<TVar> {
|
||
yield this;
|
||
}
|
||
|
||
public shallowClone(): TRigidVar {
|
||
return new TRigidVar(
|
||
this.id,
|
||
this.displayName,
|
||
this.node
|
||
);
|
||
}
|
||
|
||
public substitute(sub: TVSub): Type {
|
||
const other = sub.get(this);
|
||
return other === undefined
|
||
? this : other.substitute(sub);
|
||
}
|
||
|
||
public [toStringTag]() {
|
||
return this.displayName;
|
||
}
|
||
|
||
}
|
||
|
||
export class TUniVar extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.UniVar;
|
||
|
||
public context = new Set<ClassDeclaration>();
|
||
|
||
public constructor(
|
||
public id: number,
|
||
public node: Syntax | null = null,
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public *getTypeVars(): Iterable<TVar> {
|
||
yield this;
|
||
}
|
||
|
||
public shallowClone(): TUniVar {
|
||
return new TUniVar(this.id, this.node);
|
||
}
|
||
|
||
public substitute(sub: TVSub): Type {
|
||
const other = sub.get(this);
|
||
return other === undefined
|
||
? this : other.substitute(sub);
|
||
}
|
||
|
||
public [toStringTag]() {
|
||
return 'a' + this.id;
|
||
}
|
||
|
||
}
|
||
|
||
export class TNil extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.Nil;
|
||
|
||
public constructor(
|
||
public node: Syntax | null = null
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public substitute(_sub: TVSub): Type {
|
||
return this;
|
||
}
|
||
|
||
public shallowClone(): Type {
|
||
return new TNil(this.node);
|
||
}
|
||
|
||
public *getTypeVars(): Iterable<TVar> {
|
||
|
||
}
|
||
|
||
public [toStringTag]() {
|
||
return '∂Abs';
|
||
}
|
||
|
||
}
|
||
|
||
export class TAbsent extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.Absent;
|
||
|
||
public constructor(
|
||
public node: Syntax | null = null,
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public substitute(_sub: TVSub): Type {
|
||
return this;
|
||
}
|
||
|
||
public shallowClone(): Type {
|
||
return new TAbsent(this.node);
|
||
}
|
||
|
||
public *getTypeVars(): Iterable<TVar> {
|
||
|
||
}
|
||
|
||
public [toStringTag]() {
|
||
return 'Abs';
|
||
}
|
||
|
||
}
|
||
|
||
export class TPresent extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.Present;
|
||
|
||
public constructor(
|
||
public type: Type,
|
||
public node: Syntax | null = null,
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public substitute(sub: TVSub): Type {
|
||
return new TPresent(this.type.substitute(sub), this.node);
|
||
}
|
||
|
||
public getTypeVars(): Iterable<TVar> {
|
||
return this.type.getTypeVars();
|
||
}
|
||
|
||
public shallowClone(): Type {
|
||
return new TPresent(this.type, this.node);
|
||
}
|
||
|
||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||
return 'Pre ' + inspect(this.type, options);
|
||
}
|
||
|
||
}
|
||
|
||
export class TArrow extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.Arrow;
|
||
|
||
public constructor(
|
||
public paramType: Type,
|
||
public returnType: Type,
|
||
public node: Syntax | null = null,
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public static build(paramTypes: Type[], returnType: Type, node: Syntax | null = null): Type {
|
||
let result = returnType;
|
||
for (let i = paramTypes.length-1; i >= 0; i--) {
|
||
result = new TArrow(paramTypes[i], result, node);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
public *getTypeVars(): Iterable<TVar> {
|
||
yield* this.paramType.getTypeVars();
|
||
yield* this.returnType.getTypeVars();
|
||
}
|
||
|
||
public shallowClone(): TArrow {
|
||
return new TArrow(
|
||
this.paramType,
|
||
this.returnType,
|
||
this.node,
|
||
)
|
||
}
|
||
|
||
public substitute(sub: TVSub): Type {
|
||
let changed = false;
|
||
const newParamType = this.paramType.substitute(sub);
|
||
if (newParamType !== this.paramType) {
|
||
changed = true;
|
||
}
|
||
const newReturnType = this.returnType.substitute(sub);
|
||
if (newReturnType !== this.returnType) {
|
||
changed = true;
|
||
}
|
||
return changed ? new TArrow(newParamType, newReturnType, this.node) : this;
|
||
}
|
||
|
||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||
return inspect(this.paramType, options) + ' -> ' + inspect(this.returnType, options);
|
||
}
|
||
|
||
}
|
||
|
||
export class TCon extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.Con;
|
||
|
||
public constructor(
|
||
public id: number,
|
||
public displayName: string,
|
||
public node: Syntax | null = null,
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public *getTypeVars(): Iterable<TVar> {
|
||
|
||
}
|
||
|
||
public shallowClone(): TCon {
|
||
return new TCon(
|
||
this.id,
|
||
this.displayName,
|
||
this.node,
|
||
);
|
||
}
|
||
|
||
public substitute(_sub: TVSub): Type {
|
||
return this;
|
||
}
|
||
|
||
public [toStringTag](_depth: number, _options: InspectOptions, _inspect: InspectFn) {
|
||
return this.displayName;
|
||
}
|
||
|
||
}
|
||
|
||
export class TTupleIndex extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.TupleIndex;
|
||
|
||
public constructor(
|
||
public tupleType: Type,
|
||
public index: number,
|
||
public node: Syntax | null = null,
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public getTypeVars(): Iterable<TVar> {
|
||
return this.tupleType.getTypeVars();
|
||
}
|
||
|
||
public substitute(sub: TVSub): Type {
|
||
const newTupleType = this.tupleType.substitute(sub);
|
||
if (newTupleType === this.tupleType) {
|
||
return this;
|
||
}
|
||
return new TTupleIndex(newTupleType, this.index);
|
||
}
|
||
|
||
public shallowClone(): TTupleIndex {
|
||
return new TTupleIndex(
|
||
this.tupleType,
|
||
this.index,
|
||
);
|
||
}
|
||
|
||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn): string {
|
||
return inspect(this.tupleType, options) + '.' + this.index;
|
||
}
|
||
|
||
}
|
||
|
||
export class TTuple extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.Tuple;
|
||
|
||
public constructor(
|
||
public elementTypes: Type[],
|
||
public node: Syntax | null = null,
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public *getTypeVars(): Iterable<TVar> {
|
||
for (const elementType of this.elementTypes) {
|
||
yield* elementType.getTypeVars();
|
||
}
|
||
}
|
||
|
||
public shallowClone(): TTuple {
|
||
return new TTuple(
|
||
this.elementTypes,
|
||
this.node,
|
||
);
|
||
}
|
||
|
||
public substitute(sub: TVSub): Type {
|
||
let changed = false;
|
||
const newElementTypes = [];
|
||
for (const elementType of this.elementTypes) {
|
||
const newElementType = elementType.substitute(sub);
|
||
if (newElementType !== elementType) {
|
||
changed = true;
|
||
}
|
||
newElementTypes.push(newElementType);
|
||
}
|
||
return changed ? new TTuple(newElementTypes, this.node) : this;
|
||
}
|
||
|
||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||
return this.elementTypes.map(t => inspect(t, options)).join(' × ');
|
||
}
|
||
|
||
}
|
||
|
||
export class TField extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.Field;
|
||
|
||
public constructor(
|
||
public name: string,
|
||
public type: Type,
|
||
public restType: Type,
|
||
public node: Syntax | null = null,
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public getTypeVars(): Iterable<TVar> {
|
||
return this.type.getTypeVars();
|
||
}
|
||
|
||
public shallowClone(): TField {
|
||
return new TField(
|
||
this.name,
|
||
this.type,
|
||
this.restType,
|
||
this.node,
|
||
);
|
||
}
|
||
|
||
public static build(fields: Map<string, Type>, restType: Type): Type {
|
||
let out = restType;
|
||
for (const [name, type] of fields) {
|
||
out = new TField(name, new TPresent(type, type.node), out, type.node);
|
||
}
|
||
return out
|
||
}
|
||
|
||
public static sort(type: Type): Type {
|
||
const fields = new Map<string, TField>();
|
||
while (type.kind === TypeKind.Field) {
|
||
fields.set(type.name, type);
|
||
type = type.restType;
|
||
}
|
||
const keys = [...fields.keys()].sort().reverse();
|
||
let out: Type = type;
|
||
for (const key of keys) {
|
||
const field = fields.get(key)!;
|
||
out = new TField(key, field.type, out, field.node);
|
||
}
|
||
return out
|
||
}
|
||
|
||
public substitute(sub: TVSub): Type {
|
||
const newType = this.type.substitute(sub);
|
||
const newRestType = this.restType.substitute(sub);
|
||
return newType !== this.type || newRestType !== this.restType
|
||
? new TField(this.name, newType, newRestType, this.node) : this;
|
||
}
|
||
|
||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||
let out = '{ ' + this.name + ': ' + inspect(this.type, options);
|
||
let type = this.restType;
|
||
while (type.kind === TypeKind.Field) {
|
||
out += '; ' + type.name + ': ' + inspect(type.type, options);
|
||
type = type.restType;
|
||
}
|
||
if (type.kind !== TypeKind.Nil) {
|
||
out += '; ' + inspect(type, options);
|
||
}
|
||
return out + ' }'
|
||
}
|
||
|
||
}
|
||
|
||
export class TApp extends TypeBase {
|
||
|
||
public readonly kind = TypeKind.App;
|
||
|
||
public constructor(
|
||
public left: Type,
|
||
public right: Type,
|
||
public node: Syntax | null = null
|
||
) {
|
||
super();
|
||
}
|
||
|
||
public static build(resultType: Type, types: Type[], node: Syntax | null = null): Type {
|
||
for (let i = 0; i < types.length; i++) {
|
||
resultType = new TApp(resultType, types[i], node);
|
||
}
|
||
return resultType;
|
||
}
|
||
|
||
public *getTypeVars(): Iterable<TVar> {
|
||
yield* this.left.getTypeVars();
|
||
yield* this.right.getTypeVars();
|
||
}
|
||
|
||
public shallowClone() {
|
||
return new TApp(
|
||
this.left,
|
||
this.right,
|
||
this.node
|
||
);
|
||
}
|
||
|
||
public substitute(sub: TVSub): Type {
|
||
let changed = false;
|
||
const newOperatorType = this.left.substitute(sub);
|
||
if (newOperatorType !== this.left) {
|
||
changed = true;
|
||
}
|
||
const newArgType = this.right.substitute(sub);
|
||
if (newArgType !== this.right) {
|
||
changed = true;
|
||
}
|
||
return changed ? new TApp(newOperatorType, newArgType, this.node) : this;
|
||
}
|
||
|
||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||
return inspect(this.left, options) + ' ' + inspect(this.right, options);
|
||
}
|
||
|
||
}
|
||
|
||
export type Type
|
||
= TCon
|
||
| TArrow
|
||
| TRigidVar
|
||
| TUniVar
|
||
| TTuple
|
||
| TApp
|
||
| TField
|
||
| TNil
|
||
| TPresent
|
||
| TAbsent
|
||
| TTupleIndex
|
||
|
||
export type TVar
|
||
= TUniVar
|
||
| TRigidVar
|
||
|
||
|
||
export function typesEqual(a: Type, b: Type): boolean {
|
||
if (a.kind !== b.kind) {
|
||
return false;
|
||
}
|
||
switch (a.kind) {
|
||
case TypeKind.Con:
|
||
assert(b.kind === TypeKind.Con);
|
||
return a.id === b.id;
|
||
case TypeKind.UniVar:
|
||
assert(b.kind === TypeKind.UniVar);
|
||
return a.id === b.id;
|
||
case TypeKind.RigidVar:
|
||
assert(b.kind === TypeKind.RigidVar);
|
||
return a.id === b.id;
|
||
case TypeKind.Nil:
|
||
case TypeKind.Absent:
|
||
return true;
|
||
case TypeKind.App:
|
||
assert(b.kind === TypeKind.App);
|
||
return typesEqual(a.left, b.left) && typesEqual(a.right, b.right);
|
||
case TypeKind.Field:
|
||
assert(b.kind === TypeKind.Field);
|
||
return a.name === b.name && typesEqual(a.type, b.type) && typesEqual(a.restType, b.restType);
|
||
case TypeKind.Arrow:
|
||
assert(b.kind === TypeKind.Arrow);
|
||
return typesEqual(a.paramType, b.paramType) && typesEqual(a.returnType, b.returnType);
|
||
case TypeKind.Tuple:
|
||
assert(b.kind === TypeKind.Tuple);
|
||
if (a.elementTypes.length !== b.elementTypes.length) {
|
||
return false;
|
||
}
|
||
for (let i = 0; i < a.elementTypes.length; i++) {
|
||
if (!typesEqual(a.elementTypes[i], b.elementTypes[i])) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
case TypeKind.Present:
|
||
assert(b.kind === TypeKind.Present);
|
||
return typesEqual(a.type, b.type);
|
||
case TypeKind.TupleIndex:
|
||
assert(b.kind === TypeKind.TupleIndex);
|
||
return a.index === b.index && typesEqual(a.tupleType, b.tupleType);
|
||
default:
|
||
assertNever(a);
|
||
}
|
||
}
|
||
|
||
export class TVSet {
|
||
|
||
private mapping = new Map<number, TVar>();
|
||
|
||
public constructor(iterable?: Iterable<TVar>) {
|
||
if (iterable !== undefined) {
|
||
for (const tv of iterable) {
|
||
this.add(tv);
|
||
}
|
||
}
|
||
}
|
||
|
||
public add(tv: TVar): void {
|
||
this.mapping.set(tv.id, tv);
|
||
}
|
||
|
||
public has(tv: TVar): boolean {
|
||
return this.mapping.has(tv.id);
|
||
}
|
||
|
||
public intersectsType(type: Type): boolean {
|
||
for (const tv of type.getTypeVars()) {
|
||
if (this.has(tv)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public delete(tv: TVar): void {
|
||
this.mapping.delete(tv.id);
|
||
}
|
||
|
||
public get size(): number {
|
||
return this.mapping.size;
|
||
}
|
||
|
||
public [Symbol.iterator](): Iterator<TVar> {
|
||
return this.mapping.values();
|
||
}
|
||
|
||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||
let out = '{ ';
|
||
let first = true;
|
||
for (const tv of this) {
|
||
if (first) first = false;
|
||
else out += ', ';
|
||
out += inspect(tv, options);
|
||
}
|
||
return out + ' }';
|
||
}
|
||
|
||
}
|
||
|
||
export class TVSub {
|
||
|
||
private mapping = new Map<number, Type>();
|
||
|
||
public set(tv: TVar, type: Type): void {
|
||
this.mapping.set(tv.id, type);
|
||
}
|
||
|
||
public get(tv: TVar): Type | undefined {
|
||
return this.mapping.get(tv.id);
|
||
}
|
||
|
||
public has(tv: TVar): boolean {
|
||
return this.mapping.has(tv.id);
|
||
}
|
||
|
||
public delete(tv: TVar): void {
|
||
this.mapping.delete(tv.id);
|
||
}
|
||
|
||
public values(): Iterable<Type> {
|
||
return this.mapping.values();
|
||
}
|
||
|
||
}
|