Introduce StaticAnalyzer pass; move future await checking to this pass

This commit is contained in:
Pranav Gaddamadugu 2024-11-13 16:35:55 -08:00
parent 1e31a9045a
commit b87a79d991
23 changed files with 618 additions and 168 deletions

View File

@ -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;

View File

@ -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::<N>::do_pass((
let (symbol_table, struct_graph, call_graph) =
TypeChecker::<N>::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<SymbolTable> {
let symbol_table = StaticAnalyzer::<N>::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)?;

View File

@ -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)?;

View File

@ -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::*;

View File

@ -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)
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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."),
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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::<Vec<String>>()
.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(),
));
}
}
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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<ConditionalTreeNode> =
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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<SymbolTable>,
/// 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<Symbol>,
/// The variant of the function that we are currently traversing.
pub(crate) variant: Option<Variant>,
// Allows the type checker to be generic over the network.
phantom: PhantomData<N>,
}
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()));
}
}
}
}

View File

@ -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<Vec<ConditionalTreeNode>, TypeCheckerWarning> {
) -> Result<Vec<ConditionalTreeNode>, 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());

View File

@ -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 <https://www.gnu.org/licenses/>.
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<SymbolTable>;
fn do_pass((ast, handler, st, tt, max_depth, await_checking): Self::Input) -> Self::Output {
let mut visitor = StaticAnalyzer::<N>::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())
}
}

View File

@ -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()));

View File

@ -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::<Vec<String>>()
.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) {

View File

@ -15,7 +15,7 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
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<ConditionalTreeNode> = 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.

View File

@ -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<Location, Vec<Type>>,
/// 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,

View File

@ -14,8 +14,6 @@
// 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/>.
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::<N>::new(st, tt, handler, max_depth, await_checking);
fn do_pass((ast, handler, st, tt): Self::Input) -> Self::Output {
let mut visitor = TypeChecker::<N>::new(st, tt, handler);
visitor.visit_program(ast.as_repr());
handler.last_err().map_err(|e| *e)?;

View File

@ -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 <https://www.gnu.org/licenses/>.
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",
);

View File

@ -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(),
}
}
}

View File

@ -14,6 +14,9 @@
// 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 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::*;

View File

@ -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 <https://www.gnu.org/licenses/>.
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()),
}
);

View File

@ -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 <https://www.gnu.org/licenses/>.
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()),
}
);

View File

@ -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),

View File

@ -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),