diff --git a/compiler/ast/src/passes/visitor.rs b/compiler/ast/src/passes/visitor.rs index eb1f36a87a..324ca942e2 100644 --- a/compiler/ast/src/passes/visitor.rs +++ b/compiler/ast/src/passes/visitor.rs @@ -20,6 +20,28 @@ use crate::*; +// TODO: The Visitor and Reconstructor patterns need a redesign so that the default implementation can easily be invoked though its implemented in an overriding trait. +// Here is a pattern that seems to work +// trait ProgramVisitor { +// // The trait method that can be overridden +// fn visit_program_scope(&mut self); +// +// // Private helper function containing the default implementation +// fn default_visit_program_scope(&mut self) { +// println!("Do default stuff"); +// } +// } +// +// struct YourStruct; +// +// impl ProgramVisitor for YourStruct { +// fn visit_program_scope(&mut self) { +// println!("Do custom stuff."); +// // Call the default implementation +// self.default_visit_program_scope(); +// } +// } + /// A Visitor trait for expressions in the AST. pub trait ExpressionVisitor<'a> { type AdditionalInput: Default; diff --git a/compiler/compiler/src/compiler.rs b/compiler/compiler/src/compiler.rs index 006762dec8..960c288490 100644 --- a/compiler/compiler/src/compiler.rs +++ b/compiler/compiler/src/compiler.rs @@ -155,7 +155,17 @@ impl<'a, N: Network> Compiler<'a, N> { /// Runs the type checker pass. pub fn type_checker_pass(&'a self, symbol_table: SymbolTable) -> Result<(SymbolTable, StructGraph, CallGraph)> { - let (symbol_table, struct_graph, call_graph) = TypeChecker::::do_pass(( + let (symbol_table, struct_graph, call_graph) = + TypeChecker::::do_pass((&self.ast, self.handler, symbol_table, &self.type_table))?; + if self.compiler_options.output.type_checked_symbol_table { + self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?; + } + Ok((symbol_table, struct_graph, call_graph)) + } + + /// Runs the static analysis pass. + pub fn static_analysis_pass(&mut self, symbol_table: SymbolTable) -> Result { + let symbol_table = StaticAnalyzer::::do_pass(( &self.ast, self.handler, symbol_table, @@ -163,10 +173,7 @@ impl<'a, N: Network> Compiler<'a, N> { self.compiler_options.build.conditional_block_max_depth, self.compiler_options.build.disable_conditional_branch_type_checking, ))?; - if self.compiler_options.output.type_checked_symbol_table { - self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?; - } - Ok((symbol_table, struct_graph, call_graph)) + Ok(symbol_table) } /// Runs the loop unrolling pass. @@ -285,8 +292,11 @@ impl<'a, N: Network> Compiler<'a, N> { /// Runs the compiler stages. pub fn compiler_stages(&mut self) -> Result<(SymbolTable, StructGraph, CallGraph)> { let st = self.symbol_table_pass()?; + let (st, struct_graph, call_graph) = self.type_checker_pass(st)?; + let st = self.static_analysis_pass(st)?; + // TODO: Make this pass optional. let st = self.loop_unrolling_pass(st)?; diff --git a/compiler/compiler/tests/integration/utilities/mod.rs b/compiler/compiler/tests/integration/utilities/mod.rs index 449aa0b1bb..240843e252 100644 --- a/compiler/compiler/tests/integration/utilities/mod.rs +++ b/compiler/compiler/tests/integration/utilities/mod.rs @@ -265,6 +265,8 @@ pub fn compile_and_process<'a>(parsed: &'a mut Compiler<'a, CurrentNetwork>) -> let (st, struct_graph, call_graph) = parsed.type_checker_pass(st)?; + let st = parsed.static_analysis_pass(st)?; + CheckUniqueNodeIds::new().visit_program(&parsed.ast.ast); let st = parsed.loop_unrolling_pass(st)?; diff --git a/compiler/passes/src/lib.rs b/compiler/passes/src/lib.rs index 2a79116858..d77f14647d 100644 --- a/compiler/passes/src/lib.rs +++ b/compiler/passes/src/lib.rs @@ -17,6 +17,9 @@ #![forbid(unsafe_code)] #![doc = include_str!("../README.md")] +pub mod static_analysis; +pub use static_analysis::*; + pub mod code_generation; pub use code_generation::*; diff --git a/compiler/passes/src/loop_unrolling/unroller.rs b/compiler/passes/src/loop_unrolling/unroller.rs index 5ac47f0ff3..e63e164d02 100644 --- a/compiler/passes/src/loop_unrolling/unroller.rs +++ b/compiler/passes/src/loop_unrolling/unroller.rs @@ -99,6 +99,7 @@ impl<'a> Unroller<'a> { .swap(previous_constant_propagation_table.borrow().lookup_scope_by_index(index).unwrap()); self.constant_propagation_table.borrow_mut().parent = Some(Box::new(previous_constant_propagation_table.into_inner())); + core::mem::replace(&mut self.scope_index, 0) } diff --git a/compiler/passes/src/static_analysis/analyze_expression.rs b/compiler/passes/src/static_analysis/analyze_expression.rs new file mode 100644 index 0000000000..5a889786cd --- /dev/null +++ b/compiler/passes/src/static_analysis/analyze_expression.rs @@ -0,0 +1,55 @@ +// Copyright (C) 2019-2024 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 . + +use crate::{StaticAnalyzer, VariableSymbol}; + +use leo_ast::*; +use leo_errors::{StaticAnalyzerError, emitter::Handler}; +use leo_span::{Span, Symbol, sym}; + +use snarkvm::console::network::Network; + +use itertools::Itertools; + +impl<'a, N: Network> ExpressionVisitor<'a> for StaticAnalyzer<'a, N> { + type AdditionalInput = (); + type Output = (); + + fn visit_access(&mut self, input: &'a AccessExpression, _: &Self::AdditionalInput) -> Self::Output { + if let AccessExpression::AssociatedFunction(access) = input { + // Get the core function. + let core_function = match CoreFunction::from_symbols(access.variant.name, access.name.name) { + Some(core_function) => core_function, + None => unreachable!("Typechecking guarantees that this function exists."), + }; + + // Check that the future was awaited correctly. + if core_function == CoreFunction::FutureAwait { + self.assert_future_await(&access.arguments.first(), input.span()); + } + } + } + + fn visit_call(&mut self, input: &'a CallExpression, _: &Self::AdditionalInput) -> Self::Output { + match &*input.function { + // Note that the parser guarantees that `input.function` is always an identifier. + Expression::Identifier(ident) => { + todo!() + } + _ => unreachable!("Parsing guarantees that a function name is always an identifier."), + } + } +} diff --git a/compiler/passes/src/static_analysis/analyze_program.rs b/compiler/passes/src/static_analysis/analyze_program.rs new file mode 100644 index 0000000000..c241270027 --- /dev/null +++ b/compiler/passes/src/static_analysis/analyze_program.rs @@ -0,0 +1,128 @@ +// Copyright (C) 2019-2024 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 . + +use crate::{DiGraphError, StaticAnalyzer, TypeChecker}; + +use leo_ast::{Type, *}; +use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning}; +use leo_span::sym; + +use snarkvm::console::network::Network; + +use std::collections::HashSet; + +impl<'a, N: Network> ProgramVisitor<'a> for StaticAnalyzer<'a, N> { + fn visit_program_scope(&mut self, input: &'a ProgramScope) { + // Set the current program name. + self.current_program = Some(input.program_id.name.name); + // Do the default implementation for visiting the program scope. + input.structs.iter().for_each(|(_, c)| (self.visit_struct(c))); + input.mappings.iter().for_each(|(_, c)| (self.visit_mapping(c))); + input.functions.iter().for_each(|(_, c)| (self.visit_function(c))); + input.consts.iter().for_each(|(_, c)| (self.visit_const(c))); + } + + fn visit_function(&mut self, function: &'a Function) { + // Lookup function metadata in the symbol table. + // Note that this unwrap is safe since function metadata is stored in a prior pass. + let function_index = self + .symbol_table + .borrow() + .lookup_fn_symbol(Location::new(self.current_program, function.identifier.name)) + .unwrap() + .id; + + // Enter the function's scope. + let previous_function_index = self.enter_scope(function_index); + let previous_scope_index = self.enter_scope(self.scope_index); + + // Set the function name and variant. + self.variant = Some(function.variant); + + // If the function is an async function, initialize the await checker. + if self.variant == Some(Variant::AsyncFunction) { + // Initialize the list of input futures. Each one must be awaited before the end of the function. + self.await_checker.set_futures( + function + .input + .iter() + .filter_map(|input| { + if let Type::Future(_) = input.type_.clone() { Some(input.identifier.name) } else { None } + }) + .collect(), + ); + } + + self.visit_block(&function.block); + + // Exit the function's scope. + self.exit_scope(previous_scope_index); + self.exit_scope(previous_function_index); + + // Check that all futures were awaited exactly once. + if self.variant == Some(Variant::AsyncFunction) { + // Throw error if not all futures awaits even appear once. + if !self.await_checker.static_to_await.is_empty() { + self.emit_err(StaticAnalyzerError::future_awaits_missing( + self.await_checker + .static_to_await + .clone() + .iter() + .map(|f| f.to_string()) + .collect::>() + .join(", "), + function.span(), + )); + } else if self.await_checker.enabled && !self.await_checker.to_await.is_empty() { + // Tally up number of paths that are unawaited and number of paths that are awaited more than once. + let (num_paths_unawaited, num_paths_duplicate_awaited, num_perfect) = + self.await_checker.to_await.iter().fold((0, 0, 0), |(unawaited, duplicate, perfect), path| { + ( + unawaited + if !path.elements.is_empty() { 1 } else { 0 }, + duplicate + if path.counter > 0 { 1 } else { 0 }, + perfect + if path.counter > 0 || !path.elements.is_empty() { 0 } else { 1 }, + ) + }); + + // Throw error if there does not exist a path in which all futures are awaited exactly once. + if num_perfect == 0 { + self.emit_err(StaticAnalyzerError::no_path_awaits_all_futures_exactly_once( + self.await_checker.to_await.len(), + function.span(), + )); + } + + // Throw warning if some futures are awaited more than once in some paths. + if num_paths_unawaited > 0 { + self.emit_warning(StaticAnalyzerWarning::some_paths_do_not_await_all_futures( + self.await_checker.to_await.len(), + num_paths_unawaited, + function.span(), + )); + } + + // Throw warning if not all futures are awaited in some paths. + if num_paths_duplicate_awaited > 0 { + self.emit_warning(StaticAnalyzerWarning::some_paths_contain_duplicate_future_awaits( + self.await_checker.to_await.len(), + num_paths_duplicate_awaited, + function.span(), + )); + } + } + } + } +} diff --git a/compiler/passes/src/static_analysis/analyze_statement.rs b/compiler/passes/src/static_analysis/analyze_statement.rs new file mode 100644 index 0000000000..eeecadc43b --- /dev/null +++ b/compiler/passes/src/static_analysis/analyze_statement.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2019-2024 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 . + +use super::*; +use crate::{ConditionalTreeNode, TypeChecker, VariableSymbol, VariableType}; + +use leo_ast::{ + Type::{Future, Tuple}, + *, +}; +use leo_errors::StaticAnalyzerError; + +use itertools::Itertools; + +impl<'a, N: Network> StatementVisitor<'a> for StaticAnalyzer<'a, N> { + fn visit_block(&mut self, input: &'a Block) { + // Enter the block scope. + let scope_index = self.current_scope_index(); + let previous_scope_index = self.enter_scope(scope_index); + + input.statements.iter().for_each(|stmt| self.visit_statement(stmt)); + + // Exit the block scope. + self.exit_scope(previous_scope_index); + } + + fn visit_conditional(&mut self, input: &'a ConditionalStatement) { + self.visit_expression(&input.condition, &Default::default()); + + // Create scope for checking awaits in `then` branch of conditional. + let current_bst_nodes: Vec = + match self.await_checker.create_then_scope(self.variant == Some(Variant::AsyncFunction), input.span) { + Ok(nodes) => nodes, + Err(warn) => return self.emit_warning(warn), + }; + + // Visit block. + self.visit_block(&input.then); + + // Exit scope for checking awaits in `then` branch of conditional. + let saved_paths = + self.await_checker.exit_then_scope(self.variant == Some(Variant::AsyncFunction), current_bst_nodes); + + if let Some(otherwise) = &input.otherwise { + match &**otherwise { + Statement::Block(stmt) => { + // Visit the otherwise-block. + self.visit_block(stmt); + } + Statement::Conditional(stmt) => self.visit_conditional(stmt), + _ => unreachable!("Else-case can only be a block or conditional statement."), + } + } + + // Update the set of all possible BST paths. + self.await_checker.exit_statement_scope(self.variant == Some(Variant::AsyncFunction), saved_paths); + } +} diff --git a/compiler/passes/src/static_analysis/analyzer.rs b/compiler/passes/src/static_analysis/analyzer.rs new file mode 100644 index 0000000000..2dd7cf52ff --- /dev/null +++ b/compiler/passes/src/static_analysis/analyzer.rs @@ -0,0 +1,139 @@ +// Copyright (C) 2019-2024 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 . + +use crate::{ + CallGraph, + StructGraph, + SymbolTable, + TypeTable, + VariableSymbol, + VariableType, + static_analysis::await_checker::AwaitChecker, +}; + +use leo_ast::*; +use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning, emitter::Handler}; +use leo_span::{Span, Symbol}; + +use snarkvm::console::network::Network; + +use indexmap::{IndexMap, IndexSet}; +use itertools::Itertools; +use std::{cell::RefCell, marker::PhantomData}; + +pub struct StaticAnalyzer<'a, N: Network> { + /// The symbol table for the program. + // Note that this pass does not use the symbol table in a meaningful way. + // However, this may be useful in future static analysis passes. + pub(crate) symbol_table: RefCell, + /// The type table for the program. + // Note that this pass does not use the type table in a meaningful way. + // However, this may be useful in future static analysis passes. + pub(crate) type_table: &'a TypeTable, + /// The error handler. + pub(crate) handler: &'a Handler, + /// Struct to store the state relevant to checking all futures are awaited. + pub(crate) await_checker: AwaitChecker, + /// The index of the current scope. + pub(crate) scope_index: usize, + /// The current program name. + pub(crate) current_program: Option, + /// The variant of the function that we are currently traversing. + pub(crate) variant: Option, + // Allows the type checker to be generic over the network. + phantom: PhantomData, +} + +impl<'a, N: Network> StaticAnalyzer<'a, N> { + /// Returns a new static analyzer given a symbol table and error handler. + pub fn new( + symbol_table: SymbolTable, + type_table: &'a TypeTable, + handler: &'a Handler, + max_depth: usize, + disabled: bool, + ) -> Self { + Self { + symbol_table: RefCell::new(symbol_table), + type_table, + handler, + await_checker: AwaitChecker::new(max_depth, !disabled), + scope_index: 0, + current_program: None, + variant: None, + phantom: Default::default(), + } + } + + /// Returns the index of the current scope. + /// Note that if we are in the midst of unrolling an IterationStatement, a new scope is created. + pub(crate) fn current_scope_index(&mut self) -> usize { + self.scope_index + } + + /// Enters a child scope. + pub(crate) fn enter_scope(&mut self, index: usize) -> usize { + let previous_symbol_table = std::mem::take(&mut self.symbol_table); + self.symbol_table.swap(previous_symbol_table.borrow().lookup_scope_by_index(index).unwrap()); + self.symbol_table.borrow_mut().parent = Some(Box::new(previous_symbol_table.into_inner())); + + core::mem::replace(&mut self.scope_index, 0) + } + + /// Exits the current block scope. + pub(crate) fn exit_scope(&mut self, index: usize) { + let prev_st = *self.symbol_table.borrow_mut().parent.take().unwrap(); + self.symbol_table.swap(prev_st.lookup_scope_by_index(index).unwrap()); + self.symbol_table = RefCell::new(prev_st); + + self.scope_index = index + 1; + } + + /// Emits a type checker error. + pub(crate) fn emit_err(&self, err: StaticAnalyzerError) { + self.handler.emit_err(err); + } + + /// Emits a type checker warning + pub fn emit_warning(&self, warning: StaticAnalyzerWarning) { + self.handler.emit_warning(warning.into()); + } + + /// Type checks the awaiting of a future. + pub(crate) fn assert_future_await(&mut self, future: &Option<&Expression>, span: Span) { + // Make sure that it is an identifier expression. + let future_variable = match future { + Some(Expression::Identifier(name)) => name, + _ => { + return self.emit_err(StaticAnalyzerError::invalid_await_call(span)); + } + }; + + // Make sure that the future is defined. + match self.symbol_table.borrow().lookup_variable(Location::new(None, future_variable.name)) { + Some(var) => { + if !matches!(&var.type_, &Type::Future(_)) { + self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span())); + } + // Mark the future as consumed. + self.await_checker.remove(future_variable); + } + None => { + self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span())); + } + } + } +} diff --git a/compiler/passes/src/type_checking/await_checker.rs b/compiler/passes/src/static_analysis/await_checker.rs similarity index 94% rename from compiler/passes/src/type_checking/await_checker.rs rename to compiler/passes/src/static_analysis/await_checker.rs index b6ddf198d9..69649f08ca 100644 --- a/compiler/passes/src/type_checking/await_checker.rs +++ b/compiler/passes/src/static_analysis/await_checker.rs @@ -17,7 +17,7 @@ use crate::ConditionalTreeNode; use indexmap::IndexSet; use leo_ast::Identifier; -use leo_errors::TypeCheckerWarning; +use leo_errors::StaticAnalyzerWarning; use leo_span::{Span, Symbol}; // TODO: Could optimize by removing duplicate paths (if set of futures is the same). @@ -65,14 +65,14 @@ impl AwaitChecker { &mut self, is_finalize: bool, input: Span, - ) -> Result, TypeCheckerWarning> { + ) -> Result, StaticAnalyzerWarning> { if is_finalize && self.enabled { let mut current_nodes = Vec::new(); // Extend all paths by one node to represent the upcoming `then` branch. for node in self.to_await.iter() { // Error if exceed maximum depth. if node.depth > self.max_depth { - return Err(TypeCheckerWarning::max_conditional_block_depth_exceeded(self.max_depth, input)); + return Err(StaticAnalyzerWarning::max_conditional_block_depth_exceeded(self.max_depth, input)); } // Extend current path. current_nodes.push(node.clone().create_child()); diff --git a/compiler/passes/src/static_analysis/mod.rs b/compiler/passes/src/static_analysis/mod.rs new file mode 100644 index 0000000000..523d488d4d --- /dev/null +++ b/compiler/passes/src/static_analysis/mod.rs @@ -0,0 +1,46 @@ +// Copyright (C) 2019-2024 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 . + +mod await_checker; + +pub mod analyze_expression; + +pub mod analyze_program; + +pub mod analyze_statement; + +pub mod analyzer; +pub use analyzer::*; + +use crate::{CallGraph, Pass, StructGraph, SymbolTable, TypeTable}; + +use leo_ast::{Ast, ProgramVisitor}; +use leo_errors::{Result, emitter::Handler}; + +use snarkvm::prelude::Network; + +impl<'a, N: Network> Pass for StaticAnalyzer<'a, N> { + type Input = (&'a Ast, &'a Handler, SymbolTable, &'a TypeTable, usize, bool); + type Output = Result; + + fn do_pass((ast, handler, st, tt, max_depth, await_checking): Self::Input) -> Self::Output { + let mut visitor = StaticAnalyzer::::new(st, tt, handler, max_depth, await_checking); + visitor.visit_program(ast.as_repr()); + // TODO: Print the warnings. + handler.last_err().map_err(|e| *e)?; + Ok(visitor.symbol_table.take()) + } +} diff --git a/compiler/passes/src/type_checking/check_expressions.rs b/compiler/passes/src/type_checking/check_expressions.rs index 60bb0511f3..45cb332775 100644 --- a/compiler/passes/src/type_checking/check_expressions.rs +++ b/compiler/passes/src/type_checking/check_expressions.rs @@ -124,9 +124,7 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> { self.emit_err(TypeCheckerError::can_only_await_one_future_at_a_time(access.span)); return Some(Type::Unit); } - self.assert_future_await(&access.arguments.first(), input.span()); } - return return_type; } else { self.emit_err(TypeCheckerError::invalid_core_function_call(access, access.span())); diff --git a/compiler/passes/src/type_checking/check_program.rs b/compiler/passes/src/type_checking/check_program.rs index 342c4a91e4..913b55647f 100644 --- a/compiler/passes/src/type_checking/check_program.rs +++ b/compiler/passes/src/type_checking/check_program.rs @@ -17,7 +17,7 @@ use crate::{DiGraphError, TypeChecker}; use leo_ast::{Type, *}; -use leo_errors::{TypeCheckerError, TypeCheckerWarning}; +use leo_errors::TypeCheckerError; use leo_span::sym; use snarkvm::console::network::Network; @@ -262,19 +262,6 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { // Query helper function to type check function parameters and outputs. self.check_function_signature(function); - if self.scope_state.variant == Some(Variant::AsyncFunction) { - // Initialize the list of input futures. Each one must be awaited before the end of the function. - self.await_checker.set_futures( - function - .input - .iter() - .filter_map(|input| { - if let Type::Future(_) = input.type_.clone() { Some(input.identifier.name) } else { None } - }) - .collect(), - ); - } - if function.variant == Variant::Function && function.input.is_empty() { self.emit_err(TypeCheckerError::empty_function_arglist(function.span)); } @@ -296,59 +283,6 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { if self.scope_state.variant == Some(Variant::AsyncTransition) && !self.scope_state.has_called_finalize { self.emit_err(TypeCheckerError::async_transition_must_call_async_function(function.span)); } - - // Check that all futures were awaited exactly once. - if self.scope_state.variant == Some(Variant::AsyncFunction) { - // Throw error if not all futures awaits even appear once. - if !self.await_checker.static_to_await.is_empty() { - self.emit_err(TypeCheckerError::future_awaits_missing( - self.await_checker - .static_to_await - .clone() - .iter() - .map(|f| f.to_string()) - .collect::>() - .join(", "), - function.span(), - )); - } else if self.await_checker.enabled && !self.await_checker.to_await.is_empty() { - // Tally up number of paths that are unawaited and number of paths that are awaited more than once. - let (num_paths_unawaited, num_paths_duplicate_awaited, num_perfect) = - self.await_checker.to_await.iter().fold((0, 0, 0), |(unawaited, duplicate, perfect), path| { - ( - unawaited + if !path.elements.is_empty() { 1 } else { 0 }, - duplicate + if path.counter > 0 { 1 } else { 0 }, - perfect + if path.counter > 0 || !path.elements.is_empty() { 0 } else { 1 }, - ) - }); - - // Throw error if there does not exist a path in which all futures are awaited exactly once. - if num_perfect == 0 { - self.emit_err(TypeCheckerError::no_path_awaits_all_futures_exactly_once( - self.await_checker.to_await.len(), - function.span(), - )); - } - - // Throw warning if some futures are awaited more than once in some paths. - if num_paths_unawaited > 0 { - self.emit_warning(TypeCheckerWarning::some_paths_do_not_await_all_futures( - self.await_checker.to_await.len(), - num_paths_unawaited, - function.span(), - )); - } - - // Throw warning if not all futures are awaited in some paths. - if num_paths_duplicate_awaited > 0 { - self.emit_warning(TypeCheckerWarning::some_paths_contain_duplicate_future_awaits( - self.await_checker.to_await.len(), - num_paths_duplicate_awaited, - function.span(), - )); - } - } - } } fn visit_function_stub(&mut self, input: &'a FunctionStub) { diff --git a/compiler/passes/src/type_checking/check_statements.rs b/compiler/passes/src/type_checking/check_statements.rs index ca10467b7e..0f46797d37 100644 --- a/compiler/passes/src/type_checking/check_statements.rs +++ b/compiler/passes/src/type_checking/check_statements.rs @@ -15,7 +15,7 @@ // along with the Leo library. If not, see . use super::*; -use crate::{ConditionalTreeNode, TypeChecker, VariableSymbol, VariableType}; +use crate::{TypeChecker, VariableSymbol, VariableType}; use leo_ast::{ Type::{Future, Tuple}, @@ -133,26 +133,12 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { // Set the `is_conditional` flag. let previous_is_conditional = core::mem::replace(&mut self.scope_state.is_conditional, true); - // Create scope for checking awaits in `then` branch of conditional. - let current_bst_nodes: Vec = match self - .await_checker - .create_then_scope(self.scope_state.variant == Some(Variant::AsyncFunction), input.span) - { - Ok(nodes) => nodes, - Err(warn) => return self.emit_warning(warn), - }; - // Visit block. self.visit_block(&input.then); // Store the `has_return` flag for the then-block. then_block_has_return = self.scope_state.has_return; - // Exit scope for checking awaits in `then` branch of conditional. - let saved_paths = self - .await_checker - .exit_then_scope(self.scope_state.variant == Some(Variant::AsyncFunction), current_bst_nodes); - if let Some(otherwise) = &input.otherwise { // Set the `has_return` flag for the otherwise-block. self.scope_state.has_return = otherwise_block_has_return; @@ -170,9 +156,6 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { otherwise_block_has_return = self.scope_state.has_return; } - // Update the set of all possible BST paths. - self.await_checker.exit_statement_scope(self.scope_state.variant == Some(Variant::AsyncFunction), saved_paths); - // Restore the previous `has_return` flag. self.scope_state.has_return = previous_has_return || (then_block_has_return && otherwise_block_has_return); // Restore the previous `is_conditional` flag. diff --git a/compiler/passes/src/type_checking/checker.rs b/compiler/passes/src/type_checking/checker.rs index dfcd0afc6d..7e4cc3a72f 100644 --- a/compiler/passes/src/type_checking/checker.rs +++ b/compiler/passes/src/type_checking/checker.rs @@ -21,7 +21,7 @@ use crate::{ TypeTable, VariableSymbol, VariableType, - type_checking::{await_checker::AwaitChecker, scope_state::ScopeState}, + type_checking::scope_state::ScopeState, }; use leo_ast::*; @@ -47,8 +47,6 @@ pub struct TypeChecker<'a, N: Network> { pub(crate) handler: &'a Handler, /// The state of the current scope being traversed. pub(crate) scope_state: ScopeState, - /// Struct to store the state relevant to checking all futures are awaited. - pub(crate) await_checker: AwaitChecker, /// Mapping from async function name to the inferred input types. pub(crate) async_function_input_types: IndexMap>, /// The set of used composites. @@ -103,13 +101,7 @@ const MAGNITUDE_TYPES: [Type; 3] = impl<'a, N: Network> TypeChecker<'a, N> { /// Returns a new type checker given a symbol table and error handler. - pub fn new( - symbol_table: SymbolTable, - type_table: &'a TypeTable, - handler: &'a Handler, - max_depth: usize, - disabled: bool, - ) -> Self { + pub fn new(symbol_table: SymbolTable, type_table: &'a TypeTable, handler: &'a Handler) -> Self { let struct_names = symbol_table.structs.keys().map(|loc| loc.name).collect(); let function_names = symbol_table.functions.keys().map(|loc| loc.name).collect(); @@ -121,7 +113,6 @@ impl<'a, N: Network> TypeChecker<'a, N> { call_graph: CallGraph::new(function_names), handler, scope_state: ScopeState::new(), - await_checker: AwaitChecker::new(max_depth, !disabled), async_function_input_types: IndexMap::new(), used_structs: IndexSet::new(), phantom: Default::default(), @@ -1308,31 +1299,6 @@ impl<'a, N: Network> TypeChecker<'a, N> { struct_ } - /// Type checks the awaiting of a future. - pub(crate) fn assert_future_await(&mut self, future: &Option<&Expression>, span: Span) { - // Make sure that it is an identifier expression. - let future_variable = match future { - Some(Expression::Identifier(name)) => name, - _ => { - return self.emit_err(TypeCheckerError::invalid_await_call(span)); - } - }; - - // Make sure that the future is defined. - match self.symbol_table.borrow().lookup_variable(Location::new(None, future_variable.name)) { - Some(var) => { - if !matches!(&var.type_, &Type::Future(_)) { - self.emit_err(TypeCheckerError::expected_future(future_variable.name, future_variable.span())); - } - // Mark the future as consumed. - self.await_checker.remove(future_variable); - } - None => { - self.emit_err(TypeCheckerError::expected_future(future_variable.name, future_variable.span())); - } - } - } - /// Inserts variable to symbol table. pub(crate) fn insert_variable( &mut self, diff --git a/compiler/passes/src/type_checking/mod.rs b/compiler/passes/src/type_checking/mod.rs index d06179100d..88991c5091 100644 --- a/compiler/passes/src/type_checking/mod.rs +++ b/compiler/passes/src/type_checking/mod.rs @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -mod await_checker; - pub mod check_expressions; pub mod check_program; @@ -36,11 +34,11 @@ use leo_errors::{Result, emitter::Handler}; use snarkvm::prelude::Network; impl<'a, N: Network> Pass for TypeChecker<'a, N> { - type Input = (&'a Ast, &'a Handler, SymbolTable, &'a TypeTable, usize, bool); + type Input = (&'a Ast, &'a Handler, SymbolTable, &'a TypeTable); type Output = Result<(SymbolTable, StructGraph, CallGraph)>; - fn do_pass((ast, handler, st, tt, max_depth, await_checking): Self::Input) -> Self::Output { - let mut visitor = TypeChecker::::new(st, tt, handler, max_depth, await_checking); + fn do_pass((ast, handler, st, tt): Self::Input) -> Self::Output { + let mut visitor = TypeChecker::::new(st, tt, handler); visitor.visit_program(ast.as_repr()); handler.last_err().map_err(|e| *e)?; diff --git a/errors/src/errors/import/import_errors.rs b/errors/src/errors/import/import_errors.rs deleted file mode 100644 index e53fa6dcdd..0000000000 --- a/errors/src/errors/import/import_errors.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2019-2024 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 . - -use crate::create_errors; - -use std::{ - error::Error as ErrorArg, - fmt::{Debug, Display}, -}; - -create_errors!( - /// ImportError enum that represents all the errors for the `leo-import` crate. - ImportError, - exit_code_mask: 4000i32, - error_code_prefix: "IMP", -); diff --git a/errors/src/errors/mod.rs b/errors/src/errors/mod.rs index 95ca143421..f4a834bae8 100644 --- a/errors/src/errors/mod.rs +++ b/errors/src/errors/mod.rs @@ -45,6 +45,10 @@ pub use self::package::*; pub mod parser; pub use self::parser::*; +/// Contains the Static Analyzer error definitions. +pub mod static_analyzer; +pub use self::static_analyzer::*; + /// Contains the Type Checker error definitions. pub mod type_checker; pub use self::type_checker::*; @@ -72,6 +76,9 @@ pub enum LeoError { /// Represents a Parser Error in a Leo Error. #[error(transparent)] ParserError(#[from] ParserError), + /// Represents a Static Analyzer Error in a Leo Error. + #[error(transparent)] + StaticAnalyzerError(#[from] StaticAnalyzerError), /// Represents a Type Checker Error in a Leo Error. #[error(transparent)] TypeCheckerError(#[from] TypeCheckerError), @@ -104,6 +111,7 @@ impl LeoError { CliError(error) => error.error_code(), ParserError(error) => error.error_code(), PackageError(error) => error.error_code(), + StaticAnalyzerError(error) => error.error_code(), TypeCheckerError(error) => error.error_code(), LoopUnrollerError(error) => error.error_code(), FlattenError(error) => error.error_code(), @@ -123,6 +131,7 @@ impl LeoError { CliError(error) => error.exit_code(), ParserError(error) => error.exit_code(), PackageError(error) => error.exit_code(), + StaticAnalyzerError(error) => error.exit_code(), TypeCheckerError(error) => error.exit_code(), LoopUnrollerError(error) => error.exit_code(), FlattenError(error) => error.exit_code(), @@ -140,6 +149,9 @@ pub enum LeoWarning { /// Represents an Parser Warning in a Leo Warning. #[error(transparent)] ParserWarning(#[from] ParserWarning), + /// Represents a Static Analyzer Warning in a Leo Warning. + #[error(transparent)] + StaticAnalyzerWarning(#[from] StaticAnalyzerWarning), /// Represents a Type Checker Warning in a Leo Warning. #[error(transparent)] TypeCheckerWarning(#[from] TypeCheckerWarning), @@ -153,6 +165,7 @@ impl LeoWarning { match self { ParserWarning(warning) => warning.warning_code(), TypeCheckerWarning(warning) => warning.warning_code(), + StaticAnalyzerWarning(warning) => warning.warning_code(), } } } diff --git a/errors/src/errors/import/mod.rs b/errors/src/errors/static_analyzer/mod.rs similarity index 77% rename from errors/src/errors/import/mod.rs rename to errors/src/errors/static_analyzer/mod.rs index 184465ca8c..2fda26a5f9 100644 --- a/errors/src/errors/import/mod.rs +++ b/errors/src/errors/static_analyzer/mod.rs @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -/// This module contains the Import error definitions. -pub mod import_errors; -pub use self::import_errors::*; +/// This module contains the static analysis error definitions. +pub mod static_analyzer_error; +pub use self::static_analyzer_error::*; + +pub mod static_analyzer_warning; +pub use self::static_analyzer_warning::*; diff --git a/errors/src/errors/static_analyzer/static_analyzer_error.rs b/errors/src/errors/static_analyzer/static_analyzer_error.rs new file mode 100644 index 0000000000..52d5ba4dd3 --- /dev/null +++ b/errors/src/errors/static_analyzer/static_analyzer_error.rs @@ -0,0 +1,55 @@ +// Copyright (C) 2019-2024 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 . + +use crate::create_messages; +use std::fmt::{Debug, Display}; + +// TODO: Consolidate errors. + +create_messages!( + /// StaticAnalyzer enum that represents all the errors for static analysis. + StaticAnalyzerError, + code_mask: 4000i32, + code_prefix: "SAZ", + + @formatted + no_path_awaits_all_futures_exactly_once { + args: (num_total_paths: impl Display), + msg: format!("Futures must be awaited exactly once. Out of `{num_total_paths}`, there does not exist a single path in which all futures are awaited exactly once."), + help: Some("Ex: for `f: Future` call `f.await()` to await a future. Remove duplicate future await redundancies, and add future awaits for un-awaited futures.".to_string()), + } + + @formatted + future_awaits_missing { + args: (unawaited: impl Display), + msg: format!("The following futures were never awaited: {unawaited}"), + help: Some("Ex: for `f: Future` call `f.await()` to await a future.".to_string()), + } + + @formatted + invalid_await_call { + args: (), + msg: "Not a valid await call.".to_string(), + help: Some("Ex: for `f: Future` call `f.await()` or `Future::await(f)` to await a future.".to_string()), + } + + @formatted + expected_future { + args: (type_: impl Display), + msg: format!("Expected a future, but found `{type_}`"), + help: Some("Only futures can be awaited.".to_string()), + } +); diff --git a/errors/src/errors/static_analyzer/static_analyzer_warning.rs b/errors/src/errors/static_analyzer/static_analyzer_warning.rs new file mode 100644 index 0000000000..b3e1279ea4 --- /dev/null +++ b/errors/src/errors/static_analyzer/static_analyzer_warning.rs @@ -0,0 +1,47 @@ +// Copyright (C) 2019-2024 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 . + +use crate::create_messages; + +use std::fmt::Display; + +create_messages!( + /// ParserWarning enum that represents all the warnings for static analysis + StaticAnalyzerWarning, + code_mask: 4000i32, + code_prefix: "SAZ", + + @formatted + some_paths_do_not_await_all_futures { + args: (num_total_paths: impl Display, num_unawaited_paths: impl Display), + msg: format!("Not all paths through the function await all futures. {num_unawaited_paths}/{num_total_paths} paths contain at least one future that is never awaited."), + help: Some("Ex: `f.await()` to await a future. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.".to_string()), + } + + @formatted + some_paths_contain_duplicate_future_awaits { + args: (num_total_paths: impl Display, num_duplicate_await_paths: impl Display), + msg: format!("Some paths through the function contain duplicate future awaits. {num_duplicate_await_paths}/{num_total_paths} paths contain at least one future that is awaited more than once."), + help: Some("Look at the times `.await()` is called, and try to reduce redundancies. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.".to_string()), + } + + @formatted + max_conditional_block_depth_exceeded { + args: (max: impl Display), + msg: format!("The type checker has exceeded the max depth of nested conditional blocks: {max}."), + help: Some("Re-run with a larger maximum depth using the `--conditional_block_max_depth` build option. Ex: `leo run main --conditional_block_max_depth 25`.".to_string()), + } +); diff --git a/errors/src/errors/type_checker/type_checker_error.rs b/errors/src/errors/type_checker/type_checker_error.rs index f7248b7399..5ff27a64a7 100644 --- a/errors/src/errors/type_checker/type_checker_error.rs +++ b/errors/src/errors/type_checker/type_checker_error.rs @@ -742,6 +742,7 @@ create_messages!( help: Some(" Future arguments must be addressed by their index. Ex: `f.1.3`.".to_string()), } + // TODO: This error is deprecated. Remove. @formatted no_path_awaits_all_futures_exactly_once { args: (num_total_paths: impl Display), @@ -749,6 +750,7 @@ create_messages!( help: Some("Ex: for `f: Future` call `f.await()` to await a future. Remove duplicate future await redundancies, and add future awaits for un-awaited futures.".to_string()), } + // TODO: This error is deprecated. Remove. @formatted future_awaits_missing { args: (unawaited: impl Display), @@ -763,6 +765,7 @@ create_messages!( help: Some("Futures can only be defined as the result of async calls.".to_string()), } + // TODO: This error is deprecated. Remove. @formatted invalid_await_call { args: (), @@ -777,6 +780,7 @@ create_messages!( help: Some("Ex: for `f: Future` call `f.await()` or `Future::await(f)` to await a future.".to_string()), } + // TODO: This error is deprecated. Remove. @formatted expected_future { args: (type_: impl Display), diff --git a/errors/src/errors/type_checker/type_checker_warning.rs b/errors/src/errors/type_checker/type_checker_warning.rs index 2ec2fa53ca..1ac294cba3 100644 --- a/errors/src/errors/type_checker/type_checker_warning.rs +++ b/errors/src/errors/type_checker/type_checker_warning.rs @@ -38,6 +38,7 @@ create_messages!( help: Some("Look at the times `.await()` is called, and try to reduce redundancies. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.".to_string()), } + // TODO: This warning is deprecated, remove it in the future. @formatted async_function_is_never_called_by_transition_function { args: (name: impl Display),