diff --git a/Cargo.lock b/Cargo.lock index 35c1b808e4..d7563d136d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1073,9 +1073,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.2" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg", "hashbrown 0.11.2", diff --git a/compiler/passes/src/type_checker/check_expressions.rs b/compiler/passes/src/type_checker/check_expressions.rs index ff921d4b24..097aaf85b6 100644 --- a/compiler/passes/src/type_checker/check_expressions.rs +++ b/compiler/passes/src/type_checker/check_expressions.rs @@ -226,11 +226,20 @@ impl<'a> ExpressionVisitorDirector<'a> for Director<'a> { self.visitor.assert_type(Type::Group, expected, input.right.span()); Some(Type::Group) } - _ => { - self.visitor.assert_type(t1.unwrap(), expected, input.left.span()); - self.visitor.assert_type(t2.unwrap(), expected, input.right.span()); - return_incorrect_type(t1, t2, expected) + (Some(t1), Some(t2)) => { + self.visitor.assert_type(*t1, expected, input.left.span()); + self.visitor.assert_type(*t2, expected, input.right.span()); + return_incorrect_type(Some(*t1), Some(*t2), expected) } + (Some(type_), None) => { + self.visitor.assert_type(*type_, expected, input.left.span()); + None + } + (None, Some(type_)) => { + self.visitor.assert_type(*type_, expected, input.right.span()); + None + } + (None, None) => None, } } BinaryOperation::Div => { @@ -272,7 +281,7 @@ impl<'a> ExpressionVisitorDirector<'a> for Director<'a> { ); } // The base is some type thats not an int or field. - (Some(t), _) => { + (Some(t), _) if !matches!(t, Type::IntegerType(_) | Type::Field) => { self.visitor .handler .emit_err(TypeCheckerError::incorrect_pow_base_type(t, input.left.span()).into()); diff --git a/compiler/passes/src/type_checker/check_file.rs b/compiler/passes/src/type_checker/check_file.rs index 3258fc28f7..72d64e6f8e 100644 --- a/compiler/passes/src/type_checker/check_file.rs +++ b/compiler/passes/src/type_checker/check_file.rs @@ -15,33 +15,42 @@ // along with the Leo library. If not, see . use leo_ast::*; +use leo_errors::TypeCheckerError; use crate::{Declaration, TypeChecker, VariableSymbol}; use super::director::Director; -impl<'a> ProgramVisitor<'a> for TypeChecker<'a> { - fn visit_function(&mut self, input: &'a Function) -> VisitResult { - self.symbol_table.clear_variables(); - self.parent = Some(input.name()); - input.input.iter().for_each(|i| { - let input_var = i.get_variable(); - self.validate_ident_type(&Some(input_var.type_)); +impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {} - if let Err(err) = self.symbol_table.insert_variable( - input_var.identifier.name, - VariableSymbol { - type_: &input_var.type_, - span: input_var.identifier.span(), - declaration: Declaration::Input(input_var.mode()), - }, - ) { - self.handler.emit_err(err); +impl<'a> ProgramVisitorDirector<'a> for Director<'a> { + fn visit_function(&mut self, input: &'a Function) { + if let VisitResult::VisitChildren = self.visitor_ref().visit_function(input) { + self.visitor.has_return = false; + self.visitor.symbol_table.clear_variables(); + self.visitor.parent = Some(input.name()); + input.input.iter().for_each(|i| { + let input_var = i.get_variable(); + self.visitor.validate_ident_type(&Some(input_var.type_)); + + if let Err(err) = self.visitor.symbol_table.insert_variable( + input_var.identifier.name, + VariableSymbol { + type_: &input_var.type_, + span: input_var.identifier.span(), + declaration: Declaration::Input(input_var.mode()), + }, + ) { + self.visitor.handler.emit_err(err); + } + }); + self.visit_block(&input.block); + + if !self.visitor.has_return { + self.visitor + .handler + .emit_err(TypeCheckerError::function_has_no_return(input.name(), input.span()).into()); } - }); - - VisitResult::VisitChildren + } } } - -impl<'a> ProgramVisitorDirector<'a> for Director<'a> {} diff --git a/compiler/passes/src/type_checker/check_statements.rs b/compiler/passes/src/type_checker/check_statements.rs index 5c2e3204fc..ea24da7067 100644 --- a/compiler/passes/src/type_checker/check_statements.rs +++ b/compiler/passes/src/type_checker/check_statements.rs @@ -31,6 +31,7 @@ impl<'a> StatementVisitorDirector<'a> for Director<'a> { let return_type = &self.visitor.symbol_table.lookup_fn(&parent).map(|f| f.output); self.visitor.validate_ident_type(return_type); + self.visitor.has_return = true; self.visit_expression(&input.expression, return_type); } @@ -45,6 +46,8 @@ impl<'a> StatementVisitorDirector<'a> for Director<'a> { input.variable_names.iter().for_each(|v| { self.visitor.validate_ident_type(&Some(input.type_)); + self.visit_expression(&input.value, &Some(input.type_)); + if let Err(err) = self.visitor.symbol_table.insert_variable( v.identifier.name, VariableSymbol { @@ -55,8 +58,6 @@ impl<'a> StatementVisitorDirector<'a> for Director<'a> { ) { self.visitor.handler.emit_err(err); } - - self.visit_expression(&input.value, &Some(input.type_)); }); } @@ -92,6 +93,10 @@ impl<'a> StatementVisitorDirector<'a> for Director<'a> { fn visit_conditional(&mut self, input: &'a ConditionalStatement) { self.visit_expression(&input.condition, &Some(Type::Boolean)); + self.visit_block(&input.block); + if let Some(s) = input.next.as_ref() { + self.visit_statement(s) + } } fn visit_iteration(&mut self, input: &'a IterationStatement) { @@ -124,8 +129,8 @@ impl<'a> StatementVisitorDirector<'a> for Director<'a> { } fn visit_block(&mut self, input: &'a Block) { + // creates a new sub-scope since we are in a block. self.visitor.symbol_table.push_variable_scope(); - // have to redo the logic here so we have scoping input.statements.iter().for_each(|stmt| { match stmt { Statement::Return(stmt) => self.visit_return(stmt), diff --git a/compiler/passes/src/type_checker/checker.rs b/compiler/passes/src/type_checker/checker.rs index 40a89ec679..274041724f 100644 --- a/compiler/passes/src/type_checker/checker.rs +++ b/compiler/passes/src/type_checker/checker.rs @@ -26,6 +26,7 @@ pub struct TypeChecker<'a> { pub(crate) symbol_table: &'a mut SymbolTable<'a>, pub(crate) handler: &'a Handler, pub(crate) parent: Option, + pub(crate) has_return: bool, pub(crate) negate: bool, pub(crate) account_types: IndexSet, pub(crate) algorithms_types: IndexSet, @@ -77,6 +78,7 @@ impl<'a> TypeChecker<'a> { symbol_table, handler, parent: None, + has_return: false, negate: false, account_types: Account::types(), algorithms_types: Algorithms::types(), 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 2965891cf4..8f11c90016 100644 --- a/leo/errors/src/errors/type_checker/type_checker_error.rs +++ b/leo/errors/src/errors/type_checker/type_checker_error.rs @@ -142,4 +142,14 @@ create_messages!( ), help: None, } + + /// For when a function doesn't have a return statement. + @formatted + function_has_no_return { + args: (func: impl Display), + msg: format!( + "The function {func} has no return statement.", + ), + help: None, + } ); diff --git a/tests/compiler/group/ternary.leo b/tests/compiler/group/ternary.leo index f3bf0e0705..72312ac337 100644 --- a/tests/compiler/group/ternary.leo +++ b/tests/compiler/group/ternary.leo @@ -8,4 +8,5 @@ function main(a: group, b: group, c: group) -> bool { const r: group = true ? a : b; console.assert(r == c); + return r == c; } \ No newline at end of file diff --git a/tests/expectations/compiler/compiler/definition/use_decl_variable_as_assign_fail.leo.out b/tests/expectations/compiler/compiler/definition/use_decl_variable_as_assign_fail.leo.out new file mode 100644 index 0000000000..9442d7d720 --- /dev/null +++ b/tests/expectations/compiler/compiler/definition/use_decl_variable_as_assign_fail.leo.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372003]: Unknown variable `b`\n --> compiler-test:4:14\n |\n 4 | \tlet b: u8 = b;\n | ^\n" diff --git a/tests/expectations/compiler/compiler/function/no_return_fail.leo.out b/tests/expectations/compiler/compiler/function/no_return_fail.leo.out new file mode 100644 index 0000000000..e584776b23 --- /dev/null +++ b/tests/expectations/compiler/compiler/function/no_return_fail.leo.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372012]: The function main has no return statement.\n --> compiler-test:3:1\n |\n 3 | function main() -> u8 {}\n | ^^^^^^^^^^^^^^^^^^^^^^^^\n" diff --git a/tests/expectations/compiler/compiler/group/ternary.leo.out b/tests/expectations/compiler/compiler/group/ternary.leo.out index 90fc3d3550..3e8db521b5 100644 --- a/tests/expectations/compiler/compiler/group/ternary.leo.out +++ b/tests/expectations/compiler/compiler/group/ternary.leo.out @@ -3,6 +3,6 @@ namespace: Compile expectation: Pass outputs: - output: - - initial_input_ast: abe58c662f85737a1bdb4b3e55c3e455e128ff543cbee00637b5b33aee554202 - initial_ast: d75014d735c603b442bf7e21a9c0c40880a44bc4a2b47d99d87b1c396290f814 - symbol_table: 5e96e4138e5643222224de143ab32b6716c4dbaf965318b48eaf9e4ba63f8586 + - initial_input_ast: a18a82ee746e1fd1f9c6c09c36b055f9d3b44a17dd778c5081f9258d6bb9f3ae + initial_ast: dab5600564d366fe0b548228bd230a25aa65fa8902ea7f21cf01ba8e16df1f9f + symbol_table: 35465ba1d734747d0265a3b89c9cee8fe0beda4cfb42b477010c902472e57cc1 diff --git a/tests/expectations/compiler/compiler/statements/non_existant_var_exp_fail.leo.out b/tests/expectations/compiler/compiler/statements/non_existant_var_exp_fail.leo.out new file mode 100644 index 0000000000..7cf5b586ac --- /dev/null +++ b/tests/expectations/compiler/compiler/statements/non_existant_var_exp_fail.leo.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372003]: Unknown variable `z`\n --> compiler-test:4:19\n |\n 4 | \tlet b: u8 = 1u8**z;\n | ^\n" diff --git a/tests/expectations/compiler/compiler/statements/non_existant_vars_mul_fail.leo.out b/tests/expectations/compiler/compiler/statements/non_existant_vars_mul_fail.leo.out new file mode 100644 index 0000000000..012ae3bd2c --- /dev/null +++ b/tests/expectations/compiler/compiler/statements/non_existant_vars_mul_fail.leo.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372003]: Unknown variable `x`\n --> compiler-test:4:14\n |\n 4 | \tlet b: u8 = x*z;\n | ^\nError [ETYC0372003]: Unknown variable `z`\n --> compiler-test:4:16\n |\n 4 | \tlet b: u8 = x*z;\n | ^\n"