impl method call type checking

This commit is contained in:
collin 2022-06-08 14:44:08 -07:00
parent f0a1573058
commit 5dba9cf07f
8 changed files with 153 additions and 36 deletions

View File

@ -28,7 +28,7 @@ pub struct Function {
pub identifier: Identifier,
/// The function's parameters.
pub input: Vec<FunctionInput>,
/// The function return type, if explicitly specified, or `()` if not.
/// The function's required return type.
pub output: Type,
/// Any mapping to the core library.
/// Always `None` when initially parsed.

View File

@ -100,7 +100,11 @@ pub trait ExpressionVisitorDirector<'a>: VisitorDirector<'a> {
None
}
fn visit_method(&mut self, input: &'a MethodCallExpression, additional: &Self::AdditionalInput) -> Option<Self::Output> {
fn visit_method(
&mut self,
input: &'a MethodCallExpression,
additional: &Self::AdditionalInput,
) -> Option<Self::Output> {
self.visit_expression(&input.receiver, additional);
if let VisitResult::VisitChildren = self.visitor_ref().visit_method(input) {
input.arguments.iter().for_each(|expr| {

View File

@ -25,6 +25,9 @@ pub use account::*;
mod algorithms;
pub use algorithms::*;
mod methods;
pub use methods::*;
pub trait Types {
fn types() -> IndexSet<Symbol>;
}

View File

@ -0,0 +1,83 @@
// Copyright (C) 2019-2021 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
//! This module defines AVM opcodes as associated method calls on Leo types.
use indexmap::IndexSet;
use leo_span::Symbol;
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct Method {
// todo @collinc97: this struct contains type checking info and does not consider opcodes.
/// The name of the method as an interned string.
pub name: Symbol,
/// This count excludes the receiver e.g, `1u8.add(2u8)` contains 1 argument.
pub num_arguments: usize,
/// `true` if the receiver type == arguments type == return type
pub types_are_equal: bool,
}
impl Method {
pub(crate) fn new(name: &str, num_arguments: usize, types_are_equal: bool) -> Self {
Self {
name: Symbol::intern(name),
num_arguments,
types_are_equal,
}
}
}
use indexmap::{indexmap, indexset, IndexMap};
use leo_ast::Type;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct MethodTable {
/// Maps method name => method definition.
method_definitions: IndexMap<Symbol, Method>,
/// Supported methods for the field type.
field_methods: IndexSet<Symbol>,
}
impl MethodTable {
pub fn load() -> Self {
// Initialize method definitions.
let add = Method::new("add", 1, true);
// Initialize associated methods for each type.
Self {
field_methods: indexset! {
add.name
},
method_definitions: indexmap! {
add.name => add
},
}
}
/// Returns the method corresponding to the given symbol.
/// Used during type checking.
pub fn lookup_method(&self, symbol: &Symbol) -> Option<&Method> {
self.method_definitions.get(symbol)
}
/// Returns `true` if the associated method exists for the given type.
/// Sometimes used during type checking if the receiver type is known.
pub fn type_method_is_supported(&self, type_: &Type, method: &Symbol) -> bool {
match type_ {
Type::Field => self.field_methods.contains(method),
_ => false,
}
}
}

View File

@ -239,13 +239,11 @@ impl ParserContext<'_> {
method,
arguments,
});
println!("expr {}", expr);
continue;
}
}
let curr = &self.token;
return Err(ParserError::unexpected_str(&curr.token, "int or ident", curr.span).into());
}
if !self.check(&Token::LeftParen) {
break;

View File

@ -405,37 +405,54 @@ impl<'a> ExpressionVisitorDirector<'a> for Director<'a> {
}
}
fn visit_method(&mut self, input: &'a MethodCallExpression, expected: &Self::AdditionalInput) -> Option<Self::Output> {
None
// input.receiver.
//
// if let Some(func) = self.visitor.symbol_table.clone().lookup_fn(&input.method.name) {
// let ret = self.visitor.assert_type(func.output, expected, func.span());
//
// if func.input.len() != input.arguments.len() {
// self.visitor.handler.emit_err(
// TypeCheckerError::incorrect_num_args_to_call(
// func.input.len(),
// input.arguments.len(),
// input.span(),
// )
// .into(),
// );
// }
//
// func.input
// .iter()
// .zip(input.arguments.iter())
// .for_each(|(expected, argument)| {
// self.visit_expression(argument, &Some(expected.get_variable().type_));
// });
//
// Some(ret)
// } else {
// self.visitor
// .handler
// .emit_err(TypeCheckerError::unknown_sym("method", &ident.name, ident.span()).into());
// None
// }
fn visit_method(
&mut self,
input: &'a MethodCallExpression,
expected: &Self::AdditionalInput,
) -> Option<Self::Output> {
// Check if the method exists.
if let Some(method) = self.visitor.method_table.clone().lookup_method(&input.method.name) {
// Check number of arguments is correct.
if method.num_arguments != input.arguments.len() {
self.visitor.handler.emit_err(
TypeCheckerError::incorrect_num_args_to_call(
method.num_arguments,
input.arguments.len(),
input.span(),
)
.into(),
);
}
// Handle case where receiver type == argument type == return type.
if method.types_are_equal {
// Check if the method is supported for the type.
if let Some(type_) = expected {
if !self.visitor.method_table.type_method_is_supported(type_, &method.name) {
self.visitor.handler.emit_err(
TypeCheckerError::type_method_not_supported(type_, &input.method.name, input.span()).into(),
);
}
}
// Check if receiver is correct type for method.
let ret = self.visit_expression(&input.receiver, expected);
// Check if arguments are correct types for method.
input.arguments.iter().for_each(|arg| {
self.visit_expression(arg, expected);
});
ret
} else {
// Handle case where the method types are not known yet.
*expected
}
} else {
self.visitor
.handler
.emit_err(TypeCheckerError::unknown_sym("method", &input.method.name, input.span()).into());
None
}
}
}

View File

@ -30,6 +30,7 @@ pub struct TypeChecker<'a> {
pub(crate) negate: bool,
pub(crate) account_types: IndexSet<Symbol>,
pub(crate) algorithms_types: IndexSet<Symbol>,
pub(crate) method_table: MethodTable,
}
const INT_TYPES: [Type; 10] = [
@ -82,6 +83,7 @@ impl<'a> TypeChecker<'a> {
negate: false,
account_types: Account::types(),
algorithms_types: Algorithms::types(),
method_table: MethodTable::load(),
}
}

View File

@ -53,6 +53,16 @@ create_messages!(
help: None,
}
/// The method name is known but not supported for the given type.
@formatted
type_method_not_supported {
args: (type_: impl Display, method: impl Display),
msg: format!(
"Type `{type_}` does not support associated method `{method}`",
),
help: None,
}
/// For when the user tries to return a unknown variable.
@formatted
unknown_sym {