From c559d13df984fb2e5753228bc83de65cb0ada3d2 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Thu, 16 Mar 2023 21:50:15 +0100 Subject: [PATCH] Minor improvements, add some experimental type class logic and refactor diagnostics --- src/checker.ts | 287 +++++++++++---- src/cst.ts | 50 ++- src/diagnostics.ts | 608 ++++++++++++++++++-------------- src/emitter.ts | 10 +- src/parser.ts | 53 ++- src/passes/TypeclassDictPass.ts | 2 +- src/scope.ts | 2 +- src/util.ts | 12 +- 8 files changed, 658 insertions(+), 366 deletions(-) diff --git a/src/checker.ts b/src/checker.ts index 022f84d34..8daead5bb 100644 --- a/src/checker.ts +++ b/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(); + public constructor(iterable?: Iterable) { + 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 { 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, + typeVars: Iterable, public constraints: Iterable, 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(); @@ -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(); + 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; } @@ -2221,24 +2343,30 @@ export class Checker { } + + private lookupClass(name: string): ClassDeclaration | null { + return this.classDecls.get(name) ?? null; + } private *findInstanceContext(type: TCon, clazz: ClassDeclaration): Iterable { 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 +} + diff --git a/src/cst.ts b/src/cst.ts index 203dc56a1..76b1db596 100644 --- a/src/cst.ts +++ b/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 { + 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(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 { diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 590cfffde..6575fc20f 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -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 { + 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> = { [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> = { [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 { - 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); } diff --git a/src/emitter.ts b/src/emitter.ts index 7cb2d2b4a..9b8fede7b 100644 --- a/src/emitter.ts +++ b/src/emitter.ts @@ -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(); diff --git a/src/parser.ts b/src/parser.ts index d9da2048b..159169cf2 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -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 { diff --git a/src/passes/TypeclassDictPass.ts b/src/passes/TypeclassDictPass.ts index 330671b2f..c0f3cf157 100644 --- a/src/passes/TypeclassDictPass.ts +++ b/src/passes/TypeclassDictPass.ts @@ -45,7 +45,7 @@ function lcfirst(text: string): string { export class TypeclassDictPassing implements Pass { 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 { diff --git a/src/scope.ts b/src/scope.ts index 0c63f1970..363285bde 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -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: diff --git a/src/util.ts b/src/util.ts index b78bf6729..bb50abae5 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,6 +2,8 @@ import path from "path" import stream from "stream" +export const isDebug = true; + export function first(iter: Iterator): 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} .`); } }