bolt/src/checker.ts

1475 lines
43 KiB
TypeScript
Raw Normal View History

import { FastStringMap, assert, isPlainObject, some, prettyPrintTag, map, flatMap, filter, memoize, comparator, Ref, createRef, every, FastMultiMap, getKeyTag, pushAll } from "./util";
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, BoltCallExpression, BoltIdentifier, isBoltDeclarationLike, isBoltPattern, isJSExpression, isBoltStatement, isJSStatement, isJSPattern, isJSParameter, isBoltParameter, isBoltMatchArm, isBoltRecordFieldValue, isBoltRecordFieldPattern, isEndOfFile, isSyntax, isBoltFunctionDeclaration, isBoltTypeDeclaration, isBoltRecordDeclaration, BoltImplDeclaration, isBoltTraitDeclaration, isBoltImplDeclaration, BoltTypeExpression, BoltDeclaration, BoltTypeDeclaration, BoltReferenceExpression, BoltReferenceTypeExpression, BoltRecordDeclaration, } from "./ast";
2020-05-29 18:44:58 +02:00
import { convertNodeToSymbolPath, ScopeType, SymbolResolver, SymbolInfo, SymbolPath } from "./resolver";
import { Value, Record } from "./evaluator";
import { getAllReturnStatementsInFunctionBody, getFullyQualifiedPathToNode, hasDiagnostic } from "./common";
import { E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL, E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER, E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER, E_TYPE_MISMATCH, Diagnostic, E_ARGUMENT_TYPE_NOT_ASSIGNABLE, DiagnosticPrinter, E_THIS_NODE_CAUSED_INVALID_TYPE, E_NOT_CALLABLE, E_PARAMETER_DECLARED_HERE, E_BUILTIN_TYPE_MISSING } from "./diagnostics";
import { BOLT_MAX_FIELDS_TO_PRINT } from "./constants";
2020-05-29 18:44:58 +02:00
import { emitNode } from "./emitter";
import { type } from "os";
import { fn } from "moment";
import { intersects } from "semver";
// 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-29 18:44:58 +02:00
// This is a character that is used as a prefix in path names to distinguish
// a global symbol from a symbol coming from a specific source file.
const GLOBAL_SCOPE_MARKER = '@'
enum TypeKind {
PrimType,
AnyType,
NeverType,
VoidType,
FunctionType,
RecordType,
PlainRecordFieldType,
VariantType,
IntersectType,
UnionType,
TupleType,
CallType,
TraitType,
}
function toArray<T>(value: T[] | T | null | undefined): T[] {
if (Array.isArray(value)) {
return value;
}
if (value === null || value === undefined) {
return [];
}
return [ value ];
}
export type Type
= PrimType
| VoidType
| CallType
| AnyType
| NeverType
| FunctionType
| RecordType
| VariantType
| TupleType
| IntersectType
| UnionType
| PlainRecordFieldType
| TraitType
type TypeRef = Ref<Type>;
2020-05-29 18:44:58 +02:00
let nextTypeId = 1;
function areTypesEquivalent(a: Type, b: Type): boolean {
if (a.kind === TypeKind.TupleType && b.kind === TypeKind.VoidType) {
}
2020-05-29 18:44:58 +02:00
// This is a hack that should be fixed
2020-05-29 18:44:58 +02:00
if (a.kind !== b.kind) {
return false;
}
if (a.kind === TypeKind.NeverType && a.kind === TypeKind.NeverType) {
return true;
}
if (a.kind === TypeKind.AnyType && a.kind === TypeKind.AnyType) {
return true;
}
if (a.kind === TypeKind.PrimType && b.kind === TypeKind.PrimType) {
2020-05-29 18:44:58 +02:00
return a.name === b.name;
}
2020-05-29 18:44:58 +02:00
if (a.kind === TypeKind.FunctionType && b.kind === TypeKind.FunctionType) {
return a.source.id === b.source.id;
}
if (a.kind === TypeKind.RecordType && b.kind === TypeKind.RecordType) {
return a.source.id === b.source.id;
}
if (a.kind === TypeKind.TraitType && b.kind === TypeKind.TraitType) {
return a.source.id === b.source.id;
}
2020-05-29 18:44:58 +02:00
throw new Error(`I did not expected to see the provided type combination.`)
}
function isTypeLessThan(a: Type, b: Type): boolean {
2020-05-29 18:44:58 +02:00
if (a.kind !== b.kind) {
return a.kind < b.kind;
}
// These types have only one unique inhabitant, so they are always equal,
// and, by extension, never less than one another.
if ((a.kind === TypeKind.NeverType && a.kind === TypeKind.NeverType)
|| (a.kind === TypeKind.AnyType && a.kind === TypeKind.AnyType)
|| (a.kind === TypeKind.VoidType && b.kind === TypeKind.VoidType)) {
2020-05-29 18:44:58 +02:00
return false;
}
if (a.kind === TypeKind.PrimType && b.kind === TypeKind.PrimType) {
2020-05-29 18:44:58 +02:00
return a.name < b.name;
}
//if (a.kind === TypeKind.UnionType && b.kind === TypeKind.UnionType) {
// a.elementTypes.sort(comparator(areTypesLexicallyLessThan));
// b.elementTypes.sort(comparator(areTypesLexicallyLessThan));
// let i = 0;
// let j = 0;
// while (true) {
// if (!areTypesLexicallyLessThan(a.elementTypes[i], b.elementTypes[j])) {
// return false;
// }
// j++;
// }
//}
if ((a.kind === TypeKind.FunctionType && b.kind === TypeKind.FunctionType)
|| (a.kind === TypeKind.RecordType && b.kind === TypeKind.RecordType)
|| (a.kind === TypeKind.TraitType && b.kind === TypeKind.TraitType)) {
return a.id < b.id;
}
2020-05-29 18:44:58 +02:00
throw new Error(`I did not expected to see the provided type combination.`)
}
abstract class TypeBase {
public abstract kind: TypeKind;
2020-05-29 18:44:58 +02:00
public id = nextTypeId++;
public failed = false;
2020-05-29 18:44:58 +02:00
constructor(public sourceNodes: Syntax[] = []) {
2020-05-29 18:44:58 +02:00
}
public [getKeyTag](): string {
return this.id.toString();
}
public markAsFailed() {
this.failed = true;
}
public [prettyPrintTag](): string {
return prettyPrintType(this as Type);
}
}
export function isType(value: any) {
return typeof(value) === 'object'
&& value !== null
&& value.__IS_TYPE !== null;
}
export class PrimType extends TypeBase {
public kind: TypeKind.PrimType = TypeKind.PrimType;
constructor(public name: string, sources: Syntax[] = []) {
super(sources);
2020-05-29 18:44:58 +02:00
}
}
export class VoidType extends TypeBase {
public kind: TypeKind.VoidType = TypeKind.VoidType;
}
export class AnyType extends TypeBase {
public kind: TypeKind.AnyType = TypeKind.AnyType;
}
export class NeverType extends TypeBase {
public kind: TypeKind.NeverType = TypeKind.NeverType;
}
export class FunctionType extends TypeBase {
public kind: TypeKind.FunctionType = TypeKind.FunctionType;
constructor(
public id: number,
public paramTypes: TypeRef[],
public returnType: TypeRef,
sources: Syntax[] = [],
) {
super(sources);
}
public getParameterCount(): number {
return this.paramTypes.length;
}
public getTypeAtParameterIndex(index: number) {
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 {
public kind: TypeKind.VariantType = TypeKind.VariantType;
constructor(public elementTypes: TypeRef[], sources: Syntax[] = []) {
super(sources);
}
public getVariantTypes(): IterableIterator<TypeRef> {
return this.elementTypes[Symbol.iterator]();
}
}
export class IntersectType extends TypeBase {
public kind: TypeKind.IntersectType = TypeKind.IntersectType;
public elementTypes: TypeRef[] = [];
constructor(
elementTypes: Iterable<TypeRef>,
sources: Syntax[] = []
) {
super(sources);
this.elementTypes = [...elementTypes];
}
}
export class UnionType extends TypeBase {
public kind: TypeKind.UnionType = TypeKind.UnionType;
public elementTypes: TypeRef[] = [];
constructor(
elementTypes: Iterable<TypeRef> = [],
sources: Syntax[] = []
) {
super(sources);
this.elementTypes = [...elementTypes];
}
public addElement(element: TypeRef): void {
2020-05-29 18:44:58 +02:00
this.elementTypes.push(element);
}
public getElementTypes(): IterableIterator<TypeRef> {
2020-05-29 18:44:58 +02:00
return this.elementTypes[Symbol.iterator]();
}
}
export type RecordFieldType
= PlainRecordFieldType
2020-05-29 18:44:58 +02:00
| AnyType
| UnionType
class PlainRecordFieldType extends TypeBase {
public kind: TypeKind.PlainRecordFieldType = TypeKind.PlainRecordFieldType;
constructor(public name: string, public type: TypeRef, sources: Syntax[] = []) {
super(sources);
}
}
export class TraitType extends TypeBase {
public kind: TypeKind.TraitType = TypeKind.TraitType;
private functionTypesByName = new FastStringMap<string, FunctionType>();
constructor(
public source: Syntax,
public memberTypes: Iterable<[string, FunctionType]>,
sources: Syntax[] = []
) {
super(sources);
for (const [name, type] of memberTypes) {
this.functionTypesByName.set(name, type);
}
}
}
export class RecordType extends TypeBase {
public kind: TypeKind.RecordType = TypeKind.RecordType;
2020-05-29 18:44:58 +02:00
private memberTypes: RecordFieldType[] = [];
private memberTypesByFieldName = new FastStringMap<string, RecordFieldType>();
constructor(
public source: Syntax | Type | number,
iterable?: Iterable<RecordFieldType>,
sources: Syntax[] = []
) {
super();
if (iterable !== undefined) {
2020-05-29 18:44:58 +02:00
for (const type of iterable) {
this.addMemberType(type);
}
}
}
2020-05-29 18:44:58 +02:00
public getRequiredFieldNames(): IterableIterator<string> {
return this.memberTypesByFieldName.keys();
}
2020-05-29 18:44:58 +02:00
public addMemberType(type: RecordFieldType): void {
this.memberTypes.push(type);
if (type instanceof PlainRecordFieldType) {
this.memberTypesByFieldName.set(type.name, type);
}
}
2020-05-29 18:44:58 +02:00
public getMemberTypes(): IterableIterator<RecordFieldType> {
return this.memberTypes[Symbol.iterator]();
}
2020-05-29 18:44:58 +02:00
public isFieldRequired(name: string): boolean {
return this.memberTypesByFieldName.has(name);
}
public clear(): void {
2020-05-29 18:44:58 +02:00
this.memberTypes = [];
this.memberTypesByFieldName.clear();
}
}
export class TupleType extends TypeBase {
kind: TypeKind.TupleType = TypeKind.TupleType;
constructor(
public elementTypes: TypeRef[] = [],
sources: Syntax[] = []
) {
super(sources);
}
}
export class CallType extends TypeBase {
public kind: TypeKind.CallType = TypeKind.CallType;
constructor(
public fnType: TypeRef,
public argumentTypes: TypeRef[],
sources: Syntax[] = []
) {
super(sources);
2020-05-29 18:44:58 +02:00
}
public getArgumentTypes(): IterableIterator<TypeRef> {
2020-05-29 18:44:58 +02:00
return this.argumentTypes[Symbol.iterator]();
}
}
2020-05-29 18:44:58 +02:00
function isTypePotentiallyCallable(type: Type): boolean {
return type.kind === TypeKind.FunctionType
|| type.kind === TypeKind.AnyType
|| (type.kind === TypeKind.UnionType && type.elementTypes.some(isTypePotentiallyCallable))
}
function* getAllUnionElementTypes(type: TypeRef): IterableIterator<TypeRef> {
switch (type.kind) {
case TypeKind.UnionType:
{
for (const elementType of type.getElementTypes()) {
yield* getAllUnionElementTypes(elementType);
}
break;
}
default:
yield type;
}
}
export function prettyPrintType(typeRef: TypeRef): string {
let out = ''
let hasElementType = false;
for (const elementType of getAllUnionElementTypes(typeRef)) {
2020-05-29 18:44:58 +02:00
if (hasElementType) {
out += ' | '
}
hasElementType = true;
2020-05-29 18:44:58 +02:00
switch (elementType.kind) {
case TypeKind.PlainRecordFieldType:
out += '{ ' + elementType.name + ' } ';
break;
case TypeKind.CallType:
out += 'return type of '
2020-05-29 18:44:58 +02:00
out += prettyPrintType(elementType.fnType);
out += '(';
out += elementType.argumentTypes.map(prettyPrintType).join(', ')
out += ')'
break;
case TypeKind.PrimType:
2020-05-29 18:44:58 +02:00
out += elementType.name;
break;
case TypeKind.AnyType:
out += 'any';
break;
case TypeKind.NeverType:
out += 'never'
break;
case TypeKind.FunctionType:
{
out += 'fn (';
out += elementType.paramTypes.map(prettyPrintType).join(', ');
out += ')';
break;
}
case TypeKind.IntersectType:
{
out += elementType.elementTypes.map(t => prettyPrintType(t)).join(' & ');
2020-05-29 18:44:58 +02:00
break;
}
case TypeKind.TupleType:
{
out += '('
let i = 0;
for (const tupleElementType of elementType.elementTypes) {
out += prettyPrintType(tupleElementType);
i++
if (i >= BOLT_MAX_FIELDS_TO_PRINT) {
out += ' (element types omitted)'
break;
}
}
2020-05-29 18:44:58 +02:00
out += ')'
break;
}
case TypeKind.RecordType:
{
if (elementType.sourceNodes.length > 0) {
switch (elementType.sourceNodes[0].kind) {
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltRecordDeclaration:
out += elementType.sourceNodes[0].name.text;
2020-05-29 18:44:58 +02:00
break;
default:
throw new Error(`I did not know how to print AST node for a record type`)
}
} else {
throw new Error(`I did not know how to print the source of a record type.`)
}
break;
//out += '{'
//let i = 0;
//for (const memberType of elementType.getMemberTypes()) {
// for (const memberTypeNoUnion of getAllPossibleElementTypes(memberType)) {
// switch (memberTypeNoUnion.kind) {
// case TypeKind.AnyType:
// out += ' ...';
// break;
// case TypeKind.PlainRecordFieldType:
// out += ' ' + memberTypeNoUnion.name + ': ' + prettyPrintType(memberTypeNoUnion.type);
// break;
// default:
// throw new Error(`I did not know how to pretty-print a record field type.`)
// }
// i++;
// if (i >= BOLT_MAX_FIELDS_TO_PRINT) {
// out += ' (field types omitted)'
// break
// }
// }
//}
//out += ' }'
//break;
}
2020-05-29 18:44:58 +02:00
default:
throw new Error(`Could not pretty-print type ${TypeKind[elementType.kind]}`)
}
}
if (!hasElementType) {
2020-05-29 18:44:58 +02:00
out += 'never'
}
return out;
}
2020-05-29 18:44:58 +02:00
let nextRecordTypeId = 1;
function introducesType(node: Syntax) {
return isBoltExpression(node)
|| isBoltFunctionDeclaration(node)
|| isBoltTypeDeclaration(node)
|| isBoltRecordDeclaration(node)
2020-05-29 18:44:58 +02:00
|| isBoltParameter(node)
|| isBoltRecordFieldValue(node)
2020-05-29 18:44:58 +02:00
|| isBoltRecordFieldPattern(node)
|| isBoltPattern(node)
|| isBoltTypeExpression(node)
|| isJSExpression(node)
|| isJSPattern(node)
|| isJSParameter(node)
}
2020-05-29 18:44:58 +02:00
export class TypeChecker {
2020-05-29 18:44:58 +02:00
private dependencyGraph = new FastStringMap<string, Syntax[]>();
private neverType = new NeverType();
constructor(private resolver: SymbolResolver, private diagnostics: DiagnosticPrinter) {
}
private getNeverType() {
return this.neverType;
}
private getPrimType(path: string): Type {
2020-05-29 18:44:58 +02:00
const elements = path.split('::');
const symbolPath = new SymbolPath(elements.slice(0,-1), true, elements[elements.length-1]);
const resolvedSymbol = this.resolver.resolveGlobalSymbol(symbolPath, ScopeType.Type);
if (resolvedSymbol === null) {
this.diagnostics.add({
message: E_BUILTIN_TYPE_MISSING,
args: { name: path },
severity: 'error',
})
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
return createRef(new PrimType(GLOBAL_SCOPE_MARKER + path, [...resolvedSymbol.declarations]));
2020-05-29 18:44:58 +02:00
}
public createTypeForValue(value: Value, source: Syntax): Type {
if (typeof(value) === 'string') {
return new PrimType('@String', [ source ]);
} else if (typeof(value) === 'bigint') {
return new PrimType('@int', [ source ]);
} else if (typeof(value) === 'number') {
return new PrimType('@f64', [ source ]);
} else if (value instanceof Record) {
2020-05-29 18:44:58 +02:00
const memberTypes = [];
for (const [fieldName, fieldValue] of value.getFields()) {
const recordFieldType = new PlainRecordFieldType(name, this.createTypeForValue(fieldValue));
memberTypes.push(recordFieldType);
}
2020-05-29 18:44:58 +02:00
const recordType = new RecordType(nextRecordTypeId++, memberTypes);
return recordType;
} else {
throw new Error(`Could not determine type of given value.`);
}
}
private diagnoseTypeError(affectedType: Type, invalidType: Type): void {
for (const source of affectedType.sourceNodes) {
this.diagnostics.add({
message: E_TYPE_MISMATCH,
severity: 'error',
args: { left: affectedType, right: invalidType },
node: source,
nested: invalidType.sourceNodes.map(node => {
return {
message: E_THIS_NODE_CAUSED_INVALID_TYPE,
severity: 'info',
node,
args: { type: invalidType },
}
})
});
}
}
public registerSourceFile(sourceFile: SourceFile): void {
for (const node of sourceFile.preorder()) {
2020-05-29 18:44:58 +02:00
if (introducesType(node)) {
node.type = createRef(new AnyType);
}
if (isBoltImplDeclaration(node)) {
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Type);
assert(scope !== null);
const traitSymbol = this.resolver.resolveSymbolPath(convertNodeToSymbolPath(node.traitTypeExpr), scope!)
for (const traitDecl of traitSymbol!.declarations) {
traitDecl.addImplDeclaration(node);
}
}
2020-05-29 18:44:58 +02:00
}
}
private *getParentsThatMightNeedUpdate(node: Syntax): IterableIterator<Syntax> {
while (true) {
const parentNode = node.parentNode!;
if (!introducesType(parentNode)) {
break;
}
yield parentNode;
node = parentNode;
}
}
public solve(sourceFiles: IterableIterator<SourceFile>): void {
let queued: Syntax[] = [];
const nextQueue = new Set<Syntax>();
for (const sourceFile of sourceFiles) {
for (const node of sourceFile.preorder()) {
if (introducesType(node)) {
queued.push(node);
}
}
}
while (true) {
if (queued.length === 0) {
if (nextQueue.size > 0) {
queued = [...nextQueue];
nextQueue.clear();
continue;
} else {
break;
}
}
2020-05-29 18:44:58 +02:00
const node = queued.shift()!;
const derivedType = this.deriveTypeUsingChildren(node);
node.type.replaceWith(new IntersectType([node.type!.cloneRef(), derivedType], [ node ]));
this.checkType(node.type!);
2020-05-29 18:44:58 +02:00
if (!node.type!.failed) {
2020-05-29 18:44:58 +02:00
for (const dependantNode of this.getParentsThatMightNeedUpdate(node)) {
if (introducesType(dependantNode)) {
nextQueue.add(dependantNode);
}
2020-05-29 18:44:58 +02:00
}
for (const dependantNode of this.getNodesRequiringUpdate(node)) {
assert(introducesType(dependantNode));
2020-05-29 18:44:58 +02:00
nextQueue.add(dependantNode);
}
}
}
}
private markNodeAsRequiringUpdate(origNode: Syntax, nodeToUpdate: Syntax) {
if (!this.dependencyGraph.has(origNode.id.toString())) {
this.dependencyGraph.set(origNode.id.toString(), [ nodeToUpdate ]);
} else {
this.dependencyGraph.get(origNode.id.toString()).push(nodeToUpdate);
}
}
private *getNodesRequiringUpdate(node: Syntax): IterableIterator<Syntax> {
if (!this.dependencyGraph.has(node.id.toString())) {
return;
}
2020-05-29 18:44:58 +02:00
const visited = new Set<Syntax>();
const stack = [ ...this.dependencyGraph.get(node.id.toString()) ]
while (stack.length > 0) {
const node = stack.pop()!;
if (visited.has(node)) {
continue;
}
yield node;
visited.add(node)
if (this.dependencyGraph.has(node.id.toString())) {
for (const dependantNode of this.dependencyGraph.get(node.id.toString())) {
stack.push(dependantNode);
}
}
}
}
2020-05-29 18:44:58 +02:00
public isVoidType(type: Type): boolean {
return this.areTypesEquivalent(new TupleType, type);
2020-05-29 18:44:58 +02:00
}
/**
* Narrows @param outter down to @param inner. If @param outer could not be narrowed, this function return null.
*
* @param targetType The type that will be narrowed.
* @param smallerType The type that serves as the outer bound of `smallerType`.
2020-05-29 18:44:58 +02:00
*/
private deriveTypeUsingChildren(node: Syntax): Type {
switch (node.kind) {
2020-05-29 18:44:58 +02:00
case SyntaxKind.JSMemberExpression:
{
// TODO
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
case SyntaxKind.JSLiteralExpression:
{
2020-05-29 18:44:58 +02:00
if (typeof(node.value) === 'string') {
return this.getPrimType('String');
2020-05-29 18:44:58 +02:00
} else if (typeof(node.value) === 'number') {
return this.getPrimType('JSNum');
} else {
2020-05-29 18:44:58 +02:00
throw new Error(`I did not know how to derive a type for JavaScript value ${node.value}`)
}
2020-05-29 18:44:58 +02:00
}
case SyntaxKind.JSReferenceExpression:
{
// TODO
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
case SyntaxKind.JSCallExpression:
{
// TODO
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
case SyntaxKind.BoltMatchExpression:
{
const exprTypes = [];
for (const arm of node.arms) {
this.markNodeAsRequiringUpdate(arm.body, node);
exprTypes.push(arm.body.type!);
}
return createRef(new IntersectType(exprTypes, [ node ]));
2020-05-29 18:44:58 +02:00
}
case SyntaxKind.BoltParameter:
{
const resultTypes = [ node.bindings.type! ]
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.bindings, node);
if (node.typeExpr !== null) {
resultTypes.push(node.typeExpr.type!);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.typeExpr, node);
}
if (node.defaultValue !== null) {
resultTypes.push(node.defaultValue.type!)
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.defaultValue, node)
}
return createRef(new UnionType(resultTypes, [ node ]));
}
case SyntaxKind.BoltFunctionExpression:
{
for (const param of node.params) {
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(param, node);
}
const paramTypes = node.params.map(param => param.type!);
2020-05-29 18:44:58 +02:00
let returnType;
if (node.returnType === null) {
returnType = createRef(new AnyType([ node ]));
2020-05-29 18:44:58 +02:00
} else {
returnType = node.returnType.type!;
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.returnType, node);
}
return createRef(new FunctionType(node.id, paramTypes, returnType, [ node ]));
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltRecordDeclaration:
{
2020-05-29 18:44:58 +02:00
if (node.members === null) {
const symbolPath = getFullyQualifiedPathToNode(node);
return createRef(new PrimType(symbolPath.encode(), [ node ]));
2020-05-29 18:44:58 +02:00
} else {
let memberTypes: RecordFieldType[] = []
for (const member of node.members) {
//assert(member instanceof PlainRecordFieldType);
memberTypes.push(member.type! as RecordFieldType)
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(member, node);
}
return createRef(new RecordType(node, memberTypes));
2020-05-29 18:44:58 +02:00
}
}
case SyntaxKind.BoltRecordDeclarationField:
{
const resultType = new PlainRecordFieldType(node.name.text, node.typeExpr.type!);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.typeExpr, node)
return resultType;
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltRecordPattern:
{
2020-05-29 18:44:58 +02:00
let memberTypes = []
for (const member of node.fields) {
//assert(member.type instanceof PlainRecordFieldType);
memberTypes.push(member.type! as RecordFieldType);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(member, node);
}
return createRef(new RecordType(node.name, memberTypes));
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltRecordFieldPattern:
{
if (node.isRest) {
// TODO
} else {
assert(node.name !== null);
let nestedFieldType;
if (node.pattern === null) {
nestedFieldType = new AnyType;
} else {
nestedFieldType = node.pattern.type!;
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.pattern, node);
}
return createRef(new PlainRecordFieldType(node.name!.text, nestedFieldType));
2020-05-29 18:44:58 +02:00
}
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltTypeAliasDeclaration:
{
// TODO
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
//case SyntaxKind.BoltImplDeclaration:
//{
// return new TraitType(
// node,
// node.elements
// .filter(isBoltFunctionDeclaration)
// .map(element => {
// this.markNodeAsRequiringUpdate(element, node);
// return [
// emitNode(element.name),
// element.type!.solved
// ] as [string, FunctionType];
// })
// );
//}
//case SyntaxKind.BoltTraitDeclaration:
//{
// // TODO
// return new AnyType;
//}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltVariableDeclaration:
{
let elementTypes = []
if (node.value !== null) {
elementTypes.push(node.value.type!);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.value, node);
}
if (node.typeExpr !== null) {
elementTypes.push(node.typeExpr.type!);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.typeExpr, node);
}
return createRef(new UnionType(elementTypes));
2020-05-29 18:44:58 +02:00
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltExpressionPattern:
{
return this.deriveTypeUsingChildren(node.expression);
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltBindPattern:
{
// TODO
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltConstantExpression:
{
return this.createTypeForValue(node.value, node);
2020-05-29 18:44:58 +02:00
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltFunctionTypeExpression:
{
2020-05-29 18:44:58 +02:00
const paramTypes = node.params.map(param => {
this.markNodeAsRequiringUpdate(param, node);
return param.type!;
2020-05-29 18:44:58 +02:00
})
let returnType = null;
if (node.returnType === null) {
returnType = createRef(new AnyType);
2020-05-29 18:44:58 +02:00
} else {
returnType = node.returnType.type!;
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.returnType!, node);
}
return createRef(new FunctionType(node.id, paramTypes, returnType, [ node ]));
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltMacroCall:
{
// TODO
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
case SyntaxKind.BoltQuoteExpression:
{
return this.getPrimType('Lang::Bolt::Node');
2020-05-29 18:44:58 +02:00
}
//case SyntaxKind.BoltExpressionStatement:
//{
// return this.deriveTypeUsingChildren(node.expression);
//}
2020-05-29 18:44:58 +02:00
//case SyntaxKind.BoltReturnStatement:
//{
// if (node.value === null) {
// const tupleType = new TupleType();
// return tupleType
// }
// return node.value.type!.solved;
//}
2020-05-29 18:44:58 +02:00
//case SyntaxKind.BoltBlockExpression:
//{
// let elementTypes = [];
// if (node.elements !== null) {
// for (const returnStmt of getReturnStatementsInFunctionBody(node.elements)) {
// if (returnStmt.value !== null) {
// elementTypes.push(returnStmt.value.type!.solved)
// this.markNodeAsRequiringUpdate(returnStmt.value, node);
// }
// }
// }
// return new UnionType(elementTypes);
//}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltMemberExpression:
{
let unionType = new UnionType([]);
assert(node.path.length === 1);
const recordTypes = [];
for (const memberType of this.getTypesForMember(node.path[0], node.path[0].text, node.expression.type!)) {
2020-05-29 18:44:58 +02:00
unionType.addElement(memberType);
}
return createRef(unionType);
2020-05-29 18:44:58 +02:00
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltCallExpression:
{
let operandTypes = []
for (const operand of node.operands) {
operandTypes.push(operand.type!);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(operand, node);
}
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.operator, node);
return createRef(new CallType(node.operator.type!, operandTypes, [ node ]));
2020-05-29 18:44:58 +02:00
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltReferenceExpression:
{
2020-05-29 18:44:58 +02:00
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
if (scope === null) {
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
const symbolPath = convertNodeToSymbolPath(node.name);
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
if (resolvedSym === null) {
return createRef(new AnyType);
2020-05-29 18:44:58 +02:00
}
let elementTypes = [];
for (const decl of resolvedSym.declarations) {
elementTypes.push(decl.type!);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(decl, node)
}
return createRef(new UnionType(elementTypes));
}
2020-05-29 18:44:58 +02:00
case SyntaxKind.BoltFunctionDeclaration:
{
let returnTypes: TypeRef[] = [];
2020-05-29 18:44:58 +02:00
if (node.returnType !== null) {
returnTypes.push(node.returnType.type!);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(node.returnType, node);
}
if (node.body !== null) {
for (const returnStmt of getAllReturnStatementsInFunctionBody(node.body)) {
if (returnStmt.value !== null) {
returnTypes.push(returnStmt.value.type!);
this.markNodeAsRequiringUpdate(returnStmt.value, node);
} else {
returnTypes.push(createRef(new VoidType([ returnStmt ])));
}
2020-05-29 18:44:58 +02:00
}
}
let paramTypes = [];
for (const param of node.params) {
paramTypes.push(param.type!);
2020-05-29 18:44:58 +02:00
this.markNodeAsRequiringUpdate(param, node);
}
return createRef(new FunctionType(node.id, paramTypes, createRef(new UnionType(returnTypes))));
2020-05-29 18:44:58 +02:00
}
case SyntaxKind.BoltReferenceTypeExpression:
{
2020-05-29 18:44:58 +02:00
if (node.name.modulePath.length === 0) {
switch ((node.name.name as BoltIdentifier).text) {
case 'never':
return createRef(new NeverType);
2020-05-29 18:44:58 +02:00
case 'any':
return createRef(new AnyType);
case 'int':
return createRef(new PrimType('@int'));
case 'String':
return createRef(new PrimType('@String'));
2020-05-29 18:44:58 +02:00
}
}
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Type);
assert(scope !== null);
2020-05-29 18:44:58 +02:00
const symbolPath = convertNodeToSymbolPath(node.name);
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
2020-05-29 18:44:58 +02:00
if (resolvedSym === null) {
return createRef(new NeverType([ node ]));
}
2020-05-29 18:44:58 +02:00
let elementTypes = [];
for (const decl of resolvedSym.declarations) {
this.markNodeAsRequiringUpdate(decl, node);
elementTypes.push(decl.type!);
2020-05-29 18:44:58 +02:00
}
return createRef(new UnionType(elementTypes, [ node ]));
}
2020-05-29 18:44:58 +02:00
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
2020-05-29 18:44:58 +02:00
}
}
2020-05-29 18:44:58 +02:00
//if (returnStmt.value === null) {
// if (!this.isVoidType(returnType)) {
// returnStmt.errors.push({
// message: E_MAY_NOT_RETURN_A_VALUE,
// severity: 'error',
// nested: [{
// message: E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID,
// severity: 'error',
// node: node.returnType !== null ? node.returnType : node,
// }]
// });
// }
//} else {
// const stmtReturnType = this.getTypeOfExpression(returnStmt.value);
// if (!this.isTypeAssignableTo(returnType, stmtReturnType)) {
// if (this.isVoidType(stmtReturnType)) {
// returnStmt.value.errors.push({
// message: E_MUST_RETURN_A_VALUE,
// severity: 'error',
// nested: [{
// message: E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID,
// severity: 'error',
// node: node.returnType !== null ? node.returnType : node,
// }]
// })
// } else {
// returnStmt.value.errors.push({
// message: E_TYPES_NOT_ASSIGNABLE,
// severity: 'error',
// args: {
// left: returnType,
// right: stmtReturnType,
// }
// })
// }
// }
//}
private areTypesEquivalent(a: Type, b: Type): boolean {
2020-05-29 18:44:58 +02:00
// The next statements handle equivalence checking of the special types.
// These checks should happen before other checks.
if (a.kind === TypeKind.NeverType && b.kind === TypeKind.NeverType) {
return true;
}
if (a.kind === TypeKind.NeverType) {
return false;
}
if (a.kind === TypeKind.AnyType || b.kind === TypeKind.AnyType) {
return true;
}
2020-05-29 18:44:58 +02:00
// Next up are checks for the semantic equivalence of union types. If a union type occurs on the left,
// each and every type inside the union type must match the right-hand-side. In the other case,
// the type is semantic equivalent is one of the element types matched the left-hand-side.
2020-05-29 18:44:58 +02:00
if (a.kind === TypeKind.UnionType) {
return a.elementTypes.every(el => this.areTypesEquivalent(el, b));
2020-05-29 18:44:58 +02:00
}
if (b.kind === TypeKind.UnionType) {
return b.elementTypes.some(el => this.areTypesEquivalent(el, a));
2020-05-29 18:44:58 +02:00
}
2020-05-29 18:44:58 +02:00
// To check equivalence, we have no choice but to resolve the return types and see if the returned type matches
// the other size of the equivalence. The equivalence is anticommutative, so we have to repeat the checks for
// each side of the equivalence.
if (a.kind === TypeKind.CallType) {
2020-05-29 18:44:58 +02:00
const resolvedType = this.resolveReturnType(a);
return this.areTypesEquivalent(resolvedType, b);
2020-05-29 18:44:58 +02:00
}
if (b.kind === TypeKind.CallType) {
const resolvedType = this.resolveReturnType(b);
return this.areTypesEquivalent(a, resolvedType);
2020-05-29 18:44:58 +02:00
}
// The following cases cover types that have nominal typing as their semantics.
// Checking equivalence between them should be the same as checking whether they originated
// from the same node in the AST.
if (a.kind === TypeKind.PrimType && b.kind === TypeKind.PrimType) {
2020-05-29 18:44:58 +02:00
return a.name === b.name;
}
if (a.kind === TypeKind.FunctionType && b.kind === TypeKind.FunctionType) {
return a.source === b.source;
}
if (a.kind === TypeKind.RecordType && b.kind === TypeKind.RecordType) {
return a.source === b.source;
}
if (a.kind === TypeKind.TraitType && b.kind === TypeKind.TraitType) {
return a.source === b.source;
}
2020-05-29 18:44:58 +02:00
// FIXME There are probably more cases that should be covered.
return false;
//throw new Error(`I did not know how to calculate the equivalence of ${TypeKind[a.kind]} and ${TypeKind[b.kind]}`)
2020-05-29 18:44:58 +02:00
}
private diagnoseFunctionCall(callType: CallType, fnType: FunctionType) {
2020-05-29 18:44:58 +02:00
if (fnType.paramTypes.length > callType.argumentTypes.length) {
2020-05-29 18:44:58 +02:00
if (!callType.failed) {
2020-05-29 18:44:58 +02:00
let nested: Diagnostic[] = [];
for (let i = callType.argumentTypes.length; i < fnType.paramTypes.length; i++) {
nested.push({
message: E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER,
severity: 'info',
node: fnType.paramTypes[i].sourceNodes[0]
});
}
this.diagnostics.add({
message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
severity: 'error',
node: callType.sourceNodes[0],
args: {
expected: fnType.paramTypes.length,
actual: callType.argumentTypes.length,
},
nested,
});
2020-05-29 18:44:58 +02:00
callType.markAsFailed();
}
return false;
} else if (callType.argumentTypes.length > fnType.paramTypes.length) {
if (!callType.failed) {
const nested: Diagnostic[] = [];
for (let i = fnType.paramTypes.length; i < callType.argumentTypes.length; i++) {
nested.push({
message: E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER,
severity: 'info',
node: callType.argumentTypes[i].sourceNodes[0],
});
}
this.diagnostics.add({
message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
severity: 'error',
node: callType.sourceNodes[0],
args: {
expected: fnType.paramTypes.length,
actual: callType.argumentTypes.length,
},
nested,
});
callType.markAsFailed();
}
return false;
} else {
let hasErrors = false;
const paramCount = fnType.paramTypes.length;
for (let i = 0; i < paramCount; i++) {
const argType = callType.argumentTypes[i];
const paramType = fnType.paramTypes[i];
if (!this.isTypeAssignableTo(argType, paramType)) {
if (!argType.failed) {
this.diagnostics.add({
message: E_ARGUMENT_TYPE_NOT_ASSIGNABLE,
severity: 'error',
node: argType.sourceNodes[0],
args: { argType, paramType },
nested: [{
message: E_PARAMETER_DECLARED_HERE,
severity: 'info',
node: fnType.paramTypes[i].sourceNodes[0],
}]
});
argType.markAsFailed();
}
hasErrors = true;
}
}
return !hasErrors;
}
}
private *getReturnTypes(fnType: TypeRef, callType: TypeRef): IterableIterator<TypeRef> {
2020-05-29 18:44:58 +02:00
switch (fnType.kind) {
case TypeKind.NeverType:
// If we requested the return type of a union containing a 'never'-type, then
// we are not allowed to let any function match.
break;
case TypeKind.AnyType:
// The return type of an 'any'-type (which includes all functions that have 'any' as return type)
// is the 'any'-type. If we don't find a more specific match, this will be the type that is returned.
yield fnType;
break;
case TypeKind.PrimType:
// We can never call a primitive type, so indicate this error to the user.
this.diagnostics.add({
message: E_NOT_CALLABLE,
severity: 'error',
node: callType.sourceNodes[0],
});
break;
case TypeKind.UnionType:
for (const elementType of fnType.elementTypes) {
yield* this.getReturnTypes(elementType, callType);
}
break;
case TypeKind.FunctionType:
if (this.diagnoseFunctionCall(callType, fnType)) {
2020-05-29 18:44:58 +02:00
// If the argument types and parameter types didn't fail to match,
// the function type is eligable to be 'called' by the given ReturnType.
yield fnType.returnType;
}
break;
case TypeKind.IntersectType:
const returnTypes = [];
for (const elementType of fnType.elementTypes) {
for (const returnType of this.getReturnTypes(elementType, callType)) {
returnTypes.push(returnType);
}
}
yield createRef(new IntersectType(returnTypes));
break;
default:
throw new Error(`Resolving the given type will not work.`)
}
}
2020-05-29 18:44:58 +02:00
private mergeTypes(target: Type, source: Type): void {
if ((target.kind === TypeKind.NeverType && source.kind === TypeKind.NeverType)
|| (target.kind === TypeKind.AnyType && source.kind === TypeKind.AnyType)) {
pushAll(target.sourceNodes, source.sourceNodes);
} else {
throw new Error(`could not merge two types`);
}
}
private removeNeverTypes(types: TypeRef[]) {
for (let i = 0; i < types.length; i++) {
if (types[i].kind === TypeKind.NeverType) {
types.splice(i, 1);
}
}
}
private removeAnyTypes(types: TypeRef[]) {
for (let i = 0; i < types.length; i++) {
if (types[i].kind === TypeKind.AnyType) {
types.splice(i, 1);
}
}
}
private mergeNeverTypes(types: TypeRef[]): Type | null {
2020-05-29 18:44:58 +02:00
let firstNeverType = null;
for (let i = 0; i < types.length; i++) {
const type = types[i];
if (type.kind === TypeKind.NeverType) {
if (firstNeverType === null) {
firstNeverType = type;
} else {
this.mergeTypes(firstNeverType, type);
}
}
2020-05-29 18:44:58 +02:00
}
return firstNeverType;
}
private mergeAnyTypes(types: TypeRef[]) {
2020-05-29 18:44:58 +02:00
let firstAnyType = null;
2020-05-29 18:44:58 +02:00
for (let i = 0; i < types.length; i++) {
const type = types[i];
if (type.kind === TypeKind.AnyType) {
if (firstAnyType === null) {
firstAnyType = type;
} else {
this.mergeTypes(firstAnyType, type);
types.splice(i, 1);
}
}
}
2020-05-29 18:44:58 +02:00
}
private checkType(type: TypeRef) {
2020-05-29 18:44:58 +02:00
const resultTypes = [];
switch (type.kind) {
2020-05-29 18:44:58 +02:00
case TypeKind.PrimType:
break;
2020-05-29 18:44:58 +02:00
case TypeKind.FunctionType:
break;
2020-05-29 18:44:58 +02:00
case TypeKind.AnyType:
break;
2020-05-29 18:44:58 +02:00
case TypeKind.NeverType:
this.diagnoseTypeError(type, null);
break;
2020-05-29 18:44:58 +02:00
case TypeKind.IntersectType:
{
for (const elementType of type.elementTypes) {
this.checkType(elementType);
}
const neverType = this.mergeNeverTypes(type.elementTypes);
if (neverType !== null) {
this.diagnoseTypeError(type, neverType);
type.replaceWith(neverType);
2020-05-29 18:44:58 +02:00
break;
}
const anyType = this.mergeAnyTypes(type.elementTypes);
let firstSpecialType = null;
for (const elementType of type.elementTypes) {
if (elementType.kind === TypeKind.PrimType || elementType.kind === TypeKind.FunctionType || elementType.kind === TypeKind.RecordType) {
if (firstSpecialType === null) {
firstSpecialType = elementType;
} else {
if (!this.areTypesEquivalent(firstSpecialType, elementType)) {
this.diagnoseTypeError(firstSpecialType, elementType);
this.mergeTypes(firstSpecialType, elementType);
elementType.replaceWith(new NeverType(firstSpecialType.sourceNodes))
}
}
}
}
if (anyType !== null && firstSpecialType !== null) {
this.removeAnyTypes(type.elementTypes);
}
if (type.elementTypes.length === 1) {
type.replaceWith(type.elementTypes[0]);
}
break;
}
2020-05-29 18:44:58 +02:00
case TypeKind.UnionType:
{
for (const elementType of type.elementTypes) {
this.checkType(elementType);
}
this.removeNeverTypes(type.elementTypes);
this.mergeAnyTypes(type.elementTypes);
break;
}
2020-05-29 18:44:58 +02:00
case TypeKind.CallType:
{
this.checkType(type.fnType);
for (const argType of type.getArgumentTypes()) {
this.checkType(argType);
}
const returnTypes = [...this.getReturnTypes(type.fnType, type)];
type.replaceWith(new UnionType(returnTypes, type.sourceNodes));
break;
}
2020-05-29 18:44:58 +02:00
default:
throw new Error(`I did not know how to check type ${TypeKind[type.kind]}`)
2020-05-29 18:44:58 +02:00
}
}
2020-05-29 18:44:58 +02:00
private isTypeAssignableTo(source: Type, target: Type): boolean {
// Functions are at the moment immutable and can never be assigned to one another.
// FIXME Passing a lambda expression to a function parameter
if (source.kind === TypeKind.FunctionType || target.kind === TypeKind.FunctionType) {
return false;
}
if (target.kind === TypeKind.NeverType || source.kind === TypeKind.NeverType) {
return false;
}
if (source.kind === TypeKind.AnyType || target.kind === TypeKind.AnyType) {
return true;
}
if (source.kind === TypeKind.PrimType && target.kind === TypeKind.PrimType) {
return source.name === target.name;
}
if (target.kind === TypeKind.IntersectType) {
return target.elementTypes.every(t => this.isTypeAssignableTo(source, t));
}
if (source.kind === TypeKind.IntersectType) {
return source.elementTypes.every(t => this.isTypeAssignableTo(t, target));
}
if (source.kind === TypeKind.UnionType) {
return source.elementTypes.every(t => this.isTypeAssignableTo(t, target));
}
if (target.kind === TypeKind.UnionType) {
return target.elementTypes.every(t => this.isTypeAssignableTo(source, t));
}
// FIXME cover more cases
return false;
2020-05-29 18:44:58 +02:00
}
}