Improve handing of struct/enum types and declarations
Also removes TNominal from the list of types because it is redundant w.r.t. TCon.
This commit is contained in:
parent
bd4ed57c46
commit
985e2d0652
6 changed files with 88 additions and 132 deletions
|
@ -201,6 +201,7 @@ program.command('verify', { hidden: true })
|
|||
|
||||
const uncaughtDiagnostics = new Set(diagnostics);
|
||||
|
||||
// TODO check comments that did not match any diagnostic
|
||||
for (const [line, comment] of file.comments) {
|
||||
if (comment[0].kind === SyntaxKind.At && comment[1].kind === SyntaxKind.Identifier && comment[1].text === 'expect_diagnostic' && comment[2].kind === SyntaxKind.StringLiteral) {
|
||||
for (const diagnostic of uncaughtDiagnostics) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
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, TNominal, TPresent, TTuple, TUniVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar, TTupleIndex } from "./types";
|
||||
import { TypeKind, TApp, TArrow, TCon, TField, TNil, TPresent, TTuple, TUniVar, TVSet, TVSub, Type, TypeBase, TAbsent, TRigidVar, TVar, TTupleIndex } from "./types";
|
||||
import { CClass, CEmpty, CEqual, CMany, Constraint, ConstraintKind, ConstraintSet } from "./constraints";
|
||||
|
||||
// export class Qual {
|
||||
|
@ -384,9 +384,9 @@ export class Checker {
|
|||
private nextKindVarId = 0;
|
||||
private nextConTypeId = 0;
|
||||
|
||||
private stringType = this.createTCon([], 'String');
|
||||
private intType = this.createTCon([], 'Int');
|
||||
private boolType = this.createTCon([], 'Bool');
|
||||
private stringType = this.createTCon('String');
|
||||
private intType = this.createTCon('Int');
|
||||
private boolType = this.createTCon('Bool');
|
||||
|
||||
private contexts: InferContext[] = [];
|
||||
|
||||
|
@ -436,8 +436,8 @@ export class Checker {
|
|||
return this.boolType;
|
||||
}
|
||||
|
||||
private createTCon(types: Type[], name: string): TCon {
|
||||
return new TCon(this.nextConTypeId++, types, name);
|
||||
private createTCon(name: string, node: Syntax | null = null): TCon {
|
||||
return new TCon(this.nextConTypeId++, name, node);
|
||||
}
|
||||
|
||||
private createTypeVar(node: Syntax | null = null): TUniVar {
|
||||
|
@ -1447,12 +1447,13 @@ export class Checker {
|
|||
|
||||
case SyntaxKind.StructExpression:
|
||||
{
|
||||
type = new TNil(node);
|
||||
const fields = new Map<string, Type>();
|
||||
const restType = new TNil(node);
|
||||
for (const member of node.members) {
|
||||
switch (member.kind) {
|
||||
case SyntaxKind.StructExpressionField:
|
||||
{
|
||||
type = new TField(member.name.text, new TPresent(this.inferExpression(member.expression)), type, node);
|
||||
fields.set(member.name.text, this.inferExpression(member.expression));
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.PunnedStructExpressionField:
|
||||
|
@ -1465,15 +1466,14 @@ export class Checker {
|
|||
} else {
|
||||
fieldType = this.instantiate(scheme, member);
|
||||
}
|
||||
type = new TField(member.name.text, new TPresent(fieldType), type, node);
|
||||
fields.set(member.name.text, fieldType);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unexpected ${member}`);
|
||||
}
|
||||
}
|
||||
// FIXME build a type rather than sorting it
|
||||
type = TField.sort(type);
|
||||
type = TField.build(fields, restType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1644,21 +1644,22 @@ export class Checker {
|
|||
this.inferBindings(pattern.pattern, type, typeVars, constraints);
|
||||
break;
|
||||
|
||||
// case SyntaxKind.NamedTuplePattern:
|
||||
// {
|
||||
// const scheme = this.lookup(pattern.name, Symkind.Type);
|
||||
// if (scheme === null) {
|
||||
// return this.createTypeVar();
|
||||
// }
|
||||
// let tupleType = new TTuple(pattern.elements.map(p =>
|
||||
// this.inferBindings(p, this.createTypeVar(), typeVars, constraints));
|
||||
// // FIXME not tested
|
||||
// this.addConstraint(new CEqual(tupleType, type, pattern));
|
||||
// return TApp.build(
|
||||
// new TNominal(scheme.type.node as StructDeclaration | EnumDeclaration, pattern),
|
||||
// tupleType
|
||||
// );
|
||||
// }
|
||||
case SyntaxKind.NamedTuplePattern:
|
||||
{
|
||||
const scheme = this.lookup(pattern.name, Symkind.Var);
|
||||
if (scheme === null) {
|
||||
return;
|
||||
}
|
||||
const ctorType = this.instantiate(scheme, pattern);
|
||||
let elementTypes = [];
|
||||
for (const element of pattern.elements) {
|
||||
const tv = this.createTypeVar();
|
||||
this.inferBindings(element, tv, typeVars, constraints, generalize);
|
||||
elementTypes.push(tv);
|
||||
}
|
||||
this.addConstraint(new CEqual(TArrow.build(elementTypes, type), ctorType, pattern));
|
||||
break;
|
||||
}
|
||||
|
||||
case SyntaxKind.LiteralPattern:
|
||||
{
|
||||
|
@ -1693,13 +1694,14 @@ export class Checker {
|
|||
case SyntaxKind.StructPattern:
|
||||
{
|
||||
const variadicMember = getVariadicMember(pattern);
|
||||
let structType: Type;
|
||||
const fields = new Map<string, Type>();
|
||||
let restType: Type;
|
||||
if (variadicMember === null) {
|
||||
structType = new TNil(pattern);
|
||||
restType = new TNil(pattern);
|
||||
} else {
|
||||
structType = this.createTypeVar();
|
||||
restType = this.createTypeVar();
|
||||
if (variadicMember.pattern !== null) {
|
||||
this.inferBindings(variadicMember.pattern, structType, typeVars, constraints);
|
||||
this.inferBindings(variadicMember.pattern, restType, typeVars, constraints);
|
||||
}
|
||||
}
|
||||
for (const member of pattern.members) {
|
||||
|
@ -1708,14 +1710,14 @@ export class Checker {
|
|||
{
|
||||
const fieldType = this.createTypeVar();
|
||||
this.inferBindings(member.pattern, fieldType, typeVars, constraints);
|
||||
structType = new TField(member.name.text, new TPresent(fieldType), structType, pattern);
|
||||
fields.set(member.name.text, fieldType);
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.PunnedStructPatternField:
|
||||
{
|
||||
const fieldType = this.createTypeVar();
|
||||
this.addBinding(member.name.text, Forall.mono(fieldType), Symkind.Var);
|
||||
structType = new TField(member.name.text, new TPresent(fieldType), structType, pattern);
|
||||
fields.set(member.name.text, fieldType);
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.VariadicStructPatternElement:
|
||||
|
@ -1727,7 +1729,7 @@ export class Checker {
|
|||
this.addConstraint(
|
||||
new CEqual(
|
||||
type,
|
||||
TField.sort(structType),
|
||||
TField.build(fields, restType),
|
||||
pattern,
|
||||
)
|
||||
);
|
||||
|
@ -1820,15 +1822,20 @@ export class Checker {
|
|||
constraints,
|
||||
returnType: null,
|
||||
}
|
||||
|
||||
this.pushContext(context);
|
||||
|
||||
const kindArgs = [];
|
||||
for (const name of node.varExps) {
|
||||
const kindArg = this.createTypeVar();
|
||||
env.add(name.text, Forall.mono(kindArg), Symkind.Type);
|
||||
kindArgs.push(kindArg);
|
||||
}
|
||||
const type = TApp.build(new TNominal(node, node), kindArgs);
|
||||
|
||||
const type = this.createTCon(node.name.text, node);
|
||||
const appliedType = TApp.build(type, kindArgs);
|
||||
parentEnv.add(node.name.text, new Forall(typeVars, new CMany(constraints), type), Symkind.Type);
|
||||
|
||||
let elementTypes: Type[] = [];
|
||||
if (node.members !== null) {
|
||||
for (const member of node.members) {
|
||||
|
@ -1838,28 +1845,30 @@ export class Checker {
|
|||
{
|
||||
const argTypes = member.elements.map(el => this.inferTypeExpression(el, false));
|
||||
elementType = new TTuple(argTypes, member);
|
||||
ctorType = TArrow.build(argTypes, type, member);
|
||||
ctorType = TArrow.build(argTypes, appliedType, member);
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.EnumDeclarationStructElement:
|
||||
{
|
||||
elementType = new TNil(member);
|
||||
const restType = new TNil(member);
|
||||
const fields = new Map<string, Type>();
|
||||
for (const field of member.fields) {
|
||||
elementType = new TField(field.name.text, new TPresent(this.inferTypeExpression(field.typeExpr, false)), elementType, member);
|
||||
fields.set(field.name.text, this.inferTypeExpression(field.typeExpr, false));
|
||||
}
|
||||
elementType = TField.sort(elementType);
|
||||
ctorType = new TArrow(elementType, type);
|
||||
elementType = TField.build(fields, restType);
|
||||
ctorType = new TArrow(elementType, appliedType, member);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unexpected ${member}`);
|
||||
}
|
||||
// FIXME `typeVars` may contain too much irrelevant type variables
|
||||
parentEnv.add(member.name.text, new Forall(typeVars, new CMany(constraints), ctorType), Symkind.Var);
|
||||
elementTypes.push(elementType);
|
||||
}
|
||||
}
|
||||
|
||||
this.popContext(context);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1906,15 +1915,18 @@ export class Checker {
|
|||
env.add(varExpr.text, Forall.mono(kindArg), Symkind.Type);
|
||||
kindArgs.push(kindArg);
|
||||
}
|
||||
let type: Type = new TNil(node);
|
||||
const fields = new Map<string, Type>();
|
||||
const restType = new TNil(node);
|
||||
if (node.fields !== null) {
|
||||
for (const field of node.fields) {
|
||||
type = new TField(field.name.text, new TPresent(this.inferTypeExpression(field.typeExpr)), type, node);
|
||||
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.popContext(context);
|
||||
parentEnv.add(node.name.text, new Forall(typeVars, new CMany(constraints), TField.sort(type)), Symkind.Type);
|
||||
//parentEnv.add(node.name.text, new Forall(typeVars, constraints, new TArrow(type, TApp.build(type, kindArgs))), Symkind.Var);
|
||||
parentEnv.add(node.name.text, new Forall(typeVars, new CMany(constraints), type), Symkind.Type);
|
||||
parentEnv.add(node.name.text, new Forall(typeVars, new CMany(constraints), new TArrow(recordType, type)), Symkind.Var);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2186,18 +2198,8 @@ export class Checker {
|
|||
|
||||
if (left.kind === TypeKind.Con && right.kind === TypeKind.Con) {
|
||||
if (left.id === right.id) {
|
||||
assert(left.argTypes.length === right.argTypes.length);
|
||||
const count = left.argTypes.length;
|
||||
let success = true;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (!this.unify(left.argTypes[i], right.argTypes[i], enableDiagnostics)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
TypeBase.join(left, right);
|
||||
}
|
||||
return success;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2246,13 +2248,6 @@ export class Checker {
|
|||
return success
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.Nominal && right.kind === TypeKind.Nominal) {
|
||||
if (left.decl === right.decl) {
|
||||
return true;
|
||||
}
|
||||
// fall through to error reporting
|
||||
}
|
||||
|
||||
if (left.kind === TypeKind.App && right.kind === TypeKind.App) {
|
||||
return this.unify(left.left, right.left, enableDiagnostics)
|
||||
&& this.unify(left.right, right.right, enableDiagnostics);
|
||||
|
|
|
@ -470,11 +470,11 @@ abstract class TokenBase extends SyntaxBase {
|
|||
}
|
||||
|
||||
public getFirstToken(): Token {
|
||||
throw new Error(`Trying to get the first token of an object that is a token itself.`);
|
||||
return this as Token;
|
||||
}
|
||||
|
||||
public getLastToken(): Token {
|
||||
throw new Error(`Trying to get the last token of an object that is a token itself.`);
|
||||
return this as Token;
|
||||
}
|
||||
|
||||
public getRange(): TextRange {
|
||||
|
|
|
@ -548,11 +548,7 @@ export function describeType(type: Type): string {
|
|||
switch (type.kind) {
|
||||
case TypeKind.Con:
|
||||
{
|
||||
let out = type.displayName;
|
||||
for (const argType of type.argTypes) {
|
||||
out += ' ' + describeType(argType);
|
||||
}
|
||||
return out;
|
||||
return type.displayName;
|
||||
}
|
||||
case TypeKind.UniVar:
|
||||
return 'a' + type.id;
|
||||
|
@ -575,10 +571,6 @@ export function describeType(type: Type): string {
|
|||
}
|
||||
case TypeKind.TupleIndex:
|
||||
return describeType(type.tupleType) + '.' + type.index;
|
||||
case TypeKind.Nominal:
|
||||
{
|
||||
return type.decl.name.text;
|
||||
}
|
||||
case TypeKind.Field:
|
||||
{
|
||||
let out = '{ ' + type.name + ': ' + describeType(type.type);
|
||||
|
@ -594,7 +586,7 @@ export function describeType(type: Type): string {
|
|||
}
|
||||
case TypeKind.App:
|
||||
{
|
||||
return describeType(type.right) + ' ' + describeType(type.left);
|
||||
return describeType(type.left) + ' ' + describeType(type.right);
|
||||
}
|
||||
case TypeKind.Nil:
|
||||
return '{}';
|
||||
|
|
|
@ -163,6 +163,13 @@ export class Scanner extends BufferedStream<Token> {
|
|||
if (c0 === '#') {
|
||||
const line = this.currPos.line;
|
||||
this.getChar();
|
||||
for (;;) {
|
||||
const c1 = this.peekChar();
|
||||
if (!isWhiteSpace(c1) || c1 === '\n' || c1 === EOF) {
|
||||
break;
|
||||
}
|
||||
this.getChar();
|
||||
}
|
||||
let text = '';
|
||||
for (;;) {
|
||||
const c1 = this.getChar();
|
||||
|
@ -171,8 +178,10 @@ export class Scanner extends BufferedStream<Token> {
|
|||
}
|
||||
text += c1;
|
||||
}
|
||||
if (text[0] === '@') {
|
||||
const scanner = new Scanner(text, this.diagnostics, this.file, this.getCurrentPosition());
|
||||
this.file.comments.set(line, scanner.getAll());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -283,7 +283,6 @@ export class TCon extends TypeBase {
|
|||
|
||||
public constructor(
|
||||
public id: number,
|
||||
public argTypes: Type[],
|
||||
public displayName: string,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
|
@ -291,35 +290,23 @@ export class TCon extends TypeBase {
|
|||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
for (const argType of this.argTypes) {
|
||||
yield* argType.getTypeVars();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public shallowClone(): TCon {
|
||||
return new TCon(
|
||||
this.id,
|
||||
this.argTypes,
|
||||
this.displayName,
|
||||
this.node,
|
||||
);
|
||||
}
|
||||
|
||||
public substitute(sub: TVSub): Type {
|
||||
let changed = false;
|
||||
const newArgTypes = [];
|
||||
for (const argType of this.argTypes) {
|
||||
const newArgType = argType.substitute(sub);
|
||||
if (newArgType !== argType) {
|
||||
changed = true;
|
||||
}
|
||||
newArgTypes.push(newArgType);
|
||||
}
|
||||
return changed ? new TCon(this.id, newArgTypes, this.displayName, this.node) : this;
|
||||
public substitute(_sub: TVSub): Type {
|
||||
return this;
|
||||
}
|
||||
|
||||
public [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) {
|
||||
return this.displayName + ' ' + this.argTypes.map(t => inspect(t, options)).join(' ');
|
||||
public [toStringTag](_depth: number, _options: InspectOptions, _inspect: InspectFn) {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -430,6 +417,14 @@ export class TField extends TypeBase {
|
|||
);
|
||||
}
|
||||
|
||||
public static build(fields: Map<string, Type>, restType: Type): Type {
|
||||
let out = restType;
|
||||
for (const [name, type] of fields) {
|
||||
out = new TField(name, new TPresent(type, type.node), out, type.node);
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
public static sort(type: Type): Type {
|
||||
const fields = new Map<string, TField>();
|
||||
while (type.kind === TypeKind.Field) {
|
||||
|
@ -481,7 +476,7 @@ export class TApp extends TypeBase {
|
|||
|
||||
public static build(resultType: Type, types: Type[], node: Syntax | null = null): Type {
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
resultType = new TApp(types[i], resultType, node);
|
||||
resultType = new TApp(resultType, types[i], node);
|
||||
}
|
||||
return resultType;
|
||||
}
|
||||
|
@ -518,38 +513,6 @@ export class TApp extends TypeBase {
|
|||
|
||||
}
|
||||
|
||||
export class TNominal extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Nominal;
|
||||
|
||||
public constructor(
|
||||
public decl: StructDeclaration | EnumDeclaration,
|
||||
public node: Syntax | null = null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public *getTypeVars(): Iterable<TVar> {
|
||||
|
||||
}
|
||||
|
||||
public shallowClone(): Type {
|
||||
return new TNominal(
|
||||
this.decl,
|
||||
this.node,
|
||||
);
|
||||
}
|
||||
|
||||
public substitute(_sub: TVSub): Type {
|
||||
return this;
|
||||
}
|
||||
|
||||
public [toStringTag]() {
|
||||
return this.decl.name.text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type Type
|
||||
= TCon
|
||||
| TArrow
|
||||
|
@ -557,7 +520,6 @@ export type Type
|
|||
| TUniVar
|
||||
| TTuple
|
||||
| TApp
|
||||
| TNominal
|
||||
| TField
|
||||
| TNil
|
||||
| TPresent
|
||||
|
@ -586,9 +548,6 @@ export function typesEqual(a: Type, b: Type): boolean {
|
|||
case TypeKind.Nil:
|
||||
case TypeKind.Absent:
|
||||
return true;
|
||||
case TypeKind.Nominal:
|
||||
assert(b.kind === TypeKind.Nominal);
|
||||
return a.decl === b.decl;
|
||||
case TypeKind.App:
|
||||
assert(b.kind === TypeKind.App);
|
||||
return typesEqual(a.left, b.left) && typesEqual(a.right, b.right);
|
||||
|
|
Loading…
Reference in a new issue