2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
import { FastStringMap, assert, isPlainObject } from "./util";
|
2020-05-27 19:57:15 +02:00
|
|
|
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, isBoltMacroCall, BoltTypeExpression } from "./ast";
|
2020-05-26 22:58:31 +02:00
|
|
|
import { getSymbolPathFromNode, ScopeType, SymbolResolver, SymbolInfo } from "./resolver";
|
2020-05-27 19:57:15 +02:00
|
|
|
import { Value, Record } from "./evaluator";
|
|
|
|
|
|
|
|
// TODO For function bodies, we can do something special.
|
|
|
|
// Sort the return types and find the largest types, eliminating types that fall under other types.
|
|
|
|
// Next, add the resulting types as type hints to `fnReturnType`.
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
enum TypeKind {
|
|
|
|
OpaqueType,
|
|
|
|
AnyType,
|
|
|
|
NeverType,
|
|
|
|
FunctionType,
|
|
|
|
RecordType,
|
2020-05-26 22:58:31 +02:00
|
|
|
PlainRecordFieldType,
|
2020-05-23 21:15:20 +02:00
|
|
|
VariantType,
|
2020-05-26 21:01:38 +02:00
|
|
|
UnionType,
|
2020-05-23 21:15:20 +02:00
|
|
|
TupleType,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Type
|
|
|
|
= OpaqueType
|
|
|
|
| AnyType
|
|
|
|
| NeverType
|
|
|
|
| FunctionType
|
|
|
|
| RecordType
|
|
|
|
| VariantType
|
|
|
|
| TupleType
|
2020-05-27 19:57:15 +02:00
|
|
|
| UnionType
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
abstract class TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
public abstract kind: TypeKind;
|
|
|
|
|
|
|
|
constructor(public symbol?: SymbolInfo) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class OpaqueType extends TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
public kind: TypeKind.OpaqueType = TypeKind.OpaqueType;
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class AnyType extends TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.AnyType = TypeKind.AnyType;
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class NeverType extends TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.NeverType = TypeKind.NeverType;
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class FunctionType extends TypeBase {
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.FunctionType = TypeKind.FunctionType;
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
public paramTypes: Type[],
|
|
|
|
public returnType: Type,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public getParameterCount(): number {
|
|
|
|
return this.paramTypes.length;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public getTypeAtParameterIndex(index: number) {
|
2020-05-23 21:15:20 +02:00
|
|
|
if (index < 0 || index >= this.paramTypes.length) {
|
|
|
|
throw new Error(`Could not get the parameter type at index ${index} because the index was out of bounds.`);
|
|
|
|
}
|
|
|
|
return this.paramTypes[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export class VariantType extends TypeBase {
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.VariantType = TypeKind.VariantType;
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
constructor(public elementTypes: Type[]) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public getOwnElementTypes(): IterableIterator<Type> {
|
|
|
|
return this.elementTypes[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
export class UnionType extends TypeBase {
|
|
|
|
|
|
|
|
public kind: TypeKind.UnionType = TypeKind.UnionType;
|
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
constructor(private elements: Type[] = []) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public addElement(element: Type): void {
|
|
|
|
this.elements.push(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
public getElements(): IterableIterator<Type> {
|
|
|
|
return this.elements[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export type RecordFieldType
|
|
|
|
= PlainRecordFieldType
|
|
|
|
|
|
|
|
class PlainRecordFieldType extends TypeBase {
|
|
|
|
|
|
|
|
public kind: TypeKind.PlainRecordFieldType = TypeKind.PlainRecordFieldType;
|
|
|
|
|
|
|
|
constructor(public type: Type) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class RecordType {
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.RecordType = TypeKind.RecordType;
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
private fieldTypes = new FastStringMap<string, RecordFieldType>();
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
constructor(
|
2020-05-26 22:58:31 +02:00
|
|
|
iterable?: Iterable<[string, RecordFieldType]>,
|
2020-05-23 21:15:20 +02:00
|
|
|
) {
|
2020-05-26 22:58:31 +02:00
|
|
|
if (iterable !== undefined) {
|
|
|
|
for (const [name, type] of iterable) {
|
|
|
|
this.fieldTypes.set(name, type);
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public addField(name: string, type: RecordFieldType): void {
|
|
|
|
this.fieldTypes.set(name, type);
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
public hasField(name: string) {
|
|
|
|
return name in this.fieldTypes;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public getFieldType(name: string) {
|
2020-05-23 21:15:20 +02:00
|
|
|
return this.fieldTypes.get(name);
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public clear(): void {
|
|
|
|
this.fieldTypes.clear();
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
export enum ErrorType {
|
|
|
|
AssignmentError,
|
|
|
|
}
|
|
|
|
|
|
|
|
interface AssignmentError {
|
|
|
|
type: ErrorType.AssignmentError;
|
|
|
|
left: Syntax;
|
|
|
|
right: Syntax;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type CompileError
|
|
|
|
= AssignmentError
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
export class TupleType extends TypeBase {
|
|
|
|
|
|
|
|
kind: TypeKind.TupleType = TypeKind.TupleType;
|
|
|
|
|
|
|
|
constructor(public elementTypes: Type[]) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
//export function narrowType(outer: Type, inner: Type): Type {
|
|
|
|
// if (isAnyType(outer) || isNeverType(inner)) {
|
|
|
|
// return inner;
|
|
|
|
// }
|
|
|
|
// // TODO cover the other cases
|
|
|
|
// return outer;
|
|
|
|
//}
|
|
|
|
|
|
|
|
//export function intersectTypes(a: Type, b: Type): Type {
|
|
|
|
// if (a.kind === TypeKind.NeverType && b.kind === TypeKind.NeverType)
|
|
|
|
// return new NeverType();
|
|
|
|
// }
|
|
|
|
// if (a.kind == TypeKind.AnyType) {
|
|
|
|
// return a
|
|
|
|
// }
|
|
|
|
// if (isAnyType(a)) {
|
|
|
|
// return b;
|
|
|
|
// }
|
|
|
|
// if (a.kind === TypeKind.FunctionType && b.kind === TypeKind.FunctionType) {
|
|
|
|
// if (a.paramTypes.length !== b.paramTypes.length) {
|
|
|
|
// return new NeverType();
|
|
|
|
// }
|
|
|
|
// const returnType = intersectTypes(a.returnType, b.returnType);
|
|
|
|
// const paramTypes = a.paramTypes
|
|
|
|
// .map((_, i) => intersectTypes(a.paramTypes[i], b.paramTypes[i]));
|
|
|
|
// return new FunctionType(paramTypes, returnType)
|
|
|
|
// }
|
|
|
|
// return new NeverType();
|
|
|
|
//}
|
2020-05-24 11:17:56 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
export class TypeChecker {
|
|
|
|
|
|
|
|
private opaqueTypes = new FastStringMap<number, OpaqueType>();
|
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
private anyType = new AnyType();
|
2020-05-26 22:58:31 +02:00
|
|
|
private stringType = new OpaqueType();
|
|
|
|
private intType = new OpaqueType();
|
|
|
|
private floatType = new OpaqueType();
|
2020-05-27 19:57:15 +02:00
|
|
|
private voidType = new OpaqueType();
|
|
|
|
|
|
|
|
private syntaxType = new UnionType(); // FIXME
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
constructor(private resolver: SymbolResolver) {
|
2020-05-24 11:17:56 +02:00
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
public getTypeOfValue(value: Value): Type {
|
|
|
|
if (typeof(value) === 'string') {
|
|
|
|
return this.stringType;
|
|
|
|
} else if (typeof(value) === 'bigint') {
|
|
|
|
return this.intType;
|
|
|
|
} else if (typeof(value) === 'number') {
|
|
|
|
return this.floatType;
|
2020-05-27 19:57:15 +02:00
|
|
|
} else if (value instanceof Record) {
|
2020-05-26 22:58:31 +02:00
|
|
|
const recordType = new RecordType()
|
2020-05-27 19:57:15 +02:00
|
|
|
for (const [fieldName, fieldValue] of value.getFields()) {
|
|
|
|
recordType.addField(name, new PlainRecordFieldType(this.getTypeOfValue(fieldValue)));
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
return recordType;
|
|
|
|
} else {
|
|
|
|
throw new Error(`Could not determine type of given value.`);
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
public registerSourceFile(sourceFile: SourceFile): void {
|
|
|
|
for (const node of sourceFile.preorder()) {
|
|
|
|
if (isBoltMacroCall(node)) {
|
|
|
|
continue; // FIXME only continue when we're not in an expression context
|
|
|
|
}
|
|
|
|
if (isBoltExpression(node)) {
|
|
|
|
node.type = this.createInitialTypeForExpression(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private createInitialTypeForExpression(node: Syntax): Type {
|
|
|
|
|
|
|
|
if (node.type !== undefined) {
|
|
|
|
return node.type;
|
|
|
|
}
|
|
|
|
|
|
|
|
let resultType;
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
switch (node.kind) {
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
case SyntaxKind.BoltMatchExpression:
|
|
|
|
{
|
|
|
|
const unionType = new UnionType();
|
|
|
|
for (const matchArm of node.arms) {
|
|
|
|
unionType.addElement(this.createInitialTypeForExpression(matchArm.body));
|
|
|
|
}
|
|
|
|
resultType = unionType;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
case SyntaxKind.BoltRecordDeclaration:
|
|
|
|
{
|
2020-05-27 19:57:15 +02:00
|
|
|
const recordSym = this.resolver.getSymbolForNode(node, ScopeType.Type);
|
2020-05-26 22:58:31 +02:00
|
|
|
assert(recordSym !== null);
|
|
|
|
if (this.opaqueTypes.has(recordSym!.id)) {
|
2020-05-27 19:57:15 +02:00
|
|
|
resultType = this.opaqueTypes.get(recordSym!.id);
|
|
|
|
} else {
|
|
|
|
const opaqueType = new OpaqueType(recordSym!);
|
|
|
|
this.opaqueTypes.set(recordSym!.id, opaqueType);
|
|
|
|
resultType = opaqueType;
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltFunctionExpression:
|
|
|
|
{
|
|
|
|
const paramTypes = node.params.map(param => {
|
2020-05-27 20:59:45 +02:00
|
|
|
if (param.typeExpr === null) {
|
2020-05-27 19:57:15 +02:00
|
|
|
return this.anyType;
|
|
|
|
}
|
2020-05-27 20:59:45 +02:00
|
|
|
return this.createInitialTypeForTypeExpression(param.typeExpr);
|
2020-05-27 19:57:15 +02:00
|
|
|
});
|
|
|
|
let returnType = node.returnType === null
|
|
|
|
? this.anyType
|
|
|
|
: this.createInitialTypeForTypeExpression(node.returnType);
|
2020-05-27 20:59:45 +02:00
|
|
|
resultType = new FunctionType(paramTypes, returnType);
|
2020-05-27 19:57:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltQuoteExpression:
|
2020-05-27 20:59:45 +02:00
|
|
|
{
|
|
|
|
resultType = this.syntaxType;
|
|
|
|
break
|
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
|
|
|
case SyntaxKind.BoltMemberExpression:
|
|
|
|
case SyntaxKind.BoltReferenceExpression:
|
|
|
|
case SyntaxKind.BoltCallExpression:
|
|
|
|
case SyntaxKind.BoltBlockExpression:
|
|
|
|
{
|
|
|
|
resultType = this.anyType;
|
2020-05-26 22:58:31 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
case SyntaxKind.BoltConstantExpression:
|
2020-05-27 19:57:15 +02:00
|
|
|
{
|
|
|
|
resultType = this.getTypeOfValue(node.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(`Could not create a type for node ${kindToString(node.kind)}.`);
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
node.type = resultType;
|
|
|
|
|
|
|
|
return resultType;
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
private createInitialTypeForTypeExpression(node: BoltTypeExpression): Type {
|
2020-05-26 21:01:38 +02:00
|
|
|
switch (node.kind) {
|
2020-05-27 19:57:15 +02:00
|
|
|
case SyntaxKind.BoltLiftedTypeExpression:
|
|
|
|
return this.createInitialTypeForExpression(node.expression);
|
2020-05-26 21:01:38 +02:00
|
|
|
default:
|
2020-05-27 19:57:15 +02:00
|
|
|
throw new Error(`Could not create a type for node ${kindToString(node.kind)}.`);
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
public isVoidType(type: Type): boolean {
|
|
|
|
return type === this.voidType;
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
public getCallableFunctions(node: BoltExpression): BoltFunctionDeclaration[] {
|
|
|
|
|
|
|
|
const resolver = this.resolver;
|
|
|
|
|
|
|
|
const results: BoltFunctionDeclaration[] = [];
|
|
|
|
visitExpression(node);
|
|
|
|
return results;
|
|
|
|
|
|
|
|
function visitExpression(node: BoltExpression) {
|
|
|
|
switch (node.kind) {
|
|
|
|
case SyntaxKind.BoltMemberExpression:
|
|
|
|
{
|
|
|
|
visitExpression(node.expression);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.BoltQuoteExpression:
|
|
|
|
{
|
|
|
|
// TODO visit all unquote expressions
|
|
|
|
//visitExpression(node.tokens);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.BoltCallExpression:
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.BoltReferenceExpression:
|
|
|
|
{
|
|
|
|
const scope = resolver.getScopeForNode(node, ScopeType.Variable);
|
|
|
|
assert(scope !== null);
|
|
|
|
const resolvedSym = resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!);
|
|
|
|
if (resolvedSym !== null) {
|
|
|
|
for (const decl of resolvedSym.declarations) {
|
|
|
|
visitFunctionBodyElement(decl as BoltFunctionBodyElement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
|
|
|
function visitFunctionBodyElement(node: BoltFunctionBodyElement) {
|
|
|
|
switch (node.kind) {
|
|
|
|
case SyntaxKind.BoltFunctionDeclaration:
|
|
|
|
results.push(node);
|
|
|
|
break;
|
|
|
|
case SyntaxKind.BoltVariableDeclaration:
|
|
|
|
if (node.value !== null) {
|
|
|
|
visitExpression(node.value);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|