2022-08-31 13:29:56 +02:00
|
|
|
import {
|
2022-09-15 13:49:45 +02:00
|
|
|
EnumDeclaration,
|
2022-09-15 16:56:02 +02:00
|
|
|
EnumDeclarationStructElement,
|
2022-08-31 13:29:56 +02:00
|
|
|
Expression,
|
2022-09-05 19:38:55 +02:00
|
|
|
LetDeclaration,
|
2022-08-31 13:29:56 +02:00
|
|
|
Pattern,
|
2022-09-07 12:45:38 +02:00
|
|
|
Scope,
|
2022-09-01 20:06:43 +02:00
|
|
|
SourceFile,
|
2022-09-15 16:56:02 +02:00
|
|
|
StructDeclaration,
|
2022-09-11 11:20:21 +02:00
|
|
|
Symkind,
|
2022-08-31 13:29:56 +02:00
|
|
|
Syntax,
|
|
|
|
SyntaxKind,
|
|
|
|
TypeExpression
|
|
|
|
} from "./cst";
|
2022-09-11 11:20:21 +02:00
|
|
|
import {
|
|
|
|
describeType,
|
|
|
|
ArityMismatchDiagnostic,
|
|
|
|
BindingNotFoudDiagnostic,
|
|
|
|
Diagnostics,
|
|
|
|
FieldDoesNotExistDiagnostic,
|
|
|
|
FieldMissingDiagnostic,
|
|
|
|
UnificationFailedDiagnostic,
|
2022-09-15 13:49:45 +02:00
|
|
|
KindMismatchDiagnostic,
|
2022-09-11 11:20:21 +02:00
|
|
|
} from "./diagnostics";
|
2022-09-15 11:49:53 +02:00
|
|
|
import { assert, isEmpty, MultiMap } from "./util";
|
2022-09-15 16:04:49 +02:00
|
|
|
import { Analyser } from "./analysis";
|
2022-09-06 15:13:07 +02:00
|
|
|
|
|
|
|
const MAX_TYPE_ERROR_COUNT = 5;
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
export enum TypeKind {
|
|
|
|
Arrow,
|
|
|
|
Var,
|
|
|
|
Con,
|
|
|
|
Any,
|
|
|
|
Tuple,
|
2022-09-07 12:45:38 +02:00
|
|
|
Labeled,
|
|
|
|
Record,
|
2022-09-11 11:20:21 +02:00
|
|
|
App,
|
2022-09-11 15:23:22 +02:00
|
|
|
Variant,
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
abstract class TypeBase {
|
|
|
|
|
|
|
|
public abstract readonly kind: TypeKind;
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
public next: Type = this as any;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public node: Syntax | null = null
|
|
|
|
) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static join(a: Type, b: Type): void {
|
|
|
|
const keep = a.next;
|
|
|
|
a.next = b;
|
|
|
|
b.next = keep;
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
public abstract getTypeVars(): Iterable<TVar>;
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
public abstract shallowClone(): Type;
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
public abstract substitute(sub: TVSub): Type;
|
|
|
|
|
|
|
|
public hasTypeVar(tv: TVar): boolean {
|
|
|
|
for (const other of this.getTypeVars()) {
|
|
|
|
if (tv.id === other.id) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class TVar extends TypeBase {
|
|
|
|
|
|
|
|
public readonly kind = TypeKind.Var;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public id: number,
|
2022-09-09 20:02:35 +02:00
|
|
|
public node: Syntax | null = null,
|
2022-08-31 13:29:56 +02:00
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public *getTypeVars(): Iterable<TVar> {
|
|
|
|
yield this;
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
public shallowClone(): TVar {
|
|
|
|
return new TVar(this.id, this.node);
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
public substitute(sub: TVSub): Type {
|
2022-09-05 17:25:55 +02:00
|
|
|
const other = sub.get(this);
|
|
|
|
return other === undefined
|
|
|
|
? this : other.substitute(sub);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-01 20:06:43 +02:00
|
|
|
export class TArrow extends TypeBase {
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
public readonly kind = TypeKind.Arrow;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public paramTypes: Type[],
|
|
|
|
public returnType: Type,
|
2022-09-09 20:02:35 +02:00
|
|
|
public node: Syntax | null = null,
|
2022-08-31 13:29:56 +02:00
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public *getTypeVars(): Iterable<TVar> {
|
|
|
|
for (const paramType of this.paramTypes) {
|
|
|
|
yield* paramType.getTypeVars();
|
|
|
|
}
|
|
|
|
yield* this.returnType.getTypeVars();
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
public shallowClone(): TArrow {
|
|
|
|
return new TArrow(
|
|
|
|
this.paramTypes,
|
|
|
|
this.returnType,
|
|
|
|
this.node,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
public substitute(sub: TVSub): Type {
|
|
|
|
let changed = false;
|
|
|
|
const newParamTypes = [];
|
|
|
|
for (const paramType of this.paramTypes) {
|
|
|
|
const newParamType = paramType.substitute(sub);
|
|
|
|
if (newParamType !== paramType) {
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
newParamTypes.push(newParamType);
|
|
|
|
}
|
|
|
|
const newReturnType = this.returnType.substitute(sub);
|
|
|
|
if (newReturnType !== this.returnType) {
|
|
|
|
changed = true;
|
|
|
|
}
|
2022-09-09 20:02:35 +02:00
|
|
|
return changed ? new TArrow(newParamTypes, newReturnType, this.node) : this;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
export class TCon extends TypeBase {
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
public readonly kind = TypeKind.Con;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public id: number,
|
|
|
|
public argTypes: Type[],
|
|
|
|
public displayName: string,
|
2022-09-09 20:02:35 +02:00
|
|
|
public node: Syntax | null = null,
|
2022-08-31 13:29:56 +02:00
|
|
|
) {
|
2022-09-09 20:02:35 +02:00
|
|
|
super(node);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public *getTypeVars(): Iterable<TVar> {
|
|
|
|
for (const argType of this.argTypes) {
|
|
|
|
yield* argType.getTypeVars();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
public shallowClone(): TCon {
|
|
|
|
return new TCon(
|
|
|
|
this.id,
|
|
|
|
this.argTypes,
|
|
|
|
this.displayName,
|
|
|
|
this.node,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
public substitute(sub: TVSub): Type {
|
|
|
|
let changed = false;
|
|
|
|
const newArgTypes = [];
|
|
|
|
for (const argType of this.argTypes) {
|
|
|
|
const newArgType = argType.substitute(sub);
|
|
|
|
if (newArgType !== argType) {
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
newArgTypes.push(newArgType);
|
|
|
|
}
|
2022-09-09 20:02:35 +02:00
|
|
|
return changed ? new TCon(this.id, newArgTypes, this.displayName, this.node) : this;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class TTuple extends TypeBase {
|
|
|
|
|
|
|
|
public readonly kind = TypeKind.Tuple;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public elementTypes: Type[],
|
2022-09-09 20:02:35 +02:00
|
|
|
public node: Syntax | null = null,
|
2022-08-31 13:29:56 +02:00
|
|
|
) {
|
2022-09-09 20:02:35 +02:00
|
|
|
super(node);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public *getTypeVars(): Iterable<TVar> {
|
|
|
|
for (const elementType of this.elementTypes) {
|
|
|
|
yield* elementType.getTypeVars();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
public shallowClone(): TTuple {
|
|
|
|
return new TTuple(
|
|
|
|
this.elementTypes,
|
|
|
|
this.node,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
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);
|
|
|
|
}
|
2022-09-09 20:02:35 +02:00
|
|
|
return changed ? new TTuple(newElementTypes, this.node) : this;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
export class TLabeled extends TypeBase {
|
2022-09-07 12:45:38 +02:00
|
|
|
|
|
|
|
public readonly kind = TypeKind.Labeled;
|
|
|
|
|
|
|
|
public fields?: Map<string, Type>;
|
|
|
|
public parent: TLabeled | null = null;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public name: string,
|
|
|
|
public type: Type,
|
2022-09-09 20:02:35 +02:00
|
|
|
public node: Syntax | null = null,
|
2022-09-07 12:45:38 +02:00
|
|
|
) {
|
2022-09-09 20:02:35 +02:00
|
|
|
super(node);
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public find(): TLabeled {
|
|
|
|
let curr: TLabeled | null = this;
|
|
|
|
while (curr.parent !== null) {
|
|
|
|
curr = curr.parent;
|
|
|
|
}
|
|
|
|
this.parent = curr;
|
|
|
|
return curr;
|
|
|
|
}
|
|
|
|
|
|
|
|
public getTypeVars(): Iterable<TVar> {
|
|
|
|
return this.type.getTypeVars();
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
public shallowClone(): TLabeled {
|
|
|
|
return new TLabeled(
|
|
|
|
this.name,
|
|
|
|
this.type,
|
|
|
|
this.node,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
public substitute(sub: TVSub): Type {
|
|
|
|
const newType = this.type.substitute(sub);
|
2022-09-09 20:02:35 +02:00
|
|
|
return newType !== this.type ? new TLabeled(this.name, newType, this.node) : this;
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
export class TRecord extends TypeBase {
|
2022-09-07 12:45:38 +02:00
|
|
|
|
|
|
|
public readonly kind = TypeKind.Record;
|
|
|
|
|
|
|
|
public constructor(
|
2022-09-15 16:56:02 +02:00
|
|
|
public decl: StructDeclaration | EnumDeclarationStructElement,
|
2022-09-14 22:34:53 +02:00
|
|
|
public kindArgs: TVar[],
|
2022-09-07 12:45:38 +02:00
|
|
|
public fields: Map<string, Type>,
|
2022-09-09 20:02:35 +02:00
|
|
|
public node: Syntax | null = null,
|
2022-09-07 12:45:38 +02:00
|
|
|
) {
|
2022-09-09 20:02:35 +02:00
|
|
|
super(node);
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public *getTypeVars(): Iterable<TVar> {
|
|
|
|
for (const type of this.fields.values()) {
|
|
|
|
yield* type.getTypeVars();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
public shallowClone(): TRecord {
|
|
|
|
return new TRecord(
|
|
|
|
this.decl,
|
2022-09-14 22:34:53 +02:00
|
|
|
this.kindArgs,
|
2022-09-09 20:02:35 +02:00
|
|
|
this.fields,
|
|
|
|
this.node
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
public substitute(sub: TVSub): Type {
|
|
|
|
let changed = false;
|
2022-09-11 15:23:22 +02:00
|
|
|
const newTypeVars = [];
|
2022-09-14 22:34:53 +02:00
|
|
|
for (const typeVar of this.kindArgs) {
|
2022-09-11 15:23:22 +02:00
|
|
|
const newTypeVar = typeVar.substitute(sub);
|
|
|
|
assert(newTypeVar.kind === TypeKind.Var);
|
|
|
|
if (newTypeVar !== typeVar) {
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
newTypeVars.push(newTypeVar);
|
|
|
|
}
|
2022-09-07 12:45:38 +02:00
|
|
|
const newFields = new Map();
|
|
|
|
for (const [key, type] of this.fields) {
|
|
|
|
const newType = type.substitute(sub);
|
|
|
|
if (newType !== type) {
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
newFields.set(key, newType);
|
|
|
|
}
|
2022-09-11 15:23:22 +02:00
|
|
|
return changed ? new TRecord(this.decl, newTypeVars, newFields, this.node) : this;
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-11 11:20:21 +02:00
|
|
|
export class TApp extends TypeBase {
|
|
|
|
|
|
|
|
public readonly kind = TypeKind.App;
|
|
|
|
|
|
|
|
public constructor(
|
2022-09-14 22:34:53 +02:00
|
|
|
public left: Type,
|
|
|
|
public right: Type,
|
2022-09-11 11:20:21 +02:00
|
|
|
public node: Syntax | null = null
|
|
|
|
) {
|
|
|
|
super(node);
|
|
|
|
}
|
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
public static build(types: Type[], node: Syntax | null = null): Type {
|
2022-09-14 22:34:53 +02:00
|
|
|
let result = types[0];
|
|
|
|
for (let i = 1; i < types.length; i++) {
|
2022-09-15 11:49:53 +02:00
|
|
|
result = new TApp(result, types[i], node);
|
2022-09-11 11:20:21 +02:00
|
|
|
}
|
2022-09-14 22:34:53 +02:00
|
|
|
return result;
|
2022-09-11 11:20:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public *getTypeVars(): Iterable<TVar> {
|
2022-09-14 22:34:53 +02:00
|
|
|
yield* this.left.getTypeVars();
|
|
|
|
yield* this.right.getTypeVars();
|
2022-09-11 11:20:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public shallowClone() {
|
|
|
|
return new TApp(
|
2022-09-14 22:34:53 +02:00
|
|
|
this.left,
|
|
|
|
this.right,
|
2022-09-11 11:20:21 +02:00
|
|
|
this.node
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public substitute(sub: TVSub): Type {
|
|
|
|
let changed = false;
|
2022-09-14 22:34:53 +02:00
|
|
|
const newOperatorType = this.left.substitute(sub);
|
|
|
|
if (newOperatorType !== this.left) {
|
2022-09-11 11:20:21 +02:00
|
|
|
changed = true;
|
|
|
|
}
|
2022-09-14 22:34:53 +02:00
|
|
|
const newArgType = this.right.substitute(sub);
|
|
|
|
if (newArgType !== this.right) {
|
2022-09-11 11:20:21 +02:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
return changed ? new TApp(newOperatorType, newArgType, this.node) : this;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
export class TVariant extends TypeBase {
|
|
|
|
|
|
|
|
public readonly kind = TypeKind.Variant;
|
|
|
|
|
|
|
|
public constructor(
|
2022-09-15 16:56:02 +02:00
|
|
|
public decl: EnumDeclaration,
|
2022-09-14 22:34:53 +02:00
|
|
|
public kindArgs: Type[],
|
2022-09-11 15:23:22 +02:00
|
|
|
public elementTypes: Type[],
|
|
|
|
public node: Syntax | null = null,
|
|
|
|
) {
|
|
|
|
super(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
public *getTypeVars(): Iterable<TVar> {
|
|
|
|
for (const elementType of this.elementTypes) {
|
|
|
|
yield* elementType.getTypeVars();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public shallowClone(): Type {
|
|
|
|
return new TVariant(
|
2022-09-14 22:34:53 +02:00
|
|
|
this.decl,
|
|
|
|
this.kindArgs,
|
2022-09-11 15:23:22 +02:00
|
|
|
this.elementTypes,
|
|
|
|
this.node,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public substitute(sub: TVSub): Type {
|
|
|
|
let changed = false;
|
|
|
|
const newTypeVars = [];
|
2022-09-14 22:34:53 +02:00
|
|
|
for (const kindArg of this.kindArgs) {
|
|
|
|
const newTypeVar = kindArg.substitute(sub);
|
2022-09-11 15:23:22 +02:00
|
|
|
assert(newTypeVar.kind === TypeKind.Var);
|
2022-09-14 22:34:53 +02:00
|
|
|
if (newTypeVar !== kindArg) {
|
2022-09-11 15:23:22 +02:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
newTypeVars.push(newTypeVar);
|
|
|
|
}
|
|
|
|
const newElementTypes = [];
|
|
|
|
for (const elementType of this.elementTypes) {
|
|
|
|
const newElementType = elementType.substitute(sub);
|
|
|
|
if (newElementType !== elementType) {
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
newElementTypes.push(newElementType);
|
|
|
|
}
|
2022-09-14 22:34:53 +02:00
|
|
|
return changed ? new TVariant(this.decl, newTypeVars, newElementTypes, this.node) : this;
|
2022-09-11 15:23:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
export type Type
|
|
|
|
= TCon
|
|
|
|
| TArrow
|
|
|
|
| TVar
|
|
|
|
| TTuple
|
2022-09-07 12:45:38 +02:00
|
|
|
| TLabeled
|
|
|
|
| TRecord
|
2022-09-11 11:20:21 +02:00
|
|
|
| TApp
|
2022-09-11 15:23:22 +02:00
|
|
|
| TVariant
|
|
|
|
|
|
|
|
type KindedType
|
|
|
|
= TRecord
|
|
|
|
| TVariant
|
|
|
|
|
|
|
|
function isKindedType(type: Type): type is KindedType {
|
|
|
|
return type.kind === TypeKind.Variant
|
|
|
|
|| type.kind === TypeKind.Record;
|
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-14 16:46:30 +02:00
|
|
|
export const enum KindType {
|
|
|
|
Star,
|
|
|
|
Arrow,
|
|
|
|
Var,
|
|
|
|
}
|
|
|
|
|
|
|
|
class KVSub {
|
|
|
|
|
|
|
|
private mapping = new Map<number, Kind>();
|
|
|
|
|
|
|
|
public set(kv: KVar, kind: Kind): void {
|
|
|
|
this.mapping.set(kv.id, kind);
|
|
|
|
}
|
|
|
|
|
|
|
|
public get(kv: KVar): Kind | undefined {
|
|
|
|
return this.mapping.get(kv.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
public has(kv: KVar): boolean {
|
|
|
|
return this.mapping.has(kv.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
public values(): Iterable<Kind> {
|
|
|
|
return this.mapping.values();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class KindBase {
|
|
|
|
|
|
|
|
public abstract readonly type: KindType;
|
|
|
|
|
|
|
|
public abstract substitute(sub: KVSub): Kind;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class KVar extends KindBase {
|
|
|
|
|
|
|
|
public readonly type = KindType.Var;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public id: number,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public substitute(sub: KVSub): Kind {
|
|
|
|
const other = sub.get(this);
|
|
|
|
return other === undefined
|
|
|
|
? this : other.substitute(sub);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class KStar extends KindBase {
|
|
|
|
|
|
|
|
public readonly type = KindType.Star;
|
|
|
|
|
|
|
|
public substitute(_sub: KVSub): Kind {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class KArrow extends KindBase {
|
|
|
|
|
|
|
|
public readonly type = KindType.Arrow;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public left: Kind,
|
|
|
|
public right: Kind,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public substitute(sub: KVSub): Kind {
|
|
|
|
return new KArrow(
|
|
|
|
this.left.substitute(sub),
|
|
|
|
this.right.substitute(sub),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Kind
|
|
|
|
= KStar
|
|
|
|
| KArrow
|
|
|
|
| KVar
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
class TVSet {
|
|
|
|
|
|
|
|
private mapping = new Map<number, TVar>();
|
|
|
|
|
|
|
|
public add(tv: TVar): void {
|
|
|
|
this.mapping.set(tv.id, tv);
|
|
|
|
}
|
2022-09-01 20:06:43 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
public delete(tv: TVar): void {
|
|
|
|
this.mapping.delete(tv.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
public [Symbol.iterator](): Iterator<TVar> {
|
|
|
|
return this.mapping.values();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const enum ConstraintKind {
|
|
|
|
Equal,
|
|
|
|
Many,
|
2022-09-07 12:45:38 +02:00
|
|
|
Shaped,
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
abstract class ConstraintBase {
|
|
|
|
|
|
|
|
public abstract substitute(sub: TVSub): Constraint;
|
|
|
|
|
2022-09-06 11:56:17 +02:00
|
|
|
public constructor(
|
|
|
|
public node: Syntax | null = null
|
|
|
|
) {
|
|
|
|
}
|
|
|
|
|
|
|
|
public prevInstantiation: Constraint | null = null;
|
|
|
|
|
|
|
|
public *getNodes(): Iterable<Syntax> {
|
|
|
|
let curr: Constraint | null = this as any;
|
|
|
|
while (curr !== null) {
|
|
|
|
if (curr.node !== null) {
|
|
|
|
yield curr.node;
|
|
|
|
}
|
|
|
|
curr = curr.prevInstantiation;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
class CShaped extends ConstraintBase {
|
|
|
|
|
|
|
|
public readonly kind = ConstraintKind.Shaped;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public recordType: TLabeled,
|
|
|
|
public type: Type,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public substitute(sub: TVSub): Constraint {
|
|
|
|
return new CShaped(
|
|
|
|
this.recordType.substitute(sub) as TLabeled,
|
|
|
|
this.type.substitute(sub),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
class CEqual extends ConstraintBase {
|
|
|
|
|
|
|
|
public readonly kind = ConstraintKind.Equal;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public left: Type,
|
|
|
|
public right: Type,
|
|
|
|
public node: Syntax,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public substitute(sub: TVSub): Constraint {
|
|
|
|
return new CEqual(
|
|
|
|
this.left.substitute(sub),
|
|
|
|
this.right.substitute(sub),
|
|
|
|
this.node,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-05 17:25:55 +02:00
|
|
|
public dump(): void {
|
|
|
|
console.error(`${describeType(this.left)} ~ ${describeType(this.right)}`);
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class CMany extends ConstraintBase {
|
|
|
|
|
|
|
|
public readonly kind = ConstraintKind.Many;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
public elements: Constraint[]
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public substitute(sub: TVSub): Constraint {
|
|
|
|
const newElements = [];
|
|
|
|
for (const element of this.elements) {
|
|
|
|
newElements.push(element.substitute(sub));
|
|
|
|
}
|
|
|
|
return new CMany(newElements);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
type Constraint
|
|
|
|
= CEqual
|
|
|
|
| CMany
|
2022-09-07 12:45:38 +02:00
|
|
|
| CShaped
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
class ConstraintSet extends Array<Constraint> {
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class SchemeBase {
|
|
|
|
}
|
|
|
|
|
|
|
|
class Forall extends SchemeBase {
|
|
|
|
|
|
|
|
public constructor(
|
2022-09-14 23:09:22 +02:00
|
|
|
public typeVars: Iterable<TVar>,
|
|
|
|
public constraints: Iterable<Constraint>,
|
2022-08-31 13:29:56 +02:00
|
|
|
public type: Type,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-15 16:56:02 +02:00
|
|
|
export type Scheme
|
2022-08-31 13:29:56 +02:00
|
|
|
= Forall
|
|
|
|
|
2022-09-05 19:38:55 +02:00
|
|
|
export class TypeEnv {
|
2022-09-05 19:33:08 +02:00
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
private mapping = new MultiMap<string, [Symkind, Scheme]>();
|
2022-09-05 19:33:08 +02:00
|
|
|
|
|
|
|
public constructor(public parent: TypeEnv | null = null) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
public add(name: string, scheme: Scheme, kind: Symkind): void {
|
|
|
|
this.mapping.add(name, [kind, scheme]);
|
2022-09-05 19:33:08 +02:00
|
|
|
}
|
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
public lookup(name: string, expectedKind: Symkind): Scheme | null {
|
2022-09-05 19:33:08 +02:00
|
|
|
let curr: TypeEnv | null = this;
|
|
|
|
do {
|
2022-09-15 11:49:53 +02:00
|
|
|
for (const [kind, scheme] of curr.mapping.get(name)) {
|
|
|
|
if (kind & expectedKind) {
|
|
|
|
return scheme;
|
|
|
|
}
|
2022-09-05 19:33:08 +02:00
|
|
|
}
|
|
|
|
curr = curr.parent;
|
|
|
|
} while(curr !== null);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-14 16:46:30 +02:00
|
|
|
class KindEnv {
|
|
|
|
|
2022-09-15 13:56:58 +02:00
|
|
|
private mapping = new Map<string, Kind>();
|
2022-09-14 16:46:30 +02:00
|
|
|
|
|
|
|
public constructor(public parent: KindEnv | null = null) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public setNamed(name: string, kind: Kind): void {
|
2022-09-15 13:56:58 +02:00
|
|
|
assert(!this.mapping.has(name));
|
|
|
|
this.mapping.set(name, kind);
|
2022-09-14 16:46:30 +02:00
|
|
|
}
|
|
|
|
|
2022-09-15 13:56:58 +02:00
|
|
|
public lookup(name: string): Kind | null {
|
2022-09-14 16:46:30 +02:00
|
|
|
let curr: KindEnv | null = this;
|
|
|
|
do {
|
2022-09-15 13:56:58 +02:00
|
|
|
const kind = curr.mapping.get(name);
|
2022-09-14 16:46:30 +02:00
|
|
|
if (kind !== undefined) {
|
|
|
|
return kind;
|
|
|
|
}
|
|
|
|
curr = curr.parent;
|
|
|
|
} while (curr !== null);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
export interface InferContext {
|
|
|
|
typeVars: TVSet;
|
|
|
|
env: TypeEnv;
|
|
|
|
constraints: ConstraintSet;
|
2022-09-05 17:25:55 +02:00
|
|
|
returnType: Type | null;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-09 22:37:14 +02:00
|
|
|
function isFunctionDeclarationLike(node: LetDeclaration): boolean {
|
|
|
|
return node.pattern.kind === SyntaxKind.BindPattern
|
|
|
|
&& (node.params.length > 0 || (node.body !== null && node.body.kind === SyntaxKind.BlockBody));
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
export class Checker {
|
|
|
|
|
|
|
|
private nextTypeVarId = 0;
|
2022-09-14 16:46:30 +02:00
|
|
|
private nextKindVarId = 0;
|
2022-08-31 13:29:56 +02:00
|
|
|
private nextConTypeId = 0;
|
|
|
|
|
|
|
|
private stringType = new TCon(this.nextConTypeId++, [], 'String');
|
|
|
|
private intType = new TCon(this.nextConTypeId++, [], 'Int');
|
|
|
|
private boolType = new TCon(this.nextConTypeId++, [], 'Bool');
|
|
|
|
|
2022-09-05 17:25:55 +02:00
|
|
|
private contexts: InferContext[] = [];
|
|
|
|
|
|
|
|
private solution = new TVSub();
|
2022-09-14 16:46:30 +02:00
|
|
|
private kindSolution = new KVSub();
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
public constructor(
|
2022-09-15 16:04:49 +02:00
|
|
|
private analyser: Analyser,
|
2022-08-31 13:29:56 +02:00
|
|
|
private diagnostics: Diagnostics
|
|
|
|
) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public getIntType(): Type {
|
|
|
|
return this.intType;
|
|
|
|
}
|
|
|
|
|
|
|
|
public getStringType(): Type {
|
|
|
|
return this.stringType;
|
|
|
|
}
|
|
|
|
|
|
|
|
public getBoolType(): Type {
|
|
|
|
return this.boolType;
|
|
|
|
}
|
|
|
|
|
|
|
|
private createTypeVar(): TVar {
|
|
|
|
const typeVar = new TVar(this.nextTypeVarId++);
|
2022-09-05 17:25:55 +02:00
|
|
|
const context = this.contexts[this.contexts.length-1];
|
|
|
|
context.typeVars.add(typeVar);
|
2022-08-31 13:29:56 +02:00
|
|
|
return typeVar;
|
|
|
|
}
|
|
|
|
|
|
|
|
private addConstraint(constraint: Constraint): void {
|
2022-09-06 15:13:07 +02:00
|
|
|
this.contexts[this.contexts.length-1].constraints.push(constraint);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private pushContext(context: InferContext) {
|
2022-09-05 17:25:55 +02:00
|
|
|
this.contexts.push(context);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private popContext(context: InferContext) {
|
2022-09-05 17:25:55 +02:00
|
|
|
assert(this.contexts[this.contexts.length-1] === context);
|
|
|
|
this.contexts.pop();
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
private lookup(name: string, kind: Symkind): Scheme | null {
|
2022-09-05 19:33:08 +02:00
|
|
|
const context = this.contexts[this.contexts.length-1];
|
2022-09-15 11:49:53 +02:00
|
|
|
return context.env.lookup(name, kind);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private getReturnType(): Type {
|
2022-09-05 17:25:55 +02:00
|
|
|
const context = this.contexts[this.contexts.length-1];
|
|
|
|
assert(context && context.returnType !== null);
|
|
|
|
return context.returnType;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-11 11:20:21 +02:00
|
|
|
private createSubstitution(scheme: Scheme): TVSub {
|
2022-08-31 13:29:56 +02:00
|
|
|
const sub = new TVSub();
|
2022-09-10 16:52:14 +02:00
|
|
|
for (const tv of scheme.typeVars) {
|
2022-08-31 13:29:56 +02:00
|
|
|
sub.set(tv, this.createTypeVar());
|
|
|
|
}
|
2022-09-11 11:20:21 +02:00
|
|
|
return sub;
|
|
|
|
}
|
|
|
|
|
|
|
|
private instantiate(scheme: Scheme, node: Syntax | null, sub = this.createSubstitution(scheme)): Type {
|
2022-08-31 13:29:56 +02:00
|
|
|
for (const constraint of scheme.constraints) {
|
2022-09-06 11:56:17 +02:00
|
|
|
const substituted = constraint.substitute(sub);
|
|
|
|
substituted.node = node;
|
|
|
|
substituted.prevInstantiation = constraint;
|
|
|
|
this.addConstraint(substituted);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
return scheme.type.substitute(sub);
|
|
|
|
}
|
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
private addBinding(name: string, scheme: Scheme, kind: Symkind): void {
|
2022-09-05 17:25:55 +02:00
|
|
|
const context = this.contexts[this.contexts.length-1];
|
2022-09-15 11:49:53 +02:00
|
|
|
context.env.add(name, scheme, kind);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-14 16:46:30 +02:00
|
|
|
private inferKindFromTypeExpression(node: TypeExpression, env: KindEnv): Kind {
|
|
|
|
switch (node.kind) {
|
2022-09-15 16:04:02 +02:00
|
|
|
case SyntaxKind.ArrowTypeExpression:
|
|
|
|
{
|
|
|
|
for (const param of node.paramTypeExprs) {
|
|
|
|
this.unifyKind(this.inferKindFromTypeExpression(param, env), new KStar(), node);
|
|
|
|
}
|
|
|
|
this.unifyKind(this.inferKindFromTypeExpression(node.returnTypeExpr, env), new KStar(), node);
|
|
|
|
return new KStar();
|
|
|
|
}
|
2022-09-14 16:46:30 +02:00
|
|
|
case SyntaxKind.VarTypeExpression:
|
|
|
|
case SyntaxKind.ReferenceTypeExpression:
|
|
|
|
{
|
2022-09-15 13:56:58 +02:00
|
|
|
const kind = env.lookup(node.name.text);
|
2022-09-14 16:46:30 +02:00
|
|
|
if (kind === null) {
|
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
|
|
|
// Create a filler kind variable that still will be able to catch other errors.
|
|
|
|
return this.createKindVar();
|
|
|
|
}
|
|
|
|
return kind;
|
|
|
|
}
|
|
|
|
case SyntaxKind.AppTypeExpression:
|
|
|
|
{
|
2022-09-15 13:49:45 +02:00
|
|
|
let result = this.inferKindFromTypeExpression(node.operator, env);
|
2022-09-14 22:34:53 +02:00
|
|
|
for (const arg of node.args) {
|
|
|
|
result = this.applyKind(result, this.inferKindFromTypeExpression(arg, env), node);
|
2022-09-14 16:46:30 +02:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
case SyntaxKind.NestedTypeExpression:
|
|
|
|
{
|
|
|
|
return this.inferKindFromTypeExpression(node.typeExpr, env);
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected ${node}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private createKindVar(): KVar {
|
|
|
|
return new KVar(this.nextKindVarId++);
|
|
|
|
}
|
|
|
|
|
|
|
|
private applyKind(operator: Kind, arg: Kind, node: Syntax): Kind {
|
|
|
|
switch (operator.type) {
|
|
|
|
case KindType.Var:
|
|
|
|
{
|
|
|
|
const a1 = this.createKindVar();
|
|
|
|
const a2 = this.createKindVar();
|
|
|
|
const arrow = new KArrow(a1, a2);
|
|
|
|
this.unifyKind(arrow, operator, node);
|
2022-09-15 13:49:07 +02:00
|
|
|
this.unifyKind(a2, arg, node);
|
|
|
|
return a1;
|
2022-09-14 16:46:30 +02:00
|
|
|
}
|
|
|
|
case KindType.Arrow:
|
|
|
|
{
|
|
|
|
// Unify the argument to the operator's argument kind and return
|
|
|
|
// whatever the operator returns.
|
|
|
|
this.unifyKind(operator.left, arg, node);
|
|
|
|
return operator.right;
|
|
|
|
}
|
|
|
|
case KindType.Star:
|
|
|
|
{
|
|
|
|
this.diagnostics.add(
|
|
|
|
new KindMismatchDiagnostic(
|
|
|
|
operator,
|
|
|
|
new KArrow(
|
|
|
|
this.createKindVar(),
|
|
|
|
this.createKindVar()
|
|
|
|
),
|
|
|
|
node
|
|
|
|
)
|
|
|
|
);
|
|
|
|
// Create a filler kind variable that still will be able to catch other errors.
|
|
|
|
return this.createKindVar();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private forwardDeclareKind(node: Syntax, env: KindEnv): void {
|
|
|
|
|
|
|
|
switch (node.kind) {
|
|
|
|
|
|
|
|
case SyntaxKind.SourceFile:
|
|
|
|
{
|
|
|
|
for (const element of node.elements) {
|
|
|
|
this.forwardDeclareKind(element, env);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.StructDeclaration:
|
2022-09-15 11:49:53 +02:00
|
|
|
{
|
|
|
|
env.setNamed(node.name.text, this.createKindVar());
|
|
|
|
break;
|
|
|
|
}
|
2022-09-14 16:46:30 +02:00
|
|
|
case SyntaxKind.EnumDeclaration:
|
|
|
|
{
|
|
|
|
env.setNamed(node.name.text, this.createKindVar());
|
|
|
|
if (node.members !== null) {
|
|
|
|
for (const member of node.members) {
|
|
|
|
env.setNamed(member.name.text, this.createKindVar());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private inferKind(node: Syntax, env: KindEnv): void {
|
|
|
|
switch (node.kind) {
|
|
|
|
case SyntaxKind.SourceFile:
|
|
|
|
{
|
|
|
|
for (const element of node.elements) {
|
|
|
|
this.inferKind(element, env);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.StructDeclaration:
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.EnumDeclaration:
|
|
|
|
{
|
2022-09-15 13:56:58 +02:00
|
|
|
const declKind = env.lookup(node.name.text)!;
|
2022-09-14 16:46:30 +02:00
|
|
|
const innerEnv = new KindEnv(env);
|
|
|
|
let kind: Kind = new KStar();
|
|
|
|
// FIXME should I go from right to left or left to right?
|
|
|
|
for (let i = node.varExps.length-1; i >= 0; i--) {
|
|
|
|
const varExpr = node.varExps[i];
|
|
|
|
const paramKind = this.createKindVar();
|
|
|
|
innerEnv.setNamed(varExpr.text, paramKind);
|
|
|
|
kind = new KArrow(paramKind, kind);
|
|
|
|
}
|
|
|
|
this.unifyKind(declKind, kind, node);
|
|
|
|
if (node.members !== null) {
|
|
|
|
for (const member of node.members) {
|
|
|
|
switch (member.kind) {
|
|
|
|
case SyntaxKind.EnumDeclarationTupleElement:
|
|
|
|
{
|
|
|
|
for (const element of member.elements) {
|
|
|
|
this.unifyKind(this.inferKindFromTypeExpression(element, innerEnv), new KStar(), element);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2022-09-14 22:34:53 +02:00
|
|
|
case SyntaxKind.LetDeclaration:
|
|
|
|
{
|
|
|
|
if (node.typeAssert !== null) {
|
|
|
|
this.unifyKind(this.inferKindFromTypeExpression(node.typeAssert.typeExpression, env), new KStar(), node.typeAssert.typeExpression);
|
|
|
|
}
|
|
|
|
if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) {
|
2022-09-15 13:56:58 +02:00
|
|
|
const innerEnv = new KindEnv(env);
|
2022-09-14 22:34:53 +02:00
|
|
|
for (const element of node.body.elements) {
|
2022-09-15 13:56:58 +02:00
|
|
|
this.inferKind(element, innerEnv);
|
2022-09-14 22:34:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2022-09-14 16:46:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private unifyKind(a: Kind, b: Kind, node: Syntax): boolean {
|
|
|
|
|
|
|
|
const find = (kind: Kind): Kind => {
|
|
|
|
let curr = kind;
|
|
|
|
while (curr.type === KindType.Var && this.kindSolution.has(curr)) {
|
|
|
|
curr = this.kindSolution.get(curr)!;
|
|
|
|
}
|
|
|
|
// if (kind.type === KindType.Var && ) {
|
|
|
|
// this.kindSolution.set(kind.id, curr);
|
|
|
|
// }
|
|
|
|
return curr;
|
|
|
|
}
|
|
|
|
|
|
|
|
const solve = (kind: Kind) => kind.substitute(this.kindSolution);
|
|
|
|
|
|
|
|
a = find(a);
|
|
|
|
b = find(b);
|
|
|
|
|
|
|
|
if (a.type === KindType.Var) {
|
|
|
|
this.kindSolution.set(a, b);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (b.type === KindType.Var) {
|
|
|
|
return this.unifyKind(b, a, node);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a.type === KindType.Star && b.type === KindType.Star) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a.type === KindType.Arrow && b.type === KindType.Arrow) {
|
|
|
|
return this.unifyKind(a.left, b.left, node)
|
|
|
|
|| this.unifyKind(a.right, b.right, node);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.diagnostics.add(new KindMismatchDiagnostic(solve(a), solve(b), node));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private infer(node: Syntax): void {
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
switch (node.kind) {
|
|
|
|
|
|
|
|
case SyntaxKind.SourceFile:
|
|
|
|
{
|
|
|
|
for (const element of node.elements) {
|
|
|
|
this.infer(element);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.ExpressionStatement:
|
|
|
|
{
|
|
|
|
this.inferExpression(node.expression);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-09-01 20:06:43 +02:00
|
|
|
case SyntaxKind.IfStatement:
|
|
|
|
{
|
|
|
|
for (const cs of node.cases) {
|
|
|
|
if (cs.test !== null) {
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
this.inferExpression(cs.test),
|
|
|
|
this.getBoolType(),
|
|
|
|
cs.test
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
for (const element of cs.elements) {
|
|
|
|
this.infer(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
case SyntaxKind.ReturnStatement:
|
|
|
|
{
|
|
|
|
let type;
|
|
|
|
if (node.expression === null) {
|
|
|
|
type = new TTuple([]);
|
|
|
|
} else {
|
|
|
|
type = this.inferExpression(node.expression);
|
|
|
|
}
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
this.getReturnType(),
|
|
|
|
type,
|
|
|
|
node
|
|
|
|
)
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.LetDeclaration:
|
2022-09-07 12:45:38 +02:00
|
|
|
{
|
2022-09-09 22:37:14 +02:00
|
|
|
if (isFunctionDeclarationLike(node)) {
|
2022-09-07 12:45:38 +02:00
|
|
|
break;
|
|
|
|
}
|
2022-09-09 22:37:14 +02:00
|
|
|
let type;
|
|
|
|
if (node.pattern.kind === SyntaxKind.WrappedOperator) {
|
|
|
|
type = this.createTypeVar();
|
2022-09-15 11:49:53 +02:00
|
|
|
this.addBinding(node.pattern.operator.text, new Forall([], [], type), Symkind.Var);
|
2022-09-09 22:37:14 +02:00
|
|
|
} else {
|
|
|
|
type = this.inferBindings(node.pattern, [], []);
|
|
|
|
}
|
2022-09-07 12:45:38 +02:00
|
|
|
if (node.typeAssert !== null) {
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
this.inferTypeExpression(node.typeAssert.typeExpression),
|
|
|
|
type,
|
|
|
|
node
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (node.body !== null) {
|
|
|
|
switch (node.body.kind) {
|
|
|
|
case SyntaxKind.ExprBody:
|
|
|
|
{
|
|
|
|
const type2 = this.inferExpression(node.body.expression);
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
type,
|
|
|
|
type2,
|
|
|
|
node
|
|
|
|
)
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.BlockBody:
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-09-11 11:20:21 +02:00
|
|
|
case SyntaxKind.TypeDeclaration:
|
|
|
|
case SyntaxKind.EnumDeclaration:
|
2022-09-07 12:45:38 +02:00
|
|
|
case SyntaxKind.StructDeclaration:
|
2022-08-31 13:29:56 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2022-09-07 12:45:38 +02:00
|
|
|
throw new Error(`Unexpected ${node.constructor.name}`);
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public inferExpression(node: Expression): Type {
|
|
|
|
|
|
|
|
switch (node.kind) {
|
|
|
|
|
2022-09-01 20:06:43 +02:00
|
|
|
case SyntaxKind.NestedExpression:
|
|
|
|
return this.inferExpression(node.expression);
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
case SyntaxKind.ReferenceExpression:
|
|
|
|
{
|
|
|
|
assert(node.name.modulePath.length === 0);
|
2022-09-05 17:25:55 +02:00
|
|
|
const scope = node.getScope();
|
|
|
|
const target = scope.lookup(node.name.name.text);
|
2022-09-05 19:38:55 +02:00
|
|
|
if (target !== null && target.kind === SyntaxKind.LetDeclaration && target.active) {
|
|
|
|
return target.type!;
|
2022-09-01 20:06:43 +02:00
|
|
|
}
|
2022-09-15 11:49:53 +02:00
|
|
|
const scheme = this.lookup(node.name.name.text, Symkind.Var);
|
2022-08-31 13:29:56 +02:00
|
|
|
if (scheme === null) {
|
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.name.text, node.name.name));
|
2022-09-09 22:37:14 +02:00
|
|
|
return this.createTypeVar();
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
2022-09-09 20:02:35 +02:00
|
|
|
const type = this.instantiate(scheme, node);
|
|
|
|
type.node = node;
|
|
|
|
return type;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-09 00:00:28 +02:00
|
|
|
case SyntaxKind.MemberExpression:
|
|
|
|
{
|
|
|
|
let type = this.inferExpression(node.expression);
|
|
|
|
for (const [_dot, name] of node.path) {
|
|
|
|
const newType = this.createTypeVar();
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
type,
|
|
|
|
new TLabeled(name.text, newType),
|
|
|
|
node,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
type = newType;
|
|
|
|
}
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
case SyntaxKind.CallExpression:
|
|
|
|
{
|
|
|
|
const opType = this.inferExpression(node.func);
|
|
|
|
const retType = this.createTypeVar();
|
|
|
|
const paramTypes = [];
|
|
|
|
for (const arg of node.args) {
|
|
|
|
paramTypes.push(this.inferExpression(arg));
|
|
|
|
}
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
opType,
|
|
|
|
new TArrow(paramTypes, retType),
|
|
|
|
node
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return retType;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.ConstantExpression:
|
|
|
|
{
|
|
|
|
let ty;
|
|
|
|
switch (node.token.kind) {
|
|
|
|
case SyntaxKind.StringLiteral:
|
|
|
|
ty = this.getStringType();
|
|
|
|
break;
|
|
|
|
case SyntaxKind.Integer:
|
|
|
|
ty = this.getIntType();
|
|
|
|
break;
|
|
|
|
}
|
2022-09-09 20:02:35 +02:00
|
|
|
ty = ty.shallowClone();
|
|
|
|
ty.node = node;
|
2022-08-31 13:29:56 +02:00
|
|
|
return ty;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.NamedTupleExpression:
|
|
|
|
{
|
2022-09-14 22:34:53 +02:00
|
|
|
// TODO Only lookup constructors and skip other bindings
|
2022-09-15 11:49:53 +02:00
|
|
|
const scheme = this.lookup(node.name.text, Symkind.Var);
|
2022-08-31 13:29:56 +02:00
|
|
|
if (scheme === null) {
|
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
2022-09-09 20:02:35 +02:00
|
|
|
return this.createTypeVar();
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
2022-09-14 22:34:53 +02:00
|
|
|
const operatorType = this.instantiate(scheme, node.name);
|
|
|
|
const argTypes = node.elements.map(el => this.inferExpression(el));
|
|
|
|
const retType = this.createTypeVar();
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
new TArrow(
|
|
|
|
argTypes,
|
|
|
|
retType,
|
|
|
|
node,
|
|
|
|
),
|
|
|
|
operatorType,
|
|
|
|
node
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return retType;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
case SyntaxKind.StructExpression:
|
|
|
|
{
|
2022-09-11 11:20:21 +02:00
|
|
|
const scope = node.getScope();
|
2022-09-15 11:49:53 +02:00
|
|
|
const decl = scope.lookup(node.name.text, Symkind.Type);
|
2022-09-11 11:20:21 +02:00
|
|
|
if (decl === null) {
|
2022-09-07 12:45:38 +02:00
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
2022-09-09 22:37:14 +02:00
|
|
|
return this.createTypeVar();
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
2022-09-11 11:20:21 +02:00
|
|
|
assert(decl.kind === SyntaxKind.StructDeclaration || decl.kind === SyntaxKind.EnumDeclarationStructElement);
|
2022-09-15 16:56:02 +02:00
|
|
|
const scheme = decl.scheme!;
|
2022-09-15 11:49:53 +02:00
|
|
|
const declType = this.instantiate(scheme, node);
|
|
|
|
const kindArgs = [];
|
|
|
|
const varExps = decl.kind === SyntaxKind.StructDeclaration
|
|
|
|
? decl.varExps : (decl.parent! as EnumDeclaration).varExps;
|
|
|
|
for (const _ of varExps) {
|
|
|
|
kindArgs.push(this.createTypeVar());
|
2022-09-11 11:20:21 +02:00
|
|
|
}
|
2022-09-08 23:32:25 +02:00
|
|
|
const fields = new Map();
|
2022-09-07 12:45:38 +02:00
|
|
|
for (const member of node.members) {
|
|
|
|
switch (member.kind) {
|
|
|
|
case SyntaxKind.StructExpressionField:
|
|
|
|
{
|
2022-09-08 23:32:25 +02:00
|
|
|
fields.set(member.name.text, this.inferExpression(member.expression));
|
2022-09-07 12:45:38 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.PunnedStructExpressionField:
|
|
|
|
{
|
2022-09-15 11:49:53 +02:00
|
|
|
const scheme = this.lookup(member.name.text, Symkind.Var);
|
2022-09-07 12:45:38 +02:00
|
|
|
let fieldType;
|
|
|
|
if (scheme === null) {
|
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(member.name.text, member.name));
|
2022-09-09 22:37:14 +02:00
|
|
|
fieldType = this.createTypeVar();
|
2022-09-07 12:45:38 +02:00
|
|
|
} else {
|
|
|
|
fieldType = this.instantiate(scheme, member);
|
|
|
|
}
|
2022-09-08 23:32:25 +02:00
|
|
|
fields.set(member.name.text, fieldType);
|
2022-09-07 12:45:38 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected ${member}`);
|
|
|
|
}
|
|
|
|
}
|
2022-09-15 11:49:53 +02:00
|
|
|
let type: Type = TApp.build([ ...kindArgs, new TRecord(decl, [], fields, node) ]);
|
2022-09-14 16:46:30 +02:00
|
|
|
if (decl.kind === SyntaxKind.EnumDeclarationStructElement) {
|
2022-09-15 11:49:53 +02:00
|
|
|
// TODO
|
|
|
|
// type = this.buildVariantType(decl, type);
|
2022-09-14 16:46:30 +02:00
|
|
|
}
|
2022-09-07 12:45:38 +02:00
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
2022-09-11 15:23:22 +02:00
|
|
|
declType,
|
2022-09-07 12:45:38 +02:00
|
|
|
type,
|
|
|
|
node,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
case SyntaxKind.InfixExpression:
|
|
|
|
{
|
2022-09-15 11:49:53 +02:00
|
|
|
const scheme = this.lookup(node.operator.text, Symkind.Var);
|
2022-08-31 13:29:56 +02:00
|
|
|
if (scheme === null) {
|
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.operator.text, node.operator));
|
2022-09-09 22:37:14 +02:00
|
|
|
return this.createTypeVar();
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
2022-09-06 11:56:17 +02:00
|
|
|
const opType = this.instantiate(scheme, node.operator);
|
2022-08-31 13:29:56 +02:00
|
|
|
const retType = this.createTypeVar();
|
|
|
|
const leftType = this.inferExpression(node.left);
|
|
|
|
const rightType = this.inferExpression(node.right);
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
new TArrow([ leftType, rightType ], retType),
|
|
|
|
opType,
|
|
|
|
node,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
return retType;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
2022-09-01 20:06:43 +02:00
|
|
|
throw new Error(`Unexpected ${node.constructor.name}`);
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-10 16:52:14 +02:00
|
|
|
public inferTypeExpression(node: TypeExpression, introduceTypeVars = false): Type {
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
switch (node.kind) {
|
|
|
|
|
|
|
|
case SyntaxKind.ReferenceTypeExpression:
|
|
|
|
{
|
2022-09-15 11:49:53 +02:00
|
|
|
const scheme = this.lookup(node.name.text, Symkind.Type);
|
2022-08-31 13:29:56 +02:00
|
|
|
if (scheme === null) {
|
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
2022-09-09 22:37:14 +02:00
|
|
|
return this.createTypeVar();
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
2022-09-09 20:02:35 +02:00
|
|
|
const type = this.instantiate(scheme, node.name);
|
2022-09-14 22:34:53 +02:00
|
|
|
// FIXME it is not guaranteed that `type` is copied, so the original type might get mutated
|
2022-09-09 20:02:35 +02:00
|
|
|
type.node = node;
|
|
|
|
return type;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-14 22:34:53 +02:00
|
|
|
case SyntaxKind.NestedTypeExpression:
|
|
|
|
return this.inferTypeExpression(node.typeExpr, introduceTypeVars);
|
|
|
|
|
2022-09-10 16:52:14 +02:00
|
|
|
case SyntaxKind.VarTypeExpression:
|
|
|
|
{
|
2022-09-15 11:49:53 +02:00
|
|
|
const scheme = this.lookup(node.name.text, Symkind.Type);
|
2022-09-10 16:52:14 +02:00
|
|
|
if (scheme === null) {
|
|
|
|
if (!introduceTypeVars) {
|
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
|
|
|
}
|
|
|
|
const type = this.createTypeVar();
|
2022-09-15 11:49:53 +02:00
|
|
|
this.addBinding(node.name.text, new Forall([], [], type), Symkind.Type);
|
2022-09-10 16:52:14 +02:00
|
|
|
return type;
|
|
|
|
}
|
2022-09-14 23:09:22 +02:00
|
|
|
assert(isEmpty(scheme.typeVars));
|
|
|
|
assert(isEmpty(scheme.constraints));
|
2022-09-10 16:52:14 +02:00
|
|
|
return scheme.type;
|
|
|
|
}
|
|
|
|
|
2022-09-11 11:20:21 +02:00
|
|
|
case SyntaxKind.AppTypeExpression:
|
|
|
|
{
|
2022-09-14 22:34:53 +02:00
|
|
|
return TApp.build([
|
2022-09-14 23:09:22 +02:00
|
|
|
...node.args.map(arg => this.inferTypeExpression(arg, introduceTypeVars)),
|
2022-09-14 22:34:53 +02:00
|
|
|
this.inferTypeExpression(node.operator, introduceTypeVars),
|
|
|
|
]);
|
2022-09-11 11:20:21 +02:00
|
|
|
}
|
|
|
|
|
2022-09-09 22:37:14 +02:00
|
|
|
case SyntaxKind.ArrowTypeExpression:
|
|
|
|
{
|
|
|
|
const paramTypes = [];
|
|
|
|
for (const paramTypeExpr of node.paramTypeExprs) {
|
2022-09-14 22:34:53 +02:00
|
|
|
paramTypes.push(this.inferTypeExpression(paramTypeExpr, introduceTypeVars));
|
2022-09-09 22:37:14 +02:00
|
|
|
}
|
2022-09-14 22:34:53 +02:00
|
|
|
const returnType = this.inferTypeExpression(node.returnTypeExpr, introduceTypeVars);
|
2022-09-09 22:37:14 +02:00
|
|
|
return new TArrow(paramTypes, returnType, node);
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
default:
|
|
|
|
throw new Error(`Unrecognised ${node}`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-15 16:56:02 +02:00
|
|
|
public inferBindings(pattern: Pattern, typeVars: Iterable<TVar>, constraints: Iterable<Constraint>): Type {
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
switch (pattern.kind) {
|
|
|
|
|
|
|
|
case SyntaxKind.BindPattern:
|
|
|
|
{
|
2022-09-07 12:45:38 +02:00
|
|
|
const type = this.createTypeVar();
|
2022-09-15 11:49:53 +02:00
|
|
|
this.addBinding(pattern.name.text, new Forall(typeVars, constraints, type), Symkind.Var);
|
2022-09-07 12:45:38 +02:00
|
|
|
return type;
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
case SyntaxKind.StructPattern:
|
|
|
|
{
|
2022-09-15 11:49:53 +02:00
|
|
|
const scheme = this.lookup(pattern.name.text, Symkind.Type);
|
2022-09-07 12:45:38 +02:00
|
|
|
let recordType;
|
|
|
|
if (scheme === null) {
|
|
|
|
this.diagnostics.add(new BindingNotFoudDiagnostic(pattern.name.text, pattern.name));
|
2022-09-09 22:37:14 +02:00
|
|
|
recordType = this.createTypeVar();
|
2022-09-07 12:45:38 +02:00
|
|
|
} else {
|
|
|
|
recordType = this.instantiate(scheme, pattern.name);
|
|
|
|
}
|
|
|
|
const type = this.createTypeVar();
|
|
|
|
for (const member of pattern.members) {
|
|
|
|
switch (member.kind) {
|
|
|
|
case SyntaxKind.StructPatternField:
|
|
|
|
{
|
|
|
|
const fieldType = this.inferBindings(member.pattern, typeVars, constraints);
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
new TLabeled(member.name.text, fieldType),
|
|
|
|
type,
|
|
|
|
member
|
|
|
|
)
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.PunnedStructPatternField:
|
|
|
|
{
|
|
|
|
const fieldType = this.createTypeVar();
|
2022-09-15 11:49:53 +02:00
|
|
|
this.addBinding(member.name.text, new Forall([], [], fieldType), Symkind.Var);
|
2022-09-07 12:45:38 +02:00
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
new TLabeled(member.name.text, fieldType),
|
|
|
|
type,
|
|
|
|
member
|
|
|
|
)
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected ${member.constructor.name}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
recordType,
|
|
|
|
type,
|
|
|
|
pattern
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected ${pattern.constructor.name}`);
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
private initialize(node: Syntax, parentEnv: TypeEnv): void {
|
2022-09-05 19:33:08 +02:00
|
|
|
|
|
|
|
switch (node.kind) {
|
|
|
|
|
|
|
|
case SyntaxKind.SourceFile:
|
|
|
|
{
|
2022-09-07 12:45:38 +02:00
|
|
|
const env = node.typeEnv = new TypeEnv(parentEnv);
|
2022-09-05 19:33:08 +02:00
|
|
|
for (const element of node.elements) {
|
2022-09-07 12:45:38 +02:00
|
|
|
this.initialize(element, env);
|
2022-09-05 19:33:08 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.LetDeclaration:
|
|
|
|
{
|
|
|
|
const env = node.typeEnv = new TypeEnv(parentEnv);
|
|
|
|
if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) {
|
|
|
|
for (const element of node.body.elements) {
|
|
|
|
this.initialize(element, env);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-09-06 15:17:27 +02:00
|
|
|
case SyntaxKind.IfStatement:
|
2022-09-05 19:33:08 +02:00
|
|
|
case SyntaxKind.ExpressionStatement:
|
|
|
|
case SyntaxKind.ReturnStatement:
|
2022-09-07 12:45:38 +02:00
|
|
|
break;
|
|
|
|
|
2022-09-10 16:52:14 +02:00
|
|
|
case SyntaxKind.EnumDeclaration:
|
|
|
|
{
|
2022-09-14 22:34:53 +02:00
|
|
|
const env = node.typeEnv = new TypeEnv(parentEnv);
|
|
|
|
const constraints = new ConstraintSet();
|
|
|
|
const typeVars = new TVSet();
|
|
|
|
const context: InferContext = {
|
|
|
|
typeVars,
|
|
|
|
env,
|
|
|
|
constraints,
|
|
|
|
returnType: null,
|
|
|
|
}
|
|
|
|
this.pushContext(context);
|
|
|
|
const kindArgs = [];
|
|
|
|
for (const varExpr of node.varExps) {
|
|
|
|
const kindArg = this.createTypeVar();
|
2022-09-15 11:49:53 +02:00
|
|
|
env.add(varExpr.text, new Forall([], [], kindArg), Symkind.Type);
|
2022-09-14 22:34:53 +02:00
|
|
|
kindArgs.push(kindArg);
|
|
|
|
}
|
|
|
|
let elementTypes: Type[] = [];
|
|
|
|
const type = new TVariant(node, [], [], node);
|
|
|
|
if (node.members !== null) {
|
|
|
|
for (const member of node.members) {
|
|
|
|
let elementType;
|
|
|
|
switch (member.kind) {
|
|
|
|
case SyntaxKind.EnumDeclarationTupleElement:
|
|
|
|
{
|
|
|
|
const argTypes = member.elements.map(el => this.inferTypeExpression(el));
|
|
|
|
elementType = new TArrow(argTypes, TApp.build([ ...kindArgs, type ]));
|
2022-09-15 11:49:53 +02:00
|
|
|
parentEnv.add(member.name.text, new Forall(typeVars, constraints, elementType), Symkind.Var);
|
2022-09-14 22:34:53 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
// TODO
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected ${member}`);
|
|
|
|
}
|
|
|
|
elementTypes.push(elementType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.popContext(context);
|
2022-09-15 11:49:53 +02:00
|
|
|
parentEnv.add(node.name.text, new Forall(typeVars, constraints, type), Symkind.Type);
|
2022-09-10 16:52:14 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-09-11 11:20:21 +02:00
|
|
|
case SyntaxKind.TypeDeclaration:
|
|
|
|
{
|
|
|
|
const env = node.typeEnv = new TypeEnv(parentEnv);
|
|
|
|
const constraints = new ConstraintSet();
|
|
|
|
const typeVars = new TVSet();
|
|
|
|
const context: InferContext = {
|
|
|
|
constraints,
|
|
|
|
typeVars,
|
|
|
|
env,
|
|
|
|
returnType: null,
|
|
|
|
};
|
|
|
|
this.pushContext(context);
|
2022-09-15 11:49:53 +02:00
|
|
|
for (const varExpr of node.varExps) {
|
|
|
|
env.add(varExpr.text, new Forall([], [], this.createTypeVar()), Symkind.Type);
|
2022-09-11 11:20:21 +02:00
|
|
|
}
|
|
|
|
const type = this.inferTypeExpression(node.typeExpression);
|
|
|
|
this.popContext(context);
|
|
|
|
const scheme = new Forall(typeVars, constraints, type);
|
2022-09-15 11:49:53 +02:00
|
|
|
parentEnv.add(node.name.text, scheme, Symkind.Type);
|
2022-09-11 11:20:21 +02:00
|
|
|
node.scheme = scheme;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-09-05 19:33:08 +02:00
|
|
|
case SyntaxKind.StructDeclaration:
|
2022-09-07 12:45:38 +02:00
|
|
|
{
|
2022-09-10 16:52:14 +02:00
|
|
|
const env = node.typeEnv = new TypeEnv(parentEnv);
|
|
|
|
const typeVars = new TVSet();
|
|
|
|
const constraints = new ConstraintSet();
|
|
|
|
const context: InferContext = {
|
|
|
|
constraints,
|
|
|
|
typeVars,
|
|
|
|
env,
|
|
|
|
returnType: null,
|
|
|
|
};
|
|
|
|
this.pushContext(context);
|
2022-09-15 11:49:53 +02:00
|
|
|
const kindArgs = [];
|
|
|
|
for (const varExpr of node.varExps) {
|
|
|
|
const kindArg = this.createTypeVar();
|
|
|
|
env.add(varExpr.text, new Forall([], [], kindArg), Symkind.Type);
|
|
|
|
kindArgs.push(kindArg);
|
2022-09-10 16:52:14 +02:00
|
|
|
}
|
2022-09-07 12:45:38 +02:00
|
|
|
const fields = new Map<string, Type>();
|
|
|
|
if (node.members !== null) {
|
|
|
|
for (const member of node.members) {
|
|
|
|
fields.set(member.name.text, this.inferTypeExpression(member.typeExpr));
|
|
|
|
}
|
|
|
|
}
|
2022-09-10 16:52:14 +02:00
|
|
|
this.popContext(context);
|
2022-09-15 11:49:53 +02:00
|
|
|
const type = new TRecord(node, [], fields, node);
|
|
|
|
parentEnv.add(node.name.text, new Forall(typeVars, constraints, type), Symkind.Type);
|
|
|
|
node.scheme = new Forall(typeVars, constraints, TApp.build([ ...kindArgs, type ]));
|
2022-09-05 19:33:08 +02:00
|
|
|
break;
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
2022-09-05 19:33:08 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected ${node}`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-01 20:06:43 +02:00
|
|
|
public check(node: SourceFile): void {
|
|
|
|
|
2022-09-14 16:46:30 +02:00
|
|
|
const kenv = new KindEnv();
|
|
|
|
kenv.setNamed('Int', new KStar());
|
|
|
|
kenv.setNamed('String', new KStar());
|
|
|
|
kenv.setNamed('Bool', new KStar());
|
|
|
|
this.forwardDeclareKind(node, kenv);
|
|
|
|
this.inferKind(node, kenv);
|
|
|
|
|
2022-09-01 20:06:43 +02:00
|
|
|
const typeVars = new TVSet();
|
2022-08-31 13:29:56 +02:00
|
|
|
const constraints = new ConstraintSet();
|
|
|
|
const env = new TypeEnv();
|
2022-09-05 17:25:55 +02:00
|
|
|
const context: InferContext = { typeVars, constraints, env, returnType: null };
|
2022-09-01 20:06:43 +02:00
|
|
|
|
2022-09-05 17:25:55 +02:00
|
|
|
this.pushContext(context);
|
2022-09-01 20:06:43 +02:00
|
|
|
|
|
|
|
const a = this.createTypeVar();
|
2022-09-14 16:46:30 +02:00
|
|
|
const b = this.createTypeVar();
|
|
|
|
const f = this.createTypeVar();
|
2022-09-01 20:06:43 +02:00
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
env.add('$', new Forall([ f, a ], [], new TArrow([ new TArrow([ a ], b), a ], b)), Symkind.Var);
|
|
|
|
env.add('String', new Forall([], [], this.stringType), Symkind.Type);
|
|
|
|
env.add('Int', new Forall([], [], this.intType), Symkind.Type);
|
2022-09-15 13:48:08 +02:00
|
|
|
env.add('Bool', new Forall([], [], this.boolType), Symkind.Type);
|
2022-09-15 11:49:53 +02:00
|
|
|
env.add('True', new Forall([], [], this.boolType), Symkind.Var);
|
|
|
|
env.add('False', new Forall([], [], this.boolType), Symkind.Var);
|
|
|
|
env.add('+', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
|
|
|
env.add('-', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
|
|
|
env.add('*', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
|
|
|
env.add('/', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
|
|
|
env.add('==', new Forall([ a ], [], new TArrow([ a, a ], this.boolType)), Symkind.Var);
|
|
|
|
env.add('not', new Forall([], [], new TArrow([ this.boolType ], this.boolType)), Symkind.Var);
|
2022-09-01 20:06:43 +02:00
|
|
|
|
2022-09-05 19:33:08 +02:00
|
|
|
this.initialize(node, env);
|
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
this.pushContext({
|
|
|
|
typeVars,
|
|
|
|
constraints,
|
|
|
|
env: node.typeEnv!,
|
|
|
|
returnType: null
|
|
|
|
});
|
|
|
|
|
2022-09-15 16:04:49 +02:00
|
|
|
const sccs = [...this.analyser.getSortedDeclarations()];
|
2022-09-05 19:33:08 +02:00
|
|
|
|
|
|
|
for (const nodes of sccs) {
|
2022-09-05 17:25:55 +02:00
|
|
|
|
2022-09-06 15:13:07 +02:00
|
|
|
if (nodes.some(n => n.kind === SyntaxKind.SourceFile)) {
|
|
|
|
assert(nodes.length === 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-09-05 17:25:55 +02:00
|
|
|
const typeVars = new TVSet();
|
|
|
|
const constraints = new ConstraintSet();
|
|
|
|
|
2022-09-01 20:06:43 +02:00
|
|
|
for (const node of nodes) {
|
2022-09-05 17:25:55 +02:00
|
|
|
|
2022-09-06 15:13:07 +02:00
|
|
|
assert(node.kind === SyntaxKind.LetDeclaration);
|
|
|
|
|
2022-09-09 22:37:14 +02:00
|
|
|
if (!isFunctionDeclarationLike(node)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-09-05 19:33:08 +02:00
|
|
|
const env = node.typeEnv!;
|
2022-09-05 17:25:55 +02:00
|
|
|
const context: InferContext = {
|
|
|
|
typeVars,
|
|
|
|
constraints,
|
|
|
|
env,
|
|
|
|
returnType: null,
|
|
|
|
};
|
|
|
|
node.context = context;
|
|
|
|
|
|
|
|
this.contexts.push(context);
|
|
|
|
|
|
|
|
const returnType = this.createTypeVar();
|
|
|
|
context.returnType = returnType;
|
|
|
|
|
|
|
|
const paramTypes = [];
|
|
|
|
for (const param of node.params) {
|
2022-09-07 12:45:38 +02:00
|
|
|
const paramType = this.inferBindings(param.pattern, [], []);
|
2022-09-05 17:25:55 +02:00
|
|
|
paramTypes.push(paramType);
|
2022-09-01 20:06:43 +02:00
|
|
|
}
|
2022-09-05 17:25:55 +02:00
|
|
|
|
|
|
|
let type = new TArrow(paramTypes, returnType);
|
|
|
|
if (node.typeAssert !== null) {
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
this.inferTypeExpression(node.typeAssert.typeExpression),
|
|
|
|
type,
|
2022-09-06 13:40:20 +02:00
|
|
|
node
|
2022-09-05 17:25:55 +02:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
node.type = type;
|
|
|
|
|
|
|
|
this.contexts.pop();
|
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
// FIXME get rid of all this useless stack manipulation
|
|
|
|
const parentDecl = node.parent!.getScope().node;
|
|
|
|
const bindCtx = {
|
|
|
|
typeVars: context.typeVars,
|
|
|
|
constraints: context.constraints,
|
|
|
|
env: parentDecl.typeEnv!,
|
|
|
|
returnType: null,
|
|
|
|
};
|
|
|
|
this.contexts.push(bindCtx)
|
2022-09-09 22:37:14 +02:00
|
|
|
let ty2;
|
|
|
|
if (node.pattern.kind === SyntaxKind.WrappedOperator) {
|
|
|
|
ty2 = this.createTypeVar();
|
2022-09-15 11:49:53 +02:00
|
|
|
this.addBinding(node.pattern.operator.text, new Forall([], [], ty2), Symkind.Var);
|
2022-09-09 22:37:14 +02:00
|
|
|
} else {
|
|
|
|
ty2 = this.inferBindings(node.pattern, typeVars, constraints);
|
|
|
|
}
|
2022-09-07 12:45:38 +02:00
|
|
|
this.addConstraint(new CEqual(ty2, type, node));
|
|
|
|
this.contexts.pop();
|
2022-09-01 20:06:43 +02:00
|
|
|
}
|
2022-09-05 17:25:55 +02:00
|
|
|
|
2022-09-05 19:33:08 +02:00
|
|
|
}
|
|
|
|
|
2022-09-09 22:37:14 +02:00
|
|
|
const visitElements = (elements: Syntax[]) => {
|
|
|
|
for (const element of elements) {
|
|
|
|
if (element.kind === SyntaxKind.LetDeclaration
|
2022-09-15 16:04:49 +02:00
|
|
|
&& isFunctionDeclarationLike(element)) {
|
|
|
|
if (!this.analyser.isReferencedInParentScope(element)) {
|
|
|
|
assert(element.pattern.kind === SyntaxKind.BindPattern);
|
|
|
|
const scheme = this.lookup(element.pattern.name.text, Symkind.Var);
|
|
|
|
assert(scheme !== null);
|
|
|
|
this.instantiate(scheme, null);
|
|
|
|
}
|
2022-09-09 22:37:14 +02:00
|
|
|
} else {
|
|
|
|
this.infer(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-05 19:33:08 +02:00
|
|
|
for (const nodes of sccs) {
|
|
|
|
|
2022-09-06 15:13:07 +02:00
|
|
|
if (nodes.some(n => n.kind === SyntaxKind.SourceFile)) {
|
|
|
|
assert(nodes.length === 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-09-05 19:33:08 +02:00
|
|
|
for (const node of nodes) {
|
2022-09-06 15:13:07 +02:00
|
|
|
assert(node.kind === SyntaxKind.LetDeclaration);
|
2022-09-05 19:33:08 +02:00
|
|
|
node.active = true;
|
|
|
|
}
|
|
|
|
|
2022-09-05 17:25:55 +02:00
|
|
|
for (const node of nodes) {
|
|
|
|
|
2022-09-06 15:13:07 +02:00
|
|
|
assert(node.kind === SyntaxKind.LetDeclaration);
|
|
|
|
|
2022-09-09 22:37:14 +02:00
|
|
|
if (!isFunctionDeclarationLike(node)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-09-05 17:25:55 +02:00
|
|
|
const context = node.context!;
|
|
|
|
const returnType = context.returnType!;
|
|
|
|
this.contexts.push(context);
|
|
|
|
|
|
|
|
if (node.body !== null) {
|
|
|
|
switch (node.body.kind) {
|
|
|
|
case SyntaxKind.ExprBody:
|
|
|
|
{
|
|
|
|
this.addConstraint(
|
|
|
|
new CEqual(
|
|
|
|
this.inferExpression(node.body.expression),
|
|
|
|
returnType,
|
|
|
|
node.body.expression
|
|
|
|
)
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.BlockBody:
|
|
|
|
{
|
2022-09-09 22:37:14 +02:00
|
|
|
visitElements(node.body.elements);
|
2022-09-05 17:25:55 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.contexts.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
2022-09-06 15:13:07 +02:00
|
|
|
assert(node.kind === SyntaxKind.LetDeclaration);
|
2022-09-05 19:33:08 +02:00
|
|
|
node.active = false;
|
2022-09-05 17:25:55 +02:00
|
|
|
}
|
|
|
|
|
2022-09-01 20:06:43 +02:00
|
|
|
}
|
2022-09-09 22:37:14 +02:00
|
|
|
|
|
|
|
visitElements(node.elements);
|
2022-09-01 20:06:43 +02:00
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
this.contexts.pop();
|
2022-09-05 17:25:55 +02:00
|
|
|
this.popContext(context);
|
2022-09-01 20:06:43 +02:00
|
|
|
|
2022-09-06 15:13:07 +02:00
|
|
|
this.solve(new CMany(constraints), this.solution);
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-05 17:25:55 +02:00
|
|
|
private solve(constraint: Constraint, solution: TVSub): void {
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
const queue = [ constraint ];
|
|
|
|
|
2022-09-06 15:13:07 +02:00
|
|
|
let errorCount = 0;
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
while (queue.length > 0) {
|
|
|
|
|
2022-09-09 20:02:35 +02:00
|
|
|
const constraint = queue.shift()!;
|
2022-08-31 13:29:56 +02:00
|
|
|
|
|
|
|
switch (constraint.kind) {
|
|
|
|
|
|
|
|
case ConstraintKind.Many:
|
|
|
|
{
|
|
|
|
for (const element of constraint.elements) {
|
|
|
|
queue.push(element);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ConstraintKind.Equal:
|
|
|
|
{
|
2022-09-08 23:32:25 +02:00
|
|
|
// constraint.dump();
|
2022-09-11 15:23:22 +02:00
|
|
|
const unify = (left: Type, right: Type): boolean => {
|
|
|
|
|
2022-09-14 22:34:53 +02:00
|
|
|
const find = (type: Type): Type => {
|
2022-09-11 15:23:22 +02:00
|
|
|
while (type.kind === TypeKind.Var && solution.has(type)) {
|
|
|
|
type = solution.get(type)!;
|
|
|
|
}
|
|
|
|
return type;
|
2022-09-06 15:13:07 +02:00
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-14 22:34:53 +02:00
|
|
|
left = find(left);
|
|
|
|
right = find(right);
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (left.kind === TypeKind.Var) {
|
|
|
|
if (right.hasTypeVar(left)) {
|
|
|
|
// TODO occurs check diagnostic
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
solution.set(left, right);
|
|
|
|
TypeBase.join(left, right);
|
|
|
|
return true;
|
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (right.kind === TypeKind.Var) {
|
|
|
|
return unify(right, left);
|
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (left.kind === TypeKind.Arrow && right.kind === TypeKind.Arrow) {
|
|
|
|
if (left.paramTypes.length !== right.paramTypes.length) {
|
|
|
|
this.diagnostics.add(new ArityMismatchDiagnostic(left, right));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let success = true;
|
|
|
|
const count = left.paramTypes.length;
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
if (!unify(left.paramTypes[i], right.paramTypes[i])) {
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!unify(left.returnType, right.returnType)) {
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
if (success) {
|
|
|
|
TypeBase.join(left, right);
|
|
|
|
}
|
|
|
|
return success;
|
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (left.kind === TypeKind.Arrow && left.paramTypes.length === 0) {
|
|
|
|
return unify(left.returnType, right);
|
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (right.kind === TypeKind.Arrow) {
|
|
|
|
return unify(right, left);
|
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (left.kind === TypeKind.Con && right.kind === TypeKind.Con) {
|
|
|
|
if (left.id === right.id) {
|
|
|
|
assert(left.argTypes.length === right.argTypes.length);
|
|
|
|
const count = left.argTypes.length;
|
|
|
|
let success = true;
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
if (!unify(left.argTypes[i], right.argTypes[i])) {
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (success) {
|
|
|
|
TypeBase.join(left, right);
|
|
|
|
}
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
}
|
2022-09-01 20:18:47 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (left.kind === TypeKind.Labeled && right.kind === TypeKind.Labeled) {
|
|
|
|
let success = false;
|
|
|
|
// This works like an ordinary union-find algorithm where an additional
|
|
|
|
// property 'fields' is carried over from the child nodes to the
|
|
|
|
// ever-changing root node.
|
|
|
|
const root = left.find();
|
|
|
|
right.parent = root;
|
|
|
|
if (root.fields === undefined) {
|
|
|
|
root.fields = new Map([ [ root.name, root.type ] ]);
|
|
|
|
}
|
|
|
|
if (right.fields === undefined) {
|
|
|
|
right.fields = new Map([ [ right.name, right.type ] ]);
|
|
|
|
}
|
|
|
|
for (const [fieldName, fieldType] of right.fields) {
|
|
|
|
if (root.fields.has(fieldName)) {
|
|
|
|
if (!unify(root.fields.get(fieldName)!, fieldType)) {
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
root.fields.set(fieldName, fieldType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete right.fields;
|
|
|
|
if (success) {
|
|
|
|
TypeBase.join(left, right);
|
|
|
|
}
|
|
|
|
return success;
|
|
|
|
}
|
2022-09-01 20:18:47 +02:00
|
|
|
|
2022-09-14 22:34:53 +02:00
|
|
|
if (left.kind === TypeKind.Variant && right.kind === TypeKind.Variant) {
|
|
|
|
if (left.decl !== right.decl) {
|
|
|
|
this.diagnostics.add(new UnificationFailedDiagnostic(left, right, [...constraint.getNodes()]));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (left.kind === TypeKind.App && right.kind === TypeKind.App) {
|
|
|
|
return unify(left.left, right.left)
|
|
|
|
&& unify(left.right, right.right);
|
|
|
|
}
|
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (left.kind === TypeKind.Record && right.kind === TypeKind.Record) {
|
|
|
|
if (left.decl !== right.decl) {
|
|
|
|
this.diagnostics.add(new UnificationFailedDiagnostic(left, right, [...constraint.getNodes()]));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let success = true;
|
|
|
|
const remaining = new Set(right.fields.keys());
|
|
|
|
for (const [fieldName, fieldType] of left.fields) {
|
|
|
|
if (right.fields.has(fieldName)) {
|
|
|
|
if (!unify(fieldType, right.fields.get(fieldName)!)) {
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
remaining.delete(fieldName);
|
|
|
|
} else {
|
|
|
|
this.diagnostics.add(new FieldMissingDiagnostic(right, fieldName, constraint.node));
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const fieldName of remaining) {
|
|
|
|
this.diagnostics.add(new FieldDoesNotExistDiagnostic(left, fieldName, constraint.node));
|
|
|
|
}
|
|
|
|
if (success) {
|
|
|
|
TypeBase.join(left, right);
|
|
|
|
}
|
|
|
|
return success;
|
|
|
|
}
|
2022-08-31 13:29:56 +02:00
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
let leftElement: Type = left;
|
|
|
|
while (leftElement.kind === TypeKind.App) {
|
|
|
|
leftElement = leftElement.right;
|
|
|
|
}
|
|
|
|
let rightElement: Type = right;
|
|
|
|
while (rightElement.kind === TypeKind.App) {
|
|
|
|
rightElement = rightElement.right;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (leftElement.kind === TypeKind.Record && right.kind === TypeKind.Labeled) {
|
2022-09-11 15:23:22 +02:00
|
|
|
let success = true;
|
|
|
|
if (right.fields === undefined) {
|
|
|
|
right.fields = new Map([ [ right.name, right.type ] ]);
|
|
|
|
}
|
|
|
|
for (const [fieldName, fieldType] of right.fields) {
|
2022-09-15 11:49:53 +02:00
|
|
|
if (leftElement.fields.has(fieldName)) {
|
|
|
|
if (!unify(fieldType, leftElement.fields.get(fieldName)!)) {
|
2022-09-11 15:23:22 +02:00
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.diagnostics.add(new FieldMissingDiagnostic(left, fieldName, constraint.node));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (success) {
|
|
|
|
TypeBase.join(left, right);
|
|
|
|
}
|
|
|
|
return success;
|
|
|
|
}
|
2022-09-11 11:20:21 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (left.kind === TypeKind.Labeled && right.kind === TypeKind.Record) {
|
|
|
|
return unify(right, left);
|
|
|
|
}
|
2022-09-07 12:45:38 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
this.diagnostics.add(
|
|
|
|
new UnificationFailedDiagnostic(
|
|
|
|
left.substitute(solution),
|
|
|
|
right.substitute(solution),
|
|
|
|
[...constraint.getNodes()],
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return false;
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
2022-09-08 23:32:25 +02:00
|
|
|
|
2022-09-11 15:23:22 +02:00
|
|
|
if (!unify(constraint.left, constraint.right)) {
|
|
|
|
errorCount++;
|
|
|
|
if (errorCount === MAX_TYPE_ERROR_COUNT) {
|
|
|
|
return;
|
|
|
|
}
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
2022-09-11 15:23:22 +02:00
|
|
|
|
|
|
|
break;
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
2022-09-11 15:23:22 +02:00
|
|
|
|
2022-09-07 12:45:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|