bolt/src/checker.ts

345 lines
7.9 KiB
TypeScript
Raw Normal View History

2020-02-25 17:55:17 +01:00
import {
Syntax,
2020-05-10 15:56:34 +02:00
kindToString,
SyntaxKind,
2020-05-10 15:56:34 +02:00
BoltImportDeclaration,
BoltPattern,
2020-05-23 15:04:32 +02:00
isBoltTypeAliasDeclaration,
BoltFunctionBodyElement,
2020-02-25 17:55:17 +01:00
} from "./ast"
2020-05-10 15:56:34 +02:00
import { FastStringMap, getFullTextOfQualName } from "./util"
export class Type {
2020-02-25 17:55:17 +01:00
}
export class PrimType extends Type {
}
2020-03-03 14:53:54 +01:00
export class FunctionType extends Type {
constructor(
public paramTypes: Type[],
public returnType: Type,
) {
super();
}
}
export class VariantType extends Type {
constructor(public elementTypes: Type[]) {
super();
}
}
export const stringType = new PrimType()
export const intType = new PrimType()
export const boolType = new PrimType()
export const voidType = new PrimType()
2020-03-03 14:53:54 +01:00
export const anyType = new PrimType()
export const noneType = new PrimType();
export class RecordType {
2020-05-23 15:04:32 +02:00
private fieldTypes = new FastStringMap<string, Type>();
constructor(
iterable: IterableIterator<[string, Type]>,
) {
2020-05-23 15:04:32 +02:00
for (const [name, type] of iterable) {
this.fieldTypes.set(name, type);
}
}
hasField(name: string) {
return name in this.fieldTypes;
}
getTypeOfField(name: string) {
2020-05-23 15:04:32 +02:00
return this.fieldTypes.get(name);
}
2020-02-25 17:55:17 +01:00
}
2020-05-23 15:04:32 +02:00
interface SymbolInfo {
type: Type | null;
definitions: Syntax[];
}
2020-02-25 17:55:17 +01:00
export class Scope {
2020-05-23 15:04:32 +02:00
private symbolsByLocalName = new FastStringMap<string, SymbolInfo>();
constructor(
public originatingNode: Syntax,
public parentScope?: Scope | null
) {
}
public getSymbolNamed(name: string): SymbolInfo | null {
let currScope: Scope | null = this;
while (true) {
if (currScope.symbolsByLocalName.has(name)) {
return currScope.symbolsByLocalName.get(name);
}
currScope = currScope.parentScope;
if (currScope === null) {
break;
}
}
return null;
}
2020-02-25 17:55:17 +01:00
2020-05-23 15:04:32 +02:00
public getTypeNamed(name: string): Type | null {
const sym = this.getSymbolNamed(name);
if (sym === null || !introducesNewType(sym.definitions[0].kind)) {
return null;
}
return sym.type!;
2020-02-25 17:55:17 +01:00
}
}
function* map<T, R>(iterable: Iterable<T>, proc: (value: T) => R): IterableIterator<R> {
const iterator = iterable[Symbol.iterator]();
while (true) {
let { done, value }= iterator.next();
if (done) {
break
}
yield proc(value)
}
}
2020-02-25 17:55:17 +01:00
2020-05-23 15:04:32 +02:00
function introducesNewType(kind: SyntaxKind): boolean {
return kind === SyntaxKind.BoltRecordDeclaration
|| kind === SyntaxKind.BoltTypeAliasDeclaration;
}
function introducesNewScope(kind: SyntaxKind): boolean {
return kind === SyntaxKind.BoltFunctionDeclaration
|| kind === SyntaxKind.BoltSourceFile;
}
function getFullName(node: Syntax) {
let out = []
let curr: Syntax | null = node;
while (true) {
switch (curr.kind) {
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltIdentifier:
2020-03-03 14:53:54 +01:00
out.unshift(curr.text)
break;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltModule:
out.unshift(getFullTextOfQualName(curr.name));
break;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltRecordDeclaration:
out.unshift(getFullTextOfQualName(curr.name))
break;
2020-05-10 23:50:42 +02:00
}
curr = curr.parentNode;
if (curr === null) {
break;
}
}
return out.join('.');
}
export class TypeChecker {
2020-02-25 17:55:17 +01:00
2020-05-23 15:04:32 +02:00
private symbols = new FastStringMap<string, Type>();
private types = new Map<Syntax, Type>();
private scopes = new Map<Syntax, Scope>();
2020-03-03 14:53:54 +01:00
2020-05-23 15:04:32 +02:00
private inferTypeFromUsage(bindings: BoltPattern, body: BoltFunctionBodyElement[]) {
2020-03-03 14:53:54 +01:00
return anyType;
}
2020-05-23 15:04:32 +02:00
private getTypeOfBody(body: BoltFunctionBodyElement[]) {
2020-03-03 14:53:54 +01:00
return anyType;
}
2020-05-23 15:04:32 +02:00
private createType(node: Syntax): Type {
2020-05-10 23:50:42 +02:00
console.error(`creating type for ${kindToString(node.kind)}`);
2020-02-25 17:55:17 +01:00
switch (node.kind) {
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltReferenceExpression:
return anyType;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltConstantExpression:
return node.value.type;
2020-05-10 18:54:57 +02:00
case SyntaxKind.BoltExpressionStatement:
return voidType;
2020-05-10 23:50:42 +02:00
case SyntaxKind.BoltCallExpression:
// TODO
return anyType;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltFunctionDeclaration:
2020-03-03 14:53:54 +01:00
let returnType = anyType;
if (node.returnType !== null) {
returnType = this.getTypeOfNode(node.returnType)
}
if (node.body !== null) {
returnType = this.intersectTypes(returnType, this.getTypeOfBody(node.body))
}
let paramTypes = node.params.map(param => {
let paramType = this.getTypeOfNode(param);
if (node.body !== null) {
paramType = this.intersectTypes(
paramType,
this.inferTypeFromUsage(param.bindings, node.body)
)
}
return paramType
})
return new FunctionType(paramTypes, returnType);
2020-05-23 15:04:32 +02:00
case SyntaxKind.BoltReferenceTypeExpression:
2020-05-10 15:56:34 +02:00
const name = getFullTextOfQualName(node.name);
2020-05-23 15:04:32 +02:00
const scope = this.getScope(node);
let reffed = scope.getTypeNamed(name);
2020-03-03 14:53:54 +01:00
if (reffed === null) {
2020-05-23 15:04:32 +02:00
reffed = anyType;
2020-03-03 14:53:54 +01:00
}
return reffed;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltRecordDeclaration:
2020-05-23 15:04:32 +02:00
const fullName = getFullName(node);
let type;
2020-05-23 15:04:32 +02:00
if (node.members === null) {
type = new PrimType();
this.symbols.set(fullName, type);
} else {
type = new RecordType(map(node.members, member => ([field.name.text, this.getTypeOfNode(field.type)])));
this.symbols.set(fullName, type);
}
2020-05-23 15:04:32 +02:00
return type;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltParameter:
2020-05-10 23:50:42 +02:00
if (node.type !== null) {
return this.getTypeOfNode(node.type)
2020-03-03 14:53:54 +01:00
}
return anyType;
default:
2020-05-10 15:56:34 +02:00
throw new Error(`Could not derive type of ${kindToString(node.kind)}`)
}
}
2020-05-23 15:04:32 +02:00
public getSymbolNamed(name: string) {
if (!this.symbols.has(name)) {
return null;
}
return this.symbols.get(name);
}
2020-05-23 15:04:32 +02:00
public getTypeOfNode(node: Syntax): Type {
if (this.types.has(node)) {
return this.types.get(node)!
}
const newType = this.createType(node)
this.types.set(node, newType)
return newType;
}
2020-05-23 15:04:32 +02:00
public check(node: Syntax) {
2020-03-03 14:53:54 +01:00
this.getTypeOfNode(node);
switch (node.kind) {
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltRecordDeclaration:
2020-05-10 23:50:42 +02:00
case SyntaxKind.BoltConstantExpression:
break;
2020-05-10 18:54:57 +02:00
case SyntaxKind.BoltExpressionStatement:
this.check(node.expression);
break;
2020-05-10 23:50:42 +02:00
case SyntaxKind.BoltCallExpression:
this.check(node.operator);
for (const operand of node.operands) {
this.check(operand);
}
// TODO check whether the overload matches the referenced operator
break;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltFunctionDeclaration:
2020-03-03 14:53:54 +01:00
if (node.body !== null) {
if (Array.isArray(node.body)) {
for (const element of node.body) {
this.check(element)
}
}
}
break;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltReferenceExpression:
// TODO implement this
break;
2020-05-10 15:56:34 +02:00
case SyntaxKind.BoltModule:
case SyntaxKind.BoltSourceFile:
for (const element of node.elements) {
this.check(element)
2020-02-25 17:55:17 +01:00
}
break;
default:
2020-05-10 15:56:34 +02:00
throw new Error(`Could not type-check node ${kindToString(node.kind)}`)
2020-02-25 17:55:17 +01:00
}
2020-02-25 17:55:17 +01:00
}
2020-05-23 15:04:32 +02:00
public getScope(node: Syntax): Scope {
while (!introducesNewScope(node.kind)) {
2020-02-25 17:55:17 +01:00
node = node.parentNode!;
}
if (this.scopes.has(node)) {
return this.scopes.get(node)!
}
const scope = new Scope(node)
this.scopes.set(node, scope)
return scope
}
2020-05-23 15:04:32 +02:00
private intersectTypes(a: Type, b: Type): Type {
2020-03-03 14:53:54 +01:00
if (a === noneType || b == noneType) {
return noneType;
}
if (b === anyType) {
return a
}
if (a === anyType) {
return b;
}
if (a instanceof FunctionType && b instanceof FunctionType) {
if (a.paramTypes.length !== b.paramTypes.length) {
return noneType;
}
const returnType = this.intersectTypes(a.returnType, b.returnType);
const paramTypes = a.paramTypes.map((_, i) => this.intersectTypes(a.paramTypes[i], b.paramTypes[i]));
return new FunctionType(paramTypes, returnType)
}
return noneType;
}
2020-02-25 17:55:17 +01:00
}