diff --git a/compiler/ast/src/functions/function.rs b/compiler/ast/src/functions/function.rs index 8b1cb057d6..51cae07d82 100644 --- a/compiler/ast/src/functions/function.rs +++ b/compiler/ast/src/functions/function.rs @@ -28,7 +28,7 @@ pub struct Function { pub identifier: Identifier, /// The function's parameters. pub input: Vec, - /// 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. diff --git a/compiler/ast/src/passes/visitor_director.rs b/compiler/ast/src/passes/visitor_director.rs index d8b4a8641b..9d3610274d 100644 --- a/compiler/ast/src/passes/visitor_director.rs +++ b/compiler/ast/src/passes/visitor_director.rs @@ -100,7 +100,11 @@ pub trait ExpressionVisitorDirector<'a>: VisitorDirector<'a> { None } - fn visit_method(&mut self, input: &'a MethodCallExpression, additional: &Self::AdditionalInput) -> Option { + fn visit_method( + &mut self, + input: &'a MethodCallExpression, + additional: &Self::AdditionalInput, + ) -> Option { self.visit_expression(&input.receiver, additional); if let VisitResult::VisitChildren = self.visitor_ref().visit_method(input) { input.arguments.iter().for_each(|expr| { diff --git a/compiler/core/src/lib.rs b/compiler/core/src/lib.rs index 6ebfa0d448..f80d1d4bc7 100644 --- a/compiler/core/src/lib.rs +++ b/compiler/core/src/lib.rs @@ -25,6 +25,9 @@ pub use account::*; mod algorithms; pub use algorithms::*; +mod methods; +pub use methods::*; + pub trait Types { fn types() -> IndexSet; } diff --git a/compiler/core/src/methods/mod.rs b/compiler/core/src/methods/mod.rs new file mode 100644 index 0000000000..97742294fb --- /dev/null +++ b/compiler/core/src/methods/mod.rs @@ -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 . + +//! 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, + /// Supported methods for the field type. + field_methods: IndexSet, +} + +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, + } + } +} diff --git a/compiler/parser/src/parser/expression.rs b/compiler/parser/src/parser/expression.rs index bb62bec9be..cbb7a8e7ed 100644 --- a/compiler/parser/src/parser/expression.rs +++ b/compiler/parser/src/parser/expression.rs @@ -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; diff --git a/compiler/passes/src/type_checker/check_expressions.rs b/compiler/passes/src/type_checker/check_expressions.rs index 6f954bc8ef..34b06ce30d 100644 --- a/compiler/passes/src/type_checker/check_expressions.rs +++ b/compiler/passes/src/type_checker/check_expressions.rs @@ -405,37 +405,54 @@ impl<'a> ExpressionVisitorDirector<'a> for Director<'a> { } } - fn visit_method(&mut self, input: &'a MethodCallExpression, expected: &Self::AdditionalInput) -> Option { - 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 { + // 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 + } } } diff --git a/compiler/passes/src/type_checker/checker.rs b/compiler/passes/src/type_checker/checker.rs index 274041724f..0c4a166601 100644 --- a/compiler/passes/src/type_checker/checker.rs +++ b/compiler/passes/src/type_checker/checker.rs @@ -30,6 +30,7 @@ pub struct TypeChecker<'a> { pub(crate) negate: bool, pub(crate) account_types: IndexSet, pub(crate) algorithms_types: IndexSet, + 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(), } } diff --git a/leo/errors/src/errors/type_checker/type_checker_error.rs b/leo/errors/src/errors/type_checker/type_checker_error.rs index 8f11c90016..9a6497237a 100644 --- a/leo/errors/src/errors/type_checker/type_checker_error.rs +++ b/leo/errors/src/errors/type_checker/type_checker_error.rs @@ -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 {