2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
import { FastStringMap, assert, isPlainObject, some, prettyPrintTag, map, flatMap, filter, memoize, comparator, createTransparentProxy, TransparentProxy, every, FastMultiMap, getKeyTag } from "./util";
|
2020-05-30 11:34:45 +02:00
|
|
|
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, BoltCallExpression, BoltIdentifier, isBoltDeclarationLike, isBoltPattern, isJSExpression, isBoltStatement, isJSStatement, isJSPattern, isJSParameter, isBoltParameter, isBoltMatchArm, isBoltRecordField, isBoltRecordFieldPattern, isEndOfFile, isSyntax, isBoltFunctionDeclaration, isBoltTypeDeclaration, isBoltRecordDeclaration, } 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-05-29 20:33:04 +02:00
|
|
|
import { getReturnStatementsInFunctionBody, getAllReturnStatementsInFunctionBody, getFullyQualifiedPathToNode, hasDiagnostic, hasTypeError } from "./common";
|
2020-05-29 18:44:58 +02:00
|
|
|
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 } 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-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 {
|
|
|
|
OpaqueType,
|
|
|
|
AnyType,
|
|
|
|
NeverType,
|
|
|
|
FunctionType,
|
|
|
|
RecordType,
|
2020-05-26 22:58:31 +02:00
|
|
|
PlainRecordFieldType,
|
2020-05-23 21:15:20 +02:00
|
|
|
VariantType,
|
2020-05-26 21:01:38 +02:00
|
|
|
UnionType,
|
2020-05-23 21:15:20 +02:00
|
|
|
TupleType,
|
2020-05-29 18:44:58 +02:00
|
|
|
ReturnType,
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export type Type
|
|
|
|
= OpaqueType
|
2020-05-29 18:44:58 +02:00
|
|
|
| ReturnType
|
2020-05-23 21:15:20 +02:00
|
|
|
| AnyType
|
|
|
|
| NeverType
|
|
|
|
| FunctionType
|
|
|
|
| RecordType
|
|
|
|
| VariantType
|
|
|
|
| TupleType
|
2020-05-27 19:57:15 +02:00
|
|
|
| UnionType
|
2020-05-28 14:08:49 +02:00
|
|
|
| PlainRecordFieldType
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
let nextTypeId = 1;
|
|
|
|
|
|
|
|
function areTypesLexicallyEquivalent(a: Type, b: Type): boolean {
|
|
|
|
|
|
|
|
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.OpaqueType && b.kind === TypeKind.OpaqueType) {
|
|
|
|
return a.name === b.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error(`I did not expected to see the provided type combination.`)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function areTypesLexicallyLessThan(a: Type, b: Type): boolean {
|
|
|
|
|
|
|
|
if (a.kind !== b.kind) {
|
|
|
|
return a.kind < b.kind;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a.kind === TypeKind.NeverType && a.kind === TypeKind.NeverType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (a.kind === TypeKind.AnyType && a.kind === TypeKind.AnyType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (a.kind === TypeKind.OpaqueType && b.kind === TypeKind.OpaqueType) {
|
|
|
|
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) {
|
|
|
|
return a.source.id < b.source.id;
|
|
|
|
}
|
|
|
|
if (a.kind === TypeKind.RecordType && b.kind === TypeKind.RecordType) {
|
2020-06-01 21:49:28 +02:00
|
|
|
assert(a.source.id !== undefined)
|
|
|
|
assert(b.source.id !== undefined)
|
2020-05-29 18:44:58 +02:00
|
|
|
return a.source.id < b.source.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
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++;
|
|
|
|
|
|
|
|
public node?: Syntax;
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public nextType?: Type;
|
|
|
|
|
|
|
|
public get solved(): Type {
|
|
|
|
let type = this as Type;
|
|
|
|
while (type.nextType !== undefined) {
|
|
|
|
type = type.nextType;
|
|
|
|
}
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public [getKeyTag](): string {
|
|
|
|
return this.id.toString();
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
export class OpaqueType extends TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
public kind: TypeKind.OpaqueType = TypeKind.OpaqueType;
|
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
constructor(public name: string, public source?: Syntax) {
|
2020-05-29 18:44:58 +02:00
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
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-05-29 18:44:58 +02:00
|
|
|
public source: Syntax,
|
2020-05-23 21:15:20 +02:00
|
|
|
public paramTypes: Type[],
|
|
|
|
public returnType: Type,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public getParameterCount(): number {
|
|
|
|
return this.paramTypes.length;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public getTypeAtParameterIndex(index: number) {
|
2020-05-23 21:15:20 +02:00
|
|
|
if (index < 0 || index >= this.paramTypes.length) {
|
|
|
|
throw new Error(`Could not get the parameter type at index ${index} because the index was out of bounds.`);
|
|
|
|
}
|
|
|
|
return this.paramTypes[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export class VariantType extends TypeBase {
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.VariantType = TypeKind.VariantType;
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
constructor(public elementTypes: Type[]) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public getVariantTypes(): IterableIterator<Type> {
|
2020-05-23 21:15:20 +02:00
|
|
|
return this.elementTypes[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
export class UnionType extends TypeBase {
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public elementTypes: Type[] = [];
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.UnionType = TypeKind.UnionType;
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
constructor(elements: Iterable<Type> = []) {
|
2020-05-27 19:57:15 +02:00
|
|
|
super();
|
2020-05-29 18:44:58 +02:00
|
|
|
this.elementTypes = [...elements];
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public addElement(element: Type): void {
|
2020-05-29 18:44:58 +02:00
|
|
|
this.elementTypes.push(element);
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
public getElementTypes(): IterableIterator<Type> {
|
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-05-29 18:44:58 +02:00
|
|
|
constructor(public name: string, public type: Type) {
|
2020-05-26 22:58:31 +02:00
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
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-05-29 18:44:58 +02:00
|
|
|
constructor(public source: Syntax | Type | number, iterable?: Iterable<RecordFieldType>) {
|
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;
|
|
|
|
|
|
|
|
constructor(public elementTypes: Type[] = []) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
export class ReturnType extends TypeBase {
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public kind: TypeKind.ReturnType = TypeKind.ReturnType;
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
constructor(public fnType: Type, public argumentTypes: Type[]) {
|
|
|
|
super();
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
public getArgumentTypes(): IterableIterator<Type> {
|
|
|
|
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-05-28 14:08:49 +02:00
|
|
|
function* getAllPossibleElementTypes(type: Type): IterableIterator<Type> {
|
|
|
|
switch (type.kind) {
|
|
|
|
case TypeKind.UnionType:
|
|
|
|
{
|
|
|
|
for (const elementType of type.getElementTypes()) {
|
|
|
|
yield* getAllPossibleElementTypes(elementType);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
yield type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function prettyPrintType(type: Type): string {
|
|
|
|
let out = ''
|
|
|
|
let hasElementType = false;
|
|
|
|
for (const elementType of getAllPossibleElementTypes(type)) {
|
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;
|
|
|
|
case TypeKind.ReturnType:
|
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;
|
|
|
|
case TypeKind.OpaqueType:
|
|
|
|
out += elementType.name;
|
|
|
|
break;
|
|
|
|
case TypeKind.AnyType:
|
|
|
|
out += 'any';
|
|
|
|
break;
|
|
|
|
case TypeKind.NeverType:
|
|
|
|
out += 'never'
|
|
|
|
break;
|
|
|
|
case TypeKind.FunctionType:
|
|
|
|
{
|
|
|
|
switch (elementType.source.kind) {
|
|
|
|
case SyntaxKind.BoltFunctionDeclaration:
|
|
|
|
out += emitNode(elementType.source.name);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected source object on function type.`)
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
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:
|
|
|
|
{
|
|
|
|
if (isSyntax(elementType.source)) {
|
|
|
|
switch (elementType.source.kind) {
|
|
|
|
case SyntaxKind.BoltRecordDeclaration:
|
|
|
|
out += elementType.source.name.text;
|
|
|
|
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)
|
|
|
|
|| isBoltMatchArm(node)
|
|
|
|
|| isBoltRecordField(node)
|
|
|
|
|| 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 opaqueTypeFallbacks = new FastStringMap<string, OpaqueType>();
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
private dependencyGraph = new FastStringMap<string, Syntax[]>();
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
constructor(private resolver: SymbolResolver) {
|
2020-05-24 11:17:56 +02:00
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
private getOpaqueType(path: string): Type {
|
|
|
|
const elements = path.split('::');
|
|
|
|
const symbolPath = new SymbolPath(elements.slice(0,-1), true, elements[elements.length-1]);
|
|
|
|
const sym = this.resolver.resolveGlobalSymbol(symbolPath, ScopeType.Type);
|
|
|
|
if (sym === null) {
|
|
|
|
if (this.opaqueTypeFallbacks.has(path)) {
|
|
|
|
return this.opaqueTypeFallbacks.get(path);
|
|
|
|
}
|
|
|
|
const opaqueType = new OpaqueType(GLOBAL_SCOPE_MARKER + path);
|
|
|
|
this.opaqueTypeFallbacks.set(path, opaqueType);
|
|
|
|
return opaqueType;
|
|
|
|
}
|
|
|
|
return new OpaqueType(GLOBAL_SCOPE_MARKER + path);
|
|
|
|
}
|
|
|
|
|
|
|
|
public createTypeForValue(value: Value): Type {
|
2020-05-26 22:58:31 +02:00
|
|
|
if (typeof(value) === 'string') {
|
2020-05-29 18:44:58 +02:00
|
|
|
return this.getOpaqueType('String')
|
2020-05-26 22:58:31 +02:00
|
|
|
} else if (typeof(value) === 'bigint') {
|
2020-05-29 18:44:58 +02:00
|
|
|
return this.getOpaqueType('int');
|
2020-05-26 22:58:31 +02:00
|
|
|
} else if (typeof(value) === 'number') {
|
2020-05-29 18:44:58 +02:00
|
|
|
return this.getOpaqueType('f64');
|
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-05-29 18:44:58 +02:00
|
|
|
const recordFieldType = new PlainRecordFieldType(name,
|
|
|
|
createTransparentProxy(this.createTypeForValue(fieldValue)));
|
|
|
|
memberTypes.push(createTransparentProxy(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-05-29 20:33:04 +02:00
|
|
|
private *diagnoseTypeMismatch(a: Type, b: Type): IterableIterator<Diagnostic> {
|
|
|
|
|
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)) {
|
|
|
|
node.type = new AnyType;
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
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-05-29 20:33:04 +02:00
|
|
|
derivedType.node = node;
|
2020-05-29 18:44:58 +02:00
|
|
|
const newType = this.simplifyType(derivedType);
|
|
|
|
|
|
|
|
//if (newType !== derivedType) {
|
|
|
|
//derivedType.solved.nextType = newType;
|
|
|
|
//}
|
|
|
|
|
|
|
|
const narrowedType = this.narrowTypeDownTo(node.type!.solved, newType);
|
|
|
|
|
|
|
|
if (narrowedType === null) {
|
2020-05-29 20:33:04 +02:00
|
|
|
if (!hasTypeError(node)) {
|
|
|
|
node.errors.push({
|
|
|
|
message: E_TYPE_MISMATCH,
|
|
|
|
severity: 'error',
|
|
|
|
args: { left: node.type!, right: newType },
|
|
|
|
nested: [...this.diagnoseTypeMismatch(node.type!.solved, newType)],
|
|
|
|
});
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
} else {
|
|
|
|
narrowedType.node = node;
|
2020-06-01 21:49:28 +02:00
|
|
|
if (node.type!.solved !== narrowedType) {
|
|
|
|
node.type!.solved.nextType = narrowedType;
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
for (const dependantNode of this.getParentsThatMightNeedUpdate(node)) {
|
|
|
|
nextQueue.add(dependantNode);
|
|
|
|
}
|
|
|
|
for (const dependantNode of this.getNodesRequiringUpdate(node)) {
|
|
|
|
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 {
|
|
|
|
return this.areTypesSemanticallyEquivalent(new TupleType, type);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Narrows @param outter down to @param inner. If @param outer could not be narrowed, this function return null.
|
|
|
|
*
|
|
|
|
* @param outer The type that will be narrowed.
|
|
|
|
* @param inner The type that serves as the outer bound of @param outer.
|
|
|
|
*/
|
|
|
|
private narrowTypeDownTo(outer: Type, inner: Type): Type | null {
|
|
|
|
|
|
|
|
// Only types that can be assigned to one another can be narrowed.
|
|
|
|
// In all other cases, we should indicate to the user that the operation is invalid.
|
|
|
|
if (!this.areTypesSemanticallyEquivalent(inner, outer)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We will build a new union type that contains all type elements that are
|
|
|
|
// shared between the inner type and the outer type.
|
|
|
|
const elementTypes = [];
|
|
|
|
|
|
|
|
for (const elementType of getAllPossibleElementTypes(inner)) {
|
|
|
|
|
|
|
|
switch (elementType.kind) {
|
|
|
|
|
|
|
|
case TypeKind.AnyType:
|
|
|
|
// If we find an `any` type in the innermost type, then it does not make sense
|
|
|
|
// to search for other types, as the any-type must be matches at all cost.
|
|
|
|
return new AnyType;
|
|
|
|
|
|
|
|
case TypeKind.NeverType:
|
|
|
|
// Encountering `never` in the inner type has the same effect as asserting that
|
|
|
|
// no type in `outer` is ever matched.
|
|
|
|
return new NeverType;
|
|
|
|
|
|
|
|
// The following types kinds are all nominally typed, so we can safely add them to the resulting
|
|
|
|
// element types without risk of interference.
|
|
|
|
case TypeKind.OpaqueType:
|
|
|
|
case TypeKind.FunctionType:
|
|
|
|
case TypeKind.RecordType:
|
|
|
|
elementTypes.push(elementType);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(`I received an unexpected ${TypeKind[elementType.kind]}`)
|
|
|
|
|
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
let hasAnyType = false;
|
|
|
|
|
|
|
|
for (const elementType of getAllPossibleElementTypes(outer)) {
|
|
|
|
|
|
|
|
switch (elementType.kind) {
|
|
|
|
|
|
|
|
case TypeKind.NeverType:
|
|
|
|
// A `never`-type in the outer type means we simply skip the type, because only it is 'never'
|
|
|
|
// matched, but other types might.
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case TypeKind.AnyType:
|
|
|
|
// When encountering an `any`-type in the outer type, we can safely skip it because the
|
|
|
|
// inner type most likely has more information about the structure of our new type.
|
|
|
|
hasAnyType = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// The following types kinds are all nominally typed, so we can safely add them to the resulting
|
|
|
|
// element types without risk of interference.
|
|
|
|
case TypeKind.FunctionType:
|
|
|
|
case TypeKind.RecordType:
|
|
|
|
case TypeKind.OpaqueType:
|
|
|
|
elementTypes.push(elementType);
|
|
|
|
break
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(`I received an unexpected ${TypeKind[elementType.kind]}`)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasAnyType && elementTypes.length === 0) {
|
|
|
|
return new AnyType;
|
|
|
|
}
|
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
this.mergeDuplicateTypes(elementTypes);
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
// This is a small optimisation to prevent the complexity of processing a
|
|
|
|
// union type everwhere where it just contains one element.
|
|
|
|
if (elementTypes.length === 1) {
|
|
|
|
return elementTypes[0];
|
|
|
|
}
|
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
return new UnionType(elementTypes);
|
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
|
|
|
|
return new AnyType;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.JSLiteralExpression:
|
2020-05-26 22:58:31 +02:00
|
|
|
{
|
2020-05-29 18:44:58 +02:00
|
|
|
if (typeof(node.value) === 'string') {
|
|
|
|
return this.getOpaqueType('String');
|
|
|
|
} else if (typeof(node.value) === 'number') {
|
|
|
|
return this.getOpaqueType('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
|
|
|
|
return new AnyType;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.JSCallExpression:
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
return new AnyType;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltMatchExpression:
|
|
|
|
{
|
|
|
|
return new UnionType(node.arms.map(arm => {
|
|
|
|
this.markNodeAsRequiringUpdate(arm, node);
|
|
|
|
return arm.type!.solved;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltMatchArm:
|
|
|
|
{
|
|
|
|
const resultType = new UnionType([node.pattern.type!.solved, node.body.type!.solved]);
|
|
|
|
this.markNodeAsRequiringUpdate(node.pattern, node);
|
|
|
|
this.markNodeAsRequiringUpdate(node.body, node);
|
|
|
|
return resultType;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltParameter:
|
|
|
|
{
|
|
|
|
const resultTypes = [ node.bindings.type!.solved ]
|
|
|
|
this.markNodeAsRequiringUpdate(node.bindings, node);
|
|
|
|
if (node.typeExpr !== null) {
|
|
|
|
resultTypes.push(node.typeExpr.type!.solved);
|
|
|
|
this.markNodeAsRequiringUpdate(node.typeExpr, node);
|
|
|
|
}
|
|
|
|
if (node.defaultValue !== null) {
|
|
|
|
resultTypes.push(node.defaultValue.type!.solved)
|
|
|
|
this.markNodeAsRequiringUpdate(node.defaultValue, node)
|
|
|
|
}
|
|
|
|
return new UnionType(resultTypes);
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltFunctionExpression:
|
|
|
|
{
|
|
|
|
const paramTypes = node.params.map(param => {
|
2020-05-29 18:44:58 +02:00
|
|
|
this.markNodeAsRequiringUpdate(param, node);
|
|
|
|
return param.type!.solved;
|
2020-05-27 19:57:15 +02:00
|
|
|
});
|
2020-05-29 18:44:58 +02:00
|
|
|
let returnType;
|
|
|
|
if (node.returnType === null) {
|
|
|
|
returnType = new AnyType;
|
|
|
|
} else {
|
|
|
|
returnType = node.returnType.type!.solved;
|
|
|
|
this.markNodeAsRequiringUpdate(node.returnType, node);
|
|
|
|
}
|
|
|
|
return new FunctionType(node, paramTypes, returnType);
|
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-05-29 20:33:04 +02:00
|
|
|
return new OpaqueType(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!.solved as RecordFieldType)
|
|
|
|
this.markNodeAsRequiringUpdate(member, node);
|
|
|
|
}
|
|
|
|
return new RecordType(node, memberTypes)
|
|
|
|
}
|
2020-05-27 20:59:45 +02:00
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltRecordField:
|
2020-05-27 19:57:15 +02:00
|
|
|
{
|
2020-05-29 18:44:58 +02:00
|
|
|
const resultType = new PlainRecordFieldType(node.name.text, node.typeExpr.type!.solved);
|
|
|
|
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);
|
|
|
|
memberTypes.push(member.type!.solved as RecordFieldType);
|
|
|
|
this.markNodeAsRequiringUpdate(member, node);
|
|
|
|
}
|
2020-06-01 21:49:28 +02:00
|
|
|
return 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 {
|
|
|
|
nestedFieldType = node.pattern.type!.solved;
|
|
|
|
this.markNodeAsRequiringUpdate(node.pattern, node);
|
|
|
|
}
|
|
|
|
return new PlainRecordFieldType(node.name!.text, nestedFieldType);
|
|
|
|
}
|
|
|
|
}
|
2020-05-30 11:34:45 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltTypeAliasDeclaration:
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
return new AnyType;
|
|
|
|
}
|
2020-05-26 22:58:31 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltImplDeclaration:
|
|
|
|
{
|
|
|
|
return new AnyType;
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-29 18:44:58 +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) {
|
|
|
|
elementTypes.push(node.value.type!.solved);
|
|
|
|
this.markNodeAsRequiringUpdate(node.value, node);
|
|
|
|
}
|
|
|
|
if (node.typeExpr !== null) {
|
|
|
|
elementTypes.push(node.typeExpr.type!.solved);
|
|
|
|
this.markNodeAsRequiringUpdate(node.typeExpr, node);
|
|
|
|
}
|
|
|
|
return new UnionType(elementTypes);
|
|
|
|
}
|
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
|
|
|
|
return new AnyType;
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltConstantExpression:
|
|
|
|
{
|
|
|
|
return this.createTypeForValue(node.value);
|
|
|
|
}
|
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);
|
|
|
|
return param.type!.solved;
|
|
|
|
})
|
|
|
|
let returnType = null;
|
|
|
|
if (node.returnType === null) {
|
|
|
|
returnType = new AnyType;
|
|
|
|
} else {
|
|
|
|
returnType = node.returnType.type!.solved;
|
|
|
|
this.markNodeAsRequiringUpdate(node.returnType!, node);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
return new FunctionType(node, paramTypes, returnType);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
case SyntaxKind.BoltMacroCall:
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
return new AnyType;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltQuoteExpression:
|
|
|
|
{
|
|
|
|
return this.getOpaqueType('Lang::Bolt::Node');
|
|
|
|
}
|
|
|
|
|
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 = [];
|
|
|
|
for (const memberType of this.getTypesForMember(node.path[0], node.path[0].text, node.expression.type!.solved)) {
|
|
|
|
unionType.addElement(memberType);
|
|
|
|
}
|
|
|
|
return unionType;
|
|
|
|
}
|
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) {
|
|
|
|
operandTypes.push(operand.type!.solved);
|
|
|
|
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);
|
|
|
|
return new ReturnType(node.operator.type!.solved, operandTypes);
|
|
|
|
}
|
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) {
|
|
|
|
return new AnyType;
|
|
|
|
}
|
|
|
|
const symbolPath = convertNodeToSymbolPath(node.name);
|
|
|
|
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
|
|
|
|
if (resolvedSym === null) {
|
|
|
|
return new AnyType;
|
|
|
|
}
|
|
|
|
let elementTypes = [];
|
|
|
|
for (const decl of resolvedSym.declarations) {
|
|
|
|
elementTypes.push(decl.type!.solved);
|
|
|
|
this.markNodeAsRequiringUpdate(decl, node)
|
|
|
|
}
|
|
|
|
return new UnionType(elementTypes);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
|
|
|
case SyntaxKind.BoltFunctionDeclaration:
|
|
|
|
{
|
|
|
|
let returnTypes: Type[] = [];
|
|
|
|
if (node.returnType !== null) {
|
|
|
|
returnTypes.push(node.returnType.type!.solved);
|
|
|
|
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) {
|
|
|
|
returnTypes.push(returnStmt.value.type!.solved);
|
|
|
|
this.markNodeAsRequiringUpdate(returnStmt.value, node);
|
|
|
|
} else {
|
|
|
|
returnTypes.push(new TupleType);
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let paramTypes = [];
|
|
|
|
for (const param of node.params) {
|
|
|
|
paramTypes.push(param.type!.solved);
|
|
|
|
this.markNodeAsRequiringUpdate(param, node);
|
|
|
|
}
|
|
|
|
return new FunctionType(node, paramTypes, new UnionType(returnTypes));
|
|
|
|
}
|
|
|
|
|
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':
|
|
|
|
return new NeverType;
|
|
|
|
case 'any':
|
|
|
|
return new AnyType;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
return new AnyType;
|
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);
|
|
|
|
elementTypes.push(decl.type!.solved);
|
|
|
|
}
|
|
|
|
return new UnionType(elementTypes);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
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,
|
|
|
|
// }
|
|
|
|
// })
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
|
|
|
private areTypesSemanticallyEquivalent(a: Type, b: Type): boolean {
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
return a.elementTypes.every(el => this.areTypesSemanticallyEquivalent(el, b));
|
|
|
|
}
|
|
|
|
if (b.kind === TypeKind.UnionType) {
|
|
|
|
return b.elementTypes.some(el => this.areTypesSemanticallyEquivalent(el, a));
|
|
|
|
}
|
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.
|
|
|
|
|
|
|
|
if (a.kind === TypeKind.ReturnType) {
|
|
|
|
const resolvedType = this.resolveReturnType(a);
|
|
|
|
return this.areTypesSemanticallyEquivalent(resolvedType, b);
|
|
|
|
}
|
|
|
|
if (b.kind === TypeKind.ReturnType) {
|
2020-05-29 20:33:04 +02:00
|
|
|
const resolvedType = this.resolveReturnType(b);
|
2020-05-29 18:44:58 +02:00
|
|
|
return this.areTypesSemanticallyEquivalent(a, resolvedType);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.OpaqueType && b.kind === TypeKind.OpaqueType) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME There are probably more cases that should be covered.
|
|
|
|
throw new Error(`I did not know how to calculate the equivalence of ${TypeKind[a.kind]} and ${TypeKind[b.kind]}`)
|
|
|
|
}
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
private resolveReturnType(type: ReturnType): Type {
|
|
|
|
|
|
|
|
let resultTypes = [];
|
|
|
|
|
|
|
|
let hasAnyType = false;
|
|
|
|
|
|
|
|
for (const elementType of getAllPossibleElementTypes(type.fnType)) {
|
|
|
|
|
|
|
|
switch (elementType.kind) {
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
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.
|
|
|
|
return new NeverType;
|
|
|
|
|
|
|
|
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.
|
|
|
|
hasAnyType = true;
|
2020-05-26 21:01:38 +02:00
|
|
|
break;
|
2020-05-29 18:44:58 +02:00
|
|
|
|
|
|
|
case TypeKind.FunctionType:
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
if (elementType.paramTypes.length < type.argumentTypes.length) {
|
|
|
|
|
|
|
|
if (!hasDiagnostic(type.node!, E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL)) {
|
|
|
|
|
|
|
|
let nested: Diagnostic[] = [];
|
|
|
|
for (let i = elementType.paramTypes.length; i < type.argumentTypes.length; i++) {
|
|
|
|
nested.push({
|
|
|
|
message: E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER,
|
|
|
|
severity: 'error',
|
|
|
|
node: elementType.getTypeAtParameterIndex(type.argumentTypes.length).node!
|
|
|
|
});
|
|
|
|
}
|
|
|
|
type.node!.errors.push({
|
|
|
|
message: E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL,
|
|
|
|
severity: 'error',
|
|
|
|
args: {
|
|
|
|
expected: elementType.paramTypes.length,
|
|
|
|
actual: type.argumentTypes.length,
|
|
|
|
},
|
|
|
|
nested,
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip this return type
|
2020-05-29 18:44:58 +02:00
|
|
|
continue;
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
} else if (elementType.paramTypes.length > type.argumentTypes.length) {
|
|
|
|
|
|
|
|
if (!hasDiagnostic(type.node!, E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL)) {
|
|
|
|
|
|
|
|
const nested: Diagnostic[] = [];
|
|
|
|
for (let i = type.argumentTypes.length; i < elementType.paramTypes.length; i++) {
|
|
|
|
nested.push({
|
|
|
|
message: E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER,
|
|
|
|
severity: 'info',
|
|
|
|
node: elementType.paramTypes[i].node!
|
|
|
|
});
|
|
|
|
}
|
|
|
|
type.node!.errors.push({
|
|
|
|
message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
|
|
|
|
severity: 'error',
|
|
|
|
args: {
|
|
|
|
expected: elementType.paramTypes.length,
|
|
|
|
actual: type.argumentTypes.length,
|
|
|
|
},
|
|
|
|
nested,
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip this return type
|
|
|
|
continue;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
let hasErrors = false;
|
|
|
|
|
|
|
|
const paramCount = type.argumentTypes.length;
|
|
|
|
for (let i = 0; i < paramCount; i++) {
|
|
|
|
const argType = type.argumentTypes[i];
|
|
|
|
const paramType = elementType.paramTypes[i];
|
|
|
|
if (!this.areTypesSemanticallyEquivalent(argType, paramType)) {
|
|
|
|
if (!hasDiagnostic(type.solved.node!, E_ARGUMENT_TYPE_NOT_ASSIGNABLE)) {
|
|
|
|
type.node!.errors.push({
|
|
|
|
message: E_ARGUMENT_TYPE_NOT_ASSIGNABLE,
|
|
|
|
severity: 'error',
|
|
|
|
node: argType.node,
|
|
|
|
args: { argType, paramType }
|
|
|
|
})
|
|
|
|
}
|
|
|
|
hasErrors = true;
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
2020-05-29 20:33:04 +02:00
|
|
|
|
|
|
|
// Skip this return type if we had type errors
|
|
|
|
if (hasErrors) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
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-05-29 20:33:04 +02:00
|
|
|
resultTypes.push(elementType.returnType.solved);
|
2020-05-26 21:01:38 +02:00
|
|
|
break;
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
default:
|
2020-05-29 18:44:58 +02:00
|
|
|
throw new Error(`Resolving the given type will not work.`)
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
if (resultTypes.length === 0) {
|
|
|
|
if (hasAnyType) {
|
|
|
|
return new AnyType;
|
|
|
|
} else {
|
|
|
|
return new NeverType;
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
|
|
|
// Small optimisation to make debugging easier.
|
|
|
|
if (resultTypes.length === 1) {
|
|
|
|
return resultTypes[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
return new UnionType(resultTypes);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
private mergeDuplicateTypes(resultTypes: Type[]): void {
|
2020-05-29 18:44:58 +02:00
|
|
|
|
|
|
|
resultTypes.sort(comparator(areTypesLexicallyLessThan));
|
|
|
|
|
|
|
|
for (let i = 0; i < resultTypes.length; i++) {
|
|
|
|
const typeA = resultTypes[i];
|
|
|
|
let j = i+1;
|
|
|
|
for (; j < resultTypes.length; j++) {
|
|
|
|
if (!areTypesLexicallyEquivalent(typeA, resultTypes[j])) {
|
2020-05-28 14:08:49 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
resultTypes.splice(i+1, j-i-1);
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-29 20:33:04 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private simplifyType(type: Type): Type {
|
|
|
|
|
|
|
|
const resultTypes = [];
|
|
|
|
|
|
|
|
let hasAnyType = false;
|
|
|
|
|
|
|
|
// We will use a stack instead of a normal iteration because during the simplification
|
|
|
|
// new union types might be created, of which the elements need to take part in the
|
|
|
|
// iteration.
|
|
|
|
const stack = [ type ];
|
|
|
|
|
|
|
|
while (stack.length > 0) {
|
|
|
|
|
|
|
|
const elementType = stack.pop()!;
|
|
|
|
|
|
|
|
switch (elementType.kind) {
|
|
|
|
|
|
|
|
case TypeKind.UnionType:
|
|
|
|
for (const elementType2 of getAllPossibleElementTypes(elementType)) {
|
|
|
|
stack.push(elementType2);
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case TypeKind.AnyType:
|
|
|
|
// We just want one 'any'-type to be present in the resulting union type, so we keep
|
|
|
|
// track of a special flag that indicates whether such a type has been detected.
|
|
|
|
hasAnyType = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TypeKind.NeverType:
|
|
|
|
// If any of the union type elements is a type that never matches, then that type has
|
|
|
|
// precedence over all the other types.
|
|
|
|
return new NeverType;
|
|
|
|
|
|
|
|
case TypeKind.ReturnType:
|
|
|
|
const resolvedType = this.resolveReturnType(elementType);
|
|
|
|
stack.push(resolvedType);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TypeKind.FunctionType:
|
|
|
|
case TypeKind.NeverType:
|
|
|
|
case TypeKind.OpaqueType:
|
|
|
|
resultTypes.push(elementType);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(`I did not know how to simpllify type ${TypeKind[elementType.kind]}`)
|
2020-05-28 14:08:49 +02:00
|
|
|
|
|
|
|
}
|
2020-05-29 18:44:58 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
if (resultTypes.length === 0) {
|
|
|
|
if (hasAnyType) {
|
|
|
|
return new AnyType;
|
|
|
|
} else {
|
|
|
|
return new NeverType;
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
this.mergeDuplicateTypes(resultTypes);
|
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
// Small optimisation to make debugging easier.
|
|
|
|
if (resultTypes.length === 1) {
|
|
|
|
return resultTypes[0]
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-29 20:33:04 +02:00
|
|
|
return new UnionType(resultTypes);
|
2020-05-29 18:44:58 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|