[WIP] Further attempt to get typeclasses working

This commit is contained in:
Sam Vervaeck 2024-04-08 19:57:10 +02:00
parent 719dbfcad4
commit e01a970377
Signed by: samvv
SSH key fingerprint: SHA256:dIg0ywU1OP+ZYifrYxy8c5esO72cIKB+4/9wkZj1VaY
11 changed files with 424 additions and 102 deletions

View file

@ -13,7 +13,7 @@ export class Analyser {
const addReference = (scope: Scope, name: string) => {
const target = scope.lookup(name);
if (source === null || target === null || target.kind === SyntaxKind.Param) {
if (source === null || target === null || isParam(target.kind)) {
return;
}
assert(source.kind === SyntaxKind.LetDeclaration);

View file

@ -5,6 +5,7 @@ import {
ExprOperator,
Identifier,
IdentifierAlt,
InstanceDeclaration,
LetDeclaration,
Pattern,
ReferenceExpression,
@ -23,58 +24,31 @@ import {
KindMismatchDiagnostic,
ModuleNotFoundDiagnostic,
TypeclassNotFoundDiagnostic,
TypeclassDeclaredTwiceDiagnostic,
FieldNotFoundDiagnostic,
TypeMismatchDiagnostic,
} from "./diagnostics";
import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn } from "./util";
import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util";
import { Analyser } from "./analysis";
import { InspectOptions } from "util";
import { TypeKind, TApp, TArrow, TCon, TField, TNil, TPresent, TRegularVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar, buildTupleTypeWithLoc, buildTupleType, isTVar } from "./types";
import { TypeKind, TApp, TArrow, TCon, TField, TNil, TPresent, TRegularVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar, buildTupleTypeWithLoc, buildTupleType, labelTag } from "./types";
import { CEmpty, CEqual, CMany, Constraint, ConstraintKind, ConstraintSet } from "./constraints";
// export class Qual {
// class IsIn {
// public constructor(
// public preds: Pred[],
// public type: Type,
// ) {
// }
// public substitute(sub: TVSub): Qual {
// return new Qual(
// this.preds.map(pred => pred.substitute(sub)),
// this.type.substitute(sub),
// );
// }
// public *getTypeVars() {
// for (const pred of this.preds) {
// yield* pred.type.getTypeVars();
// }
// yield* this.type.getTypeVars();
// }
// }
// class IsInPred {
// public constructor(
// public id: string,
// public className: string,
// public type: Type,
// ) {
// }
// public substitute(sub: TVSub): Pred {
// return new IsInPred(this.id, this.type.substitute(sub));
// return new IsIn(this.className, this.type.substitute(sub));
// }
// }
// type Pred = IsInPred;
// type Pred = IsIn;
export const enum KindType {
Type,
@ -371,6 +345,11 @@ function hasTypeVar(typeVars: TVSet, type: Type): boolean {
return false;
}
interface ClassMeta {
decl: ClassDeclaration | null;
instances: Set<InstanceDeclaration>;
}
export class Checker {
private nextTypeVarId = 0;
@ -382,7 +361,7 @@ export class Checker {
private boolType = this.createTCon('Bool');
private unitType = buildTupleType([]);
private classDecls = new Map<string, ClassDeclaration>();
private classDecls = new Map<string, ClassMeta>();
private globalKindEnv = new KindEnv();
private globalTypeEnv = new TypeEnv();
@ -779,8 +758,8 @@ export class Checker {
case ConstraintKind.Empty:
return constraint;
case ConstraintKind.Equal:
constraint.left = this.simplifyType(constraint.left)
constraint.right = this.simplifyType(constraint.right)
// constraint.left = this.simplifyType(constraint.left)
// constraint.right = this.simplifyType(constraint.right)
const newConstraint = constraint.substitute(sub);
newConstraint.node = node;
newConstraint.prevInstantiation = constraint;
@ -877,13 +856,15 @@ export class Checker {
}
case SyntaxKind.NestedTypeExpression:
case SyntaxKind.InstanceTypeExpression:
{
kind = this.inferKindFromTypeExpression(node.typeExpr, env);
break;
}
default:
throw new Error(`Unexpected ${node}`);
assertNever(node);
}
// We store the kind on the node so there is a one-to-one correspondence
@ -1580,6 +1561,7 @@ export class Checker {
break;
}
case SyntaxKind.InstanceTypeExpression:
case SyntaxKind.NestedTypeExpression:
type = this.inferTypeExpression(node.typeExpr, introduceTypeVars);
break;
@ -1613,11 +1595,16 @@ export class Checker {
case SyntaxKind.TypeExpressionWithConstraints:
{
// TODO
// for (const constraint of node.constraints) {
// implementationLimitation(constraint.types.length === 1);
// this.addConstraint(new CClass(constraint.name.text, this.inferTypeExpression(constraint.types[0]), constraint.name));
// }
for (const constraint of node.constraints) {
implementationLimitation(constraint.types.length === 1);
this.addConstraint(
new CClass(
constraint.name.text,
this.inferTypeExpression(constraint.types[0]),
constraint.name
)
);
}
return this.inferTypeExpression(node.typeExpr, introduceTypeVars);
}
@ -1646,7 +1633,7 @@ export class Checker {
}
default:
throw new Error(`Unrecognised ${node}`);
assertNever(node);
}
@ -1778,6 +1765,18 @@ export class Checker {
}
private getClassMeta(name: string): ClassMeta {
let meta = this.classDecls.get(name);
if (meta === undefined) {
meta = {
decl: null,
instances: new Set(),
};
this.classDecls.set(name, meta);
}
return meta;
}
private initialize(node: Syntax): void {
switch (node.kind) {
@ -1816,6 +1815,11 @@ export class Checker {
case SyntaxKind.ClassDeclaration:
{
const meta = this.getClassMeta(node.name.text);
if (meta.decl !== undefined) {
// TODO class declaration already exists diagnostic
}
meta.decl = node;
const info = this.getInfo(node);
const env = info.typeEnv = new TypeEnv();
for (const tv of node.types) {
@ -1830,14 +1834,19 @@ export class Checker {
case SyntaxKind.InstanceDeclaration:
{
if (!this.classDecls.has(node.name.text)) {
const meta = this.getClassMeta(node.name.text);
meta.instances.add(node);
if (meta.decl === null) {
this.diagnostics.add(new TypeclassNotFoundDiagnostic(node.name.text, node.name));
}
const info = this.getInfo(node);
info.typeEnv = new TypeEnv();
for (const element of node.elements) {
this.initialize(element);
}
break;
}
@ -1974,23 +1983,30 @@ export class Checker {
typeArgs.push(typeArg);
}
// const tagType = TApp.build(
// this.createTCon(node.name.text, node.name),
// typeArgs,
// node.name
// );
const tagType = this.createTCon(node.name.text, node.name);
const fields = new Map<string, Type>();
const restType = new TNil(node);
fields.set(labelTag, tagType);
if (node.fields !== null) {
for (const field of node.fields) {
fields.set(field.name.text, this.inferTypeExpression(field.typeExpr));
}
}
const type = this.createTCon(node.name.text, node.name);
const recordType = TField.build(fields, restType);
this.polyContextStack.pop();
this.typeEnvStack.pop();
parentEnv.add(node.name.text, new Forall(poly.typeVars, new CMany(poly.constraints), type), Symkind.Type);
parentEnv.add(node.name.text, new Forall(poly.typeVars, new CMany(poly.constraints), new TArrow(recordType, type)), Symkind.Var);
parentEnv.add(node.name.text, new Forall(poly.typeVars, new CMany(poly.constraints), recordType), Symkind.Type);
// parentEnv.add(node.name.text, new Forall(poly.typeVars, new CMany(poly.constraints), new TArrow(recordType, type)), Symkind.Var);
break;
}
@ -2041,7 +2057,16 @@ export class Checker {
const paramTypes = node.params.map(param => {
const paramType = this.createTRegularVar();
this.inferBindings(param.pattern, paramType)
switch (param.kind) {
case SyntaxKind.PlainParam:
this.inferBindings(param.pattern, paramType)
break;
case SyntaxKind.InstanceParam:
this.addBinding(param.name.text, Forall.mono(paramType), Symkind.Var);
break;
default:
assertNever(param);
}
return paramType;
});
@ -2094,17 +2119,35 @@ export class Checker {
}
// private findInstanceContext(sig: Type[], clazz: ClassDeclaration): Iterable<ClassDeclaration[]> {
// const contexts = [];
// const meta = this.getClassMeta(clazz.name.text);
// // TODO should be a seperate verification pass somewhere
// // if (meta.decl === null) {
// // this.diagnostics.add(new TypeclassNotFoundDiagnostic(clazz.name.text));
// // }
// for (const instance of meta.instances) {
// let i = 0;
// for (const type of instance.types) {
// // TODO might need unification
// const left = sig[i++];
// const right = this.getTypeOfNode(type);
// if (assignableTo(left, right)) {
// contexts.push(context);
// }
// }
// }
// return contexts;
// }
private path: (string | number)[] = [];
private constraint: Constraint | null = null;
private maxTypeErrorCount = 5;
private find(type: Type): Type {
while (type.kind === TypeKind.RegularVar && this.typeSolution.has(type)) {
type = this.typeSolution.get(type)!;
}
return type;
}
private unifyField(left: Type, right: Type, enableDiagnostics: boolean): boolean {
const swap = () => { [right, left] = [left, right]; }
@ -2132,7 +2175,6 @@ export class Checker {
return this.unify(left.type, right.type, enableDiagnostics);
}
private unify(left: Type, right: Type, enableDiagnostics: boolean): boolean {
//console.log(`unify ${describeType(left)} @ ${left.node && left.node.constructor && left.node.constructor.name} ~ ${describeType(right)} @ ${right.node && right.node.constructor && right.node.constructor.name}`);
@ -2167,30 +2209,33 @@ export class Checker {
// propagating the type classes that 'left' requires to 'right'.
// If 'right' is another type variable, we're lucky. We just copy
// the missing type classes from 'left' to 'right'. Otherwise,
const propagateClasses = (classes: Iterable<ClassDeclaration>, type: Type) => {
if (isTVar(type)) {
for (const constraint of classes) {
type.context.add(constraint);
}
} else if (type.kind === TypeKind.Con) {
for (const constraint of classes) {
propagateClassTCon(constraint, type);
}
} else {
//assert(false);
//this.diagnostics.add(new );
}
}
//const propagateClasses = (classes: Iterable<ClassDeclaration>, type: Type) => {
// if (isTVar(type)) {
// for (const constraint of classes) {
// type.context.add(constraint);
// }
// } else if (isSignature(type)) {
// const sig = getSignature(type);
// for (const constraint of classes) {
// propagateClassTCon(constraint, sig);
// }
// } else {
// assert(false);
// //this.diagnostics.add(new );
// }
//}
const propagateClassTCon = (clazz: ClassDeclaration, type: TCon) => {
const s = this.findInstanceContext(type, clazz);
let i = 0;
for (const classes of s) {
propagateClasses(classes, type.types[i++]);
}
}
//const propagateClassTCon = (clazz: ClassDeclaration, sig: Type[]) => {
// const s = this.findInstanceContext(sig, clazz);
// let i = 1;
// for (const classes of s) {
// propagateClasses(classes, sig[i++]);
// }
//}
propagateClasses(left.context, right);
//if (left.context.size > 0) {
// propagateClasses(left.context, right);
//}
// We are all clear; set the actual type of left to right.
left.set(right);
@ -2305,6 +2350,35 @@ export class Checker {
return false;
}
// private inHnf(p: Pred): boolean {
// let curr = p.type;
// for (;;) {
// if (isTVar(curr)) {
// return true;
// }
// if (curr.kind === TypeKind.Con) {
// return false;
// }
// if (curr.kind === TypeKind.App) {
// curr = curr.left;
// continue;
// }
// unreachable();
// }
// }
// private toHnf(p: Pred): Pred[] {
// if (this.inHnf(p)) {
// return [ p ];
// }
// const result = this.byInst(p);
// if (result === undefined) {
// // TODO add diagnostic
// throw new Error(`context reduction`);
// }
// result.map(this.toHnf.bind(this)).flatten();
// }
public solve(constraint: Constraint): void {
let queue = [ constraint ];
@ -2317,6 +2391,9 @@ export class Checker {
switch (constraint.kind) {
case ConstraintKind.Empty:
break;
case ConstraintKind.Many:
{
for (const element of constraint.elements) {
@ -2337,13 +2414,20 @@ export class Checker {
break;
}
case ConstraintKind.Class:
// TODO
break;
default:
assertNever(constraint);;
}
}
}
private lookupClass(name: string): ClassDeclaration | null {
private lookupClass(name: string): ClassMeta | null {
return this.classDecls.get(name) ?? null;
}

View file

@ -6,6 +6,7 @@ import { first, InspectFn, last, toStringTag } from "./util";
export const enum ConstraintKind {
Equal,
// Class,
Many,
Empty,
}
@ -99,19 +100,48 @@ export class CMany extends ConstraintBase {
}
}
public [toStringTag](currentDepth: number, { depth = 2, ...options }: InspectOptions, inspect: InspectFn): string {
public [toStringTag](_depth: number, opts: InspectOptions, inspect: InspectFn): string {
if (this.elements.length === 0) {
return '[]';
}
let out = '[\n';
const newOptions = { ...options, depth: depth === null ? null : depth - 1 };
out += this.elements.map(constraint => ' ' + inspect(constraint, newOptions)).join('\n');
out += this.elements.map(constraint => ' ' + inspect(constraint, opts)).join('\n');
out += '\n]';
return out;
}
}
// export class CClass extends ConstraintBase {
// public readonly kind = ConstraintKind.Class;
// public constructor(
// public className: string,
// public type: Type,
// public node: Syntax,
// ) {
// super();
// }
// public substitute(sub: TVSub): Constraint {
// return new CClass(
// this.className,
// this.type.substitute(sub),
// this.node,
// );
// }
// public *freeTypeVars(): Iterable<TVar> {
// yield* this.type.getTypeVars();
// }
// public [toStringTag](_depth: number, opts: InspectOptions, inspect: InspectFn): string {
// return this.className + ' => ' + inspect(this.type, opts);
// }
// }
export class CEmpty extends ConstraintBase {
public readonly kind = ConstraintKind.Empty;
@ -132,9 +162,10 @@ export class CEmpty extends ConstraintBase {
export type Constraint
= CEqual
// | CClass
| CMany
| CEmpty
export class ConstraintSet extends Array<Constraint> {
export class ConstraintSet extends Array<Constraint> {
}

View file

@ -7,6 +7,7 @@ import { isNodeWithScope, Scope } from "./scope"
import type { Kind, Scheme } from "./checker"
import type { Type } from "./types";
import { Emitter } from "./emitter";
import { warn } from "console";
export type TextSpan = [number, number];
@ -81,7 +82,7 @@ export class TextFile {
}
export const enum SyntaxKind {
export enum SyntaxKind {
// Tokens
Identifier,
@ -140,6 +141,7 @@ export const enum SyntaxKind {
NestedTypeExpression,
TupleTypeExpression,
ForallTypeExpression,
InstanceTypeExpression,
TypeExpressionWithConstraints,
// Patterns
@ -204,12 +206,15 @@ export const enum SyntaxKind {
EnumDeclarationStructElement,
EnumDeclarationTupleElement,
// Parameters
PlainParam,
InstanceParam,
// Other nodes
WrappedOperator,
MatchArm,
Initializer,
TypeAssert,
Param,
SourceFile,
ClassConstraint,
ClassConstraintClause,
@ -1486,6 +1491,44 @@ export class NestedTypeExpression extends SyntaxBase {
}
export class InstanceTypeExpression extends SyntaxBase {
public readonly kind = SyntaxKind.InstanceTypeExpression;
public constructor(
public lbrace1: LBrace,
public lbrace2: LBrace,
public name: Identifier | null = null,
public colon: Colon | null = null,
public typeExpr: TypeExpression,
public rbrace1: RBrace,
public rbrace2: RBrace,
) {
super();
}
public clone(): InstanceTypeExpression {
return new InstanceTypeExpression(
this.lbrace1,
this.lbrace2,
this.name,
this.colon,
this.typeExpr,
this.rbrace1,
this.rbrace2,
);
}
public getFirstToken(): Token {
return this.lbrace1;
}
public getLastToken(): Token {
return this.rbrace2;
}
}
export type TypeExpression
= ReferenceTypeExpression
| ArrowTypeExpression
@ -1495,6 +1538,7 @@ export type TypeExpression
| TupleTypeExpression
| ForallTypeExpression
| TypeExpressionWithConstraints
| InstanceTypeExpression
export class NamedPattern extends SyntaxBase {
@ -2494,9 +2538,43 @@ export type Statement
| IfStatement
| AssignStatement
export class Param extends SyntaxBase {
export class InstanceParam extends SyntaxBase {
public readonly kind = SyntaxKind.Param;
public readonly kind = SyntaxKind.InstanceParam;
public constructor(
public lbrace1: LBrace,
public lbrace2: LBrace,
public name: Identifier,
public rbrace1: RBrace,
public rbrace2: RBrace,
) {
super();
}
public clone(): InstanceParam {
return new InstanceParam(
this.lbrace1,
this.lbrace2,
this.name,
this.rbrace1,
this.rbrace2,
);
}
public getFirstToken(): Token {
return this.lbrace1;
}
public getLastToken(): Token {
return this.rbrace2;
}
}
export class PlainParam extends SyntaxBase {
public readonly kind = SyntaxKind.PlainParam;
public constructor(
public pattern: Pattern,
@ -2504,8 +2582,8 @@ export class Param extends SyntaxBase {
super();
}
public clone(): Param {
return new Param(
public clone(): PlainParam {
return new PlainParam(
this.pattern.clone(),
);
}
@ -2520,6 +2598,15 @@ export class Param extends SyntaxBase {
}
export type Param
= InstanceParam
| PlainParam
export function isParam(node: Syntax): node is Param {
return node.kind === SyntaxKind.PlainParam
|| node.kind === SyntaxKind.InstanceParam;
}
export class EnumDeclarationStructElement extends SyntaxBase {
public readonly kind = SyntaxKind.EnumDeclarationStructElement;

View file

@ -1,8 +1,10 @@
import { Kind, KindType } from "./checker";
import { type Type, TypeKind } from "./types"
import { type Type, TypeKind, labelTag } from "./types"
import { ClassConstraint, ClassDeclaration, IdentifierAlt, InstanceDeclaration, Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
import { assertNever, countDigits, IndentWriter } from "./util";
import { assert, assertNever, countDigits, IndentWriter } from "./util";
import { unwatchFile } from "fs";
import { warn } from "console";
const ANSI_RESET = "\u001b[0m"
const ANSI_BOLD = "\u001b[1m"
@ -537,6 +539,13 @@ export function describeType(type: Type): string {
}
case TypeKind.Field:
{
// let curr: Type = type;
// while (curr.kind === TypeKind.Field) {
// if (curr.name === labelTag) {
// return describeType(curr.type);
// }
// curr = curr.restType;
// }
let out = '{ ' + type.name + ': ' + describeType(type.type);
type = type.restType;
while (type.kind === TypeKind.Field) {

View file

@ -91,7 +91,7 @@ export class Emitter {
this.writer.write(node.name.text);
break;
case SyntaxKind.Param:
case SyntaxKind.PlainParam:
this.emit(node.pattern);
break;

View file

@ -1,4 +1,5 @@
import { warn } from "console";
import {
ReferenceTypeExpression,
SourceFile,
@ -73,8 +74,12 @@ import {
Annotations,
ExprOperator,
Integer,
InstanceParam,
PlainParam,
InstanceTypeExpression,
} from "./cst"
import { Stream } from "./util";
import { wrap } from "module";
export class ParseError extends Error {
@ -196,7 +201,24 @@ export class Parser {
}
public parsePrimitiveTypeExpression(): TypeExpression {
const t0 = this.peekToken();
const t0 = this.peekToken(1);
const t1 = this.peekToken(2);
if (t0.kind === SyntaxKind.LBrace && t1.kind === SyntaxKind.LBrace) {
this.getToken();
this.getToken();
let name = null;
let colon = null;
const t2 = this.peekToken(2);
if (t2.kind === SyntaxKind.Colon) {
name = this.expectToken(SyntaxKind.Identifier);
this.getToken();
colon = t2;
}
const typeExpr = this.parseTypeExpression();
const t3 = this.expectToken(SyntaxKind.RBrace);
const t4 = this.expectToken(SyntaxKind.RBrace);
return new InstanceTypeExpression(t0, t1, name, colon, typeExpr, t3, t4);
}
switch (t0.kind) {
case SyntaxKind.Identifier:
return this.parseVarTypeExpression();
@ -893,8 +915,18 @@ export class Parser {
}
public parseParam(): Param {
const t0 = this.peekToken(1);
const t1 = this.peekToken(2);
if (t0.kind === SyntaxKind.LBrace && t1.kind === SyntaxKind.LBrace) {
this.getToken();
this.getToken();
const name = this.expectToken(SyntaxKind.Identifier);
const t3 = this.expectToken(SyntaxKind.RBrace);
const t4 = this.expectToken(SyntaxKind.RBrace);
return new InstanceParam(t0, t1, name, t3, t4);
}
const pattern = this.parsePattern();
return new Param(pattern);
return new PlainParam(pattern);
}
private lookaheadIsAssignment(): boolean {

View file

@ -17,7 +17,7 @@ import {
FunctionExpression,
Backslash,
canHaveInstanceDeclaration,
vistEachChild
visitEachChild
} from "../cst";
import { Pass } from "../program";
import { assert } from "../util";
@ -50,7 +50,7 @@ export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
private visit(node: Syntax): Syntax {
if (canHaveInstanceDeclaration(node)) {
return vistEachChild(node, this.visit.bind(this));
return visitEachChild(node, this.visit.bind(this));
}
if (node.kind === SyntaxKind.InstanceDeclaration) {
const decl = new LetDeclaration(

View file

@ -1,4 +1,5 @@
import { warn } from "console";
import {
SyntaxKind,
Token,

View file

@ -1,5 +1,6 @@
import { warn } from "console";
import { LetDeclaration, Pattern, SourceFile, Syntax, SyntaxKind } from "./cst";
import { MultiMap } from "./util";
import { MultiMap, assertNever } from "./util";
export type NodeWithScope
= SourceFile
@ -96,13 +97,23 @@ export class Scope {
case SyntaxKind.StructDeclaration:
{
this.add(node.name.text, node, Symkind.Type);
this.add(node.name.text, node, Symkind.Var);
// TODO remove this?
// this.add(node.name.text, node, Symkind.Var);
break;
}
case SyntaxKind.LetDeclaration:
{
for (const param of node.params) {
this.scanPattern(param.pattern, param);
switch (param.kind) {
case SyntaxKind.PlainParam:
this.scanPattern(param.pattern, param);
break;
case SyntaxKind.InstanceParam:
this.add(node.name.text, param, Symkind.Var);
break;
default:
assertNever(param);
}
}
if (node === this.node) {
if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) {
@ -116,7 +127,7 @@ export class Scope {
break;
}
default:
throw new Error(`Unexpected ${node.constructor.name}`);
assertNever(node);
}
}

View file

@ -14,6 +14,7 @@ export enum TypeKind {
Nil,
Absent,
Present,
Tag,
}
export abstract class TypeBase {
@ -457,6 +458,40 @@ export class TApp extends TypeBase {
}
export const labelTag = '____tag';
// export class TTag extends TypeBase {
// public readonly kind = TypeKind.Tag;
// public constructor(
// public name: string,
// public node: Syntax | null = null,
// ) {
// super();
// }
// public shallowClone(): Type {
// return new TTag(
// this.name,
// this.node,
// );
// }
// public *getTypeVars(): Iterable<TVar> {
// // noop
// }
// public substitute(sub: TVSub): Type {
// return this;
// }
// public [toStringTag]() {
// return this.name;
// }
// }
export type Type
= TCon
| TArrow
@ -467,11 +502,43 @@ export type Type
| TNil
| TPresent
| TAbsent
// | TTag
export type TVar
= TRegularVar
| TRigidVar
export function getSignature(type: Type): Type[] {
const out = [];
let stack = [ type ];
for (;;) {
const child = stack.pop()!;
if (child.kind === TypeKind.App) {
stack.push(child.left);
stack.push(child.right);
} else {
out.push(child);
}
if (stack.length === 0) {
break;
}
}
return out;
}
export function isSignature(type: Type): boolean {
return type.kind === TypeKind.Con
|| type.kind === TypeKind.App;
}
export function assignableTo(left: Type, right: Type): boolean {
if (left.kind === TypeKind.Con && right.kind == TypeKind.Con) {
return left.id === right.id;
}
return false;
}
export function typesEqual(a: Type, b: Type): boolean {
if (a.kind !== b.kind) {
return false;