From e01a97037709786abbfff825a0788e1df30a6853 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Mon, 8 Apr 2024 19:57:10 +0200 Subject: [PATCH] [WIP] Further attempt to get typeclasses working --- bootstrap/js/compiler/src/analysis.ts | 2 +- bootstrap/js/compiler/src/checker.ts | 242 ++++++++++++------ bootstrap/js/compiler/src/constraints.ts | 39 ++- bootstrap/js/compiler/src/cst.ts | 99 ++++++- bootstrap/js/compiler/src/diagnostics.ts | 13 +- bootstrap/js/compiler/src/emitter.ts | 4 +- bootstrap/js/compiler/src/parser.ts | 36 ++- .../compiler/src/passes/TypeclassDictPass.ts | 4 +- bootstrap/js/compiler/src/scanner.ts | 1 + bootstrap/js/compiler/src/scope.ts | 19 +- bootstrap/js/compiler/src/types.ts | 67 +++++ 11 files changed, 424 insertions(+), 102 deletions(-) diff --git a/bootstrap/js/compiler/src/analysis.ts b/bootstrap/js/compiler/src/analysis.ts index 7179ef3e5..c823e711d 100644 --- a/bootstrap/js/compiler/src/analysis.ts +++ b/bootstrap/js/compiler/src/analysis.ts @@ -13,7 +13,7 @@ export class Analyser { const addReference = (scope: Scope, name: string) => { const target = scope.lookup(name); - if (source === null || target === null || target.kind === SyntaxKind.Param) { + if (source === null || target === null || isParam(target.kind)) { return; } assert(source.kind === SyntaxKind.LetDeclaration); diff --git a/bootstrap/js/compiler/src/checker.ts b/bootstrap/js/compiler/src/checker.ts index 0ac901265..174d38c3e 100644 --- a/bootstrap/js/compiler/src/checker.ts +++ b/bootstrap/js/compiler/src/checker.ts @@ -5,6 +5,7 @@ import { ExprOperator, Identifier, IdentifierAlt, + InstanceDeclaration, LetDeclaration, Pattern, ReferenceExpression, @@ -23,58 +24,31 @@ import { KindMismatchDiagnostic, ModuleNotFoundDiagnostic, TypeclassNotFoundDiagnostic, - TypeclassDeclaredTwiceDiagnostic, FieldNotFoundDiagnostic, TypeMismatchDiagnostic, } from "./diagnostics"; -import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn } from "./util"; +import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util"; import { Analyser } from "./analysis"; import { InspectOptions } from "util"; -import { TypeKind, TApp, TArrow, TCon, TField, TNil, TPresent, TRegularVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar, buildTupleTypeWithLoc, buildTupleType, isTVar } from "./types"; +import { TypeKind, TApp, TArrow, TCon, TField, TNil, TPresent, TRegularVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar, buildTupleTypeWithLoc, buildTupleType, labelTag } from "./types"; import { CEmpty, CEqual, CMany, Constraint, ConstraintKind, ConstraintSet } from "./constraints"; -// export class Qual { +// class IsIn { // 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 className: string, // public type: Type, // ) { // } // public substitute(sub: TVSub): Pred { -// return new IsInPred(this.id, this.type.substitute(sub)); - +// return new IsIn(this.className, this.type.substitute(sub)); // } // } -// type Pred = IsInPred; +// type Pred = IsIn; export const enum KindType { Type, @@ -371,6 +345,11 @@ function hasTypeVar(typeVars: TVSet, type: Type): boolean { return false; } +interface ClassMeta { + decl: ClassDeclaration | null; + instances: Set; +} + export class Checker { private nextTypeVarId = 0; @@ -382,7 +361,7 @@ export class Checker { private boolType = this.createTCon('Bool'); private unitType = buildTupleType([]); - private classDecls = new Map(); + private classDecls = new Map(); private globalKindEnv = new KindEnv(); private globalTypeEnv = new TypeEnv(); @@ -779,8 +758,8 @@ export class Checker { case ConstraintKind.Empty: return constraint; case ConstraintKind.Equal: - constraint.left = this.simplifyType(constraint.left) - constraint.right = this.simplifyType(constraint.right) + // constraint.left = this.simplifyType(constraint.left) + // constraint.right = this.simplifyType(constraint.right) const newConstraint = constraint.substitute(sub); newConstraint.node = node; newConstraint.prevInstantiation = constraint; @@ -877,13 +856,15 @@ export class Checker { } case SyntaxKind.NestedTypeExpression: + case SyntaxKind.InstanceTypeExpression: { kind = this.inferKindFromTypeExpression(node.typeExpr, env); break; } default: - throw new Error(`Unexpected ${node}`); + assertNever(node); + } // We store the kind on the node so there is a one-to-one correspondence @@ -1580,6 +1561,7 @@ export class Checker { break; } + case SyntaxKind.InstanceTypeExpression: case SyntaxKind.NestedTypeExpression: type = this.inferTypeExpression(node.typeExpr, introduceTypeVars); break; @@ -1613,11 +1595,16 @@ export class Checker { case SyntaxKind.TypeExpressionWithConstraints: { - // TODO - // for (const constraint of node.constraints) { - // implementationLimitation(constraint.types.length === 1); - // this.addConstraint(new CClass(constraint.name.text, this.inferTypeExpression(constraint.types[0]), constraint.name)); - // } + for (const constraint of node.constraints) { + implementationLimitation(constraint.types.length === 1); + this.addConstraint( + new CClass( + constraint.name.text, + this.inferTypeExpression(constraint.types[0]), + constraint.name + ) + ); + } return this.inferTypeExpression(node.typeExpr, introduceTypeVars); } @@ -1646,7 +1633,7 @@ export class Checker { } default: - throw new Error(`Unrecognised ${node}`); + assertNever(node); } @@ -1778,6 +1765,18 @@ export class Checker { } + private getClassMeta(name: string): ClassMeta { + let meta = this.classDecls.get(name); + if (meta === undefined) { + meta = { + decl: null, + instances: new Set(), + }; + this.classDecls.set(name, meta); + } + return meta; + } + private initialize(node: Syntax): void { switch (node.kind) { @@ -1816,6 +1815,11 @@ export class Checker { case SyntaxKind.ClassDeclaration: { + const meta = this.getClassMeta(node.name.text); + if (meta.decl !== undefined) { + // TODO class declaration already exists diagnostic + } + meta.decl = node; const info = this.getInfo(node); const env = info.typeEnv = new TypeEnv(); for (const tv of node.types) { @@ -1830,14 +1834,19 @@ export class Checker { case SyntaxKind.InstanceDeclaration: { - if (!this.classDecls.has(node.name.text)) { + const meta = this.getClassMeta(node.name.text); + meta.instances.add(node); + + if (meta.decl === null) { this.diagnostics.add(new TypeclassNotFoundDiagnostic(node.name.text, node.name)); } + const info = this.getInfo(node); info.typeEnv = new TypeEnv(); for (const element of node.elements) { this.initialize(element); } + break; } @@ -1974,23 +1983,30 @@ export class Checker { typeArgs.push(typeArg); } + // const tagType = TApp.build( + // this.createTCon(node.name.text, node.name), + // typeArgs, + // node.name + // ); + const tagType = this.createTCon(node.name.text, node.name); const fields = new Map(); const restType = new TNil(node); + fields.set(labelTag, tagType); + if (node.fields !== null) { for (const field of node.fields) { fields.set(field.name.text, this.inferTypeExpression(field.typeExpr)); } } - const type = this.createTCon(node.name.text, node.name); const recordType = TField.build(fields, restType); this.polyContextStack.pop(); this.typeEnvStack.pop(); - parentEnv.add(node.name.text, new Forall(poly.typeVars, new CMany(poly.constraints), type), Symkind.Type); - parentEnv.add(node.name.text, new Forall(poly.typeVars, new CMany(poly.constraints), new TArrow(recordType, type)), Symkind.Var); + parentEnv.add(node.name.text, new Forall(poly.typeVars, new CMany(poly.constraints), recordType), Symkind.Type); + // parentEnv.add(node.name.text, new Forall(poly.typeVars, new CMany(poly.constraints), new TArrow(recordType, type)), Symkind.Var); break; } @@ -2041,7 +2057,16 @@ export class Checker { const paramTypes = node.params.map(param => { const paramType = this.createTRegularVar(); - this.inferBindings(param.pattern, paramType) + switch (param.kind) { + case SyntaxKind.PlainParam: + this.inferBindings(param.pattern, paramType) + break; + case SyntaxKind.InstanceParam: + this.addBinding(param.name.text, Forall.mono(paramType), Symkind.Var); + break; + default: + assertNever(param); + } return paramType; }); @@ -2094,17 +2119,35 @@ export class Checker { } + // private findInstanceContext(sig: Type[], clazz: ClassDeclaration): Iterable { + + // const contexts = []; + // const meta = this.getClassMeta(clazz.name.text); + + // // TODO should be a seperate verification pass somewhere + // // if (meta.decl === null) { + // // this.diagnostics.add(new TypeclassNotFoundDiagnostic(clazz.name.text)); + // // } + + // for (const instance of meta.instances) { + // let i = 0; + // for (const type of instance.types) { + // // TODO might need unification + // const left = sig[i++]; + // const right = this.getTypeOfNode(type); + // if (assignableTo(left, right)) { + // contexts.push(context); + // } + // } + // } + + // return contexts; + // } + private path: (string | number)[] = []; private constraint: Constraint | null = null; private maxTypeErrorCount = 5; - private find(type: Type): Type { - while (type.kind === TypeKind.RegularVar && this.typeSolution.has(type)) { - type = this.typeSolution.get(type)!; - } - return type; - } - private unifyField(left: Type, right: Type, enableDiagnostics: boolean): boolean { const swap = () => { [right, left] = [left, right]; } @@ -2132,7 +2175,6 @@ export class Checker { return this.unify(left.type, right.type, enableDiagnostics); } - private unify(left: Type, right: Type, enableDiagnostics: boolean): boolean { //console.log(`unify ${describeType(left)} @ ${left.node && left.node.constructor && left.node.constructor.name} ~ ${describeType(right)} @ ${right.node && right.node.constructor && right.node.constructor.name}`); @@ -2167,30 +2209,33 @@ export class Checker { // propagating the type classes that 'left' requires to 'right'. // If 'right' is another type variable, we're lucky. We just copy // the missing type classes from 'left' to 'right'. Otherwise, - const propagateClasses = (classes: Iterable, type: Type) => { - if (isTVar(type)) { - for (const constraint of classes) { - type.context.add(constraint); - } - } else if (type.kind === TypeKind.Con) { - for (const constraint of classes) { - propagateClassTCon(constraint, type); - } - } else { - //assert(false); - //this.diagnostics.add(new ); - } - } + //const propagateClasses = (classes: Iterable, type: Type) => { + // if (isTVar(type)) { + // for (const constraint of classes) { + // type.context.add(constraint); + // } + // } else if (isSignature(type)) { + // const sig = getSignature(type); + // for (const constraint of classes) { + // propagateClassTCon(constraint, sig); + // } + // } else { + // assert(false); + // //this.diagnostics.add(new ); + // } + //} - const propagateClassTCon = (clazz: ClassDeclaration, type: TCon) => { - const s = this.findInstanceContext(type, clazz); - let i = 0; - for (const classes of s) { - propagateClasses(classes, type.types[i++]); - } - } + //const propagateClassTCon = (clazz: ClassDeclaration, sig: Type[]) => { + // const s = this.findInstanceContext(sig, clazz); + // let i = 1; + // for (const classes of s) { + // propagateClasses(classes, sig[i++]); + // } + //} - propagateClasses(left.context, right); + //if (left.context.size > 0) { + // propagateClasses(left.context, right); + //} // We are all clear; set the actual type of left to right. left.set(right); @@ -2305,6 +2350,35 @@ export class Checker { return false; } + // private inHnf(p: Pred): boolean { + // let curr = p.type; + // for (;;) { + // if (isTVar(curr)) { + // return true; + // } + // if (curr.kind === TypeKind.Con) { + // return false; + // } + // if (curr.kind === TypeKind.App) { + // curr = curr.left; + // continue; + // } + // unreachable(); + // } + // } + + // private toHnf(p: Pred): Pred[] { + // if (this.inHnf(p)) { + // return [ p ]; + // } + // const result = this.byInst(p); + // if (result === undefined) { + // // TODO add diagnostic + // throw new Error(`context reduction`); + // } + // result.map(this.toHnf.bind(this)).flatten(); + // } + public solve(constraint: Constraint): void { let queue = [ constraint ]; @@ -2317,6 +2391,9 @@ export class Checker { switch (constraint.kind) { + case ConstraintKind.Empty: + break; + case ConstraintKind.Many: { for (const element of constraint.elements) { @@ -2337,13 +2414,20 @@ export class Checker { break; } + case ConstraintKind.Class: + // TODO + break; + + default: + assertNever(constraint);; + } } } - private lookupClass(name: string): ClassDeclaration | null { + private lookupClass(name: string): ClassMeta | null { return this.classDecls.get(name) ?? null; } diff --git a/bootstrap/js/compiler/src/constraints.ts b/bootstrap/js/compiler/src/constraints.ts index 99664f469..865aae0e9 100644 --- a/bootstrap/js/compiler/src/constraints.ts +++ b/bootstrap/js/compiler/src/constraints.ts @@ -6,6 +6,7 @@ import { first, InspectFn, last, toStringTag } from "./util"; export const enum ConstraintKind { Equal, + // Class, Many, Empty, } @@ -99,19 +100,48 @@ export class CMany extends ConstraintBase { } } - public [toStringTag](currentDepth: number, { depth = 2, ...options }: InspectOptions, inspect: InspectFn): string { + public [toStringTag](_depth: number, opts: InspectOptions, inspect: InspectFn): string { if (this.elements.length === 0) { return '[]'; } let out = '[\n'; - const newOptions = { ...options, depth: depth === null ? null : depth - 1 }; - out += this.elements.map(constraint => ' ' + inspect(constraint, newOptions)).join('\n'); + out += this.elements.map(constraint => ' ' + inspect(constraint, opts)).join('\n'); out += '\n]'; return out; } } +// export class CClass extends ConstraintBase { + +// public readonly kind = ConstraintKind.Class; + +// public constructor( +// public className: string, +// public type: Type, +// public node: Syntax, +// ) { +// super(); +// } + +// public substitute(sub: TVSub): Constraint { +// return new CClass( +// this.className, +// this.type.substitute(sub), +// this.node, +// ); +// } + +// public *freeTypeVars(): Iterable { +// yield* this.type.getTypeVars(); +// } + +// public [toStringTag](_depth: number, opts: InspectOptions, inspect: InspectFn): string { +// return this.className + ' => ' + inspect(this.type, opts); +// } + +// } + export class CEmpty extends ConstraintBase { public readonly kind = ConstraintKind.Empty; @@ -132,9 +162,10 @@ export class CEmpty extends ConstraintBase { export type Constraint = CEqual + // | CClass | CMany | CEmpty -export class ConstraintSet extends Array { +export class ConstraintSet extends Array { } diff --git a/bootstrap/js/compiler/src/cst.ts b/bootstrap/js/compiler/src/cst.ts index a67fc8ca5..ba5851683 100644 --- a/bootstrap/js/compiler/src/cst.ts +++ b/bootstrap/js/compiler/src/cst.ts @@ -7,6 +7,7 @@ import { isNodeWithScope, Scope } from "./scope" import type { Kind, Scheme } from "./checker" import type { Type } from "./types"; import { Emitter } from "./emitter"; +import { warn } from "console"; export type TextSpan = [number, number]; @@ -81,7 +82,7 @@ export class TextFile { } -export const enum SyntaxKind { +export enum SyntaxKind { // Tokens Identifier, @@ -140,6 +141,7 @@ export const enum SyntaxKind { NestedTypeExpression, TupleTypeExpression, ForallTypeExpression, + InstanceTypeExpression, TypeExpressionWithConstraints, // Patterns @@ -204,12 +206,15 @@ export const enum SyntaxKind { EnumDeclarationStructElement, EnumDeclarationTupleElement, + // Parameters + PlainParam, + InstanceParam, + // Other nodes WrappedOperator, MatchArm, Initializer, TypeAssert, - Param, SourceFile, ClassConstraint, ClassConstraintClause, @@ -1486,6 +1491,44 @@ export class NestedTypeExpression extends SyntaxBase { } +export class InstanceTypeExpression extends SyntaxBase { + + public readonly kind = SyntaxKind.InstanceTypeExpression; + + public constructor( + public lbrace1: LBrace, + public lbrace2: LBrace, + public name: Identifier | null = null, + public colon: Colon | null = null, + public typeExpr: TypeExpression, + public rbrace1: RBrace, + public rbrace2: RBrace, + ) { + super(); + } + + public clone(): InstanceTypeExpression { + return new InstanceTypeExpression( + this.lbrace1, + this.lbrace2, + this.name, + this.colon, + this.typeExpr, + this.rbrace1, + this.rbrace2, + ); + } + + public getFirstToken(): Token { + return this.lbrace1; + } + + public getLastToken(): Token { + return this.rbrace2; + } + +} + export type TypeExpression = ReferenceTypeExpression | ArrowTypeExpression @@ -1495,6 +1538,7 @@ export type TypeExpression | TupleTypeExpression | ForallTypeExpression | TypeExpressionWithConstraints + | InstanceTypeExpression export class NamedPattern extends SyntaxBase { @@ -2494,9 +2538,43 @@ export type Statement | IfStatement | AssignStatement -export class Param extends SyntaxBase { +export class InstanceParam extends SyntaxBase { - public readonly kind = SyntaxKind.Param; + public readonly kind = SyntaxKind.InstanceParam; + + public constructor( + public lbrace1: LBrace, + public lbrace2: LBrace, + public name: Identifier, + public rbrace1: RBrace, + public rbrace2: RBrace, + ) { + super(); + } + + public clone(): InstanceParam { + return new InstanceParam( + this.lbrace1, + this.lbrace2, + this.name, + this.rbrace1, + this.rbrace2, + ); + } + + public getFirstToken(): Token { + return this.lbrace1; + } + + public getLastToken(): Token { + return this.rbrace2; + } + +} + +export class PlainParam extends SyntaxBase { + + public readonly kind = SyntaxKind.PlainParam; public constructor( public pattern: Pattern, @@ -2504,8 +2582,8 @@ export class Param extends SyntaxBase { super(); } - public clone(): Param { - return new Param( + public clone(): PlainParam { + return new PlainParam( this.pattern.clone(), ); } @@ -2520,6 +2598,15 @@ export class Param extends SyntaxBase { } +export type Param + = InstanceParam + | PlainParam + +export function isParam(node: Syntax): node is Param { + return node.kind === SyntaxKind.PlainParam + || node.kind === SyntaxKind.InstanceParam; +} + export class EnumDeclarationStructElement extends SyntaxBase { public readonly kind = SyntaxKind.EnumDeclarationStructElement; diff --git a/bootstrap/js/compiler/src/diagnostics.ts b/bootstrap/js/compiler/src/diagnostics.ts index 28b6a60b1..5e5c71c3c 100644 --- a/bootstrap/js/compiler/src/diagnostics.ts +++ b/bootstrap/js/compiler/src/diagnostics.ts @@ -1,8 +1,10 @@ import { Kind, KindType } from "./checker"; -import { type Type, TypeKind } from "./types" +import { type Type, TypeKind, labelTag } from "./types" import { ClassConstraint, ClassDeclaration, IdentifierAlt, InstanceDeclaration, Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst"; -import { assertNever, countDigits, IndentWriter } from "./util"; +import { assert, assertNever, countDigits, IndentWriter } from "./util"; +import { unwatchFile } from "fs"; +import { warn } from "console"; const ANSI_RESET = "\u001b[0m" const ANSI_BOLD = "\u001b[1m" @@ -537,6 +539,13 @@ export function describeType(type: Type): string { } case TypeKind.Field: { + // let curr: Type = type; + // while (curr.kind === TypeKind.Field) { + // if (curr.name === labelTag) { + // return describeType(curr.type); + // } + // curr = curr.restType; + // } let out = '{ ' + type.name + ': ' + describeType(type.type); type = type.restType; while (type.kind === TypeKind.Field) { diff --git a/bootstrap/js/compiler/src/emitter.ts b/bootstrap/js/compiler/src/emitter.ts index 9b8fede7b..8999d2e8e 100644 --- a/bootstrap/js/compiler/src/emitter.ts +++ b/bootstrap/js/compiler/src/emitter.ts @@ -12,7 +12,7 @@ export class Emitter { public emit(node: Syntax): void { switch (node.kind) { - + case SyntaxKind.ModuleDeclaration: this.writer.write(`mod ${node.name.text}`); if (node.elements === null) { @@ -91,7 +91,7 @@ export class Emitter { this.writer.write(node.name.text); break; - case SyntaxKind.Param: + case SyntaxKind.PlainParam: this.emit(node.pattern); break; diff --git a/bootstrap/js/compiler/src/parser.ts b/bootstrap/js/compiler/src/parser.ts index 677406e23..4b02fd6de 100644 --- a/bootstrap/js/compiler/src/parser.ts +++ b/bootstrap/js/compiler/src/parser.ts @@ -1,4 +1,5 @@ +import { warn } from "console"; import { ReferenceTypeExpression, SourceFile, @@ -73,8 +74,12 @@ import { Annotations, ExprOperator, Integer, + InstanceParam, + PlainParam, + InstanceTypeExpression, } from "./cst" import { Stream } from "./util"; +import { wrap } from "module"; export class ParseError extends Error { @@ -196,7 +201,24 @@ export class Parser { } public parsePrimitiveTypeExpression(): TypeExpression { - const t0 = this.peekToken(); + const t0 = this.peekToken(1); + const t1 = this.peekToken(2); + if (t0.kind === SyntaxKind.LBrace && t1.kind === SyntaxKind.LBrace) { + this.getToken(); + this.getToken(); + let name = null; + let colon = null; + const t2 = this.peekToken(2); + if (t2.kind === SyntaxKind.Colon) { + name = this.expectToken(SyntaxKind.Identifier); + this.getToken(); + colon = t2; + } + const typeExpr = this.parseTypeExpression(); + const t3 = this.expectToken(SyntaxKind.RBrace); + const t4 = this.expectToken(SyntaxKind.RBrace); + return new InstanceTypeExpression(t0, t1, name, colon, typeExpr, t3, t4); + } switch (t0.kind) { case SyntaxKind.Identifier: return this.parseVarTypeExpression(); @@ -893,8 +915,18 @@ export class Parser { } public parseParam(): Param { + const t0 = this.peekToken(1); + const t1 = this.peekToken(2); + if (t0.kind === SyntaxKind.LBrace && t1.kind === SyntaxKind.LBrace) { + this.getToken(); + this.getToken(); + const name = this.expectToken(SyntaxKind.Identifier); + const t3 = this.expectToken(SyntaxKind.RBrace); + const t4 = this.expectToken(SyntaxKind.RBrace); + return new InstanceParam(t0, t1, name, t3, t4); + } const pattern = this.parsePattern(); - return new Param(pattern); + return new PlainParam(pattern); } private lookaheadIsAssignment(): boolean { diff --git a/bootstrap/js/compiler/src/passes/TypeclassDictPass.ts b/bootstrap/js/compiler/src/passes/TypeclassDictPass.ts index d10c355ad..529d5708b 100644 --- a/bootstrap/js/compiler/src/passes/TypeclassDictPass.ts +++ b/bootstrap/js/compiler/src/passes/TypeclassDictPass.ts @@ -17,7 +17,7 @@ import { FunctionExpression, Backslash, canHaveInstanceDeclaration, - vistEachChild + visitEachChild } from "../cst"; import { Pass } from "../program"; import { assert } from "../util"; @@ -50,7 +50,7 @@ export class TypeclassDictPassing implements Pass { private visit(node: Syntax): Syntax { if (canHaveInstanceDeclaration(node)) { - return vistEachChild(node, this.visit.bind(this)); + return visitEachChild(node, this.visit.bind(this)); } if (node.kind === SyntaxKind.InstanceDeclaration) { const decl = new LetDeclaration( diff --git a/bootstrap/js/compiler/src/scanner.ts b/bootstrap/js/compiler/src/scanner.ts index 759024085..40d1e43b9 100644 --- a/bootstrap/js/compiler/src/scanner.ts +++ b/bootstrap/js/compiler/src/scanner.ts @@ -1,4 +1,5 @@ +import { warn } from "console"; import { SyntaxKind, Token, diff --git a/bootstrap/js/compiler/src/scope.ts b/bootstrap/js/compiler/src/scope.ts index 99b015c02..9adb9fe76 100644 --- a/bootstrap/js/compiler/src/scope.ts +++ b/bootstrap/js/compiler/src/scope.ts @@ -1,5 +1,6 @@ +import { warn } from "console"; import { LetDeclaration, Pattern, SourceFile, Syntax, SyntaxKind } from "./cst"; -import { MultiMap } from "./util"; +import { MultiMap, assertNever } from "./util"; export type NodeWithScope = SourceFile @@ -96,13 +97,23 @@ export class Scope { case SyntaxKind.StructDeclaration: { this.add(node.name.text, node, Symkind.Type); - this.add(node.name.text, node, Symkind.Var); + // TODO remove this? + // this.add(node.name.text, node, Symkind.Var); break; } case SyntaxKind.LetDeclaration: { for (const param of node.params) { - this.scanPattern(param.pattern, param); + switch (param.kind) { + case SyntaxKind.PlainParam: + this.scanPattern(param.pattern, param); + break; + case SyntaxKind.InstanceParam: + this.add(node.name.text, param, Symkind.Var); + break; + default: + assertNever(param); + } } if (node === this.node) { if (node.body !== null && node.body.kind === SyntaxKind.BlockBody) { @@ -116,7 +127,7 @@ export class Scope { break; } default: - throw new Error(`Unexpected ${node.constructor.name}`); + assertNever(node); } } diff --git a/bootstrap/js/compiler/src/types.ts b/bootstrap/js/compiler/src/types.ts index 947700518..253e59a28 100644 --- a/bootstrap/js/compiler/src/types.ts +++ b/bootstrap/js/compiler/src/types.ts @@ -14,6 +14,7 @@ export enum TypeKind { Nil, Absent, Present, + Tag, } export abstract class TypeBase { @@ -457,6 +458,40 @@ export class TApp extends TypeBase { } +export const labelTag = '____tag'; + +// export class TTag extends TypeBase { + +// public readonly kind = TypeKind.Tag; + +// public constructor( +// public name: string, +// public node: Syntax | null = null, +// ) { +// super(); +// } + +// public shallowClone(): Type { +// return new TTag( +// this.name, +// this.node, +// ); +// } + +// public *getTypeVars(): Iterable { +// // noop +// } + +// public substitute(sub: TVSub): Type { +// return this; +// } + +// public [toStringTag]() { +// return this.name; +// } + +// } + export type Type = TCon | TArrow @@ -467,11 +502,43 @@ export type Type | TNil | TPresent | TAbsent + // | TTag export type TVar = TRegularVar | TRigidVar + +export function getSignature(type: Type): Type[] { + const out = []; + let stack = [ type ]; + for (;;) { + const child = stack.pop()!; + if (child.kind === TypeKind.App) { + stack.push(child.left); + stack.push(child.right); + } else { + out.push(child); + } + if (stack.length === 0) { + break; + } + } + return out; +} + +export function isSignature(type: Type): boolean { + return type.kind === TypeKind.Con + || type.kind === TypeKind.App; +} + +export function assignableTo(left: Type, right: Type): boolean { + if (left.kind === TypeKind.Con && right.kind == TypeKind.Con) { + return left.id === right.id; + } + return false; +} + export function typesEqual(a: Type, b: Type): boolean { if (a.kind !== b.kind) { return false;