Multiple updates to the kind inferencer
- Clean up some code - Ensure more syntax is covered during kind inference - Make wrongly kinded types not trigger another typing error in the next layer
This commit is contained in:
parent
4939ee5e08
commit
9221441aa4
2 changed files with 130 additions and 92 deletions
210
src/checker.ts
210
src/checker.ts
|
@ -425,7 +425,13 @@ class KVSub {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enum KindFlags {
|
||||||
|
UnificationFailed = 1,
|
||||||
|
}
|
||||||
|
|
||||||
abstract class KindBase {
|
abstract class KindBase {
|
||||||
|
|
||||||
|
public flags: KindFlags = 0;
|
||||||
|
|
||||||
public abstract readonly type: KindType;
|
public abstract readonly type: KindType;
|
||||||
|
|
||||||
|
@ -449,6 +455,10 @@ class KVar extends KindBase {
|
||||||
? this : other.substitute(sub);
|
? this : other.substitute(sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasFailed(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class KStar extends KindBase {
|
class KStar extends KindBase {
|
||||||
|
@ -459,6 +469,10 @@ class KStar extends KindBase {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasFailed(): boolean {
|
||||||
|
return (this.flags & KindFlags.UnificationFailed) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class KArrow extends KindBase {
|
class KArrow extends KindBase {
|
||||||
|
@ -472,6 +486,12 @@ class KArrow extends KindBase {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasFailed(): boolean {
|
||||||
|
return this.flags & KindFlags.UnificationFailed
|
||||||
|
|| this.left.hasFailed()
|
||||||
|
|| this.right.hasFailed();
|
||||||
|
}
|
||||||
|
|
||||||
public substitute(sub: KVSub): Kind {
|
public substitute(sub: KVSub): Kind {
|
||||||
return new KArrow(
|
return new KArrow(
|
||||||
this.left.substitute(sub),
|
this.left.substitute(sub),
|
||||||
|
@ -819,6 +839,7 @@ export class Checker {
|
||||||
}
|
}
|
||||||
|
|
||||||
private inferKindFromTypeExpression(node: TypeExpression, env: KindEnv): Kind {
|
private inferKindFromTypeExpression(node: TypeExpression, env: KindEnv): Kind {
|
||||||
|
let kind: Kind;
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case SyntaxKind.ArrowTypeExpression:
|
case SyntaxKind.ArrowTypeExpression:
|
||||||
{
|
{
|
||||||
|
@ -826,34 +847,40 @@ export class Checker {
|
||||||
this.unifyKind(this.inferKindFromTypeExpression(param, env), new KStar(), node);
|
this.unifyKind(this.inferKindFromTypeExpression(param, env), new KStar(), node);
|
||||||
}
|
}
|
||||||
this.unifyKind(this.inferKindFromTypeExpression(node.returnTypeExpr, env), new KStar(), node);
|
this.unifyKind(this.inferKindFromTypeExpression(node.returnTypeExpr, env), new KStar(), node);
|
||||||
return new KStar();
|
kind = new KStar();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case SyntaxKind.VarTypeExpression:
|
case SyntaxKind.VarTypeExpression:
|
||||||
case SyntaxKind.ReferenceTypeExpression:
|
case SyntaxKind.ReferenceTypeExpression:
|
||||||
{
|
{
|
||||||
const kind = env.lookup(node.name.text);
|
const matchedKind = env.lookup(node.name.text);
|
||||||
if (kind === null) {
|
if (matchedKind === null) {
|
||||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||||
// Create a filler kind variable that still will be able to catch other errors.
|
// Create a filler kind variable that still will be able to catch other errors.
|
||||||
return this.createKindVar();
|
kind = this.createKindVar();
|
||||||
|
} else {
|
||||||
|
kind = matchedKind;
|
||||||
}
|
}
|
||||||
return kind;
|
break;
|
||||||
}
|
}
|
||||||
case SyntaxKind.AppTypeExpression:
|
case SyntaxKind.AppTypeExpression:
|
||||||
{
|
{
|
||||||
let result = this.inferKindFromTypeExpression(node.operator, env);
|
kind = this.inferKindFromTypeExpression(node.operator, env);
|
||||||
for (const arg of node.args) {
|
for (const arg of node.args) {
|
||||||
result = this.applyKind(result, this.inferKindFromTypeExpression(arg, env), node);
|
kind = this.applyKind(kind, this.inferKindFromTypeExpression(arg, env), node);
|
||||||
}
|
}
|
||||||
return result;
|
break;
|
||||||
}
|
}
|
||||||
case SyntaxKind.NestedTypeExpression:
|
case SyntaxKind.NestedTypeExpression:
|
||||||
{
|
{
|
||||||
return this.inferKindFromTypeExpression(node.typeExpr, env);
|
kind = this.inferKindFromTypeExpression(node.typeExpr, env);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unexpected ${node}`);
|
throw new Error(`Unexpected ${node}`);
|
||||||
}
|
}
|
||||||
|
node.inferredKind = kind;
|
||||||
|
return kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createKindVar(): KVar {
|
private createKindVar(): KVar {
|
||||||
|
@ -907,7 +934,19 @@ export class Checker {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SyntaxKind.TypeDeclaration:
|
||||||
|
{
|
||||||
|
const innerEnv = new KindEnv(env);
|
||||||
|
let kind: Kind = new KStar();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
env.setNamed(node.name.text, this.inferKindFromTypeExpression(node.typeExpression, innerEnv));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case SyntaxKind.StructDeclaration:
|
case SyntaxKind.StructDeclaration:
|
||||||
{
|
{
|
||||||
env.setNamed(node.name.text, this.createKindVar());
|
env.setNamed(node.name.text, this.createKindVar());
|
||||||
|
@ -979,7 +1018,15 @@ export class Checker {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// TODO
|
case SyntaxKind.EnumDeclarationStructElement:
|
||||||
|
{
|
||||||
|
for (const field of member.fields) {
|
||||||
|
this.unifyKind(this.inferKindFromTypeExpression(field.typeExpr, innerEnv), new KStar(), field.typeExpr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected ${member}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1037,6 +1084,8 @@ export class Checker {
|
||||||
&& this.unifyKind(a.right, b.right, node);
|
&& this.unifyKind(a.right, b.right, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.flags |= KindFlags.UnificationFailed;
|
||||||
|
b.flags |= KindFlags.UnificationFailed;
|
||||||
this.diagnostics.add(new KindMismatchDiagnostic(solve(a), solve(b), node));
|
this.diagnostics.add(new KindMismatchDiagnostic(solve(a), solve(b), node));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1166,7 +1215,7 @@ export class Checker {
|
||||||
const scope = node.getScope();
|
const scope = node.getScope();
|
||||||
const target = scope.lookup(node.name.text);
|
const target = scope.lookup(node.name.text);
|
||||||
if (target !== null && target.kind === SyntaxKind.LetDeclaration && target.active) {
|
if (target !== null && target.kind === SyntaxKind.LetDeclaration && target.active) {
|
||||||
return target.type!;
|
return target.inferredType!;
|
||||||
}
|
}
|
||||||
const scheme = this.lookup(node.name.text, Symkind.Var);
|
const scheme = this.lookup(node.name.text, Symkind.Var);
|
||||||
if (scheme === null) {
|
if (scheme === null) {
|
||||||
|
@ -1229,31 +1278,6 @@ export class Checker {
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SyntaxKind.NamedTupleExpression:
|
|
||||||
{
|
|
||||||
// TODO Only lookup constructors and skip other bindings
|
|
||||||
const scheme = this.lookup(node.name.text, Symkind.Var);
|
|
||||||
if (scheme === null) {
|
|
||||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
|
||||||
return this.createTypeVar();
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SyntaxKind.StructExpression:
|
case SyntaxKind.StructExpression:
|
||||||
{
|
{
|
||||||
const fields = new Map();
|
const fields = new Map();
|
||||||
|
@ -1314,63 +1338,80 @@ export class Checker {
|
||||||
|
|
||||||
public inferTypeExpression(node: TypeExpression, introduceTypeVars = false): Type {
|
public inferTypeExpression(node: TypeExpression, introduceTypeVars = false): Type {
|
||||||
|
|
||||||
switch (node.kind) {
|
let type;
|
||||||
|
|
||||||
case SyntaxKind.ReferenceTypeExpression:
|
if (node.inferredKind!.substitute(this.kindSolution).hasFailed()) {
|
||||||
{
|
|
||||||
const scheme = this.lookup(node.name.text, Symkind.Type);
|
|
||||||
if (scheme === null) {
|
|
||||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
|
||||||
return this.createTypeVar();
|
|
||||||
}
|
|
||||||
const type = this.instantiate(scheme, node.name);
|
|
||||||
// FIXME it is not guaranteed that `type` is copied, so the original type might get mutated
|
|
||||||
type.node = node;
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SyntaxKind.NestedTypeExpression:
|
type = this.createTypeVar();
|
||||||
return this.inferTypeExpression(node.typeExpr, introduceTypeVars);
|
|
||||||
|
|
||||||
case SyntaxKind.VarTypeExpression:
|
} else {
|
||||||
{
|
|
||||||
const scheme = this.lookup(node.name.text, Symkind.Type);
|
switch (node.kind) {
|
||||||
if (scheme === null) {
|
|
||||||
if (!introduceTypeVars) {
|
case SyntaxKind.ReferenceTypeExpression:
|
||||||
|
{
|
||||||
|
const scheme = this.lookup(node.name.text, Symkind.Type);
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
const type = this.createTypeVar();
|
type = this.instantiate(scheme, node.name);
|
||||||
this.addBinding(node.name.text, new Forall([], [], type), Symkind.Type);
|
// FIXME it is not guaranteed that `type` is copied, so the original type might get mutated
|
||||||
return type;
|
type.node = node;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
assert(isEmpty(scheme.typeVars));
|
|
||||||
assert(isEmpty(scheme.constraints));
|
|
||||||
return scheme.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SyntaxKind.AppTypeExpression:
|
case SyntaxKind.NestedTypeExpression:
|
||||||
{
|
return this.inferTypeExpression(node.typeExpr, introduceTypeVars);
|
||||||
return TApp.build(
|
|
||||||
this.inferTypeExpression(node.operator, introduceTypeVars),
|
|
||||||
node.args.map(arg => this.inferTypeExpression(arg, introduceTypeVars)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
case SyntaxKind.ArrowTypeExpression:
|
case SyntaxKind.VarTypeExpression:
|
||||||
{
|
{
|
||||||
const paramTypes = [];
|
const scheme = this.lookup(node.name.text, Symkind.Type);
|
||||||
for (const paramTypeExpr of node.paramTypeExprs) {
|
if (scheme === null) {
|
||||||
paramTypes.push(this.inferTypeExpression(paramTypeExpr, introduceTypeVars));
|
if (!introduceTypeVars) {
|
||||||
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||||
|
}
|
||||||
|
type = this.createTypeVar();
|
||||||
|
this.addBinding(node.name.text, new Forall([], [], type), Symkind.Type);
|
||||||
|
} else {
|
||||||
|
assert(isEmpty(scheme.typeVars));
|
||||||
|
assert(isEmpty(scheme.constraints));
|
||||||
|
type = scheme.type;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
const returnType = this.inferTypeExpression(node.returnTypeExpr, introduceTypeVars);
|
|
||||||
return new TArrow(paramTypes, returnType, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
case SyntaxKind.AppTypeExpression:
|
||||||
throw new Error(`Unrecognised ${node}`);
|
{
|
||||||
|
type = TApp.build(
|
||||||
|
this.inferTypeExpression(node.operator, introduceTypeVars),
|
||||||
|
node.args.map(arg => this.inferTypeExpression(arg, introduceTypeVars)),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SyntaxKind.ArrowTypeExpression:
|
||||||
|
{
|
||||||
|
const paramTypes = [];
|
||||||
|
for (const paramTypeExpr of node.paramTypeExprs) {
|
||||||
|
paramTypes.push(this.inferTypeExpression(paramTypeExpr, introduceTypeVars));
|
||||||
|
}
|
||||||
|
const returnType = this.inferTypeExpression(node.returnTypeExpr, introduceTypeVars);
|
||||||
|
type = new TArrow(paramTypes, returnType, node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unrecognised ${node}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.inferredType = type;
|
||||||
|
|
||||||
|
return type;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public inferBindings(pattern: Pattern, typeVars: Iterable<TVar>, constraints: Iterable<Constraint>): Type {
|
public inferBindings(pattern: Pattern, typeVars: Iterable<TVar>, constraints: Iterable<Constraint>): Type {
|
||||||
|
@ -1568,15 +1609,14 @@ export class Checker {
|
||||||
}
|
}
|
||||||
const fields = new Map<string, Type>();
|
const fields = new Map<string, Type>();
|
||||||
if (node.fields !== null) {
|
if (node.fields !== null) {
|
||||||
for (const member of node.fields) {
|
for (const field of node.fields) {
|
||||||
fields.set(member.name.text, this.inferTypeExpression(member.typeExpr));
|
fields.set(field.name.text, this.inferTypeExpression(field.typeExpr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.popContext(context);
|
this.popContext(context);
|
||||||
const type = new TNominal(node);
|
const type = new TNominal(node);
|
||||||
parentEnv.add(node.name.text, new Forall(typeVars, constraints, type), Symkind.Type);
|
parentEnv.add(node.name.text, new Forall(typeVars, constraints, type), Symkind.Type);
|
||||||
parentEnv.add(node.name.text, new Forall(typeVars, constraints, new TArrow([ new TRecord(fields, node) ], TApp.build(type, kindArgs))), Symkind.Var);
|
parentEnv.add(node.name.text, new Forall(typeVars, constraints, new TArrow([ new TRecord(fields, node) ], TApp.build(type, kindArgs))), Symkind.Var);
|
||||||
//node.scheme = new Forall(typeVars, constraints, );
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1679,7 +1719,7 @@ export class Checker {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
node.type = type;
|
node.inferredType = type;
|
||||||
|
|
||||||
this.contexts.pop();
|
this.contexts.pop();
|
||||||
|
|
||||||
|
|
12
src/cst.ts
12
src/cst.ts
|
@ -1,5 +1,5 @@
|
||||||
import { JSONObject, JSONValue, MultiMap } from "./util";
|
import { JSONObject, JSONValue, MultiMap } from "./util";
|
||||||
import type { InferContext, Scheme, Type, TypeEnv } from "./checker"
|
import type { InferContext, Kind, Scheme, Type, TypeEnv } from "./checker"
|
||||||
|
|
||||||
export type TextSpan = [number, number];
|
export type TextSpan = [number, number];
|
||||||
|
|
||||||
|
@ -351,12 +351,12 @@ export class Scope {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class SyntaxBase {
|
abstract class SyntaxBase {
|
||||||
|
|
||||||
public parent: Syntax | null = null;
|
public parent: Syntax | null = null;
|
||||||
|
|
||||||
public abstract readonly kind: SyntaxKind;
|
public inferredKind?: Kind;
|
||||||
|
public inferredType?: Type;
|
||||||
|
|
||||||
public abstract getFirstToken(): Token;
|
public abstract getFirstToken(): Token;
|
||||||
|
|
||||||
|
@ -1826,7 +1826,6 @@ export class StructDeclaration extends SyntaxBase {
|
||||||
public readonly kind = SyntaxKind.StructDeclaration;
|
public readonly kind = SyntaxKind.StructDeclaration;
|
||||||
|
|
||||||
public typeEnv?: TypeEnv;
|
public typeEnv?: TypeEnv;
|
||||||
public scheme?: Scheme;
|
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public pubKeyword: PubKeyword | null,
|
public pubKeyword: PubKeyword | null,
|
||||||
|
@ -1954,7 +1953,6 @@ export class TypeDeclaration extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.TypeDeclaration;
|
public readonly kind = SyntaxKind.TypeDeclaration;
|
||||||
|
|
||||||
public scheme?: Scheme;
|
|
||||||
public typeEnv?: TypeEnv;
|
public typeEnv?: TypeEnv;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
|
@ -1986,9 +1984,9 @@ export class LetDeclaration extends SyntaxBase {
|
||||||
public readonly kind = SyntaxKind.LetDeclaration;
|
public readonly kind = SyntaxKind.LetDeclaration;
|
||||||
|
|
||||||
public scope?: Scope;
|
public scope?: Scope;
|
||||||
public type?: Type;
|
|
||||||
public active?: boolean;
|
|
||||||
public typeEnv?: TypeEnv;
|
public typeEnv?: TypeEnv;
|
||||||
|
|
||||||
|
public active?: boolean;
|
||||||
public context?: InferContext;
|
public context?: InferContext;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
|
|
Loading…
Reference in a new issue