From c2d101c25c3df424525158d8c232d19ac21d7fae Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Fri, 29 May 2020 18:44:58 +0200 Subject: [PATCH] Major update to code and type checker --- src/ast-spec.txt | 13 +- src/ast.d.ts | 344 +++---- src/checks.ts | 8 +- src/common.ts | 54 ++ src/diagnostics.ts | 3 +- src/evaluator.ts | 2 +- src/frontend.ts | 1 + src/resolver.ts | 67 +- src/test/types.ts | 18 + src/treegen/ast-template.js | 2 +- src/treegen/ast.dts.template | 4 +- src/treegen/index.ts | 4 +- src/types.ts | 1755 +++++++++++++++++++++++----------- src/util.ts | 144 +++ 14 files changed, 1641 insertions(+), 778 deletions(-) create mode 100644 src/test/types.ts diff --git a/src/ast-spec.txt b/src/ast-spec.txt index 6691e3449..100854b2b 100644 --- a/src/ast-spec.txt +++ b/src/ast-spec.txt @@ -2,7 +2,8 @@ node EndOfFile > BoltToken, JSToken; node Token; node SourceFile; -node FunctionBody; +node FunctionBodyElement; +node ReturnStatement; // Bolt language AST definitions @@ -217,7 +218,7 @@ node BoltConstantExpression > BoltExpression { node BoltStatement > BoltSyntax, BoltFunctionBodyElement, BoltSourceElement; -node BoltReturnStatement > BoltStatement { +node BoltReturnStatement > ReturnStatement, BoltStatement { value: Option, } @@ -262,7 +263,7 @@ node BoltModule > BoltSyntax, BoltSourceElement { node BoltDeclarationLike; -node BoltFunctionBodyElement; +node BoltFunctionBodyElement > FunctionBodyElement; node BoltFunctionDeclaration > BoltFunctionBodyElement, BoltDeclaration, BoltDeclarationLike { modifiers: BoltModifiers, @@ -467,9 +468,9 @@ node JSReferenceExpression > JSExpression { node JSSourceElement; -node JSFunctionBodyElement; +node JSFunctionBodyElement > FunctionBodyElement; -node JSStatement > JSSourceElement, JSFunctionBodyElement; +node JSStatement > JSSyntax, JSSourceElement, JSFunctionBodyElement; node JSCatchBlock > JSSyntax { bindings: Option, @@ -495,7 +496,7 @@ node JSConditionalStatement > JSStatement { cases: Vec, } -node JSReturnStatement > JSStatement { +node JSReturnStatement > ReturnStatement, JSStatement { value: Option, } diff --git a/src/ast.d.ts b/src/ast.d.ts index 6c636a2e8..8bf38d30a 100644 --- a/src/ast.d.ts +++ b/src/ast.d.ts @@ -1,5 +1,5 @@ -import { Type } from "./types" +import { TypeRef } from "./types" import { Diagnostic } from "./diagnostics" import { Package } from "./common" import { TextSpan } from "./text" @@ -13,7 +13,7 @@ export function isSyntax(value: any): value is Syntax; interface SyntaxBase { id: number; kind: SyntaxKind; - type?: Type; + type?: TypeRef; errors: Diagnostic[] parentNode: Syntax | null; span: TextSpan | null; @@ -30,7 +30,6 @@ export type ResolveSyntaxKind = Extract -} +export type FunctionBodyElement + = JSFunctionDeclaration + | JSArrowFunctionDeclaration + | JSLetDeclaration + | JSExpressionStatement + | JSConditionalStatement + | JSReturnStatement + | BoltFunctionDeclaration + | BoltVariableDeclaration + | BoltReturnStatement + | BoltConditionalStatement + | BoltResumeStatement + | BoltExpressionStatement + | BoltMacroCall -export type FunctionBodyParent -= never -export type FunctionBodyAnyParent -= never +export type ReturnStatement + = BoltReturnStatement + | JSReturnStatement -export type FunctionBodyChild -= never export type BoltSyntax = BoltSourceFile @@ -4980,6 +4984,9 @@ export type JSSyntax | JSFunctionDeclaration | JSArrowFunctionDeclaration | JSLetDeclaration + | JSExpressionStatement + | JSConditionalStatement + | JSReturnStatement | JSConstantExpression | JSMemberExpression | JSCallExpression @@ -8015,7 +8022,6 @@ export type JSSourceFileChild export type Syntax = EndOfFile - | FunctionBody | BoltStringLiteral | BoltIntegerLiteral | BoltIdentifier @@ -8172,7 +8178,6 @@ export type Syntax export function kindToString(kind: SyntaxKind): string; export function createEndOfFile(span?: TextSpan | null): EndOfFile; -export function createFunctionBody(span?: TextSpan | null): FunctionBody; export function createBoltStringLiteral(value: string, span?: TextSpan | null): BoltStringLiteral; export function createBoltIntegerLiteral(value: bigint, span?: TextSpan | null): BoltIntegerLiteral; export function createBoltIdentifier(text: string, span?: TextSpan | null): BoltIdentifier; @@ -8328,7 +8333,8 @@ export function createJSSourceFile(elements: JSSourceElement[], span?: TextSpan export function isEndOfFile(value: any): value is EndOfFile; export function isToken(value: any): value is Token; export function isSourceFile(value: any): value is SourceFile; -export function isFunctionBody(value: any): value is FunctionBody; +export function isFunctionBodyElement(value: any): value is FunctionBodyElement; +export function isReturnStatement(value: any): value is ReturnStatement; export function isBoltSyntax(value: any): value is BoltSyntax; export function isBoltToken(value: any): value is BoltToken; export function isBoltStringLiteral(value: any): value is BoltStringLiteral; diff --git a/src/checks.ts b/src/checks.ts index cf672051d..9c4b41f19 100644 --- a/src/checks.ts +++ b/src/checks.ts @@ -1,7 +1,7 @@ import { BoltImportDirective, Syntax, BoltParameter, BoltReferenceExpression, BoltReferenceTypeExpression, BoltSourceFile, BoltCallExpression, BoltReturnKeyword, BoltReturnStatement, SyntaxKind, NodeVisitor, BoltSyntax, BoltIdentifier } from "./ast"; import { Program } from "./program"; -import { DiagnosticPrinter, E_FILE_NOT_FOUND, E_TYPES_NOT_ASSIGNABLE, E_DECLARATION_NOT_FOUND, E_TYPE_DECLARATION_NOT_FOUND, E_MUST_RETURN_A_VALUE, E_MAY_NOT_RETURN_A_VALUE } from "./diagnostics"; -import { getSymbolPathFromNode } from "./resolver" +import { DiagnosticPrinter, E_FILE_NOT_FOUND, E_TYPE_MISMATCH, E_DECLARATION_NOT_FOUND, E_TYPE_DECLARATION_NOT_FOUND, E_MUST_RETURN_A_VALUE, E_MAY_NOT_RETURN_A_VALUE } from "./diagnostics"; +import { convertNodeToSymbolPath } from "./resolver" import { inject } from "./ioc"; import { SymbolResolver, ScopeType } from "./resolver"; import { assert, every } from "./util"; @@ -103,7 +103,7 @@ export class CheckReferences extends NodeVisitor { this.checkBoltModulePath(node.name, node.name.modulePath); const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable); assert(scope !== null); - const resolvedSym = this.resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!); + const resolvedSym = this.resolver.resolveSymbolPath(convertNodeToSymbolPath(node), scope!); if (resolvedSym === null) { this.diagnostics.add({ message: E_DECLARATION_NOT_FOUND, @@ -117,7 +117,7 @@ export class CheckReferences extends NodeVisitor { protected visitBoltReferenceTypeExpression(node: BoltReferenceTypeExpression) { const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Type); assert(scope !== null); - const symbolPath = getSymbolPathFromNode(node.name); + const symbolPath = convertNodeToSymbolPath(node.name); const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!); if (resolvedSym === null) { this.diagnostics.add({ diff --git a/src/common.ts b/src/common.ts index a6904e390..da7e63b24 100644 --- a/src/common.ts +++ b/src/common.ts @@ -19,6 +19,7 @@ import {FastStringMap, enumerate, escapeChar, assert} from "./util"; import {TextSpan, TextPos, TextFile} from "./text"; import {Scanner} from "./scanner"; import * as path from "path" +import { convertNodeToSymbolPath } from "./resolver"; export function getSourceFile(node: Syntax) { while (true) { @@ -249,6 +250,23 @@ export function isExported(node: Syntax) { } } +export function getFullyQualifiedPathToNode(node: Syntax) { + const symbolPath = convertNodeToSymbolPath(node); + while (true) { + const parentNode = node.parentNode; + if (parentNode === null) { + break; + } + node = parentNode; + if (node.kind === SyntaxKind.BoltModule) { + for (const element of node.name) { + symbolPath.modulePath.unshift(element.text); + } + } + } + return symbolPath; +} + export function describeKind(kind: SyntaxKind): string { switch (kind) { case SyntaxKind.BoltImportKeyword: @@ -388,3 +406,39 @@ export function describeKind(kind: SyntaxKind): string { } } +export function *getAllReturnStatementsInFunctionBody(body: FunctionBody): IterableIterator { + for (const element of body) { + switch (element.kind) { + case SyntaxKind.BoltReturnStatement: + case SyntaxKind.JSReturnStatement: + { + yield element; + break; + } + case SyntaxKind.BoltConditionalStatement: + { + for (const caseNode of element.cases) { + yield* getAllReturnStatementsInFunctionBody(caseNode.body); + } + break; + } + case SyntaxKind.JSTryCatchStatement: + { + yield* getAllReturnStatementsInFunctionBody(element.tryBlock) + if (element.catchBlock !== null) { + yield* getAllReturnStatementsInFunctionBody(element.catchBlock.elements) + } + if (element.finalBlock !== null) { + yield* getAllReturnStatementsInFunctionBody(element.finalBlock) + } + break; + } + case SyntaxKind.JSExpressionStatement: + case SyntaxKind.BoltExpressionStatement: + case SyntaxKind.JSImportDeclaration: + break; + default: + throw new Error(`I did not know how to find return statements in ${kindToString(element.kind)}`); + } + } +} diff --git a/src/diagnostics.ts b/src/diagnostics.ts index ab5d18f7e..5623128e3 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -13,7 +13,7 @@ export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present." export const E_FIELD_MUST_BE_BOOLEAN = "Field '{name}' must be a either 'true' or 'false'." export const E_TYPE_DECLARATION_NOT_FOUND = "A type declaration named '{name}' was not found." export const E_DECLARATION_NOT_FOUND = "Reference to an undefined declaration '{name}'."; -export const E_TYPES_NOT_ASSIGNABLE = "Types {left} and {right} are not assignable."; +export const E_TYPE_MISMATCH = "Types {left} and {right} are not semantically equivalent."; export const E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL = "Too few arguments for function call. Expected {expected} but got {actual}."; export const E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL = "Too many arguments for function call. Expected {expected} but got {actual}."; export const E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER = "Candidate function requires this parameter." @@ -24,6 +24,7 @@ export const E_TYPES_MISSING_MEMBER = "Not all types resolve to a record with th export const E_NODE_DOES_NOT_CONTAIN_MEMBER = "This node does not contain the the member '{name}'." export const E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID = "May not return a value because the function's return type resolves to '()'" export const E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID = "Must return a value because the function's return type does not resolve to '()'" +export const E_ARGUMENT_TYPE_NOT_ASSIGNABLE = "This argument's type is not assignable to the function's parameter type." const BOLT_HARD_ERRORS = process.env['BOLT_HARD_ERRORS'] diff --git a/src/evaluator.ts b/src/evaluator.ts index 015269368..fa33a3080 100644 --- a/src/evaluator.ts +++ b/src/evaluator.ts @@ -147,7 +147,7 @@ export class Evaluator { case SyntaxKind.BoltTypePattern: { const expectedType = this.checker.getTypeOfNode(node.type); - if (!this.checker.isTypeAssignableTo(expectedType, this.checker.getTypeOfValue(value))) { + if (!this.checker.isTypeAssignableTo(expectedType, this.checker.createTypeForValue(value))) { return false; } return false; diff --git a/src/frontend.ts b/src/frontend.ts index 1c5de6ddb..81dbb6a36 100644 --- a/src/frontend.ts +++ b/src/frontend.ts @@ -98,6 +98,7 @@ export class Frontend { for (const sourceFile of program.getAllSourceFiles()) { checker.registerSourceFile(sourceFile); } + checker.solve(program.getAllSourceFiles()); for (const pkg of program.getAllPackages()) { if (!pkg.isDependency) { for (const sourceFile of pkg.getAllSourceFiles()) { diff --git a/src/resolver.ts b/src/resolver.ts index f63d3c5fa..95f08653b 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -9,25 +9,49 @@ const GLOBAL_SCOPE_ID = 'global'; export class SymbolPath { constructor( - private parents: string[], + public modulePath: string[], public isAbsolute: boolean, public name: string ) { } - public hasParents(): boolean { - return this.parents.length > 0; + public encode(): string { + let out = ''; + if (this.isAbsolute) { + out += '::' + } + for (const element of this.modulePath) { + out += element + '::' + } + out += this.name; + return out; } - public getParents() { - return this.parents; + public hasModulePath(): boolean { + return this.modulePath.length > 0; + } + + public getModulePath() { + return this.modulePath; } } -export function getSymbolPathFromNode(node: BoltSyntax): SymbolPath { +export function convertNodeToSymbolPath(node: Syntax): SymbolPath { switch (node.kind) { + case SyntaxKind.BoltRecordDeclaration: + return new SymbolPath( + [], + false, + node.name.text, + ); + case SyntaxKind.BoltFunctionDeclaration: + return new SymbolPath( + [], + false, + emitNode(node.name), + ); case SyntaxKind.BoltReferenceExpression: return new SymbolPath( node.name.modulePath.map(id => id.text), @@ -42,14 +66,8 @@ export function getSymbolPathFromNode(node: BoltSyntax): SymbolPath { return new SymbolPath([], false, name); } return new SymbolPath(node.modulePath.map(id => id.text), false, name); - //case SyntaxKind.BoltModulePath: - // return new SymbolPath( - // node.elements.slice(0, -1).map(el => el.text), - // node.isAbsolute, - // node.elements[node.elements.length-1].text - // ); default: - throw new Error(`Could not extract a symbol path from the given node.`); + throw new Error(`Could not extract a symbol path from node ${kindToString(node.kind)}.`); } } @@ -388,7 +406,7 @@ export class SymbolResolver { for (const scopeType of getAllSymbolKinds()) { const scope = this.getScopeForNode(importDir, scopeType); assert(scope !== null); - const exported = this.resolveSymbolPath(getSymbolPathFromNode(importSymbol), scope!); + const exported = this.resolveSymbolPath(convertNodeToSymbolPath(importSymbol), scope!); if (exported !== null) { for (const decl of exported.declarations) { scope!.addNodeAsSymbol(this.strategy.getSymbolName(decl), decl); @@ -491,16 +509,17 @@ export class SymbolResolver { return scope.getSymbol(this.strategy.getSymbolName(node)); } - public resolveGlobalSymbol(name: string, kind: ScopeType) { - const symbolPath = new SymbolPath([], true, name); + public resolveGlobalSymbol(path: SymbolPath, kind: ScopeType) { for (const sourceFile of this.program.getAllGloballyDeclaredSourceFiles()) { const scope = this.getScopeForNode(sourceFile, kind); - if (scope === null) { + assert(scope !== null); + const modScope = this.resolveModulePath(path.getModulePath(), scope!); + if (modScope === null) { continue; } - const sym = scope.getLocalSymbol(name); + const sym = modScope?.getLocalSymbol(path.name) if (sym !== null) { - return sym + return sym; } } return null; @@ -508,7 +527,7 @@ export class SymbolResolver { public resolveSymbolPath(path: SymbolPath, scope: Scope): SymbolInfo | null { - if (path.hasParents()) { + if (path.hasModulePath()) { if (path.isAbsolute) { @@ -517,7 +536,7 @@ export class SymbolResolver { } else { // Perform the acutal module resolution. - const resolvedScope = this.resolveModulePath(path.getParents(), scope); + const resolvedScope = this.resolveModulePath(path.getModulePath(), scope); // Failing to find any module means that we cannot continue, because // it does not make sense to get the symbol of a non-existent module. @@ -533,7 +552,11 @@ export class SymbolResolver { // Once we've handled any module path that might have been present, // we resolve the actual symbol using a helper method. - return scope.getSymbol(path.name); + const sym = scope.getSymbol(path.name); + if (sym !== null) { + return sym + } + return this.resolveGlobalSymbol(path, scope.kind); } } diff --git a/src/test/types.ts b/src/test/types.ts new file mode 100644 index 000000000..691d29793 --- /dev/null +++ b/src/test/types.ts @@ -0,0 +1,18 @@ + +import { assert } from "chai" +import { AnyType, simplifyType, UnionType } from "../types" +import { createBoltIdentifier } from "../ast"; +import { type } from "os"; + +describe('a function that merges two equivalent types', () => { + + it('can merge two any types', () =>{ + const type1 = new AnyType; + type1.node = createBoltIdentifier('a'); + const type2 = new AnyType; + type2.node = createBoltIdentifier('b'); + const types = new UnionType([type1 type2]); + mergeTypes(types); + }) + +}) diff --git a/src/treegen/ast-template.js b/src/treegen/ast-template.js index 8c1936a13..c27c41278 100644 --- a/src/treegen/ast-template.js +++ b/src/treegen/ast-template.js @@ -18,7 +18,7 @@ const nodeProto = { *getChildNodes() { for (const key of Object.keys(this)) { - if (key === 'span' || key === 'parentNode') { + if (key === 'span' || key === 'parentNode' || key === 'type') { continue } const value = this[key]; diff --git a/src/treegen/ast.dts.template b/src/treegen/ast.dts.template index 6f7d8658b..adddec29b 100644 --- a/src/treegen/ast.dts.template +++ b/src/treegen/ast.dts.template @@ -1,5 +1,5 @@ -import { Type } from "./types" +import { TypeRef } from "./types" import { Diagnostic } from "./diagnostics" import { Package } from "./common" import { TextSpan } from "./text" @@ -13,7 +13,7 @@ export function isSyntax(value: any): value is Syntax; interface SyntaxBase { id: number; kind: SyntaxKind; - type?: Type; + type?: TypeRef; errors: Diagnostic[] parentNode: Syntax | null; span: TextSpan | null; diff --git a/src/treegen/index.ts b/src/treegen/index.ts index 43f8f1070..8a6cb7674 100644 --- a/src/treegen/index.ts +++ b/src/treegen/index.ts @@ -58,7 +58,7 @@ export function generateAST(decls: Declaration[]) { jsFile.write(`index: ${decl.index},\n`); jsFile.write(`parents: [`); for (const parentName of getParentChain(decl.name)) { - jsFile.write(`'${decl.name}', `) + jsFile.write(`'${parentName}', `) } jsFile.write(`'Syntax'],\n`); jsFile.write(`fields: new Map([\n`); @@ -359,7 +359,7 @@ export function generateAST(decls: Declaration[]) { return children; } - function *getParentChain(nodeName: string) { + function *getParentChain(nodeName: string): IterableIterator { const stack = [ nodeName ]; while (stack.length > 0) { const nodeDecl = getDeclarationNamed(stack.pop()!) as NodeDeclaration; diff --git a/src/types.ts b/src/types.ts index 3f9cb118e..61a911f5a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,20 +1,21 @@ -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"; +import { FastStringMap, assert, isPlainObject, some, prettyPrintTag, map, flatMap, filter, memoize, comparator, createTransparentProxy, TransparentProxy, every, FastMultiMap, getKeyTag } from "./util"; +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, } from "./ast"; +import { convertNodeToSymbolPath, ScopeType, SymbolResolver, SymbolInfo, SymbolPath } from "./resolver"; import { Value, Record } from "./evaluator"; -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 { getReturnStatementsInFunctionBody, getAllReturnStatementsInFunctionBody, getFullyQualifiedPathToNode } 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 } from "./diagnostics"; import { BOLT_MAX_FIELDS_TO_PRINT } from "./constants"; +import { emitNode } from "./emitter"; // 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`. +// This is a character that is used as a prefix in path names to distinguish +// a global symbol from a symbol coming from a specific source file. +const GLOBAL_SCOPE_MARKER = '@' + enum TypeKind { OpaqueType, AnyType, @@ -25,10 +26,12 @@ enum TypeKind { VariantType, UnionType, TupleType, + ReturnType, } export type Type = OpaqueType + | ReturnType | AnyType | NeverType | FunctionType @@ -38,17 +41,94 @@ export type Type | UnionType | PlainRecordFieldType +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) { + return a.source.id < b.source.id; + } + + throw new Error(`I did not expected to see the provided type combination.`) +} + abstract class TypeBase { public abstract kind: TypeKind; - /** - * Holds the node that created this type, if any. - */ - public node?: Syntax + public id = nextTypeId++; + + public node?: Syntax; - constructor(public sym?: SymbolInfo) { - + 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(); } public [prettyPrintTag](): string { @@ -67,6 +147,10 @@ export class OpaqueType extends TypeBase { public kind: TypeKind.OpaqueType = TypeKind.OpaqueType; + constructor(public name: string) { + super(); + } + } export class AnyType extends TypeBase { @@ -82,6 +166,7 @@ export class FunctionType extends TypeBase { public kind: TypeKind.FunctionType = TypeKind.FunctionType; constructor( + public source: Syntax, public paramTypes: Type[], public returnType: Type, ) { @@ -109,7 +194,7 @@ export class VariantType extends TypeBase { super(); } - public getOwnElementTypes(): IterableIterator { + public getVariantTypes(): IterableIterator { return this.elementTypes[Symbol.iterator](); } @@ -117,33 +202,35 @@ export class VariantType extends TypeBase { export class UnionType extends TypeBase { - private elements: Type[] = []; + public elementTypes: Type[] = []; public kind: TypeKind.UnionType = TypeKind.UnionType; constructor(elements: Iterable = []) { super(); - this.elements = [...elements]; + this.elementTypes = [...elements]; } public addElement(element: Type): void { - this.elements.push(element); + this.elementTypes.push(element); } public getElementTypes(): IterableIterator { - return this.elements[Symbol.iterator](); + return this.elementTypes[Symbol.iterator](); } } export type RecordFieldType = PlainRecordFieldType + | AnyType + | UnionType class PlainRecordFieldType extends TypeBase { public kind: TypeKind.PlainRecordFieldType = TypeKind.PlainRecordFieldType; - constructor(public type: Type) { + constructor(public name: string, public type: Type) { super(); } @@ -153,41 +240,40 @@ export class RecordType extends TypeBase { public kind: TypeKind.RecordType = TypeKind.RecordType; - private fieldTypes = new FastStringMap(); + private memberTypes: RecordFieldType[] = []; + private memberTypesByFieldName = new FastStringMap(); - constructor( - iterable?: Iterable<[string, RecordFieldType]>, - ) { + constructor(public source: Syntax | Type | number, iterable?: Iterable) { super(); if (iterable !== undefined) { - for (const [name, type] of iterable) { - this.fieldTypes.set(name, type); + for (const type of iterable) { + this.addMemberType(type); } } } - - public getFieldNames() { - return this.fieldTypes.keys(); + + public getRequiredFieldNames(): IterableIterator { + return this.memberTypesByFieldName.keys(); } - public addField(name: string, type: RecordFieldType): void { - this.fieldTypes.set(name, type); + public addMemberType(type: RecordFieldType): void { + this.memberTypes.push(type); + if (type instanceof PlainRecordFieldType) { + this.memberTypesByFieldName.set(type.name, type); + } } - public getFields() { - return this.fieldTypes[Symbol.iterator](); + public getMemberTypes(): IterableIterator { + return this.memberTypes[Symbol.iterator](); } - public hasField(name: string) { - return name in this.fieldTypes; - } - - public getFieldType(name: string) { - return this.fieldTypes.get(name); + public isFieldRequired(name: string): boolean { + return this.memberTypesByFieldName.has(name); } public clear(): void { - this.fieldTypes.clear(); + this.memberTypes = []; + this.memberTypesByFieldName.clear(); } } @@ -202,67 +288,24 @@ export class TupleType extends TypeBase { } -export enum ErrorType { - AssignmentError, - NotARecord, - TypeMismatch, - TooFewArguments, - TooManyArguments, - MayNotReturnValue, - MustReturnValue, +export class ReturnType extends TypeBase { + + public kind: TypeKind.ReturnType = TypeKind.ReturnType; + + constructor(public fnType: Type, public argumentTypes: Type[]) { + super(); + } + + public getArgumentTypes(): IterableIterator { + return this.argumentTypes[Symbol.iterator](); + } + } -interface NotARecordError { - type: ErrorType.NotARecord; - node: Syntax; - candidate: Syntax; -} - -interface AssignmentError { - type: ErrorType.AssignmentError; - left: Syntax; - right: Syntax; -} - -interface TypeMismatchError { - type: ErrorType.TypeMismatch; - left: Type; - right: Type; -} - -interface TooManyArgumentsError { - type: ErrorType.TooManyArguments; - caller: Syntax; - callee: Syntax; - index: number; -} - -interface TooFewArgumentsError { - type: ErrorType.TooFewArguments; - caller: Syntax; - callee: Syntax; - index: number; -} - -interface MustReturnValueError { - type: ErrorType.MustReturnValue; -} - -interface MayNotReturnValueError { - type: ErrorType.MayNotReturnValue; -} - -export type CompileError - = AssignmentError - | TypeMismatchError - | TooManyArgumentsError - | TooFewArgumentsError - | NotARecordError - | MustReturnValueError - | MayNotReturnValueError - -export interface FunctionSignature { - paramTypes: Type[]; +function isTypePotentiallyCallable(type: Type): boolean { + return type.kind === TypeKind.FunctionType + || type.kind === TypeKind.AnyType + || (type.kind === TypeKind.UnionType && type.elementTypes.some(isTypePotentiallyCallable)) } function* getAllPossibleElementTypes(type: Type): IterableIterator { @@ -283,568 +326,1140 @@ export function prettyPrintType(type: Type): string { let out = '' let hasElementType = false; for (const elementType of getAllPossibleElementTypes(type)) { + if (hasElementType) { + out += ' | ' + } hasElementType = true; - if (elementType.sym !== undefined) { - out += elementType.sym.name; - } else { - switch (elementType.kind) { - case TypeKind.AnyType: - { - out += 'any'; - break; + switch (elementType.kind) { + case TypeKind.PlainRecordFieldType: + out += '{ ' + elementType.name + ' } '; + break; + case TypeKind.ReturnType: + 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.`) } - 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]}`) + 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; + } + } + 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; + } + default: + throw new Error(`Could not pretty-print type ${TypeKind[elementType.kind]}`) } } if (!hasElementType) { - out += '()' + out += 'never' } return out; } +let nextRecordTypeId = 1; + +function introducesType(node: Syntax) { + return isBoltExpression(node) + || isBoltDeclarationLike(node) + || isBoltParameter(node) + || isBoltMatchArm(node) + || isBoltRecordField(node) + || isBoltRecordFieldPattern(node) + || isBoltPattern(node) + || isBoltStatement(node) + || isBoltTypeExpression(node) + || isJSExpression(node) + || isJSStatement(node) + || isJSPattern(node) + || isJSParameter(node) +} + export class TypeChecker { - private opaqueTypes = new FastStringMap(); + private opaqueTypeFallbacks = new FastStringMap(); - private anyType = new AnyType(); - - private syntaxType = new UnionType(); // FIXME + private dependencyGraph = new FastStringMap(); constructor(private resolver: SymbolResolver) { } - public getTypeOfValue(value: Value): Type { - if (typeof(value) === 'string') { - const sym = this.resolver.resolveGlobalSymbol('String', ScopeType.Type); - assert(sym !== null); - return new OpaqueType(sym!); - } else if (typeof(value) === 'bigint') { - const sym = this.resolver.resolveGlobalSymbol('int', ScopeType.Type); - assert(sym !== null); - return new OpaqueType(sym!); - } else if (typeof(value) === 'number') { - const sym = this.resolver.resolveGlobalSymbol('f64', ScopeType.Type); - assert(sym !== null); - return new OpaqueType(sym!); - } else if (value instanceof Record) { - const recordType = new RecordType() - for (const [fieldName, fieldValue] of value.getFields()) { - recordType.addField(name, new PlainRecordFieldType(this.getTypeOfValue(fieldValue))); + 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 { + if (typeof(value) === 'string') { + return this.getOpaqueType('String') + } else if (typeof(value) === 'bigint') { + return this.getOpaqueType('int'); + } else if (typeof(value) === 'number') { + return this.getOpaqueType('f64'); + } else if (value instanceof Record) { + const memberTypes = []; + for (const [fieldName, fieldValue] of value.getFields()) { + const recordFieldType = new PlainRecordFieldType(name, + createTransparentProxy(this.createTypeForValue(fieldValue))); + memberTypes.push(createTransparentProxy(recordFieldType)); + } + const recordType = new RecordType(nextRecordTypeId++, memberTypes); return recordType; } else { throw new Error(`Could not determine type of given value.`); } } - 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, + private diagnoseTypeMismatch(node: Syntax, a: Type, b: Type): void { + if (a.kind === TypeKind.ReturnType && b.kind === TypeKind.FunctionType) { + if (b.paramTypes.length > a.argumentTypes.length) { + let nested: Diagnostic[] = []; + //for (let i = b.paramTypes.length; i < a.argumentTypes.length; i++) { + // nested.push({ + // message: E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER, + // severity: 'error', + // node: b.getTypeAtParameterIndex(a.argumentTypes.length).node! + // }); + //} + node.errors.push({ + message: E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL, + severity: 'error', + node: a.node, + args: { + expected: b.paramTypes.length, + actual: a.argumentTypes.length, + }, + nested, + }); + } else if (b.paramTypes.length < a.argumentTypes.length) { + let nested: Diagnostic[] = []; + //for (let i = b.paramTypes.length; i < a.argumentTypes.length; i++) { + // nested.push({ + // message: E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER, + // severity: 'error', + // node: (a.node as BoltCallExpression).operands[i] + // }); + //} + node.errors.push({ + message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, + severity: 'error', + node: a.node, + args: { + expected: b.paramTypes.length, + actual: a.argumentTypes.length, + }, + nested, + }); + } else { + const paramCount = a.argumentTypes.length; + for (let i = 0; i < paramCount; i++) { + const argType = a.argumentTypes[i]; + const paramType = b.paramTypes[i]; + if (!this.areTypesSemanticallyEquivalent(argType, paramType)) { + node.errors.push({ + message: E_ARGUMENT_TYPE_NOT_ASSIGNABLE, 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! - }] + node: argType.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, - }) - } - } + //if (!this.areTypesSemanticallyEquivalent(paramA, paramB)) { + // yield { + // message: E_TYPE_MISMATCH, + // severity: 'error', + // node: a.node, + // args: { + // left: a, + // right: b, + // }, + // }; + //} } + } + } else { + node.errors.push({ + message: E_TYPE_MISMATCH, + severity: 'error', + args: { left: a, right: b }, + }) } } 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); - } - } - 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); + if (introducesType(node)) { + node.type = new AnyType; } } } - private createInitialTypeForExpression(node: Syntax): Type { + private *getParentsThatMightNeedUpdate(node: Syntax): IterableIterator { + while (true) { + const parentNode = node.parentNode!; + if (!introducesType(parentNode)) { + break; + } + yield parentNode; + node = parentNode; + } + } - if (node.type !== undefined) { - return node.type; + public solve(sourceFiles: IterableIterator): void { + + let queued: Syntax[] = []; + const nextQueue = new Set(); + + for (const sourceFile of sourceFiles) { + for (const node of sourceFile.preorder()) { + if (introducesType(node)) { + queued.push(node); + } + } } - let resultType; + while (true) { + + if (queued.length === 0) { + if (nextQueue.size > 0) { + queued = [...nextQueue]; + nextQueue.clear(); + continue; + } else { + break; + } + } + const node = queued.shift()!; + + const derivedType = this.deriveTypeUsingChildren(node); + const newType = this.simplifyType(derivedType); + + //if (newType !== derivedType) { + //derivedType.solved.nextType = newType; + //} + + const narrowedType = this.narrowTypeDownTo(node.type!.solved, newType); + + if (narrowedType === null) { + this.diagnoseTypeMismatch(node, node.type!, derivedType); + //node.errors.push({ + // message: E_TYPE_MISMATCH, + // severity: 'error', + // args: { left: node.type!, right: newType }, + // nested: [...this.diagnoseTypeMismatch(node.type!.solved, newType)], + //}); + } else { + narrowedType.node = node; + if (node.type.solved !== narrowedType) { + node.type.solved.nextType = narrowedType; + } + 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 { + if (!this.dependencyGraph.has(node.id.toString())) { + return; + } + const visited = new Set(); + 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); + } + } + } + } + + public isVoidType(type: Type): boolean { + return this.areTypesSemanticallyEquivalent(new TupleType, type); + } + + //private *getTypesForMember(origNode: Syntax, fieldName: string, type: Type): IterableIterator { + // 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 { + // 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.isFieldRequired(fieldName)) { + // const fieldType = type.getAllMemberTypesForField(fieldName); + // 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]}`) + // } + //} + + //private *getAllNodesForType(type: Type): IterableIterator { + // 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: + // } + //} + + /** + * 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]}`) + + } + + } + + 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; + } + + // 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]; + } + + return this.mergeDuplicateTypes(new UnionType(elementTypes)); + } + + private deriveTypeUsingChildren(node: Syntax): Type { switch (node.kind) { - case SyntaxKind.BoltMatchExpression: + case SyntaxKind.JSReturnStatement: { - const unionType = new UnionType(); - for (const matchArm of node.arms) { - unionType.addElement(this.createInitialTypeForExpression(matchArm.body)); + if (node.value === null) { + return this.getOpaqueType('undefined'); } - resultType = unionType; - break; + this.markNodeAsRequiringUpdate(node.value, node); + return node.value.type!.solved; } - case SyntaxKind.BoltRecordDeclaration: + case SyntaxKind.JSExpressionStatement: { - const recordSym = this.resolver.getSymbolForNode(node, ScopeType.Type); - assert(recordSym !== null); - if (this.opaqueTypes.has(recordSym!.id)) { - resultType = this.opaqueTypes.get(recordSym!.id); - } else { - const opaqueType = new OpaqueType(name, node); - this.opaqueTypes.set(recordSym!.id, opaqueType); - resultType = opaqueType; + if (node.expression === null) { + return new TupleType; } - break; + this.markNodeAsRequiringUpdate(node.expression, node); + return node.expression.type!.solved; + } + + case SyntaxKind.JSMemberExpression: + { + // TODO + return new AnyType; + } + + case SyntaxKind.JSLiteralExpression: + { + if (typeof(node.value) === 'string') { + return this.getOpaqueType('String'); + } else if (typeof(node.value) === 'number') { + return this.getOpaqueType('JSNum'); + } else { + throw new Error(`I did not know how to derive a type for JavaScript value ${node.value}`) + } + } + + 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); } case SyntaxKind.BoltFunctionExpression: { const paramTypes = node.params.map(param => { - if (param.typeExpr === null) { - return this.anyType; - } - return this.createInitialTypeForTypeExpression(param.typeExpr); + this.markNodeAsRequiringUpdate(param, node); + return param.type!.solved; }); - let returnType = node.returnType === null - ? this.anyType - : this.createInitialTypeForTypeExpression(node.returnType); - resultType = new FunctionType(paramTypes, returnType); - break; + 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); } - case SyntaxKind.BoltQuoteExpression: + case SyntaxKind.BoltRecordDeclaration: { - resultType = this.syntaxType; - break + if (node.members === null) { + const symbolPath = getFullyQualifiedPathToNode(node); + return new OpaqueType(symbolPath.encode()); + } 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) + } } - case SyntaxKind.BoltMemberExpression: - case SyntaxKind.BoltReferenceExpression: - case SyntaxKind.BoltCallExpression: - case SyntaxKind.BoltBlockExpression: + case SyntaxKind.BoltRecordField: { - resultType = this.anyType; - break; + const resultType = new PlainRecordFieldType(node.name.text, node.typeExpr.type!.solved); + this.markNodeAsRequiringUpdate(node.typeExpr, node) + return resultType; + } + + case SyntaxKind.BoltRecordPattern: + { + let memberTypes = [] + for (const member of node.fields) { + //assert(member.type instanceof PlainRecordFieldType); + memberTypes.push(member.type!.solved as RecordFieldType); + this.markNodeAsRequiringUpdate(member, node); + } + return new RecordType(node.name.type, memberTypes); + } + + 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); + } + } + + case SyntaxKind.BoltTypeAliasDeclaration: + { + // TODO + return new AnyType; + } + + case SyntaxKind.BoltImplDeclaration: + { + // TODO + return new AnyType; + } + + case SyntaxKind.BoltTraitDeclaration: + { + // TODO + return new AnyType; + } + + 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); + } + + case SyntaxKind.BoltExpressionPattern: + { + return this.deriveTypeUsingChildren(node.expression); + } + + case SyntaxKind.BoltBindPattern: + { + // TODO + return new AnyType; } case SyntaxKind.BoltConstantExpression: { - resultType = this.getTypeOfValue(node.value); - break; + return this.createTypeForValue(node.value); } - default: - throw new Error(`Could not create a type for node ${kindToString(node.kind)}.`); - - } - - node.type = resultType; - - return resultType; - - } - - private createInitialTypeForTypeExpression(node: BoltTypeExpression): Type { - switch (node.kind) { - case SyntaxKind.BoltLiftedTypeExpression: - return this.createInitialTypeForExpression(node.expression); - default: - throw new Error(`Could not create a type for node ${kindToString(node.kind)}.`); - } - } - - public isVoidType(type: Type): boolean { - return this.isTypeAssignableTo(new TupleType, type); - } - - private *getTypesForMember(origNode: Syntax, fieldName: string, type: Type): IterableIterator { - switch (type.kind) { - case TypeKind.UnionType: + case SyntaxKind.BoltFunctionTypeExpression: { - 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 { - 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; + 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 { - if (hardError) { - origNode.errors.push({ - message: E_TYPES_MISSING_MEMBER, - severity: 'error', - args: { - name: fieldName - } - }) + returnType = node.returnType.type!.solved; + this.markNodeAsRequiringUpdate(node.returnType!, node); + } + return new FunctionType(node, paramTypes, returnType); + } + + case SyntaxKind.BoltMacroCall: + { + // TODO + return new AnyType; + } + + case SyntaxKind.BoltQuoteExpression: + { + return this.getOpaqueType('Lang::Bolt::Node'); + } + + case SyntaxKind.BoltExpressionStatement: + { + return this.deriveTypeUsingChildren(node.expression); + } + + case SyntaxKind.BoltReturnStatement: + { + if (node.value === null) { + const tupleType = new TupleType(); + return tupleType + } + return node.value.type!.solved; + } + + case SyntaxKind.BoltBlockExpression: + { + let elementTypes = []; + if (node.elements !== null) { + for (const returnStmt of getReturnStatementsInFunctionBody(node.elements)) { + elementTypes.push(returnStmt.type!.solved) + this.markNodeAsRequiringUpdate(returnStmt, node); } } - break; + return new UnionType(elementTypes); } - default: - throw new Error(`I do not know how to find record member types for ${TypeKind[type.kind]}`) - } - } - private isTypeAlwaysCallable(type: Type) { - return type.kind === TypeKind.FunctionType; - } - - private *getAllNodesForType(type: Type): IterableIterator { - 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: - } - } - - private *findTypesInTypeExpression(node: BoltTypeExpression): IterableIterator { - switch (node.kind) { - case SyntaxKind.BoltTypeOfExpression: + case SyntaxKind.BoltMemberExpression: { - yield* this.findTypesInExpression(node.expression); - break; + 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; } - case SyntaxKind.BoltReferenceTypeExpression: + + case SyntaxKind.BoltCallExpression: + { + let operandTypes = [] + for (const operand of node.operands) { + operandTypes.push(operand.type!.solved); + this.markNodeAsRequiringUpdate(operand, node); + } + this.markNodeAsRequiringUpdate(node.operator, node); + return new ReturnType(node.operator.type!.solved, operandTypes); + } + + case SyntaxKind.BoltReferenceExpression: { const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable); - assert(scope !== null); - const symbolPath = getSymbolPathFromNode(node.name); + if (scope === null) { + return new AnyType; + } + const symbolPath = convertNodeToSymbolPath(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); - } + if (resolvedSym === null) { + return new AnyType; } - break; - } - default: - throw new Error(`Unexpected node type ${kindToString(node.kind)}`); - } - } - - private *findTypesInExpression(node: BoltExpression): IterableIterator { - - switch (node.kind) { - - case SyntaxKind.BoltMemberExpression: - { - for (const element of node.path) { - for (const memberType of this.getTypesForMember(element, element.text, node.expression.type!)) { - yield memberType; - } - } - break; + let elementTypes = []; + for (const decl of resolvedSym.declarations) { + elementTypes.push(decl.type!.solved); + this.markNodeAsRequiringUpdate(decl, node) } - - case SyntaxKind.BoltMatchExpression: - { - const unionType = new UnionType(); - for (const matchArm of node.arms) { - unionType.addElement(this.createInitialTypeForExpression(matchArm.body)); - } - yield unionType; - break; - } - - case SyntaxKind.BoltQuoteExpression: - { - break; - } - - case SyntaxKind.BoltCallExpression: - { - const nodeSignature = new FunctionType(node.operands.map(op => op.type!), this.anyType); - for (const callableType of this.findTypesInExpression(node.operator)) { - yield callableType; - } - break; - } - - case SyntaxKind.BoltReferenceExpression: - { - 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(isBoltDeclaration(decl)); - yield* this.findTypesInDeclaration(decl as BoltDeclaration); - } - } - break; - } - - default: - throw new Error(`Unexpected node type ${kindToString(node.kind)}`); - + return new UnionType(elementTypes); } - } - private *findTypesInTypeDeclaration(node: BoltTypeDeclaration): IterableIterator { - switch (node.kind) { - case SyntaxKind.BoltTypeAliasDeclaration: + case SyntaxKind.BoltFunctionDeclaration: { - yield* this.findTypesInTypeExpression(node.typeExpr); - break; + 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)) { + returnTypes.push(returnStmt.type!.solved); + this.markNodeAsRequiringUpdate(returnStmt, node); + } + } + 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)); } + + case SyntaxKind.BoltReferenceTypeExpression: + { + 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); + assert(scope !== null); + 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) { + this.markNodeAsRequiringUpdate(decl, node); + elementTypes.push(decl.type!.solved); + } + return new UnionType(elementTypes); + } + default: throw new Error(`Unexpected node type ${kindToString(node.kind)}`); + } } - private *findTypesInDeclaration(node: BoltDeclaration) { - switch (node.kind) { - case SyntaxKind.BoltVariableDeclaration: - if (node.typeExpr !== null) { - yield* this.findTypesInTypeExpression(node.typeExpr); - } - if (node.value !== null) { - yield* this.findTypesInExpression(node.value); - } - break; - case SyntaxKind.BoltFunctionDeclaration: - { - yield this.getFunctionReturnType(node); - break; - } - default: - throw new Error(`Could not find callable expressions in declaration ${kindToString(node.kind)}`) - } + //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; } - - 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 { - 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; - } + if (a.kind === TypeKind.NeverType) { return false; } + if (a.kind === TypeKind.AnyType || b.kind === TypeKind.AnyType) { + return true; + } + + // 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. + + 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)); + } + + // 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) { + const resolvedType = this.resolveReturnType(a); + 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]}`) + } + + private resolveReturnType(type: ReturnType): Type { + + let resultTypes = []; + + let hasAnyType = false; + + for (const elementType of getAllPossibleElementTypes(type.fnType)) { + + switch (elementType.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. + 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; + break; + + case TypeKind.FunctionType: + + // Early check to see if the signature of the function call matches that of the function declaration. + if (type.argumentTypes.length !== elementType.paramTypes.length) { + continue; + } + + // Since we are assured by now that `type.argumentTypes` and `elementType.paramTypes` have equal length, + // we may pick the length of any of these two as the index bound in the next loop. + const paramCount = type.argumentTypes.length; + + let matchFailed = false; + + // Iterate pairwise over the argument types and parameter types, checking whether they are assignable to each other. + // In the current version of the checker, this amounts to checking if they are semantically equal. + for (let i = 0; i < paramCount; i++) { + const argType = type.argumentTypes[i].solved; + const paramType = elementType.paramTypes[i].solved; + if (!this.areTypesSemanticallyEquivalent(argType, paramType)) { + //argType.node!.errors.push({ + //message: E_TYPE_MISMATCH, + //severity: 'error', + //args: { left: paramType, right: argType } + //}) + matchFailed = true; + break; + } + } + + // If the argument types and parameter types didn't fail to match, + // the function type is eligable to be 'called' by the given ReturnType. + if (!matchFailed) { + resultTypes.push(elementType.returnType.solved); + } + break; + + default: + throw new Error(`Resolving the given type will not work.`) + + } + + } + + if (resultTypes.length === 0) { + if (hasAnyType) { + return new AnyType; + } else { + return new NeverType; + } + } + + // Small optimisation to make debugging easier. + if (resultTypes.length === 1) { + return resultTypes[0]; + } + + return new UnionType(resultTypes); + } + + private mergeDuplicateTypes(type: Type): Type { + + const resultTypes = [...getAllPossibleElementTypes(type)]; + + 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])) { + break; + } + } + resultTypes.splice(i, j-i-1); + resultTypes.splice(i+1, j-i-1); + } + + if (resultTypes.length === 0) { + return new NeverType; + } + + if (resultTypes.length === 1) { + return resultTypes[0]; + } + + return new UnionType(resultTypes); + } + + 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 ]; + + //this.removeDuplicateTypes(elementTypes); + + while (stack.length > 0) { + + const elementType = stack.pop()!; + + switch (elementType.kind) { + + case TypeKind.UnionType: + for (const elementType2 of getAllPossibleElementTypes(elementType)) { + stack.push(elementType2); + } + 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]}`) + + } + + } + + if (resultTypes.length === 0) { + if (hasAnyType) { + return new AnyType; + } else { + return new NeverType; + } + } + + // Small optimisation to make debugging easier. + if (resultTypes.length === 1) { + return resultTypes[0] + } + + return this.mergeDuplicateTypes(new UnionType(resultTypes)); + } } diff --git a/src/util.ts b/src/util.ts index cd0195a73..b01e4fa5f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -6,6 +6,7 @@ import * as os from "os" import moment from "moment" import chalk from "chalk" import { LOG_DATETIME_FORMAT } from "./constants" +import { E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER } from "./diagnostics" export function isPowerOf(x: number, n: number):boolean { const a = Math.log(x) / Math.log(n); @@ -39,6 +40,53 @@ export function every(iterator: Iterator, pred: (value: T) => boolean): bo return true; } +export function* filter(iterator: Iterator, pred: (value: T) => boolean): IterableIterator { + while (true) { + const { value, done } = iterator.next(); + if (done) { + break; + } + if (pred(value)) { + yield value; + } + } +} + +export function* map(iterator: Iterator, func: (value: T) => R): IterableIterator { + while (true) { + const { value, done } = iterator.next(); + if (done) { + break; + } + yield func(value); + } +} + +export function* flatMap(iterator: Iterator, func: (value: T) => IterableIterator): IterableIterator { + while (true) { + const { value, done } = iterator.next(); + if (done) { + break; + } + for (const element of func(value)) { + yield element; + } + } + +} + +export function comparator(pred: (a: T, b: T) => boolean): (a: T, b: T) => number { + return function (a, b) { + if (pred(a, b)) { + return 1; + } else if (pred(b, a)) { + return -1; + } else { + return 0; + } + } +} + export function assert(test: boolean): void { if (!test) { throw new Error(`Invariant violation: an internal sanity check failed.`); @@ -131,6 +179,102 @@ export function prettyPrint(value: any): string { return value.toString(); } +export type TransparentProxy = T & { updateHandle(value: T): void } + +export function createTransparentProxy(value: T): TransparentProxy { + const handlerObject = { + __HANDLE: value, + __IS_HANDLE: true, + updateHandle(newValue: T) { + if (newValue.__IS_HANDLE) { + newValue = newValue.__HANDLE; + } + value = newValue; + handlerObject.__HANDLE = newValue; + } + }; + return new Proxy({}, { + getPrototypeOf(target: T): object | null { + return Reflect.getPrototypeOf(value); + }, + setPrototypeOf(target: T, v: any): boolean { + return Reflect.setPrototypeOf(value, v); + }, + isExtensible(target: T): boolean { + return Reflect.isExtensible(value); + }, + preventExtensions(target: T): boolean { + return Reflect.preventExtensions(value); + }, + getOwnPropertyDescriptor(target: T, p: PropertyKey): PropertyDescriptor | undefined { + return Reflect.getOwnPropertyDescriptor(value, p); + }, + has(target: T, p: PropertyKey): boolean { + return Reflect.has(value, p); + }, + get(target: T, p: PropertyKey, receiver: any): any { + if (hasOwnProperty(handlerObject, p)) { + return Reflect.get(handlerObject, p); + } + return Reflect.get(value, p, receiver) + }, + set(target: T, p: PropertyKey, value: any, receiver: any): boolean { + return Reflect.set(value, p, value); + }, + deleteProperty(target: T, p: PropertyKey): boolean { + return Reflect.deleteProperty(value, p); + }, + defineProperty(target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean { + return Reflect.defineProperty(value, p, attributes); + }, + enumerate(target: T): PropertyKey[] { + return [...Reflect.enumerate(value)]; + }, + ownKeys(target: T): PropertyKey[] { + return Reflect.ownKeys(value); + }, + apply(target: T, thisArg: any, argArray?: any): any { + return Reflect.apply(value as any, thisArg, argArray); + }, + construct(target: T, argArray: any, newTarget?: any): object { + return Reflect.construct(value as any, argArray, newTarget); + } + }); +} + +export const getKeyTag = Symbol('get key of object'); + +function getKey(value: any): string { + if (typeof(value) === 'string') { + return value; + } else if (typeof(value) === 'number') { + return value.toString(); + } else if (isObjectLike(value) && hasOwnProperty(value, getKeyTag)) { + return value[getKeyTag](); + } else { + throw new Error(`Could not calculate a key for ${value}`); + } +} + +export class FastMultiMap { + + private mapping = Object.create(null); + + public clear(): void { + this.mapping = Object.create(null); + } + + public add(key: K, value: V) { + const keyStr = getKey(key); + if (keyStr in this.mapping) { + this.mapping[keyStr].push(keyStr); + } else { + this.mapping[keyStr] = [ value ] + } + } + +} + export class FastStringMap { private mapping = Object.create(null);