Add support for indenting in type-checker

This commit is contained in:
Sam Vervaeck 2022-09-09 20:18:51 +02:00
parent dac8c9d946
commit 843be74e48
3 changed files with 86 additions and 49 deletions

View file

@ -311,7 +311,6 @@ export type Type
= TCon = TCon
| TArrow | TArrow
| TVar | TVar
| TAny
| TTuple | TTuple
| TLabeled | TLabeled
| TRecord | TRecord

View file

@ -2,7 +2,7 @@
import { describe } from "yargs"; import { describe } from "yargs";
import { TypeKind, type Type, type TArrow, TRecord } from "./checker"; import { TypeKind, type Type, type TArrow, TRecord } from "./checker";
import { Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst"; import { Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
import { countDigits } from "./util"; import { countDigits, IndentWriter } from "./util";
const ANSI_RESET = "\u001b[0m" const ANSI_RESET = "\u001b[0m"
const ANSI_BOLD = "\u001b[1m" const ANSI_BOLD = "\u001b[1m"
@ -48,12 +48,12 @@ export class UnexpectedCharDiagnostic {
} }
public format(): string { public format(out: IndentWriter): void {
const endPos = this.position.clone(); const endPos = this.position.clone();
endPos.advance(this.actual); endPos.advance(this.actual);
return ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
+ `unexpeced character sequence '${this.actual}'.\n\n` out.write(`unexpeced character sequence '${this.actual}'.\n\n`);
+ printExcerpt(this.file, new TextRange(this.position, endPos)) + '\n'; out.write(printExcerpt(this.file, new TextRange(this.position, endPos)) + '\n');
} }
} }
@ -133,10 +133,10 @@ export class UnexpectedTokenDiagnostic {
} }
public format(): string { public format(out: IndentWriter): void {
return ANSI_FG_RED + ANSI_BOLD + 'fatal: ' + ANSI_RESET out.write(ANSI_FG_RED + ANSI_BOLD + 'fatal: ' + ANSI_RESET);
+ `expected ${describeExpected(this.expected)} but got ${describeActual(this.actual)}\n\n` out.write(`expected ${describeExpected(this.expected)} but got ${describeActual(this.actual)}\n\n`);
+ printExcerpt(this.file, this.actual.getRange()) + '\n'; out.write(printExcerpt(this.file, this.actual.getRange()) + '\n');
} }
} }
@ -152,18 +152,16 @@ export class BindingNotFoudDiagnostic {
} }
public format(): string { public format(out: IndentWriter): void {
return ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
+ `binding '${this.name}' was not found.\n\n` out.write(`binding '${this.name}' was not found.\n\n`);
+ printNode(this.node) + '\n'; out.write(printNode(this.node) + '\n');
} }
} }
export function describeType(type: Type): string { export function describeType(type: Type): string {
switch (type.kind) { switch (type.kind) {
case TypeKind.Any:
return 'Any';
case TypeKind.Con: case TypeKind.Con:
{ {
let out = type.displayName; let out = type.displayName;
@ -235,30 +233,33 @@ export class UnificationFailedDiagnostic {
} }
public format(): string { public format(out: IndentWriter): void {
const leftNode = getFirstNodeInTypeChain(this.left); const leftNode = getFirstNodeInTypeChain(this.left);
const rightNode = getFirstNodeInTypeChain(this.right); const rightNode = getFirstNodeInTypeChain(this.right);
const node = this.nodes[0]; const node = this.nodes[0];
let out = ANSI_FG_RED + ANSI_BOLD + `error: ` + ANSI_RESET out.write(ANSI_FG_RED + ANSI_BOLD + `error: ` + ANSI_RESET);
+ `unification of ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET out.write(`unification of ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET);
+ ' and ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ' failed.\n\n' out.write(' and ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ' failed.\n\n');
+ printNode(node) + '\n'; out.write(printNode(node) + '\n');
for (let i = 1; i < this.nodes.length; i++) { for (let i = 1; i < this.nodes.length; i++) {
const node = this.nodes[i]; const node = this.nodes[i];
out += ' ... in an instantiation of the following expression\n\n' out.write(' ... in an instantiation of the following expression\n\n');
out += printNode(node, { indentation: i === 0 ? ' ' : ' ' }) + '\n'; out.write(printNode(node, { indentation: i === 0 ? ' ' : ' ' }) + '\n');
} }
if (leftNode !== null) { if (leftNode !== null) {
out += ANSI_FG_YELLOW + ANSI_BOLD + `info: ` + ANSI_RESET out.indent();
+ `type ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET + ` was inferred from this expression:\n\n` out.write(ANSI_FG_YELLOW + ANSI_BOLD + `info: ` + ANSI_RESET);
+ printNode(leftNode) + '\n'; out.write(`type ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET + ` was inferred from this expression:\n\n`);
out.write(printNode(leftNode) + '\n');
out.dedent();
} }
if (rightNode !== null) { if (rightNode !== null) {
out += ANSI_FG_YELLOW + ANSI_BOLD + `info: ` + ANSI_RESET out.indent();
+ `type ` + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ` was inferred from this expression:\n\n` out.write(ANSI_FG_YELLOW + ANSI_BOLD + `info: ` + ANSI_RESET);
+ printNode(rightNode) + '\n'; out.write(`type ` + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ` was inferred from this expression:\n\n`);
out.write(printNode(rightNode) + '\n');
out.dedent();
} }
return out;
} }
} }
@ -274,13 +275,13 @@ export class ArityMismatchDiagnostic {
} }
public format(): string { public format(out: IndentWriter): void {
return ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
+ ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET out.write(ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET);
+ ` has ${this.left.paramTypes.length} ` out.write(` has ${this.left.paramTypes.length} `);
+ (this.left.paramTypes.length === 1 ? 'parameter' : 'parameters') out.write(this.left.paramTypes.length === 1 ? 'parameter' : 'parameters');
+ ' while ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET out.write(' while ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET);
+ ` has ${this.right.paramTypes.length}.\n\n` out.write(` has ${this.right.paramTypes.length}.\n\n`);
} }
} }
@ -296,14 +297,13 @@ export class FieldMissingDiagnostic {
} }
public format(): string { public format(out: IndentWriter): void {
let out = ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
+ `field '${this.fieldName}' is missing from ` out.write(`field '${this.fieldName}' is missing from `);
+ describeType(this.recordType) + '\n\n'; out.write(describeType(this.recordType) + '\n\n');
if (this.recordType.node !== null) { if (this.recordType.node !== null) {
out += printNode(this.recordType.node) + '\n'; out.write(printNode(this.recordType.node) + '\n');
} }
return out
} }
} }
@ -313,14 +313,15 @@ export class FieldDoesNotExistDiagnostic {
public readonly level = Level.Error; public readonly level = Level.Error;
public constructor( public constructor(
public recordType: TRecord,
public fieldName: string,
) { ) {
} }
public format(): string { public format(out: IndentWriter): void {
return ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
+ `field '${this.fieldName}' does not exist on type ` out.write(`field '${this.fieldName}' does not exist on type `);
+ describeType(this.recordType) + '\n\n'; out.write(describeType(this.recordType) + '\n\n');
} }
} }
@ -364,7 +365,8 @@ export class DiagnosticStore {
export class ConsoleDiagnostics { export class ConsoleDiagnostics {
public add(diagnostic: Diagnostic): void { public add(diagnostic: Diagnostic): void {
process.stderr.write(diagnostic.format()); const writer = new IndentWriter(process.stderr);
diagnostic.format(writer);
} }
} }

View file

@ -1,4 +1,40 @@
import stream from "stream"
export class IndentWriter {
private atBlankLine = true;
private indentLevel = 0;
public constructor(
private output: stream.Writable,
private indentation = ' ',
) {
}
public write(text: string): void {
for (const ch of text) {
if (ch === '\n') {
this.atBlankLine = true;
} else if (!/[\t ]/.test(ch) && this.atBlankLine) {
this.output.write(this.indentation.repeat(this.indentLevel));
this.atBlankLine = false;
}
this.output.write(ch);
}
}
public indent(): void {
this.indentLevel++;
}
public dedent(): void {
this.indentLevel--;
}
}
export function assert(test: boolean): asserts test { export function assert(test: boolean): asserts test {
if (!test) { if (!test) {
throw new Error(`Assertion failed. See the stack trace for more information.`); throw new Error(`Assertion failed. See the stack trace for more information.`);