Extend the type checker to support function calls

This commit is contained in:
Sam Vervaeck 2020-05-23 22:23:17 +02:00
parent 32a9d288de
commit 2a873484c4
5 changed files with 178 additions and 10 deletions

View file

@ -80,7 +80,7 @@ node BoltSourceFile {
}
node BoltQualName {
modulePath: Vec<BoltIdentifier>,
modulePath: Option<Vec<BoltIdentifier>>,
name: BoltSymbol,
}
@ -166,7 +166,7 @@ node BoltCaseExpression > BoltExpression {
}
node BoltBlockExpression > BoltExpression {
statements: Vec<BoltStatement>,
elements: Vec<BoltFunctionBodyElement>,
}
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<BoltImportSymbol>,
}
node BoltTraitDeclaration > BoltDeclaration {
node BoltTraitDeclaration > BoltDeclaration, BoltTypeDeclaration {
modifiers: BoltDeclarationModifiers,
name: BoltIdentifier,
typeParams: Option<Vec<BoltTypeParameter>>,
@ -253,7 +255,7 @@ node BoltImplDeclaration > BoltDeclaration {
elements: Vec<BoltDeclaration>,
}
node BoltTypeAliasDeclaration > BoltDeclaration {
node BoltTypeAliasDeclaration > BoltDeclaration, BoltTypeDeclaration {
modifiers: BoltDeclarationModifiers,
name: BoltIdentifier,
typeParams: Option<Vec<BoltTypeParameter>>,
@ -267,9 +269,9 @@ node BoltRecordField > BoltRecordMember {
type: BoltTypeExpression,
}
node BoltRecordDeclaration > BoltDeclaration {
node BoltRecordDeclaration > BoltDeclaration, BoltTypeDeclaration {
modifiers: BoltDeclarationModifiers,
name: BoltQualName,
name: BoltIdentifier,
typeParms: Option<Vec<BoltTypeParameter>>,
members: Option<Vec<BoltRecordMember>>,
}

View file

@ -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:
{

View file

@ -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;

View file

@ -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;
}

View file

@ -468,8 +468,9 @@ export function format(message: string, data: MapLike<FormatArg>) {
if (ch === '}') {
out += data[name]!.toString();
reset();
} else {
name += ch;
}
name += ch
} else {
if (ch === '{') {
insideParam = true;