Extend the type checker to support function calls
This commit is contained in:
parent
32a9d288de
commit
2a873484c4
5 changed files with 178 additions and 10 deletions
14
spec/ast.txt
14
spec/ast.txt
|
@ -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>>,
|
||||
}
|
||||
|
|
164
src/checker.ts
164
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:
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue