Add rigid type vars and some other enhancements

- Add TRigidVar as a type
 - Make VarTypeExpression generate a TRigidVar
 - Rename `TVar` to `TUniVar`
 - Make kind checker use `kindOfType` instead of `new KType()`.
This commit is contained in:
Sam Vervaeck 2023-06-26 18:51:48 +02:00
parent 91a4872c34
commit 650cecb707
Signed by: samvv
SSH key fingerprint: SHA256:dIg0ywU1OP+ZYifrYxy8c5esO72cIKB+4/9wkZj1VaY
5 changed files with 135 additions and 73 deletions

4
.vscode/launch.json vendored
View file

@ -11,8 +11,8 @@
"skipFiles": [ "skipFiles": [
"<node_internals>/**" "<node_internals>/**"
], ],
"program": "${workspaceFolder}/lib/bin/bolt.js", "program": "${workspaceFolder}/compiler/lib/bin/bolt.js",
"args": [ "test.bolt" ], "args": [ "compiler/test.bolt" ],
"outputCapture": "std" "outputCapture": "std"
} }
] ]

View file

@ -1,6 +1,6 @@
// TODO support rigid vs free variables // FIXME Something wrong with eager solving unifying a3 ~ a and therefore removing polymorphism
// https://www.reddit.com/r/haskell/comments/d4v83/comment/c0xmc3r/ // TODO Add simplifyType() in instantiate() to fix this
import { import {
ClassDeclaration, ClassDeclaration,
@ -8,9 +8,7 @@ import {
ExprOperator, ExprOperator,
Identifier, Identifier,
IdentifierAlt, IdentifierAlt,
InstanceDeclaration,
LetDeclaration, LetDeclaration,
ModuleDeclaration,
Pattern, Pattern,
ReferenceExpression, ReferenceExpression,
ReferenceTypeExpression, ReferenceTypeExpression,
@ -35,7 +33,7 @@ import {
import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util"; import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util";
import { Analyser } from "./analysis"; import { Analyser } from "./analysis";
import { InspectOptions } from "util"; import { InspectOptions } from "util";
import { TypeKind, TApp, TArrow, TCon, TField, TNil, TNominal, TPresent, TTuple, TVar, TVSet, TVSub, Type, TypeBase, TAbsent } from "./types"; import { TypeKind, TApp, TArrow, TCon, TField, TNil, TNominal, TPresent, TTuple, TUniVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar } from "./types";
import { CClass, CEmpty, CEqual, CMany, Constraint, ConstraintKind, ConstraintSet } from "./constraints"; import { CClass, CEmpty, CEqual, CMany, Constraint, ConstraintKind, ConstraintSet } from "./constraints";
// export class Qual { // export class Qual {
@ -182,7 +180,9 @@ class KArrow extends KindBase {
// TODO actually use these // TODO actually use these
const kindOfTypes = new KType(); const kindOfTypes = new KType();
const kindOfRows = new KRow(); //const kindOfTypes = new KCon('*');
//const kindOfRows = new KCon('r');
//const kindOfConstraints = new KCon();
export type Kind export type Kind
= KType = KType
@ -230,7 +230,7 @@ class Forall extends SchemeBase {
return new Forall(new TVSet, new CEmpty, type); return new Forall(new TVSet, new CEmpty, type);
} }
public static fromArrays(typeVars: TVar[], constraints: Constraint[], type: Type): Forall { public static fromArrays(typeVars: TUniVar[], constraints: Constraint[], type: Type): Forall {
return new Forall(new TVSet(typeVars), new CMany(constraints), type); return new Forall(new TVSet(typeVars), new CMany(constraints), type);
} }
@ -247,7 +247,7 @@ type NodeWithReference
| ReferenceTypeExpression | ReferenceTypeExpression
function validateScheme(scheme: Scheme): void { function validateScheme(scheme: Scheme): void {
const isMonoVar = scheme.type.kind === TypeKind.Var && scheme.typeVars.size === 0; const isMonoVar = scheme.type.kind === TypeKind.UniVar && scheme.typeVars.size === 0;
if (!isMonoVar) { if (!isMonoVar) {
const tvs = new TVSet(scheme.type.getTypeVars()) const tvs = new TVSet(scheme.type.getTypeVars())
for (const tv of tvs) { for (const tv of tvs) {
@ -404,12 +404,12 @@ export class Checker {
private diagnostics: Diagnostics private diagnostics: Diagnostics
) { ) {
this.globalKindEnv.set('Int', new KType()); this.globalKindEnv.set('Int', kindOfTypes);
this.globalKindEnv.set('String', new KType()); this.globalKindEnv.set('String', kindOfTypes);
this.globalKindEnv.set('Bool', new KType()); this.globalKindEnv.set('Bool', kindOfTypes);
const a = new TVar(this.typeVarIds.next().value!); const a = new TUniVar(this.typeVarIds.next().value!);
const b = new TVar(this.typeVarIds.next().value!); const b = new TUniVar(this.typeVarIds.next().value!);
this.globalTypeEnv.add('$', Forall.fromArrays([ a, b ], [], new TArrow(new TArrow(new TArrow(a, b), a), b)), Symkind.Var); this.globalTypeEnv.add('$', Forall.fromArrays([ a, b ], [], new TArrow(new TArrow(new TArrow(a, b), a), b)), Symkind.Var);
this.globalTypeEnv.add('String', Forall.fromArrays([], [], this.stringType), Symkind.Type); this.globalTypeEnv.add('String', Forall.fromArrays([], [], this.stringType), Symkind.Type);
@ -442,12 +442,18 @@ export class Checker {
return new TCon(this.nextConTypeId++, types, name); return new TCon(this.nextConTypeId++, types, name);
} }
private createTypeVar(node: Syntax | null = null): TVar { private createTypeVar(node: Syntax | null = null): TUniVar {
const typeVar = new TVar(this.typeVarIds.next().value!, node); const typeVar = new TUniVar(this.typeVarIds.next().value!, node);
this.getContext().typeVars.add(typeVar); this.getContext().typeVars.add(typeVar);
return typeVar; return typeVar;
} }
private createRigidVar(displayName: string, node: Syntax | null = null): TRigidVar {
const tv = new TRigidVar(this.typeVarIds.next().value!, displayName, node);
this.getContext().typeVars.add(tv);
return tv;
}
public getContext(): InferContext { public getContext(): InferContext {
return this.contexts[this.contexts.length-1]; return this.contexts[this.contexts.length-1];
} }
@ -475,7 +481,7 @@ export class Checker {
let maxLevelRight = global; let maxLevelRight = global;
for (let i = this.contexts.length; i-- > 0;) { for (let i = this.contexts.length; i-- > 0;) {
const ctx = this.contexts[i]; const ctx = this.contexts[i];
if (hasTypeVar(ctx.typeVars, constraint.left)) { if (hasTypeVar(ctx.typeVars, constraint.right)) {
maxLevelRight = i; maxLevelRight = i;
break; break;
} }
@ -590,7 +596,7 @@ export class Checker {
} }
} }
private lookup(node: NodeWithReference, expectedKind: Symkind): Scheme | null { private lookup(node: NodeWithReference, expectedKind: Symkind, enableDiagnostics = true): Scheme | null {
const [modulePath, name] = splitReferences(node); const [modulePath, name] = splitReferences(node);
if (modulePath.length > 0) { if (modulePath.length > 0) {
let maxIndex = 0; let maxIndex = 0;
@ -602,12 +608,14 @@ export class Checker {
const nextDown = currDown.resolveModule(moduleName.text); const nextDown = currDown.resolveModule(moduleName.text);
if (nextDown === null) { if (nextDown === null) {
if (currUp.kind === SyntaxKind.SourceFile) { if (currUp.kind === SyntaxKind.SourceFile) {
this.diagnostics.add( if (enableDiagnostics) {
new ModuleNotFoundDiagnostic( this.diagnostics.add(
modulePath.slice(maxIndex).map(id => id.text), new ModuleNotFoundDiagnostic(
modulePath[maxIndex], modulePath.slice(maxIndex).map(id => id.text),
) modulePath[maxIndex],
); )
);
}
return null; return null;
} }
currUp = currUp.getEnclosingModule(); currUp = currUp.getEnclosingModule();
@ -620,13 +628,15 @@ export class Checker {
if (found !== null) { if (found !== null) {
return found; return found;
} }
this.diagnostics.add( if (enableDiagnostics) {
new BindingNotFoundDiagnostic( this.diagnostics.add(
modulePath.map(id => id.text), new BindingNotFoundDiagnostic(
name.text, modulePath.map(id => id.text),
name, name.text,
) name,
); )
);
}
return null; return null;
} }
} else { } else {
@ -638,13 +648,15 @@ export class Checker {
} }
curr = curr.parent; curr = curr.parent;
} while(curr !== null); } while(curr !== null);
this.diagnostics.add( if (enableDiagnostics) {
new BindingNotFoundDiagnostic( this.diagnostics.add(
[], new BindingNotFoundDiagnostic(
name.text, [],
name, name.text,
) name,
); )
);
}
return null; return null;
} }
} }
@ -737,7 +749,7 @@ export class Checker {
case SyntaxKind.ForallTypeExpression: case SyntaxKind.ForallTypeExpression:
{ {
// TODO we currently automatically introduce type variables but maybe we should use the Forall? // TODO we currently automatically introduce type variables but maybe we should use the ForallTypeExpression?
kind = this.inferKindFromTypeExpression(node.typeExpr, env); kind = this.inferKindFromTypeExpression(node.typeExpr, env);
break; break;
} }
@ -858,7 +870,7 @@ export class Checker {
case SyntaxKind.TypeDeclaration: case SyntaxKind.TypeDeclaration:
{ {
const innerEnv = new KindEnv(env); const innerEnv = new KindEnv(env);
let kind: Kind = new KType(); let kind: Kind = kindOfTypes;
for (let i = node.varExps.length-1; i >= 0; i--) { for (let i = node.varExps.length-1; i >= 0; i--) {
const varExpr = node.varExps[i]; const varExpr = node.varExps[i];
const paramKind = this.createKindVar(); const paramKind = this.createKindVar();
@ -905,12 +917,12 @@ export class Checker {
if (node.constraintClause !== null) { if (node.constraintClause !== null) {
for (const constraint of node.constraintClause.constraints) { for (const constraint of node.constraintClause.constraints) {
for (const typeExpr of constraint.types) { for (const typeExpr of constraint.types) {
this.unifyKind(this.inferKindFromTypeExpression(typeExpr, env), new KType(), typeExpr); this.unifyKind(this.inferKindFromTypeExpression(typeExpr, env), kindOfTypes, typeExpr);
} }
} }
} }
for (const typeExpr of node.types) { for (const typeExpr of node.types) {
this.unifyKind(this.inferKindFromTypeExpression(typeExpr, env), new KType(), typeExpr); this.unifyKind(this.inferKindFromTypeExpression(typeExpr, env), kindOfTypes, typeExpr);
} }
for (const element of node.elements) { for (const element of node.elements) {
this.inferKind(element, env); this.inferKind(element, env);
@ -930,7 +942,7 @@ export class Checker {
{ {
const declKind = env.lookup(node.name.text)!; const declKind = env.lookup(node.name.text)!;
const innerEnv = new KindEnv(env); const innerEnv = new KindEnv(env);
let kind: Kind = new KType(); let kind: Kind = kindOfTypes;
for (let i = node.varExps.length-1; i >= 0; i--) { for (let i = node.varExps.length-1; i >= 0; i--) {
const varExpr = node.varExps[i]; const varExpr = node.varExps[i];
const paramKind = this.createKindVar(); const paramKind = this.createKindVar();
@ -940,7 +952,7 @@ export class Checker {
this.unifyKind(declKind, kind, node); this.unifyKind(declKind, kind, node);
if (node.fields !== null) { if (node.fields !== null) {
for (const field of node.fields) { for (const field of node.fields) {
this.unifyKind(this.inferKindFromTypeExpression(field.typeExpr, innerEnv), new KType(), field.typeExpr); this.unifyKind(this.inferKindFromTypeExpression(field.typeExpr, innerEnv), kindOfTypes, field.typeExpr);
} }
} }
break; break;
@ -950,7 +962,7 @@ export class Checker {
{ {
const declKind = env.lookup(node.name.text)!; const declKind = env.lookup(node.name.text)!;
const innerEnv = new KindEnv(env); const innerEnv = new KindEnv(env);
let kind: Kind = new KType(); let kind: Kind = kindOfTypes;
// FIXME should I go from right to left or left to right? // FIXME should I go from right to left or left to right?
for (let i = node.varExps.length-1; i >= 0; i--) { for (let i = node.varExps.length-1; i >= 0; i--) {
const varExpr = node.varExps[i]; const varExpr = node.varExps[i];
@ -965,14 +977,14 @@ export class Checker {
case SyntaxKind.EnumDeclarationTupleElement: case SyntaxKind.EnumDeclarationTupleElement:
{ {
for (const element of member.elements) { for (const element of member.elements) {
this.unifyKind(this.inferKindFromTypeExpression(element, innerEnv), new KType(), element); this.unifyKind(this.inferKindFromTypeExpression(element, innerEnv), kindOfTypes, element);
} }
break; break;
} }
case SyntaxKind.EnumDeclarationStructElement: case SyntaxKind.EnumDeclarationStructElement:
{ {
for (const field of member.fields) { for (const field of member.fields) {
this.unifyKind(this.inferKindFromTypeExpression(field.typeExpr, innerEnv), new KType(), field.typeExpr); this.unifyKind(this.inferKindFromTypeExpression(field.typeExpr, innerEnv), kindOfTypes, field.typeExpr);
} }
break; break;
} }
@ -987,7 +999,7 @@ export class Checker {
case SyntaxKind.LetDeclaration: case SyntaxKind.LetDeclaration:
{ {
if (node.typeAssert !== null) { if (node.typeAssert !== null) {
this.unifyKind(this.inferKindFromTypeExpression(node.typeAssert.typeExpression, env), new KType(), node.typeAssert.typeExpression); this.unifyKind(this.inferKindFromTypeExpression(node.typeAssert.typeExpression, env), kindOfTypes, node.typeAssert.typeExpression);
} }
if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) { if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) {
const innerEnv = new KindEnv(env); const innerEnv = new KindEnv(env);
@ -1123,7 +1135,7 @@ export class Checker {
const context = node.context!; const context = node.context!;
const returnType = context.returnType!; const returnType = context.returnType!;
this.contexts.push(context); this.pushContext(context);
if (node.body !== null) { if (node.body !== null) {
switch (node.body.kind) { switch (node.body.kind) {
@ -1148,7 +1160,7 @@ export class Checker {
} }
} }
this.contexts.pop(); this.popContext(context);
node.activeCycle = false; node.activeCycle = false;
} else { } else {
@ -1278,7 +1290,7 @@ export class Checker {
} }
const scheme = this.lookup(node, Symkind.Var); const scheme = this.lookup(node, Symkind.Var);
if (scheme === null) { if (scheme === null) {
// this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name)); //this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
return this.createTypeVar(); return this.createTypeVar();
} }
const type = this.instantiate(scheme, node); const type = this.instantiate(scheme, node);
@ -1439,12 +1451,12 @@ export class Checker {
case SyntaxKind.VarTypeExpression: case SyntaxKind.VarTypeExpression:
{ {
const scheme = this.lookup(node.name, Symkind.Type); const scheme = this.lookup(node.name, Symkind.Type, !introduceTypeVars);
if (scheme === null) { if (scheme === null) {
if (!introduceTypeVars) { if (!introduceTypeVars) {
this.diagnostics.add(new BindingNotFoundDiagnostic([], node.name.text, node.name)); this.diagnostics.add(new BindingNotFoundDiagnostic([], node.name.text, node.name));
} }
type = this.createTypeVar(); type = this.createRigidVar(node.name.text, node);
// TODO if !introduceTypeVars: re-emit a 'var not found' whenever the same var is encountered // TODO if !introduceTypeVars: re-emit a 'var not found' whenever the same var is encountered
this.addBinding(node.name.text, Forall.mono(type), Symkind.Type); this.addBinding(node.name.text, Forall.mono(type), Symkind.Type);
} else { } else {
@ -1852,7 +1864,7 @@ export class Checker {
}; };
node.context = innerCtx; node.context = innerCtx;
this.contexts.push(innerCtx); this.pushContext(innerCtx);
const returnType = this.createTypeVar(); const returnType = this.createTypeVar();
innerCtx.returnType = returnType; innerCtx.returnType = returnType;
@ -1919,7 +1931,7 @@ export class Checker {
private maxTypeErrorCount = 5; private maxTypeErrorCount = 5;
private find(type: Type): Type { private find(type: Type): Type {
while (type.kind === TypeKind.Var && this.solution.has(type)) { while (type.kind === TypeKind.UniVar && this.solution.has(type)) {
type = this.solution.get(type)!; type = this.solution.get(type)!;
} }
return type; return type;
@ -1955,18 +1967,24 @@ export class Checker {
private unify(left: Type, right: Type, enableDiagnostics: boolean): boolean { 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}`);
left = this.find(left); left = this.find(left);
right = this.find(right); right = this.find(right);
// 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}`);
const swap = () => { [right, left] = [left, right]; } const swap = () => { [right, left] = [left, right]; }
if (left.kind !== TypeKind.Var && right.kind === TypeKind.Var) { if (left.kind === TypeKind.RigidVar && right.kind === TypeKind.RigidVar) {
if (left.id === right.id) {
return true;
}
}
if (left.kind !== TypeKind.UniVar && right.kind === TypeKind.UniVar) {
swap(); swap();
} }
if (left.kind === TypeKind.Var) { if (left.kind === TypeKind.UniVar) {
// Perform an occurs check, verifying whether left occurs // Perform an occurs check, verifying whether left occurs
// somewhere inside the structure of right. If so, unification // somewhere inside the structure of right. If so, unification
@ -2099,7 +2117,7 @@ export class Checker {
return success; return success;
} }
let success = true; let success = true;
const newRestType = new TVar(this.typeVarIds.next().value!); const newRestType = new TUniVar(this.typeVarIds.next().value!);
if (!this.unify(left.restType, new TField(right.name, right.type, newRestType), enableDiagnostics)) { if (!this.unify(left.restType, new TField(right.name, right.type, newRestType), enableDiagnostics)) {
success = false; success = false;
} }

View file

@ -1,7 +1,7 @@
import { InspectOptions } from "util"; import { InspectOptions } from "util";
import { Syntax } from "./cst" import { Syntax } from "./cst"
import { TVar, TVSub, Type } from "./types"; import { TVSub, TVar, Type } from "./types";
import { first, InspectFn, last, toStringTag } from "./util"; import { first, InspectFn, last, toStringTag } from "./util";
export const enum ConstraintKind { export const enum ConstraintKind {

View file

@ -501,8 +501,10 @@ export function describeType(type: Type): string {
} }
return out; return out;
} }
case TypeKind.Var: case TypeKind.UniVar:
return 'a' + type.id; return 'a' + type.id;
case TypeKind.RigidVar:
return type.displayName;
case TypeKind.Arrow: case TypeKind.Arrow:
{ {
return describeType(type.paramType) + ' -> ' + describeType(type.returnType); return describeType(type.paramType) + ' -> ' + describeType(type.returnType);
@ -564,7 +566,7 @@ function describeKind(kind: Kind): string {
} }
function getFirstNodeInTypeChain(type: Type): Syntax | null { function getFirstNodeInTypeChain(type: Type): Syntax | null {
while (type !== type && (type.kind === TypeKind.Var || type.node === null)) { while (type !== type && (type.kind === TypeKind.UniVar || type.node === null)) {
type = type.next; type = type.next;
} }
return type.node; return type.node;

View file

@ -4,7 +4,8 @@ import { deserializable, ignore, InspectFn, toStringTag } from "./util";
export enum TypeKind { export enum TypeKind {
Arrow, Arrow,
Var, UniVar,
RigidVar,
Con, Con,
Tuple, Tuple,
App, App,
@ -37,7 +38,7 @@ export abstract class TypeBase {
public abstract substitute(sub: TVSub): Type; public abstract substitute(sub: TVSub): Type;
public hasTypeVar(tv: TVar): boolean { public hasTypeVar(tv: TUniVar): boolean {
for (const other of this.getTypeVars()) { for (const other of this.getTypeVars()) {
if (tv.id === other.id) { if (tv.id === other.id) {
return true; return true;
@ -56,12 +57,49 @@ export function isType(value: any): value is Type {
&& value instanceof TypeBase; && value instanceof TypeBase;
} }
@deserializable() export class TRigidVar extends TypeBase {
export class TVar extends TypeBase {
public readonly kind = TypeKind.Var; public readonly kind = TypeKind.RigidVar;
public constructor(
public id: number,
public displayName: string,
public node: Syntax | null = null
) {
super();
}
public *getTypeVars(): Iterable<TVar> {
yield this;
}
public shallowClone(): TRigidVar {
return new TRigidVar(
this.id,
this.displayName,
this.node
);
}
public substitute(sub: TVSub): Type {
const other = sub.get(this);
return other === undefined
? this : other.substitute(sub);
}
public [toStringTag]() {
return this.displayName;
}
}
@deserializable()
export class TUniVar extends TypeBase {
public readonly kind = TypeKind.UniVar;
@ignore @ignore
public context = new Set<ClassDeclaration>(); public context = new Set<ClassDeclaration>();
public constructor( public constructor(
@ -75,8 +113,8 @@ export class TVar extends TypeBase {
yield this; yield this;
} }
public shallowClone(): TVar { public shallowClone(): TUniVar {
return new TVar(this.id, this.node); return new TUniVar(this.id, this.node);
} }
public substitute(sub: TVSub): Type { public substitute(sub: TVSub): Type {
@ -475,7 +513,8 @@ export class TNominal extends TypeBase {
export type Type export type Type
= TCon = TCon
| TArrow | TArrow
| TVar | TRigidVar
| TUniVar
| TTuple | TTuple
| TApp | TApp
| TNominal | TNominal
@ -484,6 +523,9 @@ export type Type
| TPresent | TPresent
| TAbsent | TAbsent
export type TVar
= TUniVar
| TRigidVar
export class TVSet { export class TVSet {