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:
Sam Vervaeck 2023-07-04 20:38:40 +02:00
parent bd4ed57c46
commit 985e2d0652
Signed by: samvv
SSH key fingerprint: SHA256:dIg0ywU1OP+ZYifrYxy8c5esO72cIKB+4/9wkZj1VaY
6 changed files with 88 additions and 132 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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