Improve diagnostic messages for unification errors
This commit is contained in:
parent
70f9f99181
commit
204d2961e3
2 changed files with 40 additions and 12 deletions
|
@ -2,6 +2,7 @@ import {
|
||||||
Expression,
|
Expression,
|
||||||
LetDeclaration,
|
LetDeclaration,
|
||||||
Pattern,
|
Pattern,
|
||||||
|
ReferenceExpression,
|
||||||
SourceFile,
|
SourceFile,
|
||||||
Syntax,
|
Syntax,
|
||||||
SyntaxKind,
|
SyntaxKind,
|
||||||
|
@ -249,6 +250,23 @@ abstract class ConstraintBase {
|
||||||
|
|
||||||
public abstract substitute(sub: TVSub): Constraint;
|
public abstract substitute(sub: TVSub): Constraint;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CEqual extends ConstraintBase {
|
class CEqual extends ConstraintBase {
|
||||||
|
@ -441,14 +459,16 @@ export class Checker {
|
||||||
return context.returnType;
|
return context.returnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private instantiate(scheme: Scheme): Type {
|
private instantiate(scheme: Scheme, node: Syntax | null): Type {
|
||||||
const sub = new TVSub();
|
const sub = new TVSub();
|
||||||
for (const tv of scheme.tvs) {
|
for (const tv of scheme.tvs) {
|
||||||
sub.set(tv, this.createTypeVar());
|
sub.set(tv, this.createTypeVar());
|
||||||
}
|
}
|
||||||
for (const constraint of scheme.constraints) {
|
for (const constraint of scheme.constraints) {
|
||||||
this.addConstraint(constraint.substitute(sub));
|
const substituted = constraint.substitute(sub);
|
||||||
// TODO keep record of a 'chain' of instantiations so that the diagnostics tool can output it on type error
|
substituted.node = node;
|
||||||
|
substituted.prevInstantiation = constraint;
|
||||||
|
this.addConstraint(substituted);
|
||||||
}
|
}
|
||||||
return scheme.type.substitute(sub);
|
return scheme.type.substitute(sub);
|
||||||
}
|
}
|
||||||
|
@ -568,7 +588,7 @@ export class Checker {
|
||||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.name.text, node.name.name));
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.name.text, node.name.name));
|
||||||
return new TAny();
|
return new TAny();
|
||||||
}
|
}
|
||||||
return this.instantiate(scheme);
|
return this.instantiate(scheme, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
case SyntaxKind.CallExpression:
|
case SyntaxKind.CallExpression:
|
||||||
|
@ -610,7 +630,7 @@ export class Checker {
|
||||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||||
return new TAny();
|
return new TAny();
|
||||||
}
|
}
|
||||||
const type = this.instantiate(scheme);
|
const type = this.instantiate(scheme, node.name);
|
||||||
assert(type.kind === TypeKind.Con);
|
assert(type.kind === TypeKind.Con);
|
||||||
const argTypes = [];
|
const argTypes = [];
|
||||||
for (const element of node.elements) {
|
for (const element of node.elements) {
|
||||||
|
@ -626,7 +646,7 @@ export class Checker {
|
||||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.operator.text, node.operator));
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.operator.text, node.operator));
|
||||||
return new TAny();
|
return new TAny();
|
||||||
}
|
}
|
||||||
const opType = this.instantiate(scheme);
|
const opType = this.instantiate(scheme, node.operator);
|
||||||
const retType = this.createTypeVar();
|
const retType = this.createTypeVar();
|
||||||
const leftType = this.inferExpression(node.left);
|
const leftType = this.inferExpression(node.left);
|
||||||
const rightType = this.inferExpression(node.right);
|
const rightType = this.inferExpression(node.right);
|
||||||
|
@ -658,7 +678,7 @@ export class Checker {
|
||||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||||
return new TAny();
|
return new TAny();
|
||||||
}
|
}
|
||||||
return this.instantiate(scheme);
|
return this.instantiate(scheme, node.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -984,7 +1004,7 @@ export class Checker {
|
||||||
new UnificationFailedDiagnostic(
|
new UnificationFailedDiagnostic(
|
||||||
constraint.left.substitute(solution),
|
constraint.left.substitute(solution),
|
||||||
constraint.right.substitute(solution),
|
constraint.right.substitute(solution),
|
||||||
constraint.node
|
[...constraint.getNodes()],
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,17 +190,25 @@ export class UnificationFailedDiagnostic {
|
||||||
public constructor(
|
public constructor(
|
||||||
public left: Type,
|
public left: Type,
|
||||||
public right: Type,
|
public right: Type,
|
||||||
public node: Syntax,
|
public nodes: Syntax[],
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public format(): string {
|
public format(): string {
|
||||||
const file = this.node.getSourceFile().getFile();
|
const node = this.nodes[0];
|
||||||
return ANSI_FG_RED + ANSI_BOLD + `error: ` + ANSI_RESET
|
const file = node.getSourceFile().getFile();
|
||||||
|
let out = ANSI_FG_RED + ANSI_BOLD + `error: ` + ANSI_RESET
|
||||||
+ `unification of ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET
|
+ `unification of ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET
|
||||||
+ ' and ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ' failed.\n\n'
|
+ ' and ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ' failed.\n\n'
|
||||||
+ printExcerpt(file, this.node.getRange()) + '\n';
|
+ printExcerpt(file, node.getRange()) + '\n';
|
||||||
|
for (let i = 1; i < this.nodes.length; i++) {
|
||||||
|
const node = this.nodes[i];
|
||||||
|
const file = node.getSourceFile().getFile();
|
||||||
|
out += ' ... in an instantiation of the following expression\n\n'
|
||||||
|
out += printExcerpt(file, node.getRange(), { indentation: i === 0 ? ' ' : ' ' }) + '\n';
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue