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 {
|
||||
ClassDeclaration,
|
||||
EnumDeclaration,
|
||||
|
@ -23,14 +27,14 @@ import {
|
|||
describeType,
|
||||
BindingNotFoundDiagnostic,
|
||||
Diagnostics,
|
||||
FieldMissingDiagnostic,
|
||||
UnificationFailedDiagnostic,
|
||||
FieldNotFoundDiagnostic,
|
||||
TypeMismatchDiagnostic,
|
||||
KindMismatchDiagnostic,
|
||||
ModuleNotFoundDiagnostic,
|
||||
TypeclassNotFoundDiagnostic,
|
||||
FieldTypeMismatchDiagnostic,
|
||||
TypeclassDeclaredTwiceDiagnostic,
|
||||
} 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";
|
||||
|
||||
const MAX_TYPE_ERROR_COUNT = 5;
|
||||
|
@ -440,6 +444,49 @@ export type Type
|
|||
| TPresent
|
||||
| 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 {
|
||||
Star,
|
||||
Arrow,
|
||||
|
@ -552,6 +599,14 @@ class TVSet {
|
|||
|
||||
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 {
|
||||
this.mapping.set(tv.id, tv);
|
||||
}
|
||||
|
@ -573,6 +628,10 @@ class TVSet {
|
|||
this.mapping.delete(tv.id);
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.mapping.size;
|
||||
}
|
||||
|
||||
public [Symbol.iterator](): Iterator<TVar> {
|
||||
return this.mapping.values();
|
||||
}
|
||||
|
@ -690,10 +749,6 @@ class CMany extends ConstraintBase {
|
|||
|
||||
}
|
||||
|
||||
interface InstanceRef {
|
||||
node: InstanceDeclaration | null;
|
||||
}
|
||||
|
||||
type Constraint
|
||||
= CEqual
|
||||
| CMany
|
||||
|
@ -706,12 +761,19 @@ abstract class SchemeBase {
|
|||
|
||||
class Forall extends SchemeBase {
|
||||
|
||||
public typeVars: TVSet;
|
||||
|
||||
public constructor(
|
||||
public typeVars: Iterable<TVar>,
|
||||
typeVars: Iterable<TVar>,
|
||||
public constraints: Iterable<Constraint>,
|
||||
public type: Type,
|
||||
) {
|
||||
super();
|
||||
if (typeVars instanceof TVSet) {
|
||||
this.typeVars = typeVars;
|
||||
} else {
|
||||
this.typeVars = new TVSet(typeVars);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -726,6 +788,23 @@ type NodeWithReference
|
|||
| ReferenceExpression
|
||||
| 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 {
|
||||
|
||||
private mapping = new MultiMap<string, [Symkind, Scheme]>();
|
||||
|
@ -735,6 +814,9 @@ class TypeEnv {
|
|||
}
|
||||
|
||||
public add(name: string, scheme: Scheme, kind: Symkind): void {
|
||||
if (isDebug) {
|
||||
validateScheme(scheme);
|
||||
}
|
||||
this.mapping.add(name, [kind, scheme]);
|
||||
}
|
||||
|
||||
|
@ -819,6 +901,10 @@ export class Checker {
|
|||
|
||||
private contexts: InferContext[] = [];
|
||||
|
||||
private classDecls = new Map<string, ClassDeclaration>();
|
||||
private globalKindEnv = new KindEnv();
|
||||
private globalTypeEnv = new TypeEnv();
|
||||
|
||||
private solution = new TVSub();
|
||||
private kindSolution = new KVSub();
|
||||
|
||||
|
@ -827,6 +913,26 @@ export class Checker {
|
|||
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 {
|
||||
|
@ -874,7 +980,7 @@ export class Checker {
|
|||
let maxIndex = 0;
|
||||
let currUp = node.getEnclosingModule();
|
||||
outer: for (;;) {
|
||||
let currDown: SourceFile | ModuleDeclaration = currUp;
|
||||
let currDown = currUp;
|
||||
for (let i = 0; i < modulePath.length; i++) {
|
||||
const moduleName = modulePath[i];
|
||||
const nextDown = currDown.resolveModule(moduleName.text);
|
||||
|
@ -939,7 +1045,7 @@ export class Checker {
|
|||
let maxIndex = 0;
|
||||
let currUp = node.getEnclosingModule();
|
||||
outer: for (;;) {
|
||||
let currDown: SourceFile | ModuleDeclaration = currUp;
|
||||
let currDown = currUp;
|
||||
for (let i = 0; i < modulePath.length; i++) {
|
||||
const moduleName = modulePath[i];
|
||||
const nextDown = currDown.resolveModule(moduleName.text);
|
||||
|
@ -1000,7 +1106,8 @@ export class Checker {
|
|||
|
||||
private createSubstitution(scheme: Scheme): 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());
|
||||
}
|
||||
return sub;
|
||||
|
@ -1211,14 +1318,14 @@ export class Checker {
|
|||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InstanceDeclaration:
|
||||
{
|
||||
if (node.constraints !== null) {
|
||||
for (const constraint of node.constraints.constraints) {
|
||||
if (node.constraintClause !== null) {
|
||||
for (const constraint of node.constraintClause.constraints) {
|
||||
for (const typeExpr of constraint.types) {
|
||||
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);
|
||||
}
|
||||
for (const element of node.elements) {
|
||||
|
@ -1374,9 +1481,9 @@ export class Checker {
|
|||
|
||||
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) {
|
||||
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) {
|
||||
this.infer(element);
|
||||
|
@ -1695,7 +1802,7 @@ export class Checker {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1708,7 +1815,7 @@ export class Checker {
|
|||
const scheme = this.lookup(node.name, Symkind.Type);
|
||||
if (scheme === null) {
|
||||
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();
|
||||
this.addBinding(node.name.text, new Forall([], [], type), Symkind.Type);
|
||||
|
@ -1873,11 +1980,37 @@ export class Checker {
|
|||
break;
|
||||
}
|
||||
|
||||
case SyntaxKind.InstanceDeclaration:
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
@ -1911,41 +2044,44 @@ export class Checker {
|
|||
}
|
||||
this.pushContext(context);
|
||||
const kindArgs = [];
|
||||
for (const varExpr of node.varExps) {
|
||||
for (const name of node.varExps) {
|
||||
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);
|
||||
}
|
||||
const type = TApp.build(new TNominal(node, node), kindArgs);
|
||||
parentEnv.add(node.name.text, new Forall(typeVars, constraints, type), Symkind.Type);
|
||||
let elementTypes: Type[] = [];
|
||||
const type = new TNominal(node, node);
|
||||
if (node.members !== null) {
|
||||
for (const member of node.members) {
|
||||
let ctorType;
|
||||
let ctorType, elementType;
|
||||
switch (member.kind) {
|
||||
case SyntaxKind.EnumDeclarationTupleElement:
|
||||
{
|
||||
const argTypes = member.elements.map(el => this.inferTypeExpression(el));
|
||||
ctorType = TArrow.build(argTypes, TApp.build(type, kindArgs), member);
|
||||
const argTypes = member.elements.map(el => this.inferTypeExpression(el, false));
|
||||
elementType = new TTuple(argTypes, member);
|
||||
ctorType = TArrow.build(argTypes, type, member);
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.EnumDeclarationStructElement:
|
||||
{
|
||||
ctorType = new TNil(member);
|
||||
elementType = new TNil(member);
|
||||
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;
|
||||
}
|
||||
default:
|
||||
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);
|
||||
elementTypes.push(ctorType);
|
||||
elementTypes.push(elementType);
|
||||
}
|
||||
}
|
||||
this.popContext(context);
|
||||
parentEnv.add(node.name.text, new Forall(typeVars, constraints, type), Symkind.Type);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2013,37 +2149,17 @@ export class Checker {
|
|||
|
||||
public check(node: SourceFile): void {
|
||||
|
||||
const kenv = new KindEnv();
|
||||
kenv.set('Int', new KType());
|
||||
kenv.set('String', new KType());
|
||||
kenv.set('Bool', new KType());
|
||||
const skenv = new KindEnv(kenv);
|
||||
this.forwardDeclareKind(node, skenv);
|
||||
this.inferKind(node, skenv);
|
||||
const kenv = new KindEnv(this.globalKindEnv);
|
||||
this.forwardDeclareKind(node, kenv);
|
||||
this.inferKind(node, kenv);
|
||||
|
||||
const typeVars = new TVSet();
|
||||
const constraints = new ConstraintSet();
|
||||
const env = new TypeEnv();
|
||||
const env = new TypeEnv(this.globalTypeEnv);
|
||||
const context: InferContext = { typeVars, constraints, env, returnType: null };
|
||||
|
||||
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.pushContext({
|
||||
|
@ -2141,7 +2257,6 @@ export class Checker {
|
|||
}
|
||||
|
||||
const visitElements = (elements: Syntax[]) => {
|
||||
|
||||
for (const element of elements) {
|
||||
if (element.kind === SyntaxKind.LetDeclaration
|
||||
&& isFunctionDeclarationLike(element)) {
|
||||
|
@ -2152,14 +2267,21 @@ export class Checker {
|
|||
this.instantiate(scheme, null);
|
||||
}
|
||||
} else {
|
||||
const elementHasTypeEnv = hasTypeEnv(element);
|
||||
if (elementHasTypeEnv) {
|
||||
this.pushContext({ ...this.getContext(), env: element.typeEnv! });
|
||||
}
|
||||
this.infer(element);
|
||||
if(elementHasTypeEnv) {
|
||||
this.contexts.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const nodes of sccs) {
|
||||
|
||||
if (nodes.some(n => n.kind === SyntaxKind.SourceFile)) {
|
||||
if (nodes[0].kind === SyntaxKind.SourceFile) {
|
||||
assert(nodes.length === 1);
|
||||
continue;
|
||||
}
|
||||
|
@ -2222,23 +2344,29 @@ export class Checker {
|
|||
|
||||
}
|
||||
|
||||
private lookupClass(name: string): ClassDeclaration | null {
|
||||
return this.classDecls.get(name) ?? null;
|
||||
}
|
||||
|
||||
private *findInstanceContext(type: TCon, clazz: ClassDeclaration): Iterable<ClassDeclaration[]> {
|
||||
for (const instance of clazz.getInstances()) {
|
||||
assert(instance.constraint.types.length === 1);
|
||||
const instTy0 = instance.constraint.types[0];
|
||||
if (instTy0.kind === SyntaxKind.AppTypeExpression
|
||||
assert(instance.types.length === 1);
|
||||
const instTy0 = instance.types[0];
|
||||
if ((instTy0.kind === SyntaxKind.AppTypeExpression
|
||||
&& instTy0.operator.kind === SyntaxKind.ReferenceTypeExpression
|
||||
&& instTy0.operator.name.text === type.displayName) {
|
||||
if (instance.constraints === null) {
|
||||
&& instTy0.operator.name.text === type.displayName)
|
||||
|| (instTy0.kind === SyntaxKind.ReferenceTypeExpression
|
||||
&& instTy0.name.text === type.displayName)) {
|
||||
if (instance.constraintClause === null) {
|
||||
return;
|
||||
}
|
||||
for (const argType of type.argTypes) {
|
||||
const classes = [];
|
||||
for (const constraint of instance.constraints.constraints) {
|
||||
for (const constraint of instance.constraintClause.constraints) {
|
||||
assert(constraint.types.length === 1);
|
||||
const classDecl = this.lookupClass(constraint.name);
|
||||
const classDecl = this.lookupClass(constraint.name.text);
|
||||
if (classDecl === null) {
|
||||
this.diagnostics.add(new TypeclassNotFoundDiagnostic(constraint.name.text, constraint.name));
|
||||
this.diagnostics.add(new TypeclassNotFoundDiagnostic(constraint.name));
|
||||
} else {
|
||||
classes.push(classDecl);
|
||||
}
|
||||
|
@ -2296,7 +2424,7 @@ export class Checker {
|
|||
assert(right.kind === TypeKind.Present);
|
||||
const fieldName = path[path.length-1];
|
||||
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;
|
||||
}
|
||||
|
@ -2305,6 +2433,13 @@ export class Checker {
|
|||
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 => {
|
||||
|
||||
left = find(left);
|
||||
|
@ -2342,6 +2477,7 @@ export class Checker {
|
|||
propagateClassTCon(constraint, type);
|
||||
}
|
||||
} else {
|
||||
//assert(false);
|
||||
//this.diagnostics.add(new );
|
||||
}
|
||||
}
|
||||
|
@ -2475,7 +2611,7 @@ export class Checker {
|
|||
}
|
||||
|
||||
this.diagnostics.add(
|
||||
new UnificationFailedDiagnostic(
|
||||
new TypeMismatchDiagnostic(
|
||||
left.substitute(solution),
|
||||
right.substitute(solution),
|
||||
[...constraint.getNodes()],
|
||||
|
@ -2512,3 +2648,18 @@ function getVariadicMember(node: StructPattern) {
|
|||
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 path from "path"
|
||||
|
||||
import { assert, IndentWriter, JSONObject, JSONValue } from "./util";
|
||||
import { assert, implementationLimitation, IndentWriter, JSONObject, JSONValue } from "./util";
|
||||
import { isNodeWithScope, Scope } from "./scope"
|
||||
import { InferContext, Kind, KindEnv, Scheme, Type, TypeEnv } from "./checker"
|
||||
import { Emitter } from "./emitter";
|
||||
|
@ -2828,22 +2828,33 @@ export class ClassDeclaration extends SyntaxBase {
|
|||
|
||||
public readonly kind = SyntaxKind.ClassDeclaration;
|
||||
|
||||
public typeEnv?: TypeEnv;
|
||||
|
||||
public constructor(
|
||||
public pubKeyword: PubKeyword | null,
|
||||
public classKeyword: ClassKeyword,
|
||||
public constraints: ClassConstraintClause | null,
|
||||
public constraint: ClassConstraint,
|
||||
public constraintClause: ClassConstraintClause | null,
|
||||
public name: IdentifierAlt,
|
||||
public types: VarTypeExpression[],
|
||||
public elements: ClassDeclarationElement[],
|
||||
) {
|
||||
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 {
|
||||
|
||||
switch (element.kind) {
|
||||
|
||||
case SyntaxKind.LetDeclaration:
|
||||
assert(element.pattern.kind === SyntaxKind.NamedPattern);
|
||||
implementationLimitation(element.pattern.kind === SyntaxKind.NamedPattern);
|
||||
for (const other of this.elements) {
|
||||
if (other.kind === SyntaxKind.LetDeclaration
|
||||
&& other.pattern.kind === SyntaxKind.NamedPattern
|
||||
|
@ -2875,7 +2886,7 @@ export class ClassDeclaration extends SyntaxBase {
|
|||
curr = curr.parent!;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -2886,8 +2897,9 @@ export class ClassDeclaration extends SyntaxBase {
|
|||
return new ClassDeclaration(
|
||||
this.pubKeyword !== null ? this.pubKeyword.clone() : null,
|
||||
this.classKeyword.clone(),
|
||||
this.constraints !== null ? this.constraints.clone() : null,
|
||||
this.constraint.clone(),
|
||||
this.constraintClause !== null ? this.constraintClause.clone() : null,
|
||||
this.name.clone(),
|
||||
this.types.map(t => t.clone()),
|
||||
this.elements.map(element => element.clone()),
|
||||
);
|
||||
}
|
||||
|
@ -2903,7 +2915,10 @@ export class ClassDeclaration extends SyntaxBase {
|
|||
if (this.elements.length > 0) {
|
||||
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 typeEnv?: TypeEnv;
|
||||
|
||||
public constructor(
|
||||
public pubKeyword: PubKeyword | null,
|
||||
public classKeyword: InstanceKeyword,
|
||||
public constraints: ClassConstraintClause | null,
|
||||
public constraint: ClassConstraint,
|
||||
public constraintClause: ClassConstraintClause | null,
|
||||
public name: IdentifierAlt,
|
||||
public types: TypeExpression[],
|
||||
public elements: InstanceDeclarationElement[],
|
||||
) {
|
||||
super();
|
||||
|
@ -2930,8 +2948,9 @@ export class InstanceDeclaration extends SyntaxBase {
|
|||
return new InstanceDeclaration(
|
||||
this.pubKeyword !== null ? this.pubKeyword.clone() : null,
|
||||
this.classKeyword.clone(),
|
||||
this.constraints !== null ? this.constraints.clone() : null,
|
||||
this.constraint.clone(),
|
||||
this.constraintClause !== null ? this.constraintClause.clone() : null,
|
||||
this.name.clone(),
|
||||
this.types.map(t => t.clone()),
|
||||
this.elements.map(element => element.clone()),
|
||||
);
|
||||
}
|
||||
|
@ -2947,7 +2966,10 @@ export class InstanceDeclaration extends SyntaxBase {
|
|||
if (this.elements.length > 0) {
|
||||
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) {
|
||||
return node;
|
||||
}
|
||||
return new node.constructor(...newArgs);
|
||||
return new (node as any).constructor(...newArgs);
|
||||
}
|
||||
|
||||
export function canHaveInstanceDeclaration(node: Syntax): boolean {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import { TypeKind, type Type, type TArrow, TRecord, Kind, KindType } from "./checker";
|
||||
import { Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
|
||||
import { countDigits, IndentWriter } from "./util";
|
||||
import { TypeKind, type Type, Kind, KindType } from "./checker";
|
||||
import { ClassConstraint, ClassDeclaration, IdentifierAlt, InstanceDeclaration, Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
|
||||
import { assertNever, countDigits, IndentWriter } from "./util";
|
||||
|
||||
const ANSI_RESET = "\u001b[0m"
|
||||
const ANSI_BOLD = "\u001b[1m"
|
||||
|
@ -35,9 +35,28 @@ const enum Level {
|
|||
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 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);
|
||||
out.write(`unexpected character sequence '${this.actual}'.\n\n`);
|
||||
out.write(printExcerpt(this.file, new TextRange(this.position, endPos)) + '\n');
|
||||
}
|
||||
|
||||
|
||||
export class UnexpectedTokenDiagnostic implements DiagnosticBase {
|
||||
|
||||
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>> = {
|
||||
[SyntaxKind.StringLiteral]: 'a string literal',
|
||||
[SyntaxKind.Identifier]: "an identifier",
|
||||
[SyntaxKind.RArrow]: "'->'",
|
||||
[SyntaxKind.RArrowAlt]: '"=>"',
|
||||
[SyntaxKind.VBar]: "'|'",
|
||||
[SyntaxKind.Comma]: "','",
|
||||
[SyntaxKind.Colon]: "':'",
|
||||
[SyntaxKind.Integer]: "an integer",
|
||||
|
@ -86,8 +418,6 @@ const DESCRIPTIONS: Partial<Record<SyntaxKind, string>> = {
|
|||
[SyntaxKind.BlockEnd]: 'the end of an indented block',
|
||||
[SyntaxKind.LineFoldEnd]: 'the end of the current line-fold',
|
||||
[SyntaxKind.EndOfFile]: 'end-of-file',
|
||||
[SyntaxKind.RArrowAlt]: '"=>"',
|
||||
[SyntaxKind.VBar]: "'|'",
|
||||
}
|
||||
|
||||
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 {
|
||||
switch (type.kind) {
|
||||
case TypeKind.Con:
|
||||
|
@ -267,196 +533,14 @@ function getFirstNodeInTypeChain(type: Type): Syntax | null {
|
|||
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 {
|
||||
indentation?: string;
|
||||
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();
|
||||
return printExcerpt(file, node.getRange(), options);
|
||||
}
|
||||
|
|
|
@ -167,14 +167,18 @@ export class Emitter {
|
|||
this.writer.write('pub ');
|
||||
}
|
||||
this.writer.write(`class `);
|
||||
if (node.constraints) {
|
||||
for (const constraint of node.constraints.constraints) {
|
||||
if (node.constraintClause) {
|
||||
for (const constraint of node.constraintClause.constraints) {
|
||||
this.emit(constraint);
|
||||
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) {
|
||||
this.writer.write('.\n');
|
||||
this.writer.indent();
|
||||
|
|
|
@ -183,14 +183,18 @@ export class Parser {
|
|||
return new ReferenceTypeExpression(modulePath, name);
|
||||
}
|
||||
|
||||
public parseVarTypeExpression(): VarTypeExpression {
|
||||
const name = this.expectToken(SyntaxKind.Identifier);
|
||||
return new VarTypeExpression(name);
|
||||
}
|
||||
|
||||
public parsePrimitiveTypeExpression(): TypeExpression {
|
||||
const t0 = this.peekToken();
|
||||
switch (t0.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
{
|
||||
this.getToken();
|
||||
return new VarTypeExpression(t0);
|
||||
}
|
||||
return this.parseVarTypeExpression();
|
||||
case SyntaxKind.IdentifierAlt:
|
||||
return this.parseReferenceTypeExpression();
|
||||
case SyntaxKind.LParen:
|
||||
{
|
||||
this.getToken();
|
||||
|
@ -220,14 +224,12 @@ export class Parser {
|
|||
}
|
||||
return new TupleTypeExpression(t0, elements, rparen);
|
||||
}
|
||||
case SyntaxKind.IdentifierAlt:
|
||||
return this.parseReferenceTypeExpression();
|
||||
default:
|
||||
this.raiseParseError(t0, [ SyntaxKind.IdentifierAlt ]);
|
||||
}
|
||||
}
|
||||
|
||||
private tryParseAppTypeExpression(): TypeExpression {
|
||||
private parseAppTypeExpressionOrBelow(): TypeExpression {
|
||||
const operator = this.parsePrimitiveTypeExpression();
|
||||
const args = [];
|
||||
for (;;) {
|
||||
|
@ -251,7 +253,7 @@ export class Parser {
|
|||
}
|
||||
|
||||
public parseTypeExpression(): TypeExpression {
|
||||
let returnType = this.tryParseAppTypeExpression();
|
||||
let returnType = this.parseAppTypeExpressionOrBelow();
|
||||
const paramTypes = [];
|
||||
for (;;) {
|
||||
const t1 = this.peekToken();
|
||||
|
@ -260,7 +262,7 @@ export class Parser {
|
|||
}
|
||||
this.getToken();
|
||||
paramTypes.push(returnType);
|
||||
returnType = this.tryParseAppTypeExpression();
|
||||
returnType = this.parseAppTypeExpressionOrBelow();
|
||||
}
|
||||
if (paramTypes.length === 0) {
|
||||
return returnType;
|
||||
|
@ -1048,13 +1050,22 @@ export class Parser {
|
|||
}
|
||||
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);
|
||||
const elements = [];
|
||||
loop: for (;;) {
|
||||
const t3 = this.peekToken();
|
||||
const t4 = this.peekToken();
|
||||
let element;
|
||||
switch (t3.kind) {
|
||||
switch (t4.kind) {
|
||||
case SyntaxKind.BlockEnd:
|
||||
this.getToken();
|
||||
break loop;
|
||||
|
@ -1065,12 +1076,12 @@ export class Parser {
|
|||
element = this.parseTypeDeclaration();
|
||||
break;
|
||||
default:
|
||||
this.raiseParseError(t3, [ SyntaxKind.LetKeyword, SyntaxKind.TypeKeyword, SyntaxKind.BlockEnd ]);
|
||||
this.raiseParseError(t4, [ SyntaxKind.LetKeyword, SyntaxKind.TypeKeyword, SyntaxKind.BlockEnd ]);
|
||||
}
|
||||
elements.push(element);
|
||||
}
|
||||
this.expectToken(SyntaxKind.LineFoldEnd);
|
||||
return new InstanceDeclaration(pubKeyword, t0, clause, constraint, elements);
|
||||
return new InstanceDeclaration(pubKeyword, t0, clause, name, types, elements);
|
||||
}
|
||||
|
||||
public parseClassDeclaration(): ClassDeclaration {
|
||||
|
@ -1097,7 +1108,17 @@ export class Parser {
|
|||
}
|
||||
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);
|
||||
const elements = [];
|
||||
loop: for (;;) {
|
||||
|
@ -1119,7 +1140,7 @@ export class Parser {
|
|||
elements.push(element);
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -45,7 +45,7 @@ function lcfirst(text: string): string {
|
|||
export class TypeclassDictPassing implements Pass<SourceFile, SourceFile> {
|
||||
|
||||
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 {
|
||||
|
|
|
@ -57,7 +57,7 @@ export class Scope {
|
|||
switch (node.kind) {
|
||||
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.SourceFile:
|
||||
|
|
12
src/util.ts
12
src/util.ts
|
@ -2,6 +2,8 @@
|
|||
import path from "path"
|
||||
import stream from "stream"
|
||||
|
||||
export const isDebug = true;
|
||||
|
||||
export function first<T>(iter: Iterator<T>): T | undefined {
|
||||
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 {
|
||||
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