2020-05-23 21:15:20 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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";
|
2020-05-27 19:57:15 +02:00
|
|
|
import { Value, Record } from "./evaluator";
|
2020-10-29 21:12:11 +01:00
|
|
|
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";
|
2020-05-28 14:08:49 +02:00
|
|
|
import { BOLT_MAX_FIELDS_TO_PRINT } from "./constants";
|
2020-05-29 18:44:58 +02:00
|
|
|
import { emitNode } from "./emitter";
|
2020-10-29 21:12:11 +01:00
|
|
|
import { type } from "os";
|
|
|
|
import { fn } from "moment";
|
|
|
|
import { intersects } from "semver";
|
2020-05-27 19:57:15 +02:00
|
|
|
|
|
|
|
// 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
|
|
|
|
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 = '@'
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
enum TypeKind {
|
2020-10-29 21:12:11 +01:00
|
|
|
PrimType,
|
2020-05-23 21:15:20 +02:00
|
|
|
AnyType,
|
|
|
|
NeverType,
|
2020-10-29 21:12:11 +01:00
|
|
|
VoidType,
|
2020-05-23 21:15:20 +02:00
|
|
|
FunctionType,
|
|
|
|
RecordType,
|
2020-05-26 22:58:31 +02:00
|
|
|
PlainRecordFieldType,
|
2020-05-23 21:15:20 +02:00
|
|
|
VariantType,
|
2020-10-29 21:12:11 +01:00
|
|
|
IntersectType,
|
2020-05-26 21:01:38 +02:00
|
|
|
UnionType,
|
2020-05-23 21:15:20 +02:00
|
|
|
TupleType,
|
2020-10-29 21:12:11 +01:00
|
|
|
CallType,
|
2020-06-16 20:45:53 +02:00
|
|
|
TraitType,
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
function toArray<T>(value: T[] | T | null | undefined): T[] {
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return [ value ];
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
export type Type
|
2020-10-29 21:12:11 +01:00
|
|
|
= PrimType
|
|
|
|
| VoidType
|
|
|
|
| CallType
|
2020-05-23 21:15:20 +02:00
|
|
|
| AnyType
|
|
|
|
| NeverType
|
|
|
|
| FunctionType
|
|
|
|
| RecordType
|
|
|
|
| VariantType
|
|
|
|
| TupleType
|
2020-10-29 21:12:11 +01:00
|
|
|
| IntersectType
|
2020-05-27 19:57:15 +02:00
|
|
|
| UnionType
|
2020-05-28 14:08:49 +02:00
|
|
|
| PlainRecordFieldType
|
2020-06-16 20:45:53 +02:00
|
|
|
| TraitType
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
type TypeRef = Ref<Type>;
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
let nextTypeId = 1;
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
function areTypesEquivalent(a: Type, b: Type): boolean {
|
|
|
|
|
|
|
|
if (a.kind === TypeKind.TupleType && b.kind === TypeKind.VoidType) {
|
|
|
|
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01: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;
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
if (a.kind === TypeKind.PrimType && b.kind === TypeKind.PrimType) {
|
2020-05-29 18:44:58 +02:00
|
|
|
return a.name === b.name;
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
|
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;
|
|
|
|
}
|
2020-06-16 20:45:53 +02:00
|
|
|
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.`)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
// 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;
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
|
|
|
|
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++;
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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-06-16 20:45:53 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
|
|
|
throw new Error(`I did not expected to see the provided type combination.`)
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
abstract class TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
public abstract kind: TypeKind;
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public id = nextTypeId++;
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public failed = false;
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
constructor(public sourceNodes: Syntax[] = []) {
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public [getKeyTag](): string {
|
|
|
|
return this.id.toString();
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public markAsFailed() {
|
|
|
|
this.failed = true;
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
public [prettyPrintTag](): string {
|
|
|
|
return prettyPrintType(this as Type);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isType(value: any) {
|
|
|
|
return typeof(value) === 'object'
|
|
|
|
&& value !== null
|
|
|
|
&& value.__IS_TYPE !== null;
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
export class PrimType extends TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public kind: TypeKind.PrimType = TypeKind.PrimType;
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
constructor(public name: string, sources: Syntax[] = []) {
|
|
|
|
super(sources);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
export class VoidType extends TypeBase {
|
|
|
|
public kind: TypeKind.VoidType = TypeKind.VoidType;
|
|
|
|
}
|
|
|
|
|
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(
|
2020-10-29 21:12:11 +01:00
|
|
|
public id: number,
|
|
|
|
public paramTypes: TypeRef[],
|
|
|
|
public returnType: TypeRef,
|
|
|
|
sources: Syntax[] = [],
|
2020-05-23 21:15:20 +02:00
|
|
|
) {
|
2020-10-29 21:12:11 +01:00
|
|
|
super(sources);
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
constructor(public elementTypes: TypeRef[], sources: Syntax[] = []) {
|
|
|
|
super(sources);
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public getVariantTypes(): IterableIterator<TypeRef> {
|
2020-05-23 21:15:20 +02:00
|
|
|
return this.elementTypes[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
export class IntersectType extends TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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 {
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.UnionType = TypeKind.UnionType;
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public elementTypes: TypeRef[] = [];
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
elementTypes: Iterable<TypeRef> = [],
|
|
|
|
sources: Syntax[] = []
|
|
|
|
) {
|
|
|
|
super(sources);
|
|
|
|
this.elementTypes = [...elementTypes];
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public addElement(element: TypeRef): void {
|
2020-05-29 18:44:58 +02:00
|
|
|
this.elementTypes.push(element);
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public getElementTypes(): IterableIterator<TypeRef> {
|
2020-05-29 18:44:58 +02:00
|
|
|
return this.elementTypes[Symbol.iterator]();
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export type RecordFieldType
|
|
|
|
= PlainRecordFieldType
|
2020-05-29 18:44:58 +02:00
|
|
|
| AnyType
|
|
|
|
| UnionType
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
class PlainRecordFieldType extends TypeBase {
|
|
|
|
|
|
|
|
public kind: TypeKind.PlainRecordFieldType = TypeKind.PlainRecordFieldType;
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
constructor(public name: string, public type: TypeRef, sources: Syntax[] = []) {
|
|
|
|
super(sources);
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-06-16 20:45:53 +02:00
|
|
|
export class TraitType extends TypeBase {
|
|
|
|
|
|
|
|
public kind: TypeKind.TraitType = TypeKind.TraitType;
|
|
|
|
|
|
|
|
private functionTypesByName = new FastStringMap<string, FunctionType>();
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
constructor(
|
|
|
|
public source: Syntax,
|
|
|
|
public memberTypes: Iterable<[string, FunctionType]>,
|
|
|
|
sources: Syntax[] = []
|
|
|
|
) {
|
|
|
|
super(sources);
|
2020-06-16 20:45:53 +02:00
|
|
|
for (const [name, type] of memberTypes) {
|
|
|
|
this.functionTypesByName.set(name, type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
export class RecordType extends TypeBase {
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.RecordType = TypeKind.RecordType;
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
private memberTypes: RecordFieldType[] = [];
|
|
|
|
private memberTypesByFieldName = new FastStringMap<string, RecordFieldType>();
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
constructor(
|
|
|
|
public source: Syntax | Type | number,
|
|
|
|
iterable?: Iterable<RecordFieldType>,
|
|
|
|
sources: Syntax[] = []
|
|
|
|
) {
|
2020-05-28 14:08:49 +02:00
|
|
|
super();
|
2020-05-26 22:58:31 +02:00
|
|
|
if (iterable !== undefined) {
|
2020-05-29 18:44:58 +02:00
|
|
|
for (const type of iterable) {
|
|
|
|
this.addMemberType(type);
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public getRequiredFieldNames(): IterableIterator<string> {
|
|
|
|
return this.memberTypesByFieldName.keys();
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
|
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-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public getMemberTypes(): IterableIterator<RecordFieldType> {
|
|
|
|
return this.memberTypes[Symbol.iterator]();
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public isFieldRequired(name: string): boolean {
|
|
|
|
return this.memberTypesByFieldName.has(name);
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public clear(): void {
|
2020-05-29 18:44:58 +02:00
|
|
|
this.memberTypes = [];
|
|
|
|
this.memberTypesByFieldName.clear();
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
export class TupleType extends TypeBase {
|
|
|
|
|
|
|
|
kind: TypeKind.TupleType = TypeKind.TupleType;
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
constructor(
|
|
|
|
public elementTypes: TypeRef[] = [],
|
|
|
|
sources: Syntax[] = []
|
|
|
|
) {
|
|
|
|
super(sources);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
export class CallType extends TypeBase {
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public kind: TypeKind.CallType = TypeKind.CallType;
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
constructor(
|
|
|
|
public fnType: TypeRef,
|
|
|
|
public argumentTypes: TypeRef[],
|
|
|
|
sources: Syntax[] = []
|
|
|
|
) {
|
|
|
|
super(sources);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public getArgumentTypes(): IterableIterator<TypeRef> {
|
2020-05-29 18:44:58 +02:00
|
|
|
return this.argumentTypes[Symbol.iterator]();
|
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
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))
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
function* getAllUnionElementTypes(type: TypeRef): IterableIterator<TypeRef> {
|
2020-05-28 14:08:49 +02:00
|
|
|
switch (type.kind) {
|
|
|
|
case TypeKind.UnionType:
|
|
|
|
{
|
|
|
|
for (const elementType of type.getElementTypes()) {
|
2020-10-29 21:12:11 +01:00
|
|
|
yield* getAllUnionElementTypes(elementType);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
yield type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
export function prettyPrintType(typeRef: TypeRef): string {
|
2020-05-28 14:08:49 +02:00
|
|
|
let out = ''
|
|
|
|
let hasElementType = false;
|
2020-10-29 21:12:11 +01:00
|
|
|
for (const elementType of getAllUnionElementTypes(typeRef)) {
|
2020-05-29 18:44:58 +02:00
|
|
|
if (hasElementType) {
|
|
|
|
out += ' | '
|
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
hasElementType = true;
|
2020-05-29 18:44:58 +02:00
|
|
|
switch (elementType.kind) {
|
|
|
|
case TypeKind.PlainRecordFieldType:
|
|
|
|
out += '{ ' + elementType.name + ' } ';
|
|
|
|
break;
|
2020-10-29 21:12:11 +01:00
|
|
|
case TypeKind.CallType:
|
2020-05-29 20:33:04 +02:00
|
|
|
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;
|
2020-10-29 21:12:11 +01:00
|
|
|
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:
|
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
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-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
out += ')'
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TypeKind.RecordType:
|
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
if (elementType.sourceNodes.length > 0) {
|
|
|
|
switch (elementType.sourceNodes[0].kind) {
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltRecordDeclaration:
|
2020-10-29 21:12:11 +01:00
|
|
|
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-28 14:08:49 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
default:
|
|
|
|
throw new Error(`Could not pretty-print type ${TypeKind[elementType.kind]}`)
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!hasElementType) {
|
2020-05-29 18:44:58 +02:00
|
|
|
out += 'never'
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
2020-05-24 11:17:56 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
let nextRecordTypeId = 1;
|
|
|
|
|
|
|
|
function introducesType(node: Syntax) {
|
|
|
|
return isBoltExpression(node)
|
2020-05-30 11:34:45 +02:00
|
|
|
|| isBoltFunctionDeclaration(node)
|
|
|
|
|| isBoltTypeDeclaration(node)
|
|
|
|
|| isBoltRecordDeclaration(node)
|
2020-05-29 18:44:58 +02:00
|
|
|
|| isBoltParameter(node)
|
2020-10-29 21:12:11 +01:00
|
|
|
|| isBoltRecordFieldValue(node)
|
2020-05-29 18:44:58 +02:00
|
|
|
|| isBoltRecordFieldPattern(node)
|
|
|
|
|| isBoltPattern(node)
|
|
|
|
|| isBoltTypeExpression(node)
|
|
|
|
|| isJSExpression(node)
|
|
|
|
|| isJSPattern(node)
|
|
|
|
|| isJSParameter(node)
|
|
|
|
}
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
export class TypeChecker {
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
private dependencyGraph = new FastStringMap<string, Syntax[]>();
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
private neverType = new NeverType();
|
|
|
|
|
|
|
|
constructor(private resolver: SymbolResolver, private diagnostics: DiagnosticPrinter) {
|
2020-05-24 11:17:56 +02:00
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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]);
|
2020-10-29 21:12:11 +01:00
|
|
|
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
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new PrimType(GLOBAL_SCOPE_MARKER + path, [...resolvedSymbol.declarations]));
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
public createTypeForValue(value: Value, source: Syntax): Type {
|
2020-05-26 22:58:31 +02:00
|
|
|
if (typeof(value) === 'string') {
|
2020-10-29 21:12:11 +01:00
|
|
|
return new PrimType('@String', [ source ]);
|
2020-05-26 22:58:31 +02:00
|
|
|
} else if (typeof(value) === 'bigint') {
|
2020-10-29 21:12:11 +01:00
|
|
|
return new PrimType('@int', [ source ]);
|
2020-05-26 22:58:31 +02:00
|
|
|
} else if (typeof(value) === 'number') {
|
2020-10-29 21:12:11 +01:00
|
|
|
return new PrimType('@f64', [ source ]);
|
2020-05-27 19:57:15 +02:00
|
|
|
} else if (value instanceof Record) {
|
2020-05-29 18:44:58 +02:00
|
|
|
const memberTypes = [];
|
2020-05-27 19:57:15 +02:00
|
|
|
for (const [fieldName, fieldValue] of value.getFields()) {
|
2020-10-29 21:12:11 +01:00
|
|
|
const recordFieldType = new PlainRecordFieldType(name, this.createTypeForValue(fieldValue));
|
|
|
|
memberTypes.push(recordFieldType);
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
const recordType = new RecordType(nextRecordTypeId++, memberTypes);
|
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-10-29 21:12:11 +01:00
|
|
|
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 },
|
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
public registerSourceFile(sourceFile: SourceFile): void {
|
|
|
|
for (const node of sourceFile.preorder()) {
|
2020-05-29 18:44:58 +02:00
|
|
|
if (introducesType(node)) {
|
2020-10-29 21:12:11 +01:00
|
|
|
node.type = createRef(new AnyType);
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
2020-06-16 20:45:53 +02:00
|
|
|
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-27 19:57:15 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
const node = queued.shift()!;
|
|
|
|
|
|
|
|
const derivedType = this.deriveTypeUsingChildren(node);
|
2020-10-29 21:12:11 +01:00
|
|
|
node.type.replaceWith(new IntersectType([node.type!.cloneRef(), derivedType], [ node ]));
|
|
|
|
this.checkType(node.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
if (!node.type!.failed) {
|
2020-05-29 18:44:58 +02:00
|
|
|
for (const dependantNode of this.getParentsThatMightNeedUpdate(node)) {
|
2020-06-16 20:45:53 +02:00
|
|
|
if (introducesType(dependantNode)) {
|
|
|
|
nextQueue.add(dependantNode);
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
for (const dependantNode of this.getNodesRequiringUpdate(node)) {
|
2020-06-16 20:45:53 +02:00
|
|
|
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-27 19:57:15 +02:00
|
|
|
}
|
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-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public isVoidType(type: Type): boolean {
|
2020-10-29 21:12:11 +01:00
|
|
|
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.
|
|
|
|
*
|
2020-10-29 21:12:11 +01:00
|
|
|
* @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 {
|
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-29 18:44:58 +02:00
|
|
|
case SyntaxKind.JSMemberExpression:
|
|
|
|
{
|
|
|
|
// TODO
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new AnyType);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.JSLiteralExpression:
|
2020-05-26 22:58:31 +02:00
|
|
|
{
|
2020-05-29 18:44:58 +02:00
|
|
|
if (typeof(node.value) === 'string') {
|
2020-10-29 21:12:11 +01:00
|
|
|
return this.getPrimType('String');
|
2020-05-29 18:44:58 +02:00
|
|
|
} else if (typeof(node.value) === 'number') {
|
2020-10-29 21:12:11 +01:00
|
|
|
return this.getPrimType('JSNum');
|
2020-05-27 19:57:15 +02:00
|
|
|
} 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-26 22:58:31 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.JSReferenceExpression:
|
|
|
|
{
|
|
|
|
// TODO
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new AnyType);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.JSCallExpression:
|
|
|
|
{
|
|
|
|
// TODO
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new AnyType);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltMatchExpression:
|
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
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:
|
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
const resultTypes = [ node.bindings.type! ]
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.bindings, node);
|
|
|
|
if (node.typeExpr !== null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
resultTypes.push(node.typeExpr.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.typeExpr, node);
|
|
|
|
}
|
|
|
|
if (node.defaultValue !== null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
resultTypes.push(node.defaultValue.type!)
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.defaultValue, node)
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new UnionType(resultTypes, [ node ]));
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltFunctionExpression:
|
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
for (const param of node.params) {
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(param, node);
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
|
|
|
const paramTypes = node.params.map(param => param.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
let returnType;
|
|
|
|
if (node.returnType === null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
returnType = createRef(new AnyType([ node ]));
|
2020-05-29 18:44:58 +02:00
|
|
|
} else {
|
2020-10-29 21:12:11 +01:00
|
|
|
returnType = node.returnType.type!;
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.returnType, node);
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new FunctionType(node.id, paramTypes, returnType, [ node ]));
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltRecordDeclaration:
|
2020-05-27 20:59:45 +02:00
|
|
|
{
|
2020-05-29 18:44:58 +02:00
|
|
|
if (node.members === null) {
|
|
|
|
const symbolPath = getFullyQualifiedPathToNode(node);
|
2020-10-29 21:12:11 +01:00
|
|
|
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);
|
2020-10-29 21:12:11 +01:00
|
|
|
memberTypes.push(member.type! as RecordFieldType)
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(member, node);
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new RecordType(node, memberTypes));
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-27 20:59:45 +02:00
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
case SyntaxKind.BoltRecordDeclarationField:
|
2020-05-27 19:57:15 +02:00
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
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-26 22:58:31 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltRecordPattern:
|
2020-05-27 19:57:15 +02:00
|
|
|
{
|
2020-05-29 18:44:58 +02:00
|
|
|
let memberTypes = []
|
|
|
|
for (const member of node.fields) {
|
|
|
|
//assert(member.type instanceof PlainRecordFieldType);
|
2020-10-29 21:12:11 +01:00
|
|
|
memberTypes.push(member.type! as RecordFieldType);
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(member, node);
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new RecordType(node.name, memberTypes));
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
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 {
|
2020-10-29 21:12:11 +01:00
|
|
|
nestedFieldType = node.pattern.type!;
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.pattern, node);
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new PlainRecordFieldType(node.name!.text, nestedFieldType));
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-30 11:34:45 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltTypeAliasDeclaration:
|
|
|
|
{
|
|
|
|
// TODO
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new AnyType);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-06-16 20:45:53 +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];
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
//}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-06-16 20:45:53 +02:00
|
|
|
//case SyntaxKind.BoltTraitDeclaration:
|
|
|
|
//{
|
|
|
|
// // TODO
|
|
|
|
// return new AnyType;
|
|
|
|
//}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltVariableDeclaration:
|
|
|
|
{
|
|
|
|
let elementTypes = []
|
|
|
|
if (node.value !== null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
elementTypes.push(node.value.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.value, node);
|
|
|
|
}
|
|
|
|
if (node.typeExpr !== null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
elementTypes.push(node.typeExpr.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.typeExpr, node);
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new UnionType(elementTypes));
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltExpressionPattern:
|
|
|
|
{
|
|
|
|
return this.deriveTypeUsingChildren(node.expression);
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltBindPattern:
|
|
|
|
{
|
|
|
|
// TODO
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new AnyType);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltConstantExpression:
|
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
return this.createTypeForValue(node.value, node);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltFunctionTypeExpression:
|
2020-05-28 14:08:49 +02:00
|
|
|
{
|
2020-05-29 18:44:58 +02:00
|
|
|
const paramTypes = node.params.map(param => {
|
|
|
|
this.markNodeAsRequiringUpdate(param, node);
|
2020-10-29 21:12:11 +01:00
|
|
|
return param.type!;
|
2020-05-29 18:44:58 +02:00
|
|
|
})
|
|
|
|
let returnType = null;
|
|
|
|
if (node.returnType === null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
returnType = createRef(new AnyType);
|
2020-05-29 18:44:58 +02:00
|
|
|
} else {
|
2020-10-29 21:12:11 +01:00
|
|
|
returnType = node.returnType.type!;
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.returnType!, node);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new FunctionType(node.id, paramTypes, returnType, [ node ]));
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltMacroCall:
|
|
|
|
{
|
|
|
|
// TODO
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new AnyType);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltQuoteExpression:
|
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
return this.getPrimType('Lang::Bolt::Node');
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
//case SyntaxKind.BoltExpressionStatement:
|
|
|
|
//{
|
|
|
|
// return this.deriveTypeUsingChildren(node.expression);
|
|
|
|
//}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-05-29 20:33:04 +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
|
|
|
|
2020-05-29 20:33:04 +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-27 19:57:15 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltMemberExpression:
|
|
|
|
{
|
|
|
|
let unionType = new UnionType([]);
|
|
|
|
assert(node.path.length === 1);
|
|
|
|
const recordTypes = [];
|
2020-10-29 21:12:11 +01:00
|
|
|
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);
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(unionType);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltCallExpression:
|
|
|
|
{
|
|
|
|
let operandTypes = []
|
|
|
|
for (const operand of node.operands) {
|
2020-10-29 21:12:11 +01:00
|
|
|
operandTypes.push(operand.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(operand, node);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(node.operator, node);
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new CallType(node.operator.type!, operandTypes, [ node ]));
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltReferenceExpression:
|
2020-05-28 14:08:49 +02:00
|
|
|
{
|
2020-05-29 18:44:58 +02:00
|
|
|
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
|
|
|
|
if (scope === null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
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) {
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new AnyType);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
let elementTypes = [];
|
|
|
|
for (const decl of resolvedSym.declarations) {
|
2020-10-29 21:12:11 +01:00
|
|
|
elementTypes.push(decl.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(decl, node)
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new UnionType(elementTypes));
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
|
|
|
case SyntaxKind.BoltFunctionDeclaration:
|
|
|
|
{
|
2020-10-29 21:12:11 +01:00
|
|
|
let returnTypes: TypeRef[] = [];
|
2020-05-29 18:44:58 +02:00
|
|
|
if (node.returnType !== null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
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)) {
|
2020-05-29 20:33:04 +02:00
|
|
|
if (returnStmt.value !== null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
returnTypes.push(returnStmt.value.type!);
|
2020-05-29 20:33:04 +02:00
|
|
|
this.markNodeAsRequiringUpdate(returnStmt.value, node);
|
|
|
|
} else {
|
2020-10-29 21:12:11 +01:00
|
|
|
returnTypes.push(createRef(new VoidType([ returnStmt ])));
|
2020-05-29 20:33:04 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let paramTypes = [];
|
|
|
|
for (const param of node.params) {
|
2020-10-29 21:12:11 +01:00
|
|
|
paramTypes.push(param.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(param, node);
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new FunctionType(node.id, paramTypes, createRef(new UnionType(returnTypes))));
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +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':
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new NeverType);
|
2020-05-29 18:44:58 +02:00
|
|
|
case 'any':
|
2020-10-29 21:12:11 +01:00
|
|
|
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);
|
2020-05-28 14:08:49 +02:00
|
|
|
assert(scope !== null);
|
2020-05-29 18:44:58 +02:00
|
|
|
const symbolPath = convertNodeToSymbolPath(node.name);
|
2020-05-28 14:08:49 +02:00
|
|
|
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
|
2020-05-29 18:44:58 +02:00
|
|
|
if (resolvedSym === null) {
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new NeverType([ node ]));
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
let elementTypes = [];
|
|
|
|
for (const decl of resolvedSym.declarations) {
|
|
|
|
this.markNodeAsRequiringUpdate(decl, node);
|
2020-10-29 21:12:11 +01:00
|
|
|
elementTypes.push(decl.type!);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
return createRef(new UnionType(elementTypes, [ node ]));
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:01:38 +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,
|
|
|
|
// }
|
|
|
|
// })
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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-26 21:01:38 +02:00
|
|
|
|
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-28 14:08:49 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
if (a.kind === TypeKind.UnionType) {
|
2020-10-29 21:12:11 +01:00
|
|
|
return a.elementTypes.every(el => this.areTypesEquivalent(el, b));
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
if (b.kind === TypeKind.UnionType) {
|
2020-10-29 21:12:11 +01:00
|
|
|
return b.elementTypes.some(el => this.areTypesEquivalent(el, a));
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-05-28 14:08:49 +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.
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
if (a.kind === TypeKind.CallType) {
|
2020-05-29 18:44:58 +02:00
|
|
|
const resolvedType = this.resolveReturnType(a);
|
2020-10-29 21:12:11 +01:00
|
|
|
return this.areTypesEquivalent(resolvedType, b);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
if (b.kind === TypeKind.CallType) {
|
2020-05-29 20:33:04 +02:00
|
|
|
const resolvedType = this.resolveReturnType(b);
|
2020-10-29 21:12:11 +01:00
|
|
|
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.
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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;
|
|
|
|
}
|
2020-06-16 20:45:53 +02:00
|
|
|
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.
|
2020-06-16 20:45:53 +02:00
|
|
|
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
|
|
|
}
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
private diagnoseFunctionCall(callType: CallType, fnType: FunctionType) {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
if (fnType.paramTypes.length > callType.argumentTypes.length) {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
if (!callType.failed) {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01: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
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
callType.markAsFailed();
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
return false;
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
} else if (callType.argumentTypes.length > fnType.paramTypes.length) {
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
if (!callType.failed) {
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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,
|
|
|
|
});
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
callType.markAsFailed();
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
return false;
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
} else {
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
return !hasErrors;
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private *getReturnTypes(fnType: TypeRef, callType: TypeRef): IterableIterator<TypeRef> {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01: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.
|
2020-10-29 21:12:11 +01:00
|
|
|
yield fnType.returnType;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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;
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
default:
|
|
|
|
throw new Error(`Resolving the given type will not work.`)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01: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`);
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private removeNeverTypes(types: TypeRef[]) {
|
|
|
|
for (let i = 0; i < types.length; i++) {
|
|
|
|
if (types[i].kind === TypeKind.NeverType) {
|
|
|
|
types.splice(i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
private removeAnyTypes(types: TypeRef[]) {
|
|
|
|
for (let i = 0; i < types.length; i++) {
|
|
|
|
if (types[i].kind === TypeKind.AnyType) {
|
|
|
|
types.splice(i, 1);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private mergeNeverTypes(types: TypeRef[]): Type | null {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01: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
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
return firstNeverType;
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
private mergeAnyTypes(types: TypeRef[]) {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
let firstAnyType = null;
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01: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-28 14:08:49 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-29 21:12:11 +01:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
private checkType(type: TypeRef) {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
|
|
|
const resultTypes = [];
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
switch (type.kind) {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
case TypeKind.PrimType:
|
|
|
|
break;
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
case TypeKind.FunctionType:
|
|
|
|
break;
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
case TypeKind.AnyType:
|
|
|
|
break;
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
case TypeKind.NeverType:
|
|
|
|
this.diagnoseTypeError(type, null);
|
|
|
|
break;
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01: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;
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
|
|
|
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
|
|
|
|
2020-10-29 21:12:11 +01: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
|
|
|
|
2020-10-29 21:12:11 +01: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
|
|
|
|
2020-10-29 21:12:11 +01: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-10-29 21:12:11 +01:00
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-10-29 21:12:11 +01: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;
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
if (target.kind === TypeKind.NeverType || source.kind === TypeKind.NeverType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (source.kind === TypeKind.AnyType || target.kind === TypeKind.AnyType) {
|
|
|
|
return true;
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
if (source.kind === TypeKind.PrimType && target.kind === TypeKind.PrimType) {
|
|
|
|
return source.name === target.name;
|
|
|
|
}
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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));
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-10-29 21:12:11 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|