Minor improvements, add some experimental type class logic and refactor diagnostics

This commit is contained in:
Sam Vervaeck 2023-03-16 21:50:15 +01:00
parent df5f857905
commit c559d13df9
8 changed files with 658 additions and 366 deletions

View file

@ -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;
} }
@ -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[]> { 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
}

View file

@ -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 {

View file

@ -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);
} }

View file

@ -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();

View file

@ -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 {

View file

@ -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 {

View file

@ -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:

View file

@ -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} .`);
} }
} }