2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
import { FastStringMap, assert, isPlainObject, some, prettyPrintTag } from "./util";
|
|
|
|
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, isBoltMacroCall, BoltTypeExpression, BoltCallExpression, BoltSyntax, BoltMemberExpression, BoltDeclaration, isBoltDeclaration, isBoltTypeDeclaration, BoltTypeDeclaration, BoltReturnStatement, BoltIdentifier, BoltRecordDeclaration, isBoltRecordDeclaration, isBoltDeclarationLike } from "./ast";
|
|
|
|
import { getSymbolPathFromNode, ScopeType, SymbolResolver, SymbolInfo, SymbolPath } from "./resolver";
|
2020-05-27 19:57:15 +02:00
|
|
|
import { Value, Record } from "./evaluator";
|
2020-05-28 14:08:49 +02:00
|
|
|
import { SourceMap } from "module";
|
|
|
|
import { timingSafeEqual } from "crypto";
|
|
|
|
import { isRightAssoc, getReturnStatementsInFunctionBody, BoltFunctionBody, getModulePathToNode } from "./common";
|
|
|
|
import { relativeTimeThreshold } from "moment";
|
|
|
|
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_TYPES_NOT_ASSIGNABLE, E_TYPES_MISSING_MEMBER, E_NODE_DOES_NOT_CONTAIN_MEMBER, E_RECORD_MISSING_MEMBER, E_MUST_RETURN_A_VALUE, E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID, E_MAY_NOT_RETURN_A_VALUE, E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID } from "./diagnostics";
|
|
|
|
import { emitNode } from "./emitter";
|
|
|
|
import { BOLT_MAX_FIELDS_TO_PRINT } from "./constants";
|
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
|
|
|
|
|
|
|
enum TypeKind {
|
|
|
|
OpaqueType,
|
|
|
|
AnyType,
|
|
|
|
NeverType,
|
|
|
|
FunctionType,
|
|
|
|
RecordType,
|
2020-05-26 22:58:31 +02:00
|
|
|
PlainRecordFieldType,
|
2020-05-23 21:15:20 +02:00
|
|
|
VariantType,
|
2020-05-26 21:01:38 +02:00
|
|
|
UnionType,
|
2020-05-23 21:15:20 +02:00
|
|
|
TupleType,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Type
|
|
|
|
= OpaqueType
|
|
|
|
| AnyType
|
|
|
|
| NeverType
|
|
|
|
| FunctionType
|
|
|
|
| RecordType
|
|
|
|
| VariantType
|
|
|
|
| TupleType
|
2020-05-27 19:57:15 +02:00
|
|
|
| UnionType
|
2020-05-28 14:08:49 +02:00
|
|
|
| PlainRecordFieldType
|
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-28 14:08:49 +02:00
|
|
|
/**
|
|
|
|
* Holds the node that created this type, if any.
|
|
|
|
*/
|
|
|
|
public node?: Syntax
|
|
|
|
|
|
|
|
constructor(public sym?: SymbolInfo) {
|
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-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class AnyType extends TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.AnyType = TypeKind.AnyType;
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class NeverType extends TypeBase {
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.NeverType = TypeKind.NeverType;
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class FunctionType extends TypeBase {
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.FunctionType = TypeKind.FunctionType;
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
public paramTypes: Type[],
|
|
|
|
public returnType: Type,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public getParameterCount(): number {
|
|
|
|
return this.paramTypes.length;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public getTypeAtParameterIndex(index: number) {
|
2020-05-23 21:15:20 +02:00
|
|
|
if (index < 0 || index >= this.paramTypes.length) {
|
|
|
|
throw new Error(`Could not get the parameter type at index ${index} because the index was out of bounds.`);
|
|
|
|
}
|
|
|
|
return this.paramTypes[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export class VariantType extends TypeBase {
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public kind: TypeKind.VariantType = TypeKind.VariantType;
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
constructor(public elementTypes: Type[]) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public getOwnElementTypes(): IterableIterator<Type> {
|
|
|
|
return this.elementTypes[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
export class UnionType extends TypeBase {
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
private elements: Type[] = [];
|
|
|
|
|
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-28 14:08:49 +02:00
|
|
|
this.elements = [...elements];
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public addElement(element: Type): void {
|
|
|
|
this.elements.push(element);
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
public getElementTypes(): IterableIterator<Type> {
|
2020-05-27 19:57:15 +02:00
|
|
|
return this.elements[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export type RecordFieldType
|
|
|
|
= PlainRecordFieldType
|
|
|
|
|
|
|
|
class PlainRecordFieldType extends TypeBase {
|
|
|
|
|
|
|
|
public kind: TypeKind.PlainRecordFieldType = TypeKind.PlainRecordFieldType;
|
|
|
|
|
|
|
|
constructor(public type: Type) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
|
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-26 22:58:31 +02:00
|
|
|
private fieldTypes = new FastStringMap<string, RecordFieldType>();
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
constructor(
|
2020-05-26 22:58:31 +02:00
|
|
|
iterable?: Iterable<[string, RecordFieldType]>,
|
2020-05-23 21:15:20 +02:00
|
|
|
) {
|
2020-05-28 14:08:49 +02:00
|
|
|
super();
|
2020-05-26 22:58:31 +02:00
|
|
|
if (iterable !== undefined) {
|
|
|
|
for (const [name, type] of iterable) {
|
|
|
|
this.fieldTypes.set(name, type);
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
|
|
|
public getFieldNames() {
|
|
|
|
return this.fieldTypes.keys();
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public addField(name: string, type: RecordFieldType): void {
|
|
|
|
this.fieldTypes.set(name, type);
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
public getFields() {
|
|
|
|
return this.fieldTypes[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
public hasField(name: string) {
|
|
|
|
return name in this.fieldTypes;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public getFieldType(name: string) {
|
2020-05-23 21:15:20 +02:00
|
|
|
return this.fieldTypes.get(name);
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public clear(): void {
|
|
|
|
this.fieldTypes.clear();
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
export class TupleType extends TypeBase {
|
|
|
|
|
|
|
|
kind: TypeKind.TupleType = TypeKind.TupleType;
|
|
|
|
|
|
|
|
constructor(public elementTypes: Type[] = []) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
export enum ErrorType {
|
|
|
|
AssignmentError,
|
2020-05-28 14:08:49 +02:00
|
|
|
NotARecord,
|
|
|
|
TypeMismatch,
|
|
|
|
TooFewArguments,
|
|
|
|
TooManyArguments,
|
|
|
|
MayNotReturnValue,
|
|
|
|
MustReturnValue,
|
|
|
|
}
|
|
|
|
|
|
|
|
interface NotARecordError {
|
|
|
|
type: ErrorType.NotARecord;
|
|
|
|
node: Syntax;
|
|
|
|
candidate: Syntax;
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface AssignmentError {
|
|
|
|
type: ErrorType.AssignmentError;
|
|
|
|
left: Syntax;
|
|
|
|
right: Syntax;
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
interface TypeMismatchError {
|
|
|
|
type: ErrorType.TypeMismatch;
|
|
|
|
left: Type;
|
|
|
|
right: Type;
|
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
interface TooManyArgumentsError {
|
|
|
|
type: ErrorType.TooManyArguments;
|
|
|
|
caller: Syntax;
|
|
|
|
callee: Syntax;
|
|
|
|
index: number;
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
interface TooFewArgumentsError {
|
|
|
|
type: ErrorType.TooFewArguments;
|
|
|
|
caller: Syntax;
|
|
|
|
callee: Syntax;
|
|
|
|
index: number;
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
interface MustReturnValueError {
|
|
|
|
type: ErrorType.MustReturnValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface MayNotReturnValueError {
|
|
|
|
type: ErrorType.MayNotReturnValue;
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
export type CompileError
|
|
|
|
= AssignmentError
|
|
|
|
| TypeMismatchError
|
|
|
|
| TooManyArgumentsError
|
|
|
|
| TooFewArgumentsError
|
|
|
|
| NotARecordError
|
|
|
|
| MustReturnValueError
|
|
|
|
| MayNotReturnValueError
|
|
|
|
|
|
|
|
export interface FunctionSignature {
|
|
|
|
paramTypes: Type[];
|
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)) {
|
|
|
|
hasElementType = true;
|
|
|
|
if (elementType.sym !== undefined) {
|
|
|
|
out += elementType.sym.name;
|
|
|
|
} else {
|
|
|
|
switch (elementType.kind) {
|
|
|
|
case TypeKind.AnyType:
|
|
|
|
{
|
|
|
|
out += 'any';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TypeKind.RecordType:
|
|
|
|
{
|
|
|
|
out += '{'
|
|
|
|
let i = 0;
|
|
|
|
for (const [fieldName, fieldType] of elementType.getFields()) {
|
|
|
|
out += fieldName + ': ' + prettyPrintType(fieldType);
|
|
|
|
i++;
|
|
|
|
if (i >= BOLT_MAX_FIELDS_TO_PRINT) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out += '}'
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw new Error(`Could not pretty-print type ${TypeKind[elementType.kind]}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!hasElementType) {
|
|
|
|
out += '()'
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
2020-05-24 11:17:56 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
export class TypeChecker {
|
|
|
|
|
|
|
|
private opaqueTypes = new FastStringMap<number, OpaqueType>();
|
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
private anyType = new AnyType();
|
|
|
|
|
|
|
|
private syntaxType = new UnionType(); // FIXME
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
constructor(private resolver: SymbolResolver) {
|
2020-05-24 11:17:56 +02:00
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
public getTypeOfValue(value: Value): Type {
|
|
|
|
if (typeof(value) === 'string') {
|
2020-05-28 14:08:49 +02:00
|
|
|
const sym = this.resolver.resolveGlobalSymbol('String', ScopeType.Type);
|
|
|
|
assert(sym !== null);
|
|
|
|
return new OpaqueType(sym!);
|
2020-05-26 22:58:31 +02:00
|
|
|
} else if (typeof(value) === 'bigint') {
|
2020-05-28 14:08:49 +02:00
|
|
|
const sym = this.resolver.resolveGlobalSymbol('int', ScopeType.Type);
|
|
|
|
assert(sym !== null);
|
|
|
|
return new OpaqueType(sym!);
|
2020-05-26 22:58:31 +02:00
|
|
|
} else if (typeof(value) === 'number') {
|
2020-05-28 14:08:49 +02:00
|
|
|
const sym = this.resolver.resolveGlobalSymbol('f64', ScopeType.Type);
|
|
|
|
assert(sym !== null);
|
|
|
|
return new OpaqueType(sym!);
|
2020-05-27 19:57:15 +02:00
|
|
|
} else if (value instanceof Record) {
|
2020-05-26 22:58:31 +02:00
|
|
|
const recordType = new RecordType()
|
2020-05-27 19:57:15 +02:00
|
|
|
for (const [fieldName, fieldValue] of value.getFields()) {
|
|
|
|
recordType.addField(name, new PlainRecordFieldType(this.getTypeOfValue(fieldValue)));
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
|
|
|
return recordType;
|
|
|
|
} else {
|
|
|
|
throw new Error(`Could not determine type of given value.`);
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
private checkTypeMatches(a: Type, b: Type) {
|
|
|
|
switch (b.kind) {
|
|
|
|
case TypeKind.FunctionType:
|
|
|
|
if (a.kind === TypeKind.AnyType) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (a.kind === TypeKind.FunctionType) {
|
|
|
|
if (b.getParameterCount() > a.getParameterCount()) {
|
|
|
|
a.node?.errors.push({
|
|
|
|
message: E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL,
|
|
|
|
severity: 'error',
|
|
|
|
args: {
|
|
|
|
expected: a.getParameterCount(),
|
|
|
|
actual: a.getParameterCount(),
|
|
|
|
},
|
|
|
|
nested: [{
|
|
|
|
message: E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER,
|
|
|
|
severity: 'error',
|
|
|
|
node: b.getTypeAtParameterIndex(a.getParameterCount()).node!
|
|
|
|
}]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (b.getParameterCount() < a.getParameterCount()) {
|
|
|
|
let nested = [];
|
|
|
|
for (let i = b.getParameterCount(); i < a.getParameterCount(); i++) {
|
|
|
|
nested.push({
|
|
|
|
message: E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER,
|
|
|
|
severity: 'error',
|
|
|
|
node: (a.node as BoltCallExpression).operands[i]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
a.node?.errors.push({
|
|
|
|
message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
|
|
|
|
severity: 'error',
|
|
|
|
args: {
|
|
|
|
expected: a.getParameterCount(),
|
|
|
|
actual: b.getParameterCount(),
|
|
|
|
},
|
|
|
|
nested,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
const paramCount = a.getParameterCount();
|
|
|
|
for (let i = 0; i < paramCount; i++) {
|
|
|
|
const paramA = a.getTypeAtParameterIndex(i);
|
|
|
|
const paramB = b.getTypeAtParameterIndex(i);
|
|
|
|
if (this.isTypeAssignableTo(paramA, paramB)) {
|
|
|
|
a.node?.errors.push({
|
|
|
|
message: E_TYPES_NOT_ASSIGNABLE,
|
|
|
|
severity: 'error',
|
|
|
|
args: {
|
|
|
|
left: a,
|
|
|
|
right: b,
|
|
|
|
},
|
|
|
|
node: a.node,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
public registerSourceFile(sourceFile: SourceFile): void {
|
|
|
|
for (const node of sourceFile.preorder()) {
|
|
|
|
if (isBoltMacroCall(node)) {
|
|
|
|
continue; // FIXME only continue when we're not in an expression context
|
|
|
|
}
|
|
|
|
if (isBoltExpression(node)) {
|
|
|
|
node.type = this.createInitialTypeForExpression(node);
|
|
|
|
}
|
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
for (const callExpr of sourceFile.findAllChildrenOfKind(SyntaxKind.BoltCallExpression)) {
|
|
|
|
const callTypeSig = new FunctionType(callExpr.operands.map(op => op.type!), this.anyType);
|
|
|
|
for (const callableType of this.findTypesInExpression(callExpr.operator)) {
|
|
|
|
this.checkTypeMatches(callableType, callTypeSig);
|
|
|
|
}
|
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private createInitialTypeForExpression(node: Syntax): Type {
|
|
|
|
|
|
|
|
if (node.type !== undefined) {
|
|
|
|
return node.type;
|
|
|
|
}
|
|
|
|
|
|
|
|
let resultType;
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
switch (node.kind) {
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
case SyntaxKind.BoltMatchExpression:
|
|
|
|
{
|
|
|
|
const unionType = new UnionType();
|
|
|
|
for (const matchArm of node.arms) {
|
|
|
|
unionType.addElement(this.createInitialTypeForExpression(matchArm.body));
|
|
|
|
}
|
|
|
|
resultType = unionType;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
case SyntaxKind.BoltRecordDeclaration:
|
|
|
|
{
|
2020-05-27 19:57:15 +02:00
|
|
|
const recordSym = this.resolver.getSymbolForNode(node, ScopeType.Type);
|
2020-05-26 22:58:31 +02:00
|
|
|
assert(recordSym !== null);
|
|
|
|
if (this.opaqueTypes.has(recordSym!.id)) {
|
2020-05-27 19:57:15 +02:00
|
|
|
resultType = this.opaqueTypes.get(recordSym!.id);
|
|
|
|
} else {
|
2020-05-28 14:08:49 +02:00
|
|
|
const opaqueType = new OpaqueType(name, node);
|
2020-05-27 19:57:15 +02:00
|
|
|
this.opaqueTypes.set(recordSym!.id, opaqueType);
|
|
|
|
resultType = opaqueType;
|
2020-05-26 22:58:31 +02:00
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltFunctionExpression:
|
|
|
|
{
|
|
|
|
const paramTypes = node.params.map(param => {
|
2020-05-27 20:59:45 +02:00
|
|
|
if (param.typeExpr === null) {
|
2020-05-27 19:57:15 +02:00
|
|
|
return this.anyType;
|
|
|
|
}
|
2020-05-27 20:59:45 +02:00
|
|
|
return this.createInitialTypeForTypeExpression(param.typeExpr);
|
2020-05-27 19:57:15 +02:00
|
|
|
});
|
|
|
|
let returnType = node.returnType === null
|
|
|
|
? this.anyType
|
|
|
|
: this.createInitialTypeForTypeExpression(node.returnType);
|
2020-05-27 20:59:45 +02:00
|
|
|
resultType = new FunctionType(paramTypes, returnType);
|
2020-05-27 19:57:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SyntaxKind.BoltQuoteExpression:
|
2020-05-27 20:59:45 +02:00
|
|
|
{
|
|
|
|
resultType = this.syntaxType;
|
|
|
|
break
|
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
|
|
|
case SyntaxKind.BoltMemberExpression:
|
|
|
|
case SyntaxKind.BoltReferenceExpression:
|
|
|
|
case SyntaxKind.BoltCallExpression:
|
|
|
|
case SyntaxKind.BoltBlockExpression:
|
|
|
|
{
|
|
|
|
resultType = this.anyType;
|
2020-05-26 22:58:31 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
case SyntaxKind.BoltConstantExpression:
|
2020-05-27 19:57:15 +02:00
|
|
|
{
|
|
|
|
resultType = this.getTypeOfValue(node.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(`Could not create a type for node ${kindToString(node.kind)}.`);
|
2020-05-26 22:58:31 +02:00
|
|
|
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
node.type = resultType;
|
|
|
|
|
|
|
|
return resultType;
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
private createInitialTypeForTypeExpression(node: BoltTypeExpression): Type {
|
2020-05-26 21:01:38 +02:00
|
|
|
switch (node.kind) {
|
2020-05-27 19:57:15 +02:00
|
|
|
case SyntaxKind.BoltLiftedTypeExpression:
|
|
|
|
return this.createInitialTypeForExpression(node.expression);
|
2020-05-26 21:01:38 +02:00
|
|
|
default:
|
2020-05-27 19:57:15 +02:00
|
|
|
throw new Error(`Could not create a type for node ${kindToString(node.kind)}.`);
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-27 19:57:15 +02:00
|
|
|
public isVoidType(type: Type): boolean {
|
2020-05-28 14:08:49 +02:00
|
|
|
return this.isTypeAssignableTo(new TupleType, type);
|
|
|
|
}
|
|
|
|
|
|
|
|
private *getTypesForMember(origNode: Syntax, fieldName: string, type: Type): IterableIterator<Type> {
|
|
|
|
switch (type.kind) {
|
|
|
|
case TypeKind.UnionType:
|
|
|
|
{
|
|
|
|
const typesMissingMember = [];
|
|
|
|
for (const elementType of getAllPossibleElementTypes(type)) {
|
|
|
|
let foundType = false;
|
|
|
|
for (const recordType of this.getTypesForMemberNoUnionType(origNode, fieldName, elementType, false)) {
|
|
|
|
yield recordType;
|
|
|
|
foundType = true;
|
|
|
|
}
|
|
|
|
if (!foundType) {
|
|
|
|
origNode.errors.push({
|
|
|
|
message: E_TYPES_MISSING_MEMBER,
|
|
|
|
severity: 'error',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return this.getTypesForMemberNoUnionType(origNode, fieldName, type, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private *getTypesForMemberNoUnionType(origNode: Syntax, fieldName: string, type: Type, hardError: boolean): IterableIterator<Type> {
|
|
|
|
switch (type.kind) {
|
|
|
|
case TypeKind.AnyType:
|
|
|
|
break;
|
|
|
|
case TypeKind.FunctionType:
|
|
|
|
if (hardError) {
|
|
|
|
origNode.errors.push({
|
|
|
|
message: E_TYPES_MISSING_MEMBER,
|
|
|
|
severity: 'error',
|
|
|
|
args: {
|
|
|
|
name: fieldName,
|
|
|
|
},
|
|
|
|
nested: [{
|
|
|
|
message: E_NODE_DOES_NOT_CONTAIN_MEMBER,
|
|
|
|
severity: 'error',
|
|
|
|
node: type.node!,
|
|
|
|
}]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TypeKind.RecordType:
|
|
|
|
{
|
|
|
|
if (type.hasField(fieldName)) {
|
|
|
|
const fieldType = type.getFieldType(fieldName);
|
|
|
|
assert(fieldType.kind === TypeKind.PlainRecordFieldType);
|
|
|
|
yield (fieldType as PlainRecordFieldType).type;
|
|
|
|
} else {
|
|
|
|
if (hardError) {
|
|
|
|
origNode.errors.push({
|
|
|
|
message: E_TYPES_MISSING_MEMBER,
|
|
|
|
severity: 'error',
|
|
|
|
args: {
|
|
|
|
name: fieldName
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw new Error(`I do not know how to find record member types for ${TypeKind[type.kind]}`)
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
2020-05-27 19:57:15 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
private isTypeAlwaysCallable(type: Type) {
|
|
|
|
return type.kind === TypeKind.FunctionType;
|
|
|
|
}
|
|
|
|
|
|
|
|
private *getAllNodesForType(type: Type): IterableIterator<Syntax> {
|
|
|
|
if (type.node !== undefined) {
|
|
|
|
yield type.node;
|
|
|
|
}
|
|
|
|
switch (type.kind) {
|
|
|
|
case TypeKind.UnionType:
|
|
|
|
for (const elementType of type.getElementTypes()) {
|
|
|
|
yield* this.getAllNodesForType(type);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
private *findTypesInTypeExpression(node: BoltTypeExpression): IterableIterator<Type> {
|
|
|
|
switch (node.kind) {
|
|
|
|
case SyntaxKind.BoltTypeOfExpression:
|
|
|
|
{
|
|
|
|
yield* this.findTypesInExpression(node.expression);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.BoltReferenceTypeExpression:
|
|
|
|
{
|
|
|
|
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
|
|
|
|
assert(scope !== null);
|
|
|
|
const symbolPath = getSymbolPathFromNode(node.name);
|
|
|
|
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
|
|
|
|
if (resolvedSym !== null) {
|
|
|
|
for (const decl of resolvedSym.declarations) {
|
|
|
|
assert(isBoltTypeDeclaration(decl));
|
|
|
|
this.findTypesInTypeDeclaration(decl as BoltTypeDeclaration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
|
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
private *findTypesInExpression(node: BoltExpression): IterableIterator<Type> {
|
2020-05-26 21:01:38 +02:00
|
|
|
|
|
|
|
switch (node.kind) {
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
case SyntaxKind.BoltMemberExpression:
|
|
|
|
{
|
2020-05-28 14:08:49 +02:00
|
|
|
for (const element of node.path) {
|
|
|
|
for (const memberType of this.getTypesForMember(element, element.text, node.expression.type!)) {
|
|
|
|
yield memberType;
|
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
|
|
|
case SyntaxKind.BoltMatchExpression:
|
|
|
|
{
|
|
|
|
const unionType = new UnionType();
|
|
|
|
for (const matchArm of node.arms) {
|
|
|
|
unionType.addElement(this.createInitialTypeForExpression(matchArm.body));
|
|
|
|
}
|
|
|
|
yield unionType;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
case SyntaxKind.BoltQuoteExpression:
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
case SyntaxKind.BoltCallExpression:
|
|
|
|
{
|
2020-05-28 14:08:49 +02:00
|
|
|
const nodeSignature = new FunctionType(node.operands.map(op => op.type!), this.anyType);
|
|
|
|
for (const callableType of this.findTypesInExpression(node.operator)) {
|
|
|
|
yield callableType;
|
|
|
|
}
|
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
|
|
|
case SyntaxKind.BoltReferenceExpression:
|
|
|
|
{
|
2020-05-28 14:08:49 +02:00
|
|
|
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
|
2020-05-26 21:01:38 +02:00
|
|
|
assert(scope !== null);
|
2020-05-28 14:08:49 +02:00
|
|
|
const symbolPath = getSymbolPathFromNode(node.name);
|
|
|
|
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
|
2020-05-26 21:01:38 +02:00
|
|
|
if (resolvedSym !== null) {
|
|
|
|
for (const decl of resolvedSym.declarations) {
|
2020-05-28 14:08:49 +02:00
|
|
|
assert(isBoltDeclaration(decl));
|
|
|
|
yield* this.findTypesInDeclaration(decl as BoltDeclaration);
|
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:
|
|
|
|
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
|
2020-05-28 14:08:49 +02:00
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
private *findTypesInTypeDeclaration(node: BoltTypeDeclaration): IterableIterator<Type> {
|
|
|
|
switch (node.kind) {
|
|
|
|
case SyntaxKind.BoltTypeAliasDeclaration:
|
|
|
|
{
|
|
|
|
yield* this.findTypesInTypeExpression(node.typeExpr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private *findTypesInDeclaration(node: BoltDeclaration) {
|
2020-05-26 21:01:38 +02:00
|
|
|
switch (node.kind) {
|
|
|
|
case SyntaxKind.BoltVariableDeclaration:
|
2020-05-28 14:08:49 +02:00
|
|
|
if (node.typeExpr !== null) {
|
|
|
|
yield* this.findTypesInTypeExpression(node.typeExpr);
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
if (node.value !== null) {
|
2020-05-28 14:08:49 +02:00
|
|
|
yield* this.findTypesInExpression(node.value);
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|
|
|
|
break;
|
2020-05-28 14:08:49 +02:00
|
|
|
case SyntaxKind.BoltFunctionDeclaration:
|
|
|
|
{
|
|
|
|
yield this.getFunctionReturnType(node);
|
|
|
|
break;
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
default:
|
2020-05-28 14:08:49 +02:00
|
|
|
throw new Error(`Could not find callable expressions in declaration ${kindToString(node.kind)}`)
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:01:38 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
private getTypeOfExpression(node: BoltExpression): Type {
|
|
|
|
return new UnionType(this.findTypesInExpression(node));
|
|
|
|
}
|
|
|
|
|
|
|
|
private getFunctionReturnType(node: BoltFunctionDeclaration): Type {
|
|
|
|
let returnType: Type = this.anyType;
|
|
|
|
if (node.returnType !== null) {
|
|
|
|
returnType = new UnionType(this.findTypesInTypeExpression(node.returnType));
|
|
|
|
}
|
|
|
|
for (const returnStmt of this.getAllReturnStatementsInFunctionBody(node.body)) {
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return returnType;
|
|
|
|
}
|
|
|
|
|
|
|
|
private *getAllReturnStatementsInFunctionBody(body: BoltFunctionBody): IterableIterator<BoltReturnStatement> {
|
|
|
|
for (const element of body) {
|
|
|
|
switch (element.kind) {
|
|
|
|
case SyntaxKind.BoltReturnStatement:
|
|
|
|
{
|
|
|
|
yield element;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.BoltConditionalStatement:
|
|
|
|
{
|
|
|
|
for (const caseNode of element.cases) {
|
|
|
|
yield* this.getAllReturnStatementsInFunctionBody(caseNode.body);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SyntaxKind.BoltExpressionStatement:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error(`I did not know how to find return statements in ${kindToString(node.kind)}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private isTypeAssignableTo(left: Type, right: Type): boolean {
|
|
|
|
if (left.kind === TypeKind.NeverType || right.kind === TypeKind.NeverType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (left.kind === TypeKind.AnyType || right.kind === TypeKind.AnyType) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (left.kind === TypeKind.OpaqueType && right.kind === TypeKind.OpaqueType) {
|
|
|
|
return left === right;
|
|
|
|
}
|
|
|
|
if (left.kind === TypeKind.RecordType && right.kind === TypeKind.RecordType) {
|
|
|
|
for (const fieldName of left.getFieldNames()) {
|
|
|
|
if (!right.hasField(fieldName)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const fieldName of right.getFieldNames()) {
|
|
|
|
if (!left.hasField(fieldName)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this.isTypeAssignableTo(left.getFieldType(fieldName), right.getFieldType(fieldName))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (left.kind === TypeKind.FunctionType && right.kind === TypeKind.FunctionType) {
|
|
|
|
if (left.getParameterCount() !== right.getParameterCount()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (let i = 0; i < left.getParameterCount(); i++) {
|
|
|
|
if (!this.isTypeAssignableTo(left.getTypeAtParameterIndex(i), right.getTypeAtParameterIndex(i))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!this.isTypeAssignableTo(left.returnType, right.returnType)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2020-05-23 21:15:20 +02:00
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
}
|