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,
|
||||
LetDeclaration,
|
||||
Pattern,
|
||||
ReferenceExpression,
|
||||
SourceFile,
|
||||
Syntax,
|
||||
SyntaxKind,
|
||||
|
@ -249,6 +250,23 @@ abstract class ConstraintBase {
|
|||
|
||||
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 {
|
||||
|
@ -441,14 +459,16 @@ export class Checker {
|
|||
return context.returnType;
|
||||
}
|
||||
|
||||
private instantiate(scheme: Scheme): Type {
|
||||
private instantiate(scheme: Scheme, node: Syntax | null): Type {
|
||||
const sub = new TVSub();
|
||||
for (const tv of scheme.tvs) {
|
||||
sub.set(tv, this.createTypeVar());
|
||||
}
|
||||
for (const constraint of scheme.constraints) {
|
||||
this.addConstraint(constraint.substitute(sub));
|
||||
// TODO keep record of a 'chain' of instantiations so that the diagnostics tool can output it on type error
|
||||
const substituted = constraint.substitute(sub);
|
||||
substituted.node = node;
|
||||
substituted.prevInstantiation = constraint;
|
||||
this.addConstraint(substituted);
|
||||
}
|
||||
return scheme.type.substitute(sub);
|
||||
}
|
||||
|
@ -568,7 +588,7 @@ export class Checker {
|
|||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.name.text, node.name.name));
|
||||
return new TAny();
|
||||
}
|
||||
return this.instantiate(scheme);
|
||||
return this.instantiate(scheme, node);
|
||||
}
|
||||
|
||||
case SyntaxKind.CallExpression:
|
||||
|
@ -610,7 +630,7 @@ export class Checker {
|
|||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
return new TAny();
|
||||
}
|
||||
const type = this.instantiate(scheme);
|
||||
const type = this.instantiate(scheme, node.name);
|
||||
assert(type.kind === TypeKind.Con);
|
||||
const argTypes = [];
|
||||
for (const element of node.elements) {
|
||||
|
@ -626,7 +646,7 @@ export class Checker {
|
|||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.operator.text, node.operator));
|
||||
return new TAny();
|
||||
}
|
||||
const opType = this.instantiate(scheme);
|
||||
const opType = this.instantiate(scheme, node.operator);
|
||||
const retType = this.createTypeVar();
|
||||
const leftType = this.inferExpression(node.left);
|
||||
const rightType = this.inferExpression(node.right);
|
||||
|
@ -658,7 +678,7 @@ export class Checker {
|
|||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
return new TAny();
|
||||
}
|
||||
return this.instantiate(scheme);
|
||||
return this.instantiate(scheme, node.name);
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -984,7 +1004,7 @@ export class Checker {
|
|||
new UnificationFailedDiagnostic(
|
||||
constraint.left.substitute(solution),
|
||||
constraint.right.substitute(solution),
|
||||
constraint.node
|
||||
[...constraint.getNodes()],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -190,17 +190,25 @@ export class UnificationFailedDiagnostic {
|
|||
public constructor(
|
||||
public left: Type,
|
||||
public right: Type,
|
||||
public node: Syntax,
|
||||
public nodes: Syntax[],
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public format(): string {
|
||||
const file = this.node.getSourceFile().getFile();
|
||||
return ANSI_FG_RED + ANSI_BOLD + `error: ` + ANSI_RESET
|
||||
const node = this.nodes[0];
|
||||
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
|
||||
+ ' 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