diff --git a/spec/ast.txt b/spec/ast.txt index 117f96649..5f497a3eb 100644 --- a/spec/ast.txt +++ b/spec/ast.txt @@ -80,7 +80,7 @@ node BoltSourceFile { } node BoltQualName { - modulePath: Vec, + modulePath: Option>, name: BoltSymbol, } @@ -166,7 +166,7 @@ node BoltCaseExpression > BoltExpression { } node BoltBlockExpression > BoltExpression { - statements: Vec, + elements: Vec, } node BoltConstantExpression > BoltExpression { @@ -196,6 +196,8 @@ node BoltParameter { node BoltDeclaration > BoltSourceElement; +node BoltTypeDeclaration > BoltSourceElement; + enum BoltDeclarationModifiers { Mutable = 0x1, Public = 0x2, @@ -238,7 +240,7 @@ node BoltImportDeclaration > BoltDeclaration { symbols: Vec, } -node BoltTraitDeclaration > BoltDeclaration { +node BoltTraitDeclaration > BoltDeclaration, BoltTypeDeclaration { modifiers: BoltDeclarationModifiers, name: BoltIdentifier, typeParams: Option>, @@ -253,7 +255,7 @@ node BoltImplDeclaration > BoltDeclaration { elements: Vec, } -node BoltTypeAliasDeclaration > BoltDeclaration { +node BoltTypeAliasDeclaration > BoltDeclaration, BoltTypeDeclaration { modifiers: BoltDeclarationModifiers, name: BoltIdentifier, typeParams: Option>, @@ -267,9 +269,9 @@ node BoltRecordField > BoltRecordMember { type: BoltTypeExpression, } -node BoltRecordDeclaration > BoltDeclaration { +node BoltRecordDeclaration > BoltDeclaration, BoltTypeDeclaration { modifiers: BoltDeclarationModifiers, - name: BoltQualName, + name: BoltIdentifier, typeParms: Option>, members: Option>, } diff --git a/src/checker.ts b/src/checker.ts index 5028c534d..9fc55d4ca 100644 --- a/src/checker.ts +++ b/src/checker.ts @@ -23,9 +23,18 @@ * Note that the `pub`-keyword is not present on `MyType1`. */ -import {Syntax, SyntaxKind, BoltReferenceExpression, BoltDeclaration, BoltSourceFile, BoltSyntax, BoltReferenceTypeExpression, BoltTypeDeclaration} from "./ast"; -import {FastStringMap} from "./util"; -import {DiagnosticPrinter, E_TYPES_NOT_ASSIGNABLE, E_TYPE_DECLARATION_NOT_FOUND, E_DECLARATION_NOT_FOUND} from "./diagnostics"; +import {Syntax, SyntaxKind, BoltReferenceExpression, BoltDeclaration, BoltSourceFile, BoltSyntax, BoltReferenceTypeExpression, BoltTypeDeclaration, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, createBoltReferenceTypeExpression, createBoltIdentifier} from "./ast"; +import {FastStringMap, memoize, assert} from "./util"; +import { + DiagnosticPrinter, + E_TYPES_NOT_ASSIGNABLE, + E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL, + E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, + E_TYPE_DECLARATION_NOT_FOUND, + E_DECLARATION_NOT_FOUND, + E_INVALID_ARGUMENTS +} from "./diagnostics"; +import {createAnyType, isOpaqueType, createOpaqueType, Type} from "./types"; interface SymbolInfo { declarations: BoltDeclaration[]; @@ -93,6 +102,153 @@ export class TypeChecker { } } + const callExps = node.findAllChildrenOfKind(SyntaxKind.BoltCallExpression); + + for (const callExp of callExps) { + + const fnDecls = this.getAllFunctionsInExpression(callExp.operator); + + for (const fnDecl of fnDecls) { + + if (fnDecl.params.length > callExp.operands.length) { + this.diagnostics.add({ + message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, + args: { expected: fnDecl.params.length, actual: callExp.operands.length }, + severity: 'error', + node: callExp, + }) + } + + if (fnDecl.params.length < callExp.operands.length) { + this.diagnostics.add({ + message: E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL, + args: { expected: fnDecl.params.length, actual: callExp.operands.length }, + severity: 'error', + node: callExp, + }) + } + + const paramCount = fnDecl.params.length; + for (let i = 0; i < paramCount; i++) { + const arg = callExp.operands[i]; + const param = fnDecl.params[i]; + let argType = this.getTypeOfNode(arg); + let paramType = this.getTypeOfNode(param); + if (!this.isTypeAssignableTo(argType, paramType)) { + this.diagnostics.add({ + message: E_INVALID_ARGUMENTS, + severity: 'error', + args: { name: fnDecl.name.text }, + node: arg, + }); + } + + } + + } + + } + + } + + private resolveType(name: string, node: BoltSyntax): Type | null { + const sym = this.findSymbolInTypeScopeOf(name, this.getTypeScopeSurroundingNode(node)) + if (sym === null) { + return null; + } + return this.getTypeOfNode(sym.declarations[0]); + } + + @memoize + private getTypeOfNode(node: BoltSyntax): Type { + switch (node.kind) { + case SyntaxKind.BoltReferenceTypeExpression: + { + const referenced = this.resolveTypeReferenceExpression(node); + if (referenced === null) { + return createAnyType(); + } + return this.getTypeOfNode(referenced); + } + case SyntaxKind.BoltRecordDeclaration: + { + if (node.members === null) { + return createOpaqueType(); + } + // TODO + throw new Error(`Not yet implemented.`); + } + case SyntaxKind.BoltParameter: + { + let type: Type = createAnyType(); + if (node.type !== null) { + type = this.getTypeOfNode(node.type); + } + return type; + } + case SyntaxKind.BoltConstantExpression: + { + let type; + if (typeof node.value === 'string') { + type = this.resolveType('String', node)!; + } else if (typeof node.value === 'boolean') { + type = this.resolveType('bool', node)!; + } else if (typeof node.value === 'number') { + type = this.resolveType('int32', node)!; + } else { + throw new Error(`Could not derive type of constant expression.`); + } + assert(type !== null); + return type; + } + default: + throw new Error(`Could not derive type of node ${kindToString(node.kind)}.`); + } + } + + private isTypeAssignableTo(left: Type, right: Type): boolean { + if (isOpaqueType(left) && isOpaqueType(right)) { + return left === right; + } + return false; + } + + private getAllFunctionsInExpression(node: BoltExpression): BoltFunctionDeclaration[] { + + const self = this; + + const results: BoltFunctionDeclaration[] = []; + visitExpression(node); + return results; + + function visitExpression(node: BoltExpression) { + switch (node.kind) { + case SyntaxKind.BoltReferenceExpression: + const resolved = self.resolveReferenceExpression(node); + if (resolved !== null) { + visitFunctionBodyElement(resolved); + } + break; + default: + throw new Error(`Unexpected node type ${kindToString(node.kind)}`); + } + } + + function visitFunctionBodyElement(node: BoltFunctionBodyElement) { + switch (node.kind) { + case SyntaxKind.BoltFunctionDeclaration: + results.push(node); + break; + case SyntaxKind.BoltVariableDeclaration: + if (node.value !== null) { + visitExpression(node.value); + } + break; + default: + throw new Error(`Unexpected node type ${kindToString(node.kind)}`); + } + } + } public registerSourceFile(node: BoltSourceFile): void { @@ -105,10 +261,12 @@ export class TypeChecker { case SyntaxKind.BoltSourceFile: case SyntaxKind.BoltModule: + { for (const element of node.elements) { this.addAllSymbolsInNode(element); } break; + } case SyntaxKind.BoltFunctionDeclaration: { diff --git a/src/diagnostics.ts b/src/diagnostics.ts index f982e2b17..6c0446d8c 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -6,6 +6,9 @@ import {format, MapLike, FormatArg} from "./util"; 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_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_INVALID_ARGUMENTS = "Invalid arguments passed to function '{name}'." export interface Diagnostic { message: string; diff --git a/src/types.ts b/src/types.ts index 1c4ef0019..2a9a584a8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,6 +32,10 @@ export function isOpaqueType(value: any): value is OpaqueType { return value.kind === TypeKind.OpaqueType; } +export function createOpaqueType(): OpaqueType { + return new OpaqueType(); +} + export class AnyType extends TypeBase { kind: TypeKind.AnyType = TypeKind.AnyType; } diff --git a/src/util.ts b/src/util.ts index 0dfd2059d..939c278c9 100644 --- a/src/util.ts +++ b/src/util.ts @@ -468,8 +468,9 @@ export function format(message: string, data: MapLike) { if (ch === '}') { out += data[name]!.toString(); reset(); + } else { + name += ch; } - name += ch } else { if (ch === '{') { insideParam = true;