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); const uncaughtDiagnostics = new Set(diagnostics);
// TODO check comments that did not match any diagnostic
for (const [line, comment] of file.comments) { 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) { 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) { for (const diagnostic of uncaughtDiagnostics) {

View file

@ -31,7 +31,7 @@ import {
import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util"; import { assert, assertNever, isEmpty, MultiMap, toStringTag, InspectFn, implementationLimitation } from "./util";
import { Analyser } from "./analysis"; import { Analyser } from "./analysis";
import { InspectOptions } from "util"; 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"; import { CClass, CEmpty, CEqual, CMany, Constraint, ConstraintKind, ConstraintSet } from "./constraints";
// export class Qual { // export class Qual {
@ -384,9 +384,9 @@ export class Checker {
private nextKindVarId = 0; private nextKindVarId = 0;
private nextConTypeId = 0; private nextConTypeId = 0;
private stringType = this.createTCon([], 'String'); private stringType = this.createTCon('String');
private intType = this.createTCon([], 'Int'); private intType = this.createTCon('Int');
private boolType = this.createTCon([], 'Bool'); private boolType = this.createTCon('Bool');
private contexts: InferContext[] = []; private contexts: InferContext[] = [];
@ -436,8 +436,8 @@ export class Checker {
return this.boolType; return this.boolType;
} }
private createTCon(types: Type[], name: string): TCon { private createTCon(name: string, node: Syntax | null = null): TCon {
return new TCon(this.nextConTypeId++, types, name); return new TCon(this.nextConTypeId++, name, node);
} }
private createTypeVar(node: Syntax | null = null): TUniVar { private createTypeVar(node: Syntax | null = null): TUniVar {
@ -1447,12 +1447,13 @@ export class Checker {
case SyntaxKind.StructExpression: case SyntaxKind.StructExpression:
{ {
type = new TNil(node); const fields = new Map<string, Type>();
const restType = new TNil(node);
for (const member of node.members) { for (const member of node.members) {
switch (member.kind) { switch (member.kind) {
case SyntaxKind.StructExpressionField: 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; break;
} }
case SyntaxKind.PunnedStructExpressionField: case SyntaxKind.PunnedStructExpressionField:
@ -1465,15 +1466,14 @@ export class Checker {
} else { } else {
fieldType = this.instantiate(scheme, member); fieldType = this.instantiate(scheme, member);
} }
type = new TField(member.name.text, new TPresent(fieldType), type, node); fields.set(member.name.text, fieldType);
break; break;
} }
default: default:
throw new Error(`Unexpected ${member}`); throw new Error(`Unexpected ${member}`);
} }
} }
// FIXME build a type rather than sorting it type = TField.build(fields, restType);
type = TField.sort(type);
break; break;
} }
@ -1644,21 +1644,22 @@ export class Checker {
this.inferBindings(pattern.pattern, type, typeVars, constraints); this.inferBindings(pattern.pattern, type, typeVars, constraints);
break; break;
// case SyntaxKind.NamedTuplePattern: case SyntaxKind.NamedTuplePattern:
// { {
// const scheme = this.lookup(pattern.name, Symkind.Type); const scheme = this.lookup(pattern.name, Symkind.Var);
// if (scheme === null) { if (scheme === null) {
// return this.createTypeVar(); return;
// } }
// let tupleType = new TTuple(pattern.elements.map(p => const ctorType = this.instantiate(scheme, pattern);
// this.inferBindings(p, this.createTypeVar(), typeVars, constraints)); let elementTypes = [];
// // FIXME not tested for (const element of pattern.elements) {
// this.addConstraint(new CEqual(tupleType, type, pattern)); const tv = this.createTypeVar();
// return TApp.build( this.inferBindings(element, tv, typeVars, constraints, generalize);
// new TNominal(scheme.type.node as StructDeclaration | EnumDeclaration, pattern), elementTypes.push(tv);
// tupleType }
// ); this.addConstraint(new CEqual(TArrow.build(elementTypes, type), ctorType, pattern));
// } break;
}
case SyntaxKind.LiteralPattern: case SyntaxKind.LiteralPattern:
{ {
@ -1693,13 +1694,14 @@ export class Checker {
case SyntaxKind.StructPattern: case SyntaxKind.StructPattern:
{ {
const variadicMember = getVariadicMember(pattern); const variadicMember = getVariadicMember(pattern);
let structType: Type; const fields = new Map<string, Type>();
let restType: Type;
if (variadicMember === null) { if (variadicMember === null) {
structType = new TNil(pattern); restType = new TNil(pattern);
} else { } else {
structType = this.createTypeVar(); restType = this.createTypeVar();
if (variadicMember.pattern !== null) { if (variadicMember.pattern !== null) {
this.inferBindings(variadicMember.pattern, structType, typeVars, constraints); this.inferBindings(variadicMember.pattern, restType, typeVars, constraints);
} }
} }
for (const member of pattern.members) { for (const member of pattern.members) {
@ -1708,14 +1710,14 @@ export class Checker {
{ {
const fieldType = this.createTypeVar(); const fieldType = this.createTypeVar();
this.inferBindings(member.pattern, fieldType, typeVars, constraints); 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; break;
} }
case SyntaxKind.PunnedStructPatternField: case SyntaxKind.PunnedStructPatternField:
{ {
const fieldType = this.createTypeVar(); const fieldType = this.createTypeVar();
this.addBinding(member.name.text, Forall.mono(fieldType), Symkind.Var); 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; break;
} }
case SyntaxKind.VariadicStructPatternElement: case SyntaxKind.VariadicStructPatternElement:
@ -1727,7 +1729,7 @@ export class Checker {
this.addConstraint( this.addConstraint(
new CEqual( new CEqual(
type, type,
TField.sort(structType), TField.build(fields, restType),
pattern, pattern,
) )
); );
@ -1820,15 +1822,20 @@ export class Checker {
constraints, constraints,
returnType: null, returnType: null,
} }
this.pushContext(context); this.pushContext(context);
const kindArgs = []; const kindArgs = [];
for (const name of node.varExps) { for (const name of node.varExps) {
const kindArg = this.createTypeVar(); const kindArg = this.createTypeVar();
env.add(name.text, Forall.mono(kindArg), Symkind.Type); env.add(name.text, Forall.mono(kindArg), Symkind.Type);
kindArgs.push(kindArg); 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); parentEnv.add(node.name.text, new Forall(typeVars, new CMany(constraints), type), Symkind.Type);
let elementTypes: Type[] = []; let elementTypes: Type[] = [];
if (node.members !== null) { if (node.members !== null) {
for (const member of node.members) { for (const member of node.members) {
@ -1838,28 +1845,30 @@ export class Checker {
{ {
const argTypes = member.elements.map(el => this.inferTypeExpression(el, false)); const argTypes = member.elements.map(el => this.inferTypeExpression(el, false));
elementType = new TTuple(argTypes, member); elementType = new TTuple(argTypes, member);
ctorType = TArrow.build(argTypes, type, member); ctorType = TArrow.build(argTypes, appliedType, member);
break; break;
} }
case SyntaxKind.EnumDeclarationStructElement: case SyntaxKind.EnumDeclarationStructElement:
{ {
elementType = new TNil(member); const restType = new TNil(member);
const fields = new Map<string, Type>();
for (const field of member.fields) { 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); elementType = TField.build(fields, restType);
ctorType = new TArrow(elementType, type); ctorType = new TArrow(elementType, appliedType, member);
break; break;
} }
default: default:
throw new Error(`Unexpected ${member}`); throw new Error(`Unexpected ${member}`);
} }
// FIXME `typeVars` may contain too much irrelevant type variables
parentEnv.add(member.name.text, new Forall(typeVars, new CMany(constraints), ctorType), Symkind.Var); parentEnv.add(member.name.text, new Forall(typeVars, new CMany(constraints), ctorType), Symkind.Var);
elementTypes.push(elementType); elementTypes.push(elementType);
} }
} }
this.popContext(context); this.popContext(context);
break; break;
} }
@ -1906,15 +1915,18 @@ export class Checker {
env.add(varExpr.text, Forall.mono(kindArg), Symkind.Type); env.add(varExpr.text, Forall.mono(kindArg), Symkind.Type);
kindArgs.push(kindArg); kindArgs.push(kindArg);
} }
let type: Type = new TNil(node); const fields = new Map<string, Type>();
const restType = new TNil(node);
if (node.fields !== null) { if (node.fields !== null) {
for (const field of node.fields) { 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); 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, new CMany(constraints), 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), new TArrow(recordType, type)), Symkind.Var);
break; break;
} }
@ -2186,18 +2198,8 @@ export class Checker {
if (left.kind === TypeKind.Con && right.kind === TypeKind.Con) { if (left.kind === TypeKind.Con && right.kind === TypeKind.Con) {
if (left.id === right.id) { if (left.id === right.id) {
assert(left.argTypes.length === right.argTypes.length); TypeBase.join(left, right);
const count = left.argTypes.length; return true;
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;
} }
} }
@ -2246,13 +2248,6 @@ export class Checker {
return success 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) { if (left.kind === TypeKind.App && right.kind === TypeKind.App) {
return this.unify(left.left, right.left, enableDiagnostics) return this.unify(left.left, right.left, enableDiagnostics)
&& this.unify(left.right, right.right, enableDiagnostics); && this.unify(left.right, right.right, enableDiagnostics);

View file

@ -470,11 +470,11 @@ abstract class TokenBase extends SyntaxBase {
} }
public getFirstToken(): Token { 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 { 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 { public getRange(): TextRange {

View file

@ -548,11 +548,7 @@ export function describeType(type: Type): string {
switch (type.kind) { switch (type.kind) {
case TypeKind.Con: case TypeKind.Con:
{ {
let out = type.displayName; return type.displayName;
for (const argType of type.argTypes) {
out += ' ' + describeType(argType);
}
return out;
} }
case TypeKind.UniVar: case TypeKind.UniVar:
return 'a' + type.id; return 'a' + type.id;
@ -575,10 +571,6 @@ export function describeType(type: Type): string {
} }
case TypeKind.TupleIndex: case TypeKind.TupleIndex:
return describeType(type.tupleType) + '.' + type.index; return describeType(type.tupleType) + '.' + type.index;
case TypeKind.Nominal:
{
return type.decl.name.text;
}
case TypeKind.Field: case TypeKind.Field:
{ {
let out = '{ ' + type.name + ': ' + describeType(type.type); let out = '{ ' + type.name + ': ' + describeType(type.type);
@ -594,7 +586,7 @@ export function describeType(type: Type): string {
} }
case TypeKind.App: case TypeKind.App:
{ {
return describeType(type.right) + ' ' + describeType(type.left); return describeType(type.left) + ' ' + describeType(type.right);
} }
case TypeKind.Nil: case TypeKind.Nil:
return '{}'; return '{}';

View file

@ -163,6 +163,13 @@ export class Scanner extends BufferedStream<Token> {
if (c0 === '#') { if (c0 === '#') {
const line = this.currPos.line; const line = this.currPos.line;
this.getChar(); this.getChar();
for (;;) {
const c1 = this.peekChar();
if (!isWhiteSpace(c1) || c1 === '\n' || c1 === EOF) {
break;
}
this.getChar();
}
let text = ''; let text = '';
for (;;) { for (;;) {
const c1 = this.getChar(); const c1 = this.getChar();
@ -171,8 +178,10 @@ export class Scanner extends BufferedStream<Token> {
} }
text += c1; text += c1;
} }
const scanner = new Scanner(text, this.diagnostics, this.file, this.getCurrentPosition()); if (text[0] === '@') {
this.file.comments.set(line, scanner.getAll()); const scanner = new Scanner(text, this.diagnostics, this.file, this.getCurrentPosition());
this.file.comments.set(line, scanner.getAll());
}
continue; continue;
} }

View file

@ -283,7 +283,6 @@ export class TCon extends TypeBase {
public constructor( public constructor(
public id: number, public id: number,
public argTypes: Type[],
public displayName: string, public displayName: string,
public node: Syntax | null = null, public node: Syntax | null = null,
) { ) {
@ -291,35 +290,23 @@ export class TCon extends TypeBase {
} }
public *getTypeVars(): Iterable<TVar> { public *getTypeVars(): Iterable<TVar> {
for (const argType of this.argTypes) {
yield* argType.getTypeVars();
}
} }
public shallowClone(): TCon { public shallowClone(): TCon {
return new TCon( return new TCon(
this.id, this.id,
this.argTypes,
this.displayName, this.displayName,
this.node, this.node,
); );
} }
public substitute(sub: TVSub): Type { public substitute(_sub: TVSub): Type {
let changed = false; return this;
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 [toStringTag](_depth: number, options: InspectOptions, inspect: InspectFn) { public [toStringTag](_depth: number, _options: InspectOptions, _inspect: InspectFn) {
return this.displayName + ' ' + this.argTypes.map(t => inspect(t, options)).join(' '); 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 { public static sort(type: Type): Type {
const fields = new Map<string, TField>(); const fields = new Map<string, TField>();
while (type.kind === TypeKind.Field) { 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 { public static build(resultType: Type, types: Type[], node: Syntax | null = null): Type {
for (let i = 0; i < types.length; i++) { for (let i = 0; i < types.length; i++) {
resultType = new TApp(types[i], resultType, node); resultType = new TApp(resultType, types[i], node);
} }
return resultType; 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 export type Type
= TCon = TCon
| TArrow | TArrow
@ -557,7 +520,6 @@ export type Type
| TUniVar | TUniVar
| TTuple | TTuple
| TApp | TApp
| TNominal
| TField | TField
| TNil | TNil
| TPresent | TPresent
@ -586,9 +548,6 @@ export function typesEqual(a: Type, b: Type): boolean {
case TypeKind.Nil: case TypeKind.Nil:
case TypeKind.Absent: case TypeKind.Absent:
return true; return true;
case TypeKind.Nominal:
assert(b.kind === TypeKind.Nominal);
return a.decl === b.decl;
case TypeKind.App: case TypeKind.App:
assert(b.kind === TypeKind.App); assert(b.kind === TypeKind.App);
return typesEqual(a.left, b.left) && typesEqual(a.right, b.right); return typesEqual(a.left, b.left) && typesEqual(a.right, b.right);