Minor improvements, add some experimental type class logic and refactor diagnostics
This commit is contained in:
parent
df5f857905
commit
c559d13df9
8 changed files with 658 additions and 366 deletions
287
src/checker.ts
287
src/checker.ts
|
@ -1,3 +1,7 @@
|
||||||
|
|
||||||
|
// TODO support rigid vs free variables
|
||||||
|
// https://www.reddit.com/r/haskell/comments/d4v83/comment/c0xmc3r/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ClassDeclaration,
|
ClassDeclaration,
|
||||||
EnumDeclaration,
|
EnumDeclaration,
|
||||||
|
@ -23,14 +27,14 @@ import {
|
||||||
describeType,
|
describeType,
|
||||||
BindingNotFoundDiagnostic,
|
BindingNotFoundDiagnostic,
|
||||||
Diagnostics,
|
Diagnostics,
|
||||||
FieldMissingDiagnostic,
|
FieldNotFoundDiagnostic,
|
||||||
UnificationFailedDiagnostic,
|
TypeMismatchDiagnostic,
|
||||||
KindMismatchDiagnostic,
|
KindMismatchDiagnostic,
|
||||||
ModuleNotFoundDiagnostic,
|
ModuleNotFoundDiagnostic,
|
||||||
TypeclassNotFoundDiagnostic,
|
TypeclassNotFoundDiagnostic,
|
||||||
FieldTypeMismatchDiagnostic,
|
TypeclassDeclaredTwiceDiagnostic,
|
||||||
} from "./diagnostics";
|
} from "./diagnostics";
|
||||||
import { assert, assertNever, first, isEmpty, last, MultiMap } from "./util";
|
import { assert, isDebug, assertNever, first, isEmpty, last, MultiMap } from "./util";
|
||||||
import { Analyser } from "./analysis";
|
import { Analyser } from "./analysis";
|
||||||
|
|
||||||
const MAX_TYPE_ERROR_COUNT = 5;
|
const MAX_TYPE_ERROR_COUNT = 5;
|
||||||
|
@ -440,6 +444,49 @@ export type Type
|
||||||
| TPresent
|
| TPresent
|
||||||
| TAbsent
|
| TAbsent
|
||||||
|
|
||||||
|
export class Qual {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public preds: Pred[],
|
||||||
|
public type: Type,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public substitute(sub: TVSub): Qual {
|
||||||
|
return new Qual(
|
||||||
|
this.preds.map(pred => pred.substitute(sub)),
|
||||||
|
this.type.substitute(sub),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public *getTypeVars() {
|
||||||
|
for (const pred of this.preds) {
|
||||||
|
yield* pred.type.getTypeVars();
|
||||||
|
}
|
||||||
|
yield* this.type.getTypeVars();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class IsInPred {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public id: string,
|
||||||
|
public type: Type,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public substitute(sub: TVSub): Pred {
|
||||||
|
return new IsInPred(this.id, this.type.substitute(sub));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pred = IsInPred;
|
||||||
|
|
||||||
export const enum KindType {
|
export const enum KindType {
|
||||||
Star,
|
Star,
|
||||||
Arrow,
|
Arrow,
|
||||||
|
@ -552,6 +599,14 @@ class TVSet {
|
||||||
|
|
||||||
private mapping = new Map<number, TVar>();
|
private mapping = new Map<number, TVar>();
|
||||||
|
|
||||||
|
public constructor(iterable?: Iterable<TVar>) {
|
||||||
|
if (iterable !== undefined) {
|
||||||
|
for (const tv of iterable) {
|
||||||
|
this.add(tv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public add(tv: TVar): void {
|
public add(tv: TVar): void {
|
||||||
this.mapping.set(tv.id, tv);
|
this.mapping.set(tv.id, tv);
|
||||||
}
|
}
|
||||||
|
@ -573,6 +628,10 @@ class TVSet {
|
||||||
this.mapping.delete(tv.id);
|
this.mapping.delete(tv.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get size(): number {
|
||||||
|
return this.mapping.size;
|
||||||
|
}
|
||||||
|
|
||||||
public [Symbol.iterator](): Iterator<TVar> {
|
public [Symbol.iterator](): Iterator<TVar> {
|
||||||
return this.mapping.values();
|
return this.mapping.values();
|
||||||
}
|
}
|
||||||
|
@ -690,10 +749,6 @@ class CMany extends ConstraintBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InstanceRef {
|
|
||||||
node: InstanceDeclaration | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Constraint
|
type Constraint
|
||||||
= CEqual
|
= CEqual
|
||||||
| CMany
|
| CMany
|
||||||
|
@ -706,12 +761,19 @@ abstract class SchemeBase {
|
||||||
|
|
||||||
class Forall extends SchemeBase {
|
class Forall extends SchemeBase {
|
||||||
|
|
||||||
|
public typeVars: TVSet;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public typeVars: Iterable<TVar>,
|
typeVars: Iterable<TVar>,
|
||||||
public constraints: Iterable<Constraint>,
|
public constraints: Iterable<Constraint>,
|
||||||
public type: Type,
|
public type: Type,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
if (typeVars instanceof TVSet) {
|
||||||
|
this.typeVars = typeVars;
|
||||||
|
} else {
|
||||||
|
this.typeVars = new TVSet(typeVars);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -726,6 +788,23 @@ type NodeWithReference
|
||||||
| ReferenceExpression
|
| ReferenceExpression
|
||||||
| ReferenceTypeExpression
|
| ReferenceTypeExpression
|
||||||
|
|
||||||
|
function validateScheme(scheme: Scheme): void {
|
||||||
|
const isMonoVar = scheme.type.kind === TypeKind.Var && scheme.typeVars.size === 0;
|
||||||
|
if (!isMonoVar) {
|
||||||
|
const tvs = new TVSet(scheme.type.getTypeVars())
|
||||||
|
for (const tv of tvs) {
|
||||||
|
if (!scheme.typeVars.has(tv)) {
|
||||||
|
throw new Error(`Type variable ${describeType(tv)} is free because does not appear in the scheme's type variable list`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const tv of scheme.typeVars) {
|
||||||
|
if (!tvs.has(tv)) {
|
||||||
|
throw new Error(`Polymorphic type variable ${describeType(tv)} does not occur anywhere in scheme's type ${describeType(scheme.type)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TypeEnv {
|
class TypeEnv {
|
||||||
|
|
||||||
private mapping = new MultiMap<string, [Symkind, Scheme]>();
|
private mapping = new MultiMap<string, [Symkind, Scheme]>();
|
||||||
|
@ -735,6 +814,9 @@ class TypeEnv {
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(name: string, scheme: Scheme, kind: Symkind): void {
|
public add(name: string, scheme: Scheme, kind: Symkind): void {
|
||||||
|
if (isDebug) {
|
||||||
|
validateScheme(scheme);
|
||||||
|
}
|
||||||
this.mapping.add(name, [kind, scheme]);
|
this.mapping.add(name, [kind, scheme]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,6 +901,10 @@ export class Checker {
|
||||||
|
|
||||||
private contexts: InferContext[] = [];
|
private contexts: InferContext[] = [];
|
||||||
|
|
||||||
|
private classDecls = new Map<string, ClassDeclaration>();
|
||||||
|
private globalKindEnv = new KindEnv();
|
||||||
|
private globalTypeEnv = new TypeEnv();
|
||||||
|
|
||||||
private solution = new TVSub();
|
private solution = new TVSub();
|
||||||
private kindSolution = new KVSub();
|
private kindSolution = new KVSub();
|
||||||
|
|
||||||
|
@ -827,6 +913,26 @@ export class Checker {
|
||||||
private diagnostics: Diagnostics
|
private diagnostics: Diagnostics
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
this.globalKindEnv.set('Int', new KType());
|
||||||
|
this.globalKindEnv.set('String', new KType());
|
||||||
|
this.globalKindEnv.set('Bool', new KType());
|
||||||
|
|
||||||
|
const a = new TVar(this.nextTypeVarId++);
|
||||||
|
const b = new TVar(this.nextTypeVarId++);
|
||||||
|
|
||||||
|
this.globalTypeEnv.add('$', new Forall([ a, b ], [], new TArrow(new TArrow(new TArrow(a, b), a), b)), Symkind.Var);
|
||||||
|
this.globalTypeEnv.add('String', new Forall([], [], this.stringType), Symkind.Type);
|
||||||
|
this.globalTypeEnv.add('Int', new Forall([], [], this.intType), Symkind.Type);
|
||||||
|
this.globalTypeEnv.add('Bool', new Forall([], [], this.boolType), Symkind.Type);
|
||||||
|
this.globalTypeEnv.add('True', new Forall([], [], this.boolType), Symkind.Var);
|
||||||
|
this.globalTypeEnv.add('False', new Forall([], [], this.boolType), Symkind.Var);
|
||||||
|
this.globalTypeEnv.add('+', new Forall([], [], TArrow.build([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
||||||
|
this.globalTypeEnv.add('-', new Forall([], [], TArrow.build([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
||||||
|
this.globalTypeEnv.add('*', new Forall([], [], TArrow.build([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
||||||
|
this.globalTypeEnv.add('/', new Forall([], [], TArrow.build([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
||||||
|
this.globalTypeEnv.add('==', new Forall([ a ], [], TArrow.build([ a, a ], this.boolType)), Symkind.Var);
|
||||||
|
this.globalTypeEnv.add('not', new Forall([], [], new TArrow(this.boolType, this.boolType)), Symkind.Var);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getIntType(): Type {
|
public getIntType(): Type {
|
||||||
|
@ -874,7 +980,7 @@ export class Checker {
|
||||||
let maxIndex = 0;
|
let maxIndex = 0;
|
||||||
let currUp = node.getEnclosingModule();
|
let currUp = node.getEnclosingModule();
|
||||||
outer: for (;;) {
|
outer: for (;;) {
|
||||||
let currDown: SourceFile | ModuleDeclaration = currUp;
|
let currDown = currUp;
|
||||||
for (let i = 0; i < modulePath.length; i++) {
|
for (let i = 0; i < modulePath.length; i++) {
|
||||||
const moduleName = modulePath[i];
|
const moduleName = modulePath[i];
|
||||||
const nextDown = currDown.resolveModule(moduleName.text);
|
const nextDown = currDown.resolveModule(moduleName.text);
|
||||||
|
@ -939,7 +1045,7 @@ export class Checker {
|
||||||
let maxIndex = 0;
|
let maxIndex = 0;
|
||||||
let currUp = node.getEnclosingModule();
|
let currUp = node.getEnclosingModule();
|
||||||
outer: for (;;) {
|
outer: for (;;) {
|
||||||
let currDown: SourceFile | ModuleDeclaration = currUp;
|
let currDown = currUp;
|
||||||
for (let i = 0; i < modulePath.length; i++) {
|
for (let i = 0; i < modulePath.length; i++) {
|
||||||
const moduleName = modulePath[i];
|
const moduleName = modulePath[i];
|
||||||
const nextDown = currDown.resolveModule(moduleName.text);
|
const nextDown = currDown.resolveModule(moduleName.text);
|
||||||
|
@ -1000,7 +1106,8 @@ export class Checker {
|
||||||
|
|
||||||
private createSubstitution(scheme: Scheme): TVSub {
|
private createSubstitution(scheme: Scheme): TVSub {
|
||||||
const sub = new TVSub();
|
const sub = new TVSub();
|
||||||
for (const tv of scheme.typeVars) {
|
const tvs = [...scheme.typeVars]
|
||||||
|
for (const tv of tvs) {
|
||||||
sub.set(tv, this.createTypeVar());
|
sub.set(tv, this.createTypeVar());
|
||||||
}
|
}
|
||||||
return sub;
|
return sub;
|
||||||
|
@ -1211,14 +1318,14 @@ export class Checker {
|
||||||
case SyntaxKind.ClassDeclaration:
|
case SyntaxKind.ClassDeclaration:
|
||||||
case SyntaxKind.InstanceDeclaration:
|
case SyntaxKind.InstanceDeclaration:
|
||||||
{
|
{
|
||||||
if (node.constraints !== null) {
|
if (node.constraintClause !== null) {
|
||||||
for (const constraint of node.constraints.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), new KType(), typeExpr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const typeExpr of node.constraint.types) {
|
for (const typeExpr of node.types) {
|
||||||
this.unifyKind(this.inferKindFromTypeExpression(typeExpr, env), new KType(), typeExpr);
|
this.unifyKind(this.inferKindFromTypeExpression(typeExpr, env), new KType(), typeExpr);
|
||||||
}
|
}
|
||||||
for (const element of node.elements) {
|
for (const element of node.elements) {
|
||||||
|
@ -1374,9 +1481,9 @@ export class Checker {
|
||||||
|
|
||||||
case SyntaxKind.InstanceDeclaration:
|
case SyntaxKind.InstanceDeclaration:
|
||||||
{
|
{
|
||||||
const cls = node.getScope().lookup(node.constraint.name.text, Symkind.Typeclass) as ClassDeclaration | null;
|
const cls = node.getScope().lookup(node.name.text, Symkind.Typeclass) as ClassDeclaration | null;
|
||||||
if (cls === null) {
|
if (cls === null) {
|
||||||
this.diagnostics.add(new TypeclassNotFoundDiagnostic(node.constraint.name.text, node.constraint.name));
|
this.diagnostics.add(new TypeclassNotFoundDiagnostic(node.name));
|
||||||
}
|
}
|
||||||
for (const element of node.elements) {
|
for (const element of node.elements) {
|
||||||
this.infer(element);
|
this.infer(element);
|
||||||
|
@ -1695,7 +1802,7 @@ export class Checker {
|
||||||
|
|
||||||
case SyntaxKind.TupleTypeExpression:
|
case SyntaxKind.TupleTypeExpression:
|
||||||
{
|
{
|
||||||
type = new TTuple(node.elements.map(el => this.inferTypeExpression(el)), node);
|
type = new TTuple(node.elements.map(el => this.inferTypeExpression(el, introduceTypeVars)), node);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1708,7 +1815,7 @@ export class Checker {
|
||||||
const scheme = this.lookup(node.name, Symkind.Type);
|
const scheme = this.lookup(node.name, Symkind.Type);
|
||||||
if (scheme === null) {
|
if (scheme === null) {
|
||||||
if (!introduceTypeVars) {
|
if (!introduceTypeVars) {
|
||||||
// this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
this.diagnostics.add(new BindingNotFoundDiagnostic([], node.name.text, node.name));
|
||||||
}
|
}
|
||||||
type = this.createTypeVar();
|
type = this.createTypeVar();
|
||||||
this.addBinding(node.name.text, new Forall([], [], type), Symkind.Type);
|
this.addBinding(node.name.text, new Forall([], [], type), Symkind.Type);
|
||||||
|
@ -1873,11 +1980,37 @@ export class Checker {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SyntaxKind.InstanceDeclaration:
|
|
||||||
case SyntaxKind.ClassDeclaration:
|
case SyntaxKind.ClassDeclaration:
|
||||||
{
|
{
|
||||||
|
const other = this.classDecls.get(node.name.text);
|
||||||
|
if (other !== undefined) {
|
||||||
|
this.diagnostics.add(new TypeclassDeclaredTwiceDiagnostic(node.name, other));
|
||||||
|
} else {
|
||||||
|
if (node.constraintClause !== null) {
|
||||||
|
for (const constraint of node.constraintClause.constraints) {
|
||||||
|
if (!this.classDecls.has(constraint.name.text)) {
|
||||||
|
this.diagnostics.add(new TypeclassNotFoundDiagnostic(constraint.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.classDecls.set(node.name.text, node);
|
||||||
|
}
|
||||||
|
const env = node.typeEnv = new TypeEnv(parentEnv);
|
||||||
|
for (const tv of node.types) {
|
||||||
|
assert(tv.kind === SyntaxKind.VarTypeExpression);
|
||||||
|
env.add(tv.name.text, new Forall([], [], this.createTypeVar(tv)), Symkind.Type);
|
||||||
|
}
|
||||||
for (const element of node.elements) {
|
for (const element of node.elements) {
|
||||||
this.initialize(element, parentEnv);
|
this.initialize(element, env);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SyntaxKind.InstanceDeclaration:
|
||||||
|
{
|
||||||
|
const env = node.typeEnv = new TypeEnv(parentEnv);
|
||||||
|
for (const element of node.elements) {
|
||||||
|
this.initialize(element, env);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1911,41 +2044,44 @@ export class Checker {
|
||||||
}
|
}
|
||||||
this.pushContext(context);
|
this.pushContext(context);
|
||||||
const kindArgs = [];
|
const kindArgs = [];
|
||||||
for (const varExpr of node.varExps) {
|
for (const name of node.varExps) {
|
||||||
const kindArg = this.createTypeVar();
|
const kindArg = this.createTypeVar();
|
||||||
env.add(varExpr.text, new Forall([], [], kindArg), Symkind.Type);
|
env.add(name.text, new Forall([], [], kindArg), Symkind.Type);
|
||||||
kindArgs.push(kindArg);
|
kindArgs.push(kindArg);
|
||||||
}
|
}
|
||||||
|
const type = TApp.build(new TNominal(node, node), kindArgs);
|
||||||
|
parentEnv.add(node.name.text, new Forall(typeVars, constraints, type), Symkind.Type);
|
||||||
let elementTypes: Type[] = [];
|
let elementTypes: Type[] = [];
|
||||||
const type = new TNominal(node, node);
|
|
||||||
if (node.members !== null) {
|
if (node.members !== null) {
|
||||||
for (const member of node.members) {
|
for (const member of node.members) {
|
||||||
let ctorType;
|
let ctorType, elementType;
|
||||||
switch (member.kind) {
|
switch (member.kind) {
|
||||||
case SyntaxKind.EnumDeclarationTupleElement:
|
case SyntaxKind.EnumDeclarationTupleElement:
|
||||||
{
|
{
|
||||||
const argTypes = member.elements.map(el => this.inferTypeExpression(el));
|
const argTypes = member.elements.map(el => this.inferTypeExpression(el, false));
|
||||||
ctorType = TArrow.build(argTypes, TApp.build(type, kindArgs), member);
|
elementType = new TTuple(argTypes, member);
|
||||||
|
ctorType = TArrow.build(argTypes, type, member);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SyntaxKind.EnumDeclarationStructElement:
|
case SyntaxKind.EnumDeclarationStructElement:
|
||||||
{
|
{
|
||||||
ctorType = new TNil(member);
|
elementType = new TNil(member);
|
||||||
for (const field of member.fields) {
|
for (const field of member.fields) {
|
||||||
ctorType = new TField(field.name.text, new TPresent(this.inferTypeExpression(field.typeExpr)), ctorType, member);
|
elementType = new TField(field.name.text, new TPresent(this.inferTypeExpression(field.typeExpr, false)), elementType, member);
|
||||||
}
|
}
|
||||||
ctorType = new TArrow(TField.sort(ctorType), TApp.build(type, kindArgs));
|
elementType = TField.sort(elementType);
|
||||||
|
ctorType = new TArrow(elementType, type);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unexpected ${member}`);
|
throw new Error(`Unexpected ${member}`);
|
||||||
}
|
}
|
||||||
|
// FIXME `typeVars` may contain too much irrelevant type variables
|
||||||
parentEnv.add(member.name.text, new Forall(typeVars, constraints, ctorType), Symkind.Var);
|
parentEnv.add(member.name.text, new Forall(typeVars, constraints, ctorType), Symkind.Var);
|
||||||
elementTypes.push(ctorType);
|
elementTypes.push(elementType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.popContext(context);
|
this.popContext(context);
|
||||||
parentEnv.add(node.name.text, new Forall(typeVars, constraints, type), Symkind.Type);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2013,37 +2149,17 @@ export class Checker {
|
||||||
|
|
||||||
public check(node: SourceFile): void {
|
public check(node: SourceFile): void {
|
||||||
|
|
||||||
const kenv = new KindEnv();
|
const kenv = new KindEnv(this.globalKindEnv);
|
||||||
kenv.set('Int', new KType());
|
this.forwardDeclareKind(node, kenv);
|
||||||
kenv.set('String', new KType());
|
this.inferKind(node, kenv);
|
||||||
kenv.set('Bool', new KType());
|
|
||||||
const skenv = new KindEnv(kenv);
|
|
||||||
this.forwardDeclareKind(node, skenv);
|
|
||||||
this.inferKind(node, skenv);
|
|
||||||
|
|
||||||
const typeVars = new TVSet();
|
const typeVars = new TVSet();
|
||||||
const constraints = new ConstraintSet();
|
const constraints = new ConstraintSet();
|
||||||
const env = new TypeEnv();
|
const env = new TypeEnv(this.globalTypeEnv);
|
||||||
const context: InferContext = { typeVars, constraints, env, returnType: null };
|
const context: InferContext = { typeVars, constraints, env, returnType: null };
|
||||||
|
|
||||||
this.pushContext(context);
|
this.pushContext(context);
|
||||||
|
|
||||||
const a = this.createTypeVar();
|
|
||||||
const b = this.createTypeVar();
|
|
||||||
|
|
||||||
env.add('$', new Forall([ a, b ], [], new TArrow(new TArrow(new TArrow(a, b), a), b)), Symkind.Var);
|
|
||||||
env.add('String', new Forall([], [], this.stringType), Symkind.Type);
|
|
||||||
env.add('Int', new Forall([], [], this.intType), Symkind.Type);
|
|
||||||
env.add('Bool', new Forall([], [], this.boolType), Symkind.Type);
|
|
||||||
env.add('True', new Forall([], [], this.boolType), Symkind.Var);
|
|
||||||
env.add('False', new Forall([], [], this.boolType), Symkind.Var);
|
|
||||||
env.add('+', new Forall([], [], TArrow.build([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
|
||||||
env.add('-', new Forall([], [], TArrow.build([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
|
||||||
env.add('*', new Forall([], [], TArrow.build([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
|
||||||
env.add('/', new Forall([], [], TArrow.build([ this.intType, this.intType ], this.intType)), Symkind.Var);
|
|
||||||
env.add('==', new Forall([ a ], [], TArrow.build([ a, a ], this.boolType)), Symkind.Var);
|
|
||||||
env.add('not', new Forall([], [], new TArrow(this.boolType, this.boolType)), Symkind.Var);
|
|
||||||
|
|
||||||
this.initialize(node, env);
|
this.initialize(node, env);
|
||||||
|
|
||||||
this.pushContext({
|
this.pushContext({
|
||||||
|
@ -2141,7 +2257,6 @@ export class Checker {
|
||||||
}
|
}
|
||||||
|
|
||||||
const visitElements = (elements: Syntax[]) => {
|
const visitElements = (elements: Syntax[]) => {
|
||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
if (element.kind === SyntaxKind.LetDeclaration
|
if (element.kind === SyntaxKind.LetDeclaration
|
||||||
&& isFunctionDeclarationLike(element)) {
|
&& isFunctionDeclarationLike(element)) {
|
||||||
|
@ -2152,14 +2267,21 @@ export class Checker {
|
||||||
this.instantiate(scheme, null);
|
this.instantiate(scheme, null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const elementHasTypeEnv = hasTypeEnv(element);
|
||||||
|
if (elementHasTypeEnv) {
|
||||||
|
this.pushContext({ ...this.getContext(), env: element.typeEnv! });
|
||||||
|
}
|
||||||
this.infer(element);
|
this.infer(element);
|
||||||
|
if(elementHasTypeEnv) {
|
||||||
|
this.contexts.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const nodes of sccs) {
|
for (const nodes of sccs) {
|
||||||
|
|
||||||
if (nodes.some(n => n.kind === SyntaxKind.SourceFile)) {
|
if (nodes[0].kind === SyntaxKind.SourceFile) {
|
||||||
assert(nodes.length === 1);
|
assert(nodes.length === 1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2221,24 +2343,30 @@ export class Checker {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lookupClass(name: string): ClassDeclaration | null {
|
||||||
|
return this.classDecls.get(name) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
private *findInstanceContext(type: TCon, clazz: ClassDeclaration): Iterable<ClassDeclaration[]> {
|
private *findInstanceContext(type: TCon, clazz: ClassDeclaration): Iterable<ClassDeclaration[]> {
|
||||||
for (const instance of clazz.getInstances()) {
|
for (const instance of clazz.getInstances()) {
|
||||||
assert(instance.constraint.types.length === 1);
|
assert(instance.types.length === 1);
|
||||||
const instTy0 = instance.constraint.types[0];
|
const instTy0 = instance.types[0];
|
||||||
if (instTy0.kind === SyntaxKind.AppTypeExpression
|
if ((instTy0.kind === SyntaxKind.AppTypeExpression
|
||||||
&& instTy0.operator.kind === SyntaxKind.ReferenceTypeExpression
|
&& instTy0.operator.kind === SyntaxKind.ReferenceTypeExpression
|
||||||
&& instTy0.operator.name.text === type.displayName) {
|
&& instTy0.operator.name.text === type.displayName)
|
||||||
if (instance.constraints === null) {
|
|| (instTy0.kind === SyntaxKind.ReferenceTypeExpression
|
||||||
|
&& instTy0.name.text === type.displayName)) {
|
||||||
|
if (instance.constraintClause === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const argType of type.argTypes) {
|
for (const argType of type.argTypes) {
|
||||||
const classes = [];
|
const classes = [];
|
||||||
for (const constraint of instance.constraints.constraints) {
|
for (const constraint of instance.constraintClause.constraints) {
|
||||||
assert(constraint.types.length === 1);
|
assert(constraint.types.length === 1);
|
||||||
const classDecl = this.lookupClass(constraint.name);
|
const classDecl = this.lookupClass(constraint.name.text);
|
||||||
if (classDecl === null) {
|
if (classDecl === null) {
|
||||||
this.diagnostics.add(new TypeclassNotFoundDiagnostic(constraint.name.text, constraint.name));
|
this.diagnostics.add(new TypeclassNotFoundDiagnostic(constraint.name));
|
||||||
} else {
|
} else {
|
||||||
classes.push(classDecl);
|
classes.push(classDecl);
|
||||||
}
|
}
|
||||||
|
@ -2296,7 +2424,7 @@ export class Checker {
|
||||||
assert(right.kind === TypeKind.Present);
|
assert(right.kind === TypeKind.Present);
|
||||||
const fieldName = path[path.length-1];
|
const fieldName = path[path.length-1];
|
||||||
this.diagnostics.add(
|
this.diagnostics.add(
|
||||||
new FieldMissingDiagnostic(fieldName, left.node, right.type.node, constraint.firstNode)
|
new FieldNotFoundDiagnostic(fieldName, left.node, right.type.node, constraint.firstNode)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2305,6 +2433,13 @@ export class Checker {
|
||||||
return unify(left.type, right.type);
|
return unify(left.type, right.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unifyPred = (left: Pred, right: Pred) => {
|
||||||
|
if (left.id === right.id) {
|
||||||
|
return unify(left.type, right.type);
|
||||||
|
}
|
||||||
|
throw new Error(`Classes do not match and no diagnostic defined`);
|
||||||
|
}
|
||||||
|
|
||||||
const unify = (left: Type, right: Type): boolean => {
|
const unify = (left: Type, right: Type): boolean => {
|
||||||
|
|
||||||
left = find(left);
|
left = find(left);
|
||||||
|
@ -2342,6 +2477,7 @@ export class Checker {
|
||||||
propagateClassTCon(constraint, type);
|
propagateClassTCon(constraint, type);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//assert(false);
|
||||||
//this.diagnostics.add(new );
|
//this.diagnostics.add(new );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2475,7 +2611,7 @@ export class Checker {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.diagnostics.add(
|
this.diagnostics.add(
|
||||||
new UnificationFailedDiagnostic(
|
new TypeMismatchDiagnostic(
|
||||||
left.substitute(solution),
|
left.substitute(solution),
|
||||||
right.substitute(solution),
|
right.substitute(solution),
|
||||||
[...constraint.getNodes()],
|
[...constraint.getNodes()],
|
||||||
|
@ -2512,3 +2648,18 @@ function getVariadicMember(node: StructPattern) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HasTypeEnv
|
||||||
|
= ClassDeclaration
|
||||||
|
| InstanceDeclaration
|
||||||
|
| LetDeclaration
|
||||||
|
| ModuleDeclaration
|
||||||
|
| SourceFile
|
||||||
|
|
||||||
|
function hasTypeEnv(node: Syntax): node is HasTypeEnv {
|
||||||
|
return node.kind === SyntaxKind.ClassDeclaration
|
||||||
|
|| node.kind === SyntaxKind.InstanceDeclaration
|
||||||
|
|| node.kind === SyntaxKind.LetDeclaration
|
||||||
|
|| node.kind === SyntaxKind.ModuleDeclaration
|
||||||
|
|| node.kind === SyntaxKind.SourceFile
|
||||||
|
}
|
||||||
|
|
||||||
|
|
50
src/cst.ts
50
src/cst.ts
|
@ -2,7 +2,7 @@
|
||||||
import type stream from "stream";
|
import type stream from "stream";
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
import { assert, IndentWriter, JSONObject, JSONValue } from "./util";
|
import { assert, implementationLimitation, IndentWriter, JSONObject, JSONValue } from "./util";
|
||||||
import { isNodeWithScope, Scope } from "./scope"
|
import { isNodeWithScope, Scope } from "./scope"
|
||||||
import { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
|
import { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
|
||||||
import { Emitter } from "./emitter";
|
import { Emitter } from "./emitter";
|
||||||
|
@ -2828,22 +2828,33 @@ export class ClassDeclaration extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.ClassDeclaration;
|
public readonly kind = SyntaxKind.ClassDeclaration;
|
||||||
|
|
||||||
|
public typeEnv?: TypeEnv;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public pubKeyword: PubKeyword | null,
|
public pubKeyword: PubKeyword | null,
|
||||||
public classKeyword: ClassKeyword,
|
public classKeyword: ClassKeyword,
|
||||||
public constraints: ClassConstraintClause | null,
|
public constraintClause: ClassConstraintClause | null,
|
||||||
public constraint: ClassConstraint,
|
public name: IdentifierAlt,
|
||||||
|
public types: VarTypeExpression[],
|
||||||
public elements: ClassDeclarationElement[],
|
public elements: ClassDeclarationElement[],
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public *getSupers(): Iterable<IdentifierAlt> {
|
||||||
|
if (this.constraintClause !== null) {
|
||||||
|
for (const constraint of this.constraintClause.constraints) {
|
||||||
|
yield constraint.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public lookup(element: InstanceDeclarationElement): ClassDeclarationElement | null {
|
public lookup(element: InstanceDeclarationElement): ClassDeclarationElement | null {
|
||||||
|
|
||||||
switch (element.kind) {
|
switch (element.kind) {
|
||||||
|
|
||||||
case SyntaxKind.LetDeclaration:
|
case SyntaxKind.LetDeclaration:
|
||||||
assert(element.pattern.kind === SyntaxKind.NamedPattern);
|
implementationLimitation(element.pattern.kind === SyntaxKind.NamedPattern);
|
||||||
for (const other of this.elements) {
|
for (const other of this.elements) {
|
||||||
if (other.kind === SyntaxKind.LetDeclaration
|
if (other.kind === SyntaxKind.LetDeclaration
|
||||||
&& other.pattern.kind === SyntaxKind.NamedPattern
|
&& other.pattern.kind === SyntaxKind.NamedPattern
|
||||||
|
@ -2875,7 +2886,7 @@ export class ClassDeclaration extends SyntaxBase {
|
||||||
curr = curr.parent!;
|
curr = curr.parent!;
|
||||||
}
|
}
|
||||||
for (const element of getElements(curr)) {
|
for (const element of getElements(curr)) {
|
||||||
if (element.kind === SyntaxKind.InstanceDeclaration && element.constraint.name === this.constraint.name) {
|
if (element.kind === SyntaxKind.InstanceDeclaration && element.name.text === this.name.text) {
|
||||||
yield element;
|
yield element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2886,8 +2897,9 @@ export class ClassDeclaration extends SyntaxBase {
|
||||||
return new ClassDeclaration(
|
return new ClassDeclaration(
|
||||||
this.pubKeyword !== null ? this.pubKeyword.clone() : null,
|
this.pubKeyword !== null ? this.pubKeyword.clone() : null,
|
||||||
this.classKeyword.clone(),
|
this.classKeyword.clone(),
|
||||||
this.constraints !== null ? this.constraints.clone() : null,
|
this.constraintClause !== null ? this.constraintClause.clone() : null,
|
||||||
this.constraint.clone(),
|
this.name.clone(),
|
||||||
|
this.types.map(t => t.clone()),
|
||||||
this.elements.map(element => element.clone()),
|
this.elements.map(element => element.clone()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2903,7 +2915,10 @@ export class ClassDeclaration extends SyntaxBase {
|
||||||
if (this.elements.length > 0) {
|
if (this.elements.length > 0) {
|
||||||
return this.elements[this.elements.length-1].getLastToken();
|
return this.elements[this.elements.length-1].getLastToken();
|
||||||
}
|
}
|
||||||
return this.constraint.getLastToken();
|
if (this.types.length > 0) {
|
||||||
|
return this.types[this.types.length-1].getLastToken();
|
||||||
|
}
|
||||||
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2916,11 +2931,14 @@ export class InstanceDeclaration extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.InstanceDeclaration;
|
public readonly kind = SyntaxKind.InstanceDeclaration;
|
||||||
|
|
||||||
|
public typeEnv?: TypeEnv;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public pubKeyword: PubKeyword | null,
|
public pubKeyword: PubKeyword | null,
|
||||||
public classKeyword: InstanceKeyword,
|
public classKeyword: InstanceKeyword,
|
||||||
public constraints: ClassConstraintClause | null,
|
public constraintClause: ClassConstraintClause | null,
|
||||||
public constraint: ClassConstraint,
|
public name: IdentifierAlt,
|
||||||
|
public types: TypeExpression[],
|
||||||
public elements: InstanceDeclarationElement[],
|
public elements: InstanceDeclarationElement[],
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -2930,8 +2948,9 @@ export class InstanceDeclaration extends SyntaxBase {
|
||||||
return new InstanceDeclaration(
|
return new InstanceDeclaration(
|
||||||
this.pubKeyword !== null ? this.pubKeyword.clone() : null,
|
this.pubKeyword !== null ? this.pubKeyword.clone() : null,
|
||||||
this.classKeyword.clone(),
|
this.classKeyword.clone(),
|
||||||
this.constraints !== null ? this.constraints.clone() : null,
|
this.constraintClause !== null ? this.constraintClause.clone() : null,
|
||||||
this.constraint.clone(),
|
this.name.clone(),
|
||||||
|
this.types.map(t => t.clone()),
|
||||||
this.elements.map(element => element.clone()),
|
this.elements.map(element => element.clone()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2947,7 +2966,10 @@ export class InstanceDeclaration extends SyntaxBase {
|
||||||
if (this.elements.length > 0) {
|
if (this.elements.length > 0) {
|
||||||
return this.elements[this.elements.length-1].getLastToken();
|
return this.elements[this.elements.length-1].getLastToken();
|
||||||
}
|
}
|
||||||
return this.constraint.getLastToken();
|
if (this.types.length > 0) {
|
||||||
|
return this.types[this.types.length-1].getLastToken();
|
||||||
|
}
|
||||||
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3095,7 +3117,7 @@ export function vistEachChild<T extends Syntax>(node: T, proc: (node: Syntax) =>
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
return new node.constructor(...newArgs);
|
return new (node as any).constructor(...newArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canHaveInstanceDeclaration(node: Syntax): boolean {
|
export function canHaveInstanceDeclaration(node: Syntax): boolean {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
import { TypeKind, type Type, type TArrow, TRecord, Kind, KindType } from "./checker";
|
import { TypeKind, type Type, Kind, KindType } from "./checker";
|
||||||
import { Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
|
import { ClassConstraint, ClassDeclaration, IdentifierAlt, InstanceDeclaration, Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
|
||||||
import { countDigits, IndentWriter } from "./util";
|
import { assertNever, countDigits, IndentWriter } from "./util";
|
||||||
|
|
||||||
const ANSI_RESET = "\u001b[0m"
|
const ANSI_RESET = "\u001b[0m"
|
||||||
const ANSI_BOLD = "\u001b[1m"
|
const ANSI_BOLD = "\u001b[1m"
|
||||||
|
@ -35,9 +35,28 @@ const enum Level {
|
||||||
Fatal,
|
Fatal,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnexpectedCharDiagnostic {
|
const enum DiagnosticKind {
|
||||||
|
UnexpectedChar,
|
||||||
|
UnexpectedToken,
|
||||||
|
KindMismatch,
|
||||||
|
TypeMismatch,
|
||||||
|
TypeclassNotFound,
|
||||||
|
TypeclassDecaredTwice,
|
||||||
|
BindingNotFound,
|
||||||
|
ModuleNotFound,
|
||||||
|
FieldNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
public readonly level = Level.Error;
|
interface DiagnosticBase {
|
||||||
|
level: Level;
|
||||||
|
readonly kind: DiagnosticKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnexpectedCharDiagnostic implements DiagnosticBase {
|
||||||
|
|
||||||
|
public readonly kind = DiagnosticKind.UnexpectedChar;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public file: TextFile,
|
public file: TextFile,
|
||||||
|
@ -47,12 +66,322 @@ export class UnexpectedCharDiagnostic {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public format(out: IndentWriter): void {
|
}
|
||||||
const endPos = this.position.clone();
|
|
||||||
endPos.advance(this.actual);
|
|
||||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
export class UnexpectedTokenDiagnostic implements DiagnosticBase {
|
||||||
out.write(`unexpected character sequence '${this.actual}'.\n\n`);
|
|
||||||
out.write(printExcerpt(this.file, new TextRange(this.position, endPos)) + '\n');
|
public readonly kind = DiagnosticKind.UnexpectedToken;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public file: TextFile,
|
||||||
|
public actual: Token,
|
||||||
|
public expected: SyntaxKind[],
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypeclassDeclaredTwiceDiagnostic implements DiagnosticBase {
|
||||||
|
|
||||||
|
public readonly kind = DiagnosticKind.TypeclassDecaredTwice;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: IdentifierAlt,
|
||||||
|
public origDecl: ClassDeclaration,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypeclassNotFoundDiagnostic implements DiagnosticBase {
|
||||||
|
|
||||||
|
public readonly kind = DiagnosticKind.TypeclassNotFound;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: IdentifierAlt,
|
||||||
|
public origin: InstanceDeclaration | ClassConstraint | null = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BindingNotFoundDiagnostic implements DiagnosticBase {
|
||||||
|
|
||||||
|
public readonly kind = DiagnosticKind.BindingNotFound;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public modulePath: string[],
|
||||||
|
public name: string,
|
||||||
|
public node: Syntax,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypeMismatchDiagnostic implements DiagnosticBase {
|
||||||
|
|
||||||
|
public readonly kind = DiagnosticKind.TypeMismatch;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public left: Type,
|
||||||
|
public right: Type,
|
||||||
|
public trace: Syntax[],
|
||||||
|
public fieldPath: string[],
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FieldNotFoundDiagnostic implements DiagnosticBase {
|
||||||
|
|
||||||
|
public readonly kind = DiagnosticKind.FieldNotFound;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public fieldName: string,
|
||||||
|
public missing: Syntax | null,
|
||||||
|
public present: Syntax | null,
|
||||||
|
public cause: Syntax | null = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KindMismatchDiagnostic implements DiagnosticBase {
|
||||||
|
|
||||||
|
public readonly kind = DiagnosticKind.KindMismatch;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public left: Kind,
|
||||||
|
public right: Kind,
|
||||||
|
public origin: Syntax | null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ModuleNotFoundDiagnostic implements DiagnosticBase {
|
||||||
|
|
||||||
|
public readonly kind = DiagnosticKind.ModuleNotFound;
|
||||||
|
|
||||||
|
public level = Level.Error;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public modulePath: string[],
|
||||||
|
public node: Syntax,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Diagnostic
|
||||||
|
= UnexpectedCharDiagnostic
|
||||||
|
| TypeclassNotFoundDiagnostic
|
||||||
|
| TypeclassDeclaredTwiceDiagnostic
|
||||||
|
| BindingNotFoundDiagnostic
|
||||||
|
| TypeMismatchDiagnostic
|
||||||
|
| UnexpectedTokenDiagnostic
|
||||||
|
| FieldNotFoundDiagnostic
|
||||||
|
| KindMismatchDiagnostic
|
||||||
|
| ModuleNotFoundDiagnostic
|
||||||
|
|
||||||
|
export interface Diagnostics {
|
||||||
|
readonly hasError: boolean;
|
||||||
|
readonly hasFatal: boolean;
|
||||||
|
add(diagnostic: Diagnostic): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiagnosticStore implements Diagnostics {
|
||||||
|
|
||||||
|
private storage: Diagnostic[] = [];
|
||||||
|
|
||||||
|
public hasError = false;
|
||||||
|
public hasFatal = false;
|
||||||
|
|
||||||
|
public add(diagnostic: Diagnostic): void {
|
||||||
|
this.storage.push(diagnostic);
|
||||||
|
if (diagnostic.level >= Level.Error) {
|
||||||
|
this.hasError = true;
|
||||||
|
}
|
||||||
|
if (diagnostic.level >= Level.Fatal) {
|
||||||
|
this.hasFatal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDiagnostics(): Iterable<Diagnostic> {
|
||||||
|
return this.storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConsoleDiagnostics implements Diagnostics {
|
||||||
|
|
||||||
|
private writer = new IndentWriter(process.stderr);
|
||||||
|
|
||||||
|
public hasError = false;
|
||||||
|
public hasFatal = false;
|
||||||
|
|
||||||
|
public add(diagnostic: Diagnostic): void {
|
||||||
|
|
||||||
|
if (diagnostic.level >= Level.Error) {
|
||||||
|
this.hasError = true;
|
||||||
|
}
|
||||||
|
if (diagnostic.level >= Level.Fatal) {
|
||||||
|
this.hasFatal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (diagnostic.level) {
|
||||||
|
case Level.Fatal:
|
||||||
|
this.writer.write(ANSI_FG_RED + ANSI_BOLD + 'fatal: ' + ANSI_RESET);
|
||||||
|
break;
|
||||||
|
case Level.Error:
|
||||||
|
this.writer.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
||||||
|
break;
|
||||||
|
case Level.Warning:
|
||||||
|
this.writer.write(ANSI_FG_RED + ANSI_BOLD + 'warning: ' + ANSI_RESET);
|
||||||
|
break;
|
||||||
|
case Level.Info:
|
||||||
|
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
||||||
|
break;
|
||||||
|
case Level.Verbose:
|
||||||
|
this.writer.write(ANSI_FG_CYAN + ANSI_BOLD + 'verbose: ' + ANSI_RESET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (diagnostic.kind) {
|
||||||
|
|
||||||
|
case DiagnosticKind.UnexpectedChar:
|
||||||
|
const endPos = diagnostic.position.clone();
|
||||||
|
endPos.advance(diagnostic.actual);
|
||||||
|
this.writer.write(`unexpected character sequence '${diagnostic.actual}'.\n\n`);
|
||||||
|
this.writer.write(printExcerpt(diagnostic.file, new TextRange(diagnostic.position, endPos)) + '\n');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DiagnosticKind.UnexpectedToken:
|
||||||
|
this.writer.write(`expected ${describeExpected(diagnostic.expected)} but got ${describeActual(diagnostic.actual)}\n\n`);
|
||||||
|
this.writer.write(printExcerpt(diagnostic.file, diagnostic.actual.getRange()) + '\n');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DiagnosticKind.TypeclassDecaredTwice:
|
||||||
|
this.writer.write(`type class '${diagnostic.name.text}' was already declared somewhere else.\n\n`);
|
||||||
|
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
||||||
|
this.writer.write(`type class '${diagnostic.name.text}' is already declared here\n\n`);
|
||||||
|
this.writer.write(printNode(diagnostic.origDecl) + '\n');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DiagnosticKind.TypeclassNotFound:
|
||||||
|
this.writer.write(`the type class ${ANSI_FG_MAGENTA + diagnostic.name.text + ANSI_RESET} was not found.\n\n`);
|
||||||
|
this.writer.write(printNode(diagnostic.name) + '\n');
|
||||||
|
if (diagnostic.origin !== null) {
|
||||||
|
this.writer.indent();
|
||||||
|
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
||||||
|
this.writer.write(`${ANSI_FG_MAGENTA + diagnostic.name.text + ANSI_RESET} is required by ${ANSI_FG_MAGENTA + diagnostic.origin.name.text + ANSI_RESET}\n\n`);
|
||||||
|
this.writer.write(printNode(diagnostic.origin.name) + '\n');
|
||||||
|
this.writer.dedent();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DiagnosticKind.BindingNotFound:
|
||||||
|
this.writer.write(`binding '${diagnostic.name}' was not found`);
|
||||||
|
if (diagnostic.modulePath.length > 0) {
|
||||||
|
this.writer.write(` in module ${ANSI_FG_BLUE + diagnostic.modulePath.join('.') + ANSI_RESET}`);
|
||||||
|
}
|
||||||
|
this.writer.write(`.\n\n`);
|
||||||
|
this.writer.write(printNode(diagnostic.node) + '\n');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DiagnosticKind.TypeMismatch:
|
||||||
|
const leftNode = getFirstNodeInTypeChain(diagnostic.left);
|
||||||
|
const rightNode = getFirstNodeInTypeChain(diagnostic.right);
|
||||||
|
const node = diagnostic.trace[0];
|
||||||
|
this.writer.write(`unification of ` + ANSI_FG_GREEN + describeType(diagnostic.left) + ANSI_RESET);
|
||||||
|
this.writer.write(' and ' + ANSI_FG_GREEN + describeType(diagnostic.right) + ANSI_RESET + ' failed');
|
||||||
|
if (diagnostic.fieldPath.length > 0) {
|
||||||
|
this.writer.write(` in field '${diagnostic.fieldPath.join('.')}'`);
|
||||||
|
}
|
||||||
|
this.writer.write('.\n\n');
|
||||||
|
this.writer.write(printNode(node) + '\n');
|
||||||
|
for (let i = 1; i < diagnostic.trace.length; i++) {
|
||||||
|
const node = diagnostic.trace[i];
|
||||||
|
this.writer.write(' ... in an instantiation of the following expression\n\n');
|
||||||
|
this.writer.write(printNode(node, { indentation: i === 0 ? ' ' : ' ' }) + '\n');
|
||||||
|
}
|
||||||
|
if (leftNode !== null) {
|
||||||
|
this.writer.indent();
|
||||||
|
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + `info: ` + ANSI_RESET);
|
||||||
|
this.writer.write(`type ` + ANSI_FG_GREEN + describeType(diagnostic.left) + ANSI_RESET + ` was inferred from diagnostic expression:\n\n`);
|
||||||
|
this.writer.write(printNode(leftNode) + '\n');
|
||||||
|
this.writer.dedent();
|
||||||
|
}
|
||||||
|
if (rightNode !== null) {
|
||||||
|
this.writer.indent();
|
||||||
|
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + `info: ` + ANSI_RESET);
|
||||||
|
this.writer.write(`type ` + ANSI_FG_GREEN + describeType(diagnostic.right) + ANSI_RESET + ` was inferred from diagnostic expression:\n\n`);
|
||||||
|
this.writer.write(printNode(rightNode) + '\n');
|
||||||
|
this.writer.dedent();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DiagnosticKind.KindMismatch:
|
||||||
|
this.writer.write(`kind ${describeKind(diagnostic.left)} does not match with ${describeKind(diagnostic.right)}\n\n`);
|
||||||
|
if (diagnostic.origin !== null) {
|
||||||
|
this.writer.write(printNode(diagnostic.origin) + '\n');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DiagnosticKind.ModuleNotFound:
|
||||||
|
this.writer.write(`a module named ${ANSI_FG_BLUE + diagnostic.modulePath.join('.') + ANSI_RESET} was not found.\n\n`);
|
||||||
|
this.writer.write(printNode(diagnostic.node) + '\n');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DiagnosticKind.FieldNotFound:
|
||||||
|
this.writer.write(`field '${diagnostic.fieldName}' is required in one type but missing in another\n\n`);
|
||||||
|
this.writer.indent();
|
||||||
|
if (diagnostic.missing !== null) {
|
||||||
|
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
||||||
|
this.writer.write(`field '${diagnostic.fieldName}' is missing in diagnostic construct\n\n`);
|
||||||
|
this.writer.write(printNode(diagnostic.missing) + '\n');
|
||||||
|
}
|
||||||
|
if (diagnostic.present !== null) {
|
||||||
|
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
||||||
|
this.writer.write(`field '${diagnostic.fieldName}' is required in diagnostic construct\n\n`);
|
||||||
|
this.writer.write(printNode(diagnostic.present) + '\n');
|
||||||
|
}
|
||||||
|
if (diagnostic.cause !== null) {
|
||||||
|
this.writer.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
||||||
|
this.writer.write(`because of a constraint on diagnostic node:\n\n`);
|
||||||
|
this.writer.write(printNode(diagnostic.cause) + '\n');
|
||||||
|
}
|
||||||
|
this.writer.dedent();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertNever(diagnostic);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -60,6 +389,9 @@ export class UnexpectedCharDiagnostic {
|
||||||
const DESCRIPTIONS: Partial<Record<SyntaxKind, string>> = {
|
const DESCRIPTIONS: Partial<Record<SyntaxKind, string>> = {
|
||||||
[SyntaxKind.StringLiteral]: 'a string literal',
|
[SyntaxKind.StringLiteral]: 'a string literal',
|
||||||
[SyntaxKind.Identifier]: "an identifier",
|
[SyntaxKind.Identifier]: "an identifier",
|
||||||
|
[SyntaxKind.RArrow]: "'->'",
|
||||||
|
[SyntaxKind.RArrowAlt]: '"=>"',
|
||||||
|
[SyntaxKind.VBar]: "'|'",
|
||||||
[SyntaxKind.Comma]: "','",
|
[SyntaxKind.Comma]: "','",
|
||||||
[SyntaxKind.Colon]: "':'",
|
[SyntaxKind.Colon]: "':'",
|
||||||
[SyntaxKind.Integer]: "an integer",
|
[SyntaxKind.Integer]: "an integer",
|
||||||
|
@ -86,8 +418,6 @@ const DESCRIPTIONS: Partial<Record<SyntaxKind, string>> = {
|
||||||
[SyntaxKind.BlockEnd]: 'the end of an indented block',
|
[SyntaxKind.BlockEnd]: 'the end of an indented block',
|
||||||
[SyntaxKind.LineFoldEnd]: 'the end of the current line-fold',
|
[SyntaxKind.LineFoldEnd]: 'the end of the current line-fold',
|
||||||
[SyntaxKind.EndOfFile]: 'end-of-file',
|
[SyntaxKind.EndOfFile]: 'end-of-file',
|
||||||
[SyntaxKind.RArrowAlt]: '"=>"',
|
|
||||||
[SyntaxKind.VBar]: "'|'",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function describeSyntaxKind(kind: SyntaxKind): string {
|
function describeSyntaxKind(kind: SyntaxKind): string {
|
||||||
|
@ -126,70 +456,6 @@ function describeActual(token: Token): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnexpectedTokenDiagnostic {
|
|
||||||
|
|
||||||
public readonly level = Level.Error;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
public file: TextFile,
|
|
||||||
public actual: Token,
|
|
||||||
public expected: SyntaxKind[],
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public format(out: IndentWriter): void {
|
|
||||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'fatal: ' + ANSI_RESET);
|
|
||||||
out.write(`expected ${describeExpected(this.expected)} but got ${describeActual(this.actual)}\n\n`);
|
|
||||||
out.write(printExcerpt(this.file, this.actual.getRange()) + '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TypeclassNotFoundDiagnostic {
|
|
||||||
|
|
||||||
public readonly level = Level.Error;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
public name: string,
|
|
||||||
public node: Syntax,
|
|
||||||
) {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public format(out: IndentWriter): void {
|
|
||||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
|
||||||
out.write(`could not implement type class because class '${this.name}' was not found.\n\n`);
|
|
||||||
out.write(printNode(this.node) + '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BindingNotFoundDiagnostic {
|
|
||||||
|
|
||||||
public readonly level = Level.Error;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
public modulePath: string[],
|
|
||||||
public name: string,
|
|
||||||
public node: Syntax,
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public format(out: IndentWriter): void {
|
|
||||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
|
||||||
out.write(`binding '${this.name}' was not found`);
|
|
||||||
if (this.modulePath.length > 0) {
|
|
||||||
out.write(` in module ${ANSI_FG_BLUE + this.modulePath.join('.') + ANSI_RESET}`);
|
|
||||||
}
|
|
||||||
out.write(`.\n\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.Con:
|
case TypeKind.Con:
|
||||||
|
@ -267,196 +533,14 @@ function getFirstNodeInTypeChain(type: Type): Syntax | null {
|
||||||
return curr.node;
|
return curr.node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnificationFailedDiagnostic {
|
|
||||||
|
|
||||||
public readonly level = Level.Error;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
public left: Type,
|
|
||||||
public right: Type,
|
|
||||||
public nodes: Syntax[],
|
|
||||||
public path: string[],
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public format(out: IndentWriter): void {
|
|
||||||
const leftNode = getFirstNodeInTypeChain(this.left);
|
|
||||||
const rightNode = getFirstNodeInTypeChain(this.right);
|
|
||||||
const node = this.nodes[0];
|
|
||||||
out.write(ANSI_FG_RED + ANSI_BOLD + `error: ` + ANSI_RESET);
|
|
||||||
out.write(`unification of ` + ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET);
|
|
||||||
out.write(' and ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET + ' failed');
|
|
||||||
if (this.path.length > 0) {
|
|
||||||
out.write(` in field '${this.path.join('.')}'`);
|
|
||||||
}
|
|
||||||
out.write('.\n\n');
|
|
||||||
out.write(printNode(node) + '\n');
|
|
||||||
for (let i = 1; i < this.nodes.length; i++) {
|
|
||||||
const node = this.nodes[i];
|
|
||||||
out.write(' ... in an instantiation of the following expression\n\n');
|
|
||||||
out.write(printNode(node, { indentation: i === 0 ? ' ' : ' ' }) + '\n');
|
|
||||||
}
|
|
||||||
if (leftNode !== null) {
|
|
||||||
out.indent();
|
|
||||||
out.write(ANSI_FG_YELLOW + ANSI_BOLD + `info: ` + ANSI_RESET);
|
|
||||||
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) {
|
|
||||||
out.indent();
|
|
||||||
out.write(ANSI_FG_YELLOW + ANSI_BOLD + `info: ` + ANSI_RESET);
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FieldMissingDiagnostic {
|
|
||||||
|
|
||||||
public readonly level = Level.Error;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
public fieldName: string,
|
|
||||||
public missing: Syntax | null,
|
|
||||||
public present: Syntax | null,
|
|
||||||
public cause: Syntax | null = null,
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public format(out: IndentWriter): void {
|
|
||||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
|
||||||
out.write(`field '${this.fieldName}' is required in one type but missing in another\n\n`);
|
|
||||||
out.indent();
|
|
||||||
if (this.missing !== null) {
|
|
||||||
out.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
|
||||||
out.write(`field '${this.fieldName}' is missing in this construct\n\n`);
|
|
||||||
out.write(printNode(this.missing) + '\n');
|
|
||||||
}
|
|
||||||
if (this.present !== null) {
|
|
||||||
out.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
|
||||||
out.write(`field '${this.fieldName}' is required in this construct\n\n`);
|
|
||||||
out.write(printNode(this.present) + '\n');
|
|
||||||
}
|
|
||||||
if (this.cause !== null) {
|
|
||||||
out.write(ANSI_FG_YELLOW + ANSI_BOLD + 'info: ' + ANSI_RESET);
|
|
||||||
out.write(`because of a constraint on this node:\n\n`);
|
|
||||||
out.write(printNode(this.cause) + '\n');
|
|
||||||
}
|
|
||||||
out.dedent();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class KindMismatchDiagnostic {
|
|
||||||
|
|
||||||
public readonly level = Level.Error;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
public left: Kind,
|
|
||||||
public right: Kind,
|
|
||||||
public node: Syntax | null,
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public format(out: IndentWriter): void {
|
|
||||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
|
||||||
out.write(`kind ${describeKind(this.left)} does not match with ${describeKind(this.right)}\n\n`);
|
|
||||||
if (this.node !== null) {
|
|
||||||
out.write(printNode(this.node) + '\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ModuleNotFoundDiagnostic {
|
|
||||||
|
|
||||||
public readonly level = Level.Error;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
public modulePath: string[],
|
|
||||||
public node: Syntax,
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public format(out: IndentWriter): void {
|
|
||||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
|
||||||
out.write(`a module named ${ANSI_FG_BLUE + this.modulePath.join('.') + ANSI_RESET} was not found.\n\n`);
|
|
||||||
out.write(printNode(this.node) + '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Diagnostic
|
|
||||||
= UnexpectedCharDiagnostic
|
|
||||||
| TypeclassNotFoundDiagnostic
|
|
||||||
| BindingNotFoundDiagnostic
|
|
||||||
| UnificationFailedDiagnostic
|
|
||||||
| UnexpectedTokenDiagnostic
|
|
||||||
| FieldMissingDiagnostic
|
|
||||||
| KindMismatchDiagnostic
|
|
||||||
| ModuleNotFoundDiagnostic
|
|
||||||
|
|
||||||
export interface Diagnostics {
|
|
||||||
readonly hasError: boolean;
|
|
||||||
readonly hasFatal: boolean;
|
|
||||||
add(diagnostic: Diagnostic): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DiagnosticStore {
|
|
||||||
|
|
||||||
private storage: Diagnostic[] = [];
|
|
||||||
|
|
||||||
public hasError = false;
|
|
||||||
public hasFatal = false;
|
|
||||||
|
|
||||||
public add(diagnostic: Diagnostic): void {
|
|
||||||
this.storage.push(diagnostic);
|
|
||||||
if (diagnostic.level >= Level.Error) {
|
|
||||||
this.hasError = true;
|
|
||||||
}
|
|
||||||
if (diagnostic.level >= Level.Fatal) {
|
|
||||||
this.hasFatal = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDiagnostics(): Iterable<Diagnostic> {
|
|
||||||
return this.storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ConsoleDiagnostics {
|
|
||||||
|
|
||||||
private writer = new IndentWriter(process.stderr);
|
|
||||||
|
|
||||||
public hasError = false;
|
|
||||||
public hasFatal = false;
|
|
||||||
|
|
||||||
public add(diagnostic: Diagnostic): void {
|
|
||||||
diagnostic.format(this.writer);
|
|
||||||
if (diagnostic.level >= Level.Error) {
|
|
||||||
this.hasError = true;
|
|
||||||
}
|
|
||||||
if (diagnostic.level >= Level.Fatal) {
|
|
||||||
this.hasFatal = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PrintExcerptOptions {
|
interface PrintExcerptOptions {
|
||||||
indentation?: string;
|
indentation?: string;
|
||||||
extraLineCount?: number;
|
extraLineCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function printNode(node: Syntax, options?: PrintExcerptOptions): string {
|
interface PrintNodeOptions extends PrintExcerptOptions { }
|
||||||
|
|
||||||
|
function printNode(node: Syntax, options?: PrintNodeOptions): string {
|
||||||
const file = node.getSourceFile().getFile();
|
const file = node.getSourceFile().getFile();
|
||||||
return printExcerpt(file, node.getRange(), options);
|
return printExcerpt(file, node.getRange(), options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,14 +167,18 @@ export class Emitter {
|
||||||
this.writer.write('pub ');
|
this.writer.write('pub ');
|
||||||
}
|
}
|
||||||
this.writer.write(`class `);
|
this.writer.write(`class `);
|
||||||
if (node.constraints) {
|
if (node.constraintClause) {
|
||||||
for (const constraint of node.constraints.constraints) {
|
for (const constraint of node.constraintClause.constraints) {
|
||||||
this.emit(constraint);
|
this.emit(constraint);
|
||||||
this.writer.write(`, `);
|
this.writer.write(`, `);
|
||||||
}
|
}
|
||||||
this.writer.write(' => ');
|
this.writer.write(' => ');
|
||||||
}
|
}
|
||||||
this.emit(node.constraint);
|
this.emit(node.name);
|
||||||
|
for (const type of node.types) {
|
||||||
|
this.writer.write(' ');
|
||||||
|
this.emit(type);
|
||||||
|
}
|
||||||
if (node.elements !== null) {
|
if (node.elements !== null) {
|
||||||
this.writer.write('.\n');
|
this.writer.write('.\n');
|
||||||
this.writer.indent();
|
this.writer.indent();
|
||||||
|
|
|
@ -183,14 +183,18 @@ export class Parser {
|
||||||
return new ReferenceTypeExpression(modulePath, name);
|
return new ReferenceTypeExpression(modulePath, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public parseVarTypeExpression(): VarTypeExpression {
|
||||||
|
const name = this.expectToken(SyntaxKind.Identifier);
|
||||||
|
return new VarTypeExpression(name);
|
||||||
|
}
|
||||||
|
|
||||||
public parsePrimitiveTypeExpression(): TypeExpression {
|
public parsePrimitiveTypeExpression(): TypeExpression {
|
||||||
const t0 = this.peekToken();
|
const t0 = this.peekToken();
|
||||||
switch (t0.kind) {
|
switch (t0.kind) {
|
||||||
case SyntaxKind.Identifier:
|
case SyntaxKind.Identifier:
|
||||||
{
|
return this.parseVarTypeExpression();
|
||||||
this.getToken();
|
case SyntaxKind.IdentifierAlt:
|
||||||
return new VarTypeExpression(t0);
|
return this.parseReferenceTypeExpression();
|
||||||
}
|
|
||||||
case SyntaxKind.LParen:
|
case SyntaxKind.LParen:
|
||||||
{
|
{
|
||||||
this.getToken();
|
this.getToken();
|
||||||
|
@ -220,14 +224,12 @@ export class Parser {
|
||||||
}
|
}
|
||||||
return new TupleTypeExpression(t0, elements, rparen);
|
return new TupleTypeExpression(t0, elements, rparen);
|
||||||
}
|
}
|
||||||
case SyntaxKind.IdentifierAlt:
|
|
||||||
return this.parseReferenceTypeExpression();
|
|
||||||
default:
|
default:
|
||||||
this.raiseParseError(t0, [ SyntaxKind.IdentifierAlt ]);
|
this.raiseParseError(t0, [ SyntaxKind.IdentifierAlt ]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryParseAppTypeExpression(): TypeExpression {
|
private parseAppTypeExpressionOrBelow(): TypeExpression {
|
||||||
const operator = this.parsePrimitiveTypeExpression();
|
const operator = this.parsePrimitiveTypeExpression();
|
||||||
const args = [];
|
const args = [];
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -251,7 +253,7 @@ export class Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseTypeExpression(): TypeExpression {
|
public parseTypeExpression(): TypeExpression {
|
||||||
let returnType = this.tryParseAppTypeExpression();
|
let returnType = this.parseAppTypeExpressionOrBelow();
|
||||||
const paramTypes = [];
|
const paramTypes = [];
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const t1 = this.peekToken();
|
const t1 = this.peekToken();
|
||||||
|
@ -260,7 +262,7 @@ export class Parser {
|
||||||
}
|
}
|
||||||
this.getToken();
|
this.getToken();
|
||||||
paramTypes.push(returnType);
|
paramTypes.push(returnType);
|
||||||
returnType = this.tryParseAppTypeExpression();
|
returnType = this.parseAppTypeExpressionOrBelow();
|
||||||
}
|
}
|
||||||
if (paramTypes.length === 0) {
|
if (paramTypes.length === 0) {
|
||||||
return returnType;
|
return returnType;
|
||||||
|
@ -1048,13 +1050,22 @@ export class Parser {
|
||||||
}
|
}
|
||||||
clause = new ClassConstraintClause(constraints, rarrowAlt);
|
clause = new ClassConstraintClause(constraints, rarrowAlt);
|
||||||
}
|
}
|
||||||
const constraint = this.parseClassConstraint();
|
const name = this.expectToken(SyntaxKind.IdentifierAlt);
|
||||||
|
const types = [];
|
||||||
|
for (;;) {
|
||||||
|
const t3 = this.peekToken();
|
||||||
|
if (t3.kind === SyntaxKind.BlockStart || t3.kind === SyntaxKind.LineFoldEnd) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const type = this.parseTypeExpression();
|
||||||
|
types.push(type);
|
||||||
|
}
|
||||||
this.expectToken(SyntaxKind.BlockStart);
|
this.expectToken(SyntaxKind.BlockStart);
|
||||||
const elements = [];
|
const elements = [];
|
||||||
loop: for (;;) {
|
loop: for (;;) {
|
||||||
const t3 = this.peekToken();
|
const t4 = this.peekToken();
|
||||||
let element;
|
let element;
|
||||||
switch (t3.kind) {
|
switch (t4.kind) {
|
||||||
case SyntaxKind.BlockEnd:
|
case SyntaxKind.BlockEnd:
|
||||||
this.getToken();
|
this.getToken();
|
||||||
break loop;
|
break loop;
|
||||||
|
@ -1065,12 +1076,12 @@ export class Parser {
|
||||||
element = this.parseTypeDeclaration();
|
element = this.parseTypeDeclaration();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.raiseParseError(t3, [ SyntaxKind.LetKeyword, SyntaxKind.TypeKeyword, SyntaxKind.BlockEnd ]);
|
this.raiseParseError(t4, [ SyntaxKind.LetKeyword, SyntaxKind.TypeKeyword, SyntaxKind.BlockEnd ]);
|
||||||
}
|
}
|
||||||
elements.push(element);
|
elements.push(element);
|
||||||
}
|
}
|
||||||
this.expectToken(SyntaxKind.LineFoldEnd);
|
this.expectToken(SyntaxKind.LineFoldEnd);
|
||||||
return new InstanceDeclaration(pubKeyword, t0, clause, constraint, elements);
|
return new InstanceDeclaration(pubKeyword, t0, clause, name, types, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseClassDeclaration(): ClassDeclaration {
|
public parseClassDeclaration(): ClassDeclaration {
|
||||||
|
@ -1097,7 +1108,17 @@ export class Parser {
|
||||||
}
|
}
|
||||||
clause = new ClassConstraintClause(constraints, rarrowAlt);
|
clause = new ClassConstraintClause(constraints, rarrowAlt);
|
||||||
}
|
}
|
||||||
const constraint = this.parseClassConstraint();
|
const name = this.expectToken(SyntaxKind.IdentifierAlt);
|
||||||
|
const types = [];
|
||||||
|
for (;;) {
|
||||||
|
const t1 = this.peekToken();
|
||||||
|
if (t1.kind === SyntaxKind.Identifier) {
|
||||||
|
const type = this.parseVarTypeExpression();
|
||||||
|
types.push(type);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.expectToken(SyntaxKind.BlockStart);
|
this.expectToken(SyntaxKind.BlockStart);
|
||||||
const elements = [];
|
const elements = [];
|
||||||
loop: for (;;) {
|
loop: for (;;) {
|
||||||
|
@ -1119,7 +1140,7 @@ export class Parser {
|
||||||
elements.push(element);
|
elements.push(element);
|
||||||
}
|
}
|
||||||
this.expectToken(SyntaxKind.LineFoldEnd);
|
this.expectToken(SyntaxKind.LineFoldEnd);
|
||||||
return new ClassDeclaration(pubKeyword, t0 as ClassKeyword, clause, constraint, elements);
|
return new ClassDeclaration(pubKeyword, t0 as ClassKeyword, clause, name, types, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseSourceFileElement(): SourceFileElement {
|
public parseSourceFileElement(): SourceFileElement {
|
||||||
|
|
|
@ -45,7 +45,7 @@ function lcfirst(text: string): string {
|
||||||
export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
|
export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
|
||||||
|
|
||||||
private mangleInstance(node: InstanceDeclaration): string {
|
private mangleInstance(node: InstanceDeclaration): string {
|
||||||
return lcfirst(node.constraint.name.text) + '_' + node.constraint.types.map(encode).join('');
|
return lcfirst(node.name.text) + '_' + node.types.map(encode).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
private visit(node: Syntax): Syntax {
|
private visit(node: Syntax): Syntax {
|
||||||
|
|
|
@ -57,7 +57,7 @@ export class Scope {
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case SyntaxKind.ClassDeclaration:
|
case SyntaxKind.ClassDeclaration:
|
||||||
{
|
{
|
||||||
this.add(node.constraint.name.text, node, Symkind.Typeclass);
|
this.add(node.name.text, node, Symkind.Typeclass);
|
||||||
}
|
}
|
||||||
case SyntaxKind.InstanceDeclaration:
|
case SyntaxKind.InstanceDeclaration:
|
||||||
case SyntaxKind.SourceFile:
|
case SyntaxKind.SourceFile:
|
||||||
|
|
12
src/util.ts
12
src/util.ts
|
@ -2,6 +2,8 @@
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import stream from "stream"
|
import stream from "stream"
|
||||||
|
|
||||||
|
export const isDebug = true;
|
||||||
|
|
||||||
export function first<T>(iter: Iterator<T>): T | undefined {
|
export function first<T>(iter: Iterator<T>): T | undefined {
|
||||||
return iter.next().value;
|
return iter.next().value;
|
||||||
}
|
}
|
||||||
|
@ -60,9 +62,17 @@ export class IndentWriter {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GITHUB_ISSUE_URL = 'https://github.com/boltlang/bolt/issues/'
|
||||||
|
|
||||||
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. You are invited to search this issue on GitHub or to create a new one at ${GITHUB_ISSUE_URL} .`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function implementationLimitation(test: boolean): asserts test {
|
||||||
|
if (!test) {
|
||||||
|
throw new Error(`We encountered a limitation to the implementation of this compiler. You are invited to search this issue on GitHub or to create a new one at ${GITHUB_ISSUE_URL} .`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue