From b87a79d991d765cdde9905fefd3a45a2d1f7df6f Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:35:55 -0800 Subject: [PATCH 1/7] Introduce StaticAnalyzer pass; move future await checking to this pass --- compiler/ast/src/passes/visitor.rs | 22 +++ compiler/compiler/src/compiler.rs | 20 ++- .../tests/integration/utilities/mod.rs | 2 + compiler/passes/src/lib.rs | 3 + .../passes/src/loop_unrolling/unroller.rs | 1 + .../src/static_analysis/analyze_expression.rs | 55 +++++++ .../src/static_analysis/analyze_program.rs | 128 ++++++++++++++++ .../src/static_analysis/analyze_statement.rs | 71 +++++++++ .../passes/src/static_analysis/analyzer.rs | 139 ++++++++++++++++++ .../await_checker.rs | 6 +- compiler/passes/src/static_analysis/mod.rs | 46 ++++++ .../src/type_checking/check_expressions.rs | 2 - .../passes/src/type_checking/check_program.rs | 68 +-------- .../src/type_checking/check_statements.rs | 19 +-- compiler/passes/src/type_checking/checker.rs | 38 +---- compiler/passes/src/type_checking/mod.rs | 8 +- errors/src/errors/import/import_errors.rs | 29 ---- errors/src/errors/mod.rs | 13 ++ .../errors/{import => static_analyzer}/mod.rs | 9 +- .../static_analyzer/static_analyzer_error.rs | 55 +++++++ .../static_analyzer_warning.rs | 47 ++++++ .../errors/type_checker/type_checker_error.rs | 4 + .../type_checker/type_checker_warning.rs | 1 + 23 files changed, 618 insertions(+), 168 deletions(-) create mode 100644 compiler/passes/src/static_analysis/analyze_expression.rs create mode 100644 compiler/passes/src/static_analysis/analyze_program.rs create mode 100644 compiler/passes/src/static_analysis/analyze_statement.rs create mode 100644 compiler/passes/src/static_analysis/analyzer.rs rename compiler/passes/src/{type_checking => static_analysis}/await_checker.rs (94%) create mode 100644 compiler/passes/src/static_analysis/mod.rs delete mode 100644 errors/src/errors/import/import_errors.rs rename errors/src/errors/{import => static_analyzer}/mod.rs (77%) create mode 100644 errors/src/errors/static_analyzer/static_analyzer_error.rs create mode 100644 errors/src/errors/static_analyzer/static_analyzer_warning.rs 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), From 1379843a8f96359dabe131db125c0c00c597fde8 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:13:22 -0800 Subject: [PATCH 2/7] Add check for futures awaited out of order --- compiler/passes/src/common/tree_node/mod.rs | 13 ++++++++++++- .../passes/src/static_analysis/analyze_program.rs | 4 ++-- compiler/passes/src/static_analysis/analyzer.rs | 8 +++++++- .../passes/src/static_analysis/await_checker.rs | 13 +++++++++---- .../static_analyzer/static_analyzer_warning.rs | 7 +++++++ 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/compiler/passes/src/common/tree_node/mod.rs b/compiler/passes/src/common/tree_node/mod.rs index cab96aaffa..b9a42dafda 100644 --- a/compiler/passes/src/common/tree_node/mod.rs +++ b/compiler/passes/src/common/tree_node/mod.rs @@ -50,9 +50,20 @@ impl TreeNode { } /// Removes an element from the current node. - pub fn remove_element(&mut self, element: &N) { + /// If the element does not exist, increment an internal counter which later used to generate an error that the user attempted to await a future twice. + /// Returns `true` if the element was removed but not the first one in the node. + pub fn remove_element(&mut self, element: &N) -> bool { + // Check if the element is the first one in the node. + let is_not_first = match self.elements.first() { + Some(first) => first != element, + None => false, + }; + // Remove the element from the node. if !self.elements.shift_remove(element) { self.counter += 1; + return false; + } else { + return is_not_first; } } } diff --git a/compiler/passes/src/static_analysis/analyze_program.rs b/compiler/passes/src/static_analysis/analyze_program.rs index c241270027..d5e244cc29 100644 --- a/compiler/passes/src/static_analysis/analyze_program.rs +++ b/compiler/passes/src/static_analysis/analyze_program.rs @@ -105,7 +105,7 @@ impl<'a, N: Network> ProgramVisitor<'a> for StaticAnalyzer<'a, N> { )); } - // Throw warning if some futures are awaited more than once in some paths. + // Throw warning if not all futures are awaited 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(), @@ -114,7 +114,7 @@ impl<'a, N: Network> ProgramVisitor<'a> for StaticAnalyzer<'a, N> { )); } - // Throw warning if not all futures are awaited in some paths. + // Throw warning if some futures are awaited more than once 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(), diff --git a/compiler/passes/src/static_analysis/analyzer.rs b/compiler/passes/src/static_analysis/analyzer.rs index 2dd7cf52ff..5cd02cb237 100644 --- a/compiler/passes/src/static_analysis/analyzer.rs +++ b/compiler/passes/src/static_analysis/analyzer.rs @@ -129,7 +129,13 @@ impl<'a, N: Network> StaticAnalyzer<'a, N> { self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span())); } // Mark the future as consumed. - self.await_checker.remove(future_variable); + // If the call returns false, it means that a future was not awaited in the order of the input list, emit a warning. + if !self.await_checker.remove(future_variable) { + self.emit_warning(StaticAnalyzerWarning::future_not_awaited_in_order( + future_variable.name, + future_variable.span(), + )); + } } None => { self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span())); diff --git a/compiler/passes/src/static_analysis/await_checker.rs b/compiler/passes/src/static_analysis/await_checker.rs index 69649f08ca..85a45b8ded 100644 --- a/compiler/passes/src/static_analysis/await_checker.rs +++ b/compiler/passes/src/static_analysis/await_checker.rs @@ -39,15 +39,20 @@ impl AwaitChecker { } /// Remove from list. - pub fn remove(&mut self, id: &Identifier) { + /// Returns `true` if there was a path where the future was not awaited in the order of the input list. + pub fn remove(&mut self, id: &Identifier) -> bool { // Can assume in finalize block. - if self.enabled { + let is_not_first = if self.enabled { // Remove from dynamic list. - self.to_await.iter_mut().for_each(|node| node.remove_element(&id.name)); - } + self.to_await.iter_mut().fold(false, |is_not_first, node| node.remove_element(&id.name)) + } else { + false + }; // Remove from static list. self.static_to_await.shift_remove(&id.name); + + is_not_first } /// Initialize futures. diff --git a/errors/src/errors/static_analyzer/static_analyzer_warning.rs b/errors/src/errors/static_analyzer/static_analyzer_warning.rs index b3e1279ea4..635d78ace9 100644 --- a/errors/src/errors/static_analyzer/static_analyzer_warning.rs +++ b/errors/src/errors/static_analyzer/static_analyzer_warning.rs @@ -44,4 +44,11 @@ create_messages!( 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()), } + + @formatted + future_not_awaited_in_order { + args: (future_name: impl Display), + msg: format!("The future `{}` is not awaited in order.", future_name), + help: Some("While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context.".to_string()), + } ); From 5501a405c22cfbac1890c5d442277b91842d4840 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:14:55 -0800 Subject: [PATCH 3/7] Cleanup --- compiler/passes/src/common/tree_node/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/passes/src/common/tree_node/mod.rs b/compiler/passes/src/common/tree_node/mod.rs index b9a42dafda..45ef3c5fdc 100644 --- a/compiler/passes/src/common/tree_node/mod.rs +++ b/compiler/passes/src/common/tree_node/mod.rs @@ -61,9 +61,9 @@ impl TreeNode { // Remove the element from the node. if !self.elements.shift_remove(element) { self.counter += 1; - return false; + false } else { - return is_not_first; + is_not_first } } } From d3b0780907fe5d1aaa6aaae785994c15eeb56513 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Wed, 13 Nov 2024 19:05:42 -0800 Subject: [PATCH 4/7] Add check for complex async call --- .../src/static_analysis/analyze_expression.rs | 7 +++- .../passes/src/static_analysis/analyzer.rs | 35 +++++++++++++++++++ .../src/static_analysis/await_checker.rs | 2 +- .../static_analyzer/static_analyzer_error.rs | 7 ++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/compiler/passes/src/static_analysis/analyze_expression.rs b/compiler/passes/src/static_analysis/analyze_expression.rs index 5a889786cd..bd840d9ee2 100644 --- a/compiler/passes/src/static_analysis/analyze_expression.rs +++ b/compiler/passes/src/static_analysis/analyze_expression.rs @@ -47,7 +47,12 @@ impl<'a, N: Network> ExpressionVisitor<'a> for StaticAnalyzer<'a, N> { match &*input.function { // Note that the parser guarantees that `input.function` is always an identifier. Expression::Identifier(ident) => { - todo!() + // If the function call is an external async transition, then for all async calls that follow a non-async call, + // we must check that the async call is not an async function that takes a future as an argument. + if self.variant == Some(Variant::AsyncTransition) && input.program.is_some() { + // Note that this unwrap is safe since we check that `input.program` is `Some` above. + self.assert_simple_async_transition_call(input.program.unwrap(), ident.name, input.span()); + } } _ => unreachable!("Parsing guarantees that a function name is always an identifier."), } diff --git a/compiler/passes/src/static_analysis/analyzer.rs b/compiler/passes/src/static_analysis/analyzer.rs index 5cd02cb237..635f0232e1 100644 --- a/compiler/passes/src/static_analysis/analyzer.rs +++ b/compiler/passes/src/static_analysis/analyzer.rs @@ -142,4 +142,39 @@ impl<'a, N: Network> StaticAnalyzer<'a, N> { } } } + + /// Assert that an async call is a "simple" one. + /// Simple is defined as an async transition function which does not return a `Future` that itself takes a `Future` as an argument. + pub(crate) fn assert_simple_async_transition_call(&self, program: Symbol, function_name: Symbol, span: Span) { + // Note: The function symbol lookup is performed outside of the `if let Some(func) ...` block to avoid a RefCell lifetime bug in Rust. + // Do not move it into the `if let Some(func) ...` block or it will keep `self.symbol_table_creation` alive for the entire block and will be very memory inefficient! + if let Some(function) = self.symbol_table.borrow().lookup_fn_symbol(Location::new(Some(program), function_name)) { + // Check that the function is an async function. + if function.variant != Variant::AsyncFunction { + return; + } + + // A helper function to check if a `Future` type takes a `Future` as an argument. + let check_future = |future_type: &FutureType| { + if future_type.inputs.iter().any(|input| matches!(input, Type::Future(_))) { + self.emit_err(StaticAnalyzerError::async_transition_call_with_future_argument(function_name, span)); + } + }; + + // Check the output type of the function. + match &function.output_type { + Type::Future(future_type) => check_future(&future_type), + Type::Tuple(tuple_type) => { + for element in tuple_type.elements() { + if let Type::Future(future_type) = element { + check_future(&future_type); + } + } + } + _ => () // Do nothing. + } + } else { + unreachable!("Type checking guarantees that this function exists.") + } + } } diff --git a/compiler/passes/src/static_analysis/await_checker.rs b/compiler/passes/src/static_analysis/await_checker.rs index 85a45b8ded..7440223aeb 100644 --- a/compiler/passes/src/static_analysis/await_checker.rs +++ b/compiler/passes/src/static_analysis/await_checker.rs @@ -44,7 +44,7 @@ impl AwaitChecker { // Can assume in finalize block. let is_not_first = if self.enabled { // Remove from dynamic list. - self.to_await.iter_mut().fold(false, |is_not_first, node| node.remove_element(&id.name)) + self.to_await.iter_mut().fold(false, |is_not_first, node| is_not_first || node.remove_element(&id.name)) } else { false }; diff --git a/errors/src/errors/static_analyzer/static_analyzer_error.rs b/errors/src/errors/static_analyzer/static_analyzer_error.rs index 52d5ba4dd3..698a717647 100644 --- a/errors/src/errors/static_analyzer/static_analyzer_error.rs +++ b/errors/src/errors/static_analyzer/static_analyzer_error.rs @@ -52,4 +52,11 @@ create_messages!( msg: format!("Expected a future, but found `{type_}`"), help: Some("Only futures can be awaited.".to_string()), } + + @formatted + async_transition_call_with_future_argument { + args: (function_name: impl Display), + msg: format!("The call to {function_name} will result in failed executions on-chain."), + help: Some("There is a subtle error that occurs if an async transition call follows a non-async transition call, and the async call returns a `Future` that itself takes a `Future` as an input. See See `https://github.com/AleoNet/snarkVM/issues/2570` for more context.".to_string()), + } ); From f27d1573a2b7475b234c08cc2175acc9104b6ab7 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 14 Nov 2024 07:23:34 -0800 Subject: [PATCH 5/7] Fix and regen some expectations --- compiler/compiler/src/compiler.rs | 11 ++-- .../tests/integration/utilities/mod.rs | 2 +- .../src/static_analysis/analyze_expression.rs | 11 ++-- .../src/static_analysis/analyze_program.rs | 24 ++----- .../src/static_analysis/analyze_statement.rs | 16 +---- .../passes/src/static_analysis/analyzer.rs | 65 +++++-------------- compiler/passes/src/static_analysis/mod.rs | 10 ++- .../static_analyzer_warning.rs | 2 +- .../futures/future_not_all_awaited_fail.out | 31 ++++++++- .../expectations/compiler/futures/nested.out | 2 +- .../futures/partial_type_specification.out | 2 +- 11 files changed, 70 insertions(+), 106 deletions(-) diff --git a/compiler/compiler/src/compiler.rs b/compiler/compiler/src/compiler.rs index 960c288490..b7900a6304 100644 --- a/compiler/compiler/src/compiler.rs +++ b/compiler/compiler/src/compiler.rs @@ -164,16 +164,15 @@ impl<'a, N: Network> Compiler<'a, N> { } /// Runs the static analysis pass. - pub fn static_analysis_pass(&mut self, symbol_table: SymbolTable) -> Result { - let symbol_table = StaticAnalyzer::::do_pass(( + pub fn static_analysis_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> { + StaticAnalyzer::::do_pass(( &self.ast, self.handler, - symbol_table, + &symbol_table, &self.type_table, self.compiler_options.build.conditional_block_max_depth, self.compiler_options.build.disable_conditional_branch_type_checking, - ))?; - Ok(symbol_table) + )) } /// Runs the loop unrolling pass. @@ -295,7 +294,7 @@ impl<'a, N: Network> Compiler<'a, N> { let (st, struct_graph, call_graph) = self.type_checker_pass(st)?; - let st = self.static_analysis_pass(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 240843e252..b9ef01213b 100644 --- a/compiler/compiler/tests/integration/utilities/mod.rs +++ b/compiler/compiler/tests/integration/utilities/mod.rs @@ -265,7 +265,7 @@ 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)?; + parsed.static_analysis_pass(&st)?; CheckUniqueNodeIds::new().visit_program(&parsed.ast.ast); diff --git a/compiler/passes/src/static_analysis/analyze_expression.rs b/compiler/passes/src/static_analysis/analyze_expression.rs index bd840d9ee2..6c827ea6a3 100644 --- a/compiler/passes/src/static_analysis/analyze_expression.rs +++ b/compiler/passes/src/static_analysis/analyze_expression.rs @@ -14,15 +14,12 @@ // 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 crate::{StaticAnalyzer}; 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 = (); @@ -49,10 +46,14 @@ impl<'a, N: Network> ExpressionVisitor<'a> for StaticAnalyzer<'a, N> { Expression::Identifier(ident) => { // If the function call is an external async transition, then for all async calls that follow a non-async call, // we must check that the async call is not an async function that takes a future as an argument. - if self.variant == Some(Variant::AsyncTransition) && input.program.is_some() { + if self.non_async_external_call_seen && self.variant == Some(Variant::AsyncTransition) && input.program.is_some() { // Note that this unwrap is safe since we check that `input.program` is `Some` above. self.assert_simple_async_transition_call(input.program.unwrap(), ident.name, input.span()); } + // Otherwise if the variant is a non-async external call, update the flag. + else if self.variant == Some(Variant::Transition) { + self.non_async_external_call_seen = true; + } } _ => 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 index d5e244cc29..cd6eada544 100644 --- a/compiler/passes/src/static_analysis/analyze_program.rs +++ b/compiler/passes/src/static_analysis/analyze_program.rs @@ -14,15 +14,13 @@ // 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 crate::{StaticAnalyzer}; 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) { @@ -36,22 +34,12 @@ impl<'a, N: Network> ProgramVisitor<'a> for StaticAnalyzer<'a, N> { } 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); + // Set `non_async_external_call_seen` to false. + self.non_async_external_call_seen = false; + // 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. @@ -68,10 +56,6 @@ impl<'a, N: Network> ProgramVisitor<'a> for StaticAnalyzer<'a, N> { 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. diff --git a/compiler/passes/src/static_analysis/analyze_statement.rs b/compiler/passes/src/static_analysis/analyze_statement.rs index eeecadc43b..b6f0d717e4 100644 --- a/compiler/passes/src/static_analysis/analyze_statement.rs +++ b/compiler/passes/src/static_analysis/analyze_statement.rs @@ -15,28 +15,14 @@ // along with the Leo library. If not, see . use super::*; -use crate::{ConditionalTreeNode, TypeChecker, VariableSymbol, VariableType}; +use crate::{ConditionalTreeNode}; 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()); diff --git a/compiler/passes/src/static_analysis/analyzer.rs b/compiler/passes/src/static_analysis/analyzer.rs index 635f0232e1..e90da6ca00 100644 --- a/compiler/passes/src/static_analysis/analyzer.rs +++ b/compiler/passes/src/static_analysis/analyzer.rs @@ -15,12 +15,8 @@ // along with the Leo library. If not, see . use crate::{ - CallGraph, - StructGraph, SymbolTable, TypeTable, - VariableSymbol, - VariableType, static_analysis::await_checker::AwaitChecker, }; @@ -30,15 +26,11 @@ use leo_span::{Span, Symbol}; use snarkvm::console::network::Network; -use indexmap::{IndexMap, IndexSet}; -use itertools::Itertools; -use std::{cell::RefCell, marker::PhantomData}; +use std::{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, + pub(crate) symbol_table: &'a 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. @@ -47,12 +39,12 @@ pub struct StaticAnalyzer<'a, N: Network> { 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, + /// Whether or not a non-async external call has been seen in this function. + pub(crate) non_async_external_call_seen: bool, // Allows the type checker to be generic over the network. phantom: PhantomData, } @@ -60,48 +52,24 @@ pub struct StaticAnalyzer<'a, N: Network> { 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, + symbol_table: &'a SymbolTable, + _type_table: &'a TypeTable, handler: &'a Handler, max_depth: usize, disabled: bool, ) -> Self { Self { - symbol_table: RefCell::new(symbol_table), - type_table, + symbol_table, + type_table: _type_table, handler, await_checker: AwaitChecker::new(max_depth, !disabled), - scope_index: 0, current_program: None, variant: None, + non_async_external_call_seen: false, 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); @@ -123,14 +91,14 @@ impl<'a, N: Network> StaticAnalyzer<'a, N> { }; // 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(_)) { + match self.type_table.get(&future_variable.id) { + Some(type_) => { + if !matches!(type_, Type::Future(_)) { self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span())); } // Mark the future as consumed. - // If the call returns false, it means that a future was not awaited in the order of the input list, emit a warning. - if !self.await_checker.remove(future_variable) { + // If the call returns true, it means that a future was not awaited in the order of the input list, emit a warning. + if self.await_checker.remove(future_variable) { self.emit_warning(StaticAnalyzerWarning::future_not_awaited_in_order( future_variable.name, future_variable.span(), @@ -148,7 +116,8 @@ impl<'a, N: Network> StaticAnalyzer<'a, N> { pub(crate) fn assert_simple_async_transition_call(&self, program: Symbol, function_name: Symbol, span: Span) { // Note: The function symbol lookup is performed outside of the `if let Some(func) ...` block to avoid a RefCell lifetime bug in Rust. // Do not move it into the `if let Some(func) ...` block or it will keep `self.symbol_table_creation` alive for the entire block and will be very memory inefficient! - if let Some(function) = self.symbol_table.borrow().lookup_fn_symbol(Location::new(Some(program), function_name)) { + if let Some(function) = self.symbol_table.lookup_fn_symbol(Location::new(Some(program), function_name)) + { // Check that the function is an async function. if function.variant != Variant::AsyncFunction { return; @@ -171,7 +140,7 @@ impl<'a, N: Network> StaticAnalyzer<'a, N> { } } } - _ => () // Do nothing. + _ => (), // Do nothing. } } else { unreachable!("Type checking guarantees that this function exists.") diff --git a/compiler/passes/src/static_analysis/mod.rs b/compiler/passes/src/static_analysis/mod.rs index 523d488d4d..fc4ea20029 100644 --- a/compiler/passes/src/static_analysis/mod.rs +++ b/compiler/passes/src/static_analysis/mod.rs @@ -25,7 +25,7 @@ pub mod analyze_statement; pub mod analyzer; pub use analyzer::*; -use crate::{CallGraph, Pass, StructGraph, SymbolTable, TypeTable}; +use crate::{Pass, SymbolTable, TypeTable}; use leo_ast::{Ast, ProgramVisitor}; use leo_errors::{Result, emitter::Handler}; @@ -33,14 +33,12 @@ 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; + type Input = (&'a Ast, &'a Handler, &'a 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()) + handler.last_err().map_err(|e| *e) } } diff --git a/errors/src/errors/static_analyzer/static_analyzer_warning.rs b/errors/src/errors/static_analyzer/static_analyzer_warning.rs index 635d78ace9..316665d4df 100644 --- a/errors/src/errors/static_analyzer/static_analyzer_warning.rs +++ b/errors/src/errors/static_analyzer/static_analyzer_warning.rs @@ -48,7 +48,7 @@ create_messages!( @formatted future_not_awaited_in_order { args: (future_name: impl Display), - msg: format!("The future `{}` is not awaited in order.", future_name), + msg: format!("The future `{}` is not awaited in the order in which they were passed in to the `async` function.", future_name), help: Some("While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context.".to_string()), } ); diff --git a/tests/expectations/compiler/futures/future_not_all_awaited_fail.out b/tests/expectations/compiler/futures/future_not_all_awaited_fail.out index f7ef0cdbdb..f861b7771b 100644 --- a/tests/expectations/compiler/futures/future_not_all_awaited_fail.out +++ b/tests/expectations/compiler/futures/future_not_all_awaited_fail.out @@ -1,7 +1,7 @@ namespace = "Compile" expectation = "Fail" outputs = [""" -Error [ETYC0372093]: The following futures were never awaited: f4 +Error [ESAZ0374001]: The following futures were never awaited: f4 --> compiler-test:12:5 | 12 | async function finalize_foo(f0: Future, f1: Future, f2: Future, f3: Future, f4: Future, f5: Future) { @@ -14,4 +14,31 @@ Error [ETYC0372093]: The following futures were never awaited: f4 | ^ | = Ex: for `f: Future` call `f.await()` to await a future. -"""] +Warning [WSAZ0374003]: The future `f1` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:13:9 + | + 13 | f1.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Warning [WSAZ0374003]: The future `f2` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:14:9 + | + 14 | f2.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Warning [WSAZ0374003]: The future `f3` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:15:9 + | + 15 | f3.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Warning [WSAZ0374003]: The future `f5` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:17:9 + | + 17 | f5.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context."""] diff --git a/tests/expectations/compiler/futures/nested.out b/tests/expectations/compiler/futures/nested.out index b4d7cc2ee9..79ca32cc39 100644 --- a/tests/expectations/compiler/futures/nested.out +++ b/tests/expectations/compiler/futures/nested.out @@ -65,7 +65,7 @@ finalize main: add r0[0u32] r1[0u32] into r5; set r5 into ayo[1u32]; """, errors = "", warnings = """ -Warning [WTYC0372000]: Not all paths through the function await all futures. 2/4 paths contain at least one future that is never awaited. +Warning [WSAZ0374000]: Not all paths through the function await all futures. 2/4 paths contain at least one future that is never awaited. --> compiler-test:17:5 | 17 | async function finalize_main(f: Future, f2: Future, a: u32) { diff --git a/tests/expectations/compiler/futures/partial_type_specification.out b/tests/expectations/compiler/futures/partial_type_specification.out index 6c24e52e4d..771b8b318b 100644 --- a/tests/expectations/compiler/futures/partial_type_specification.out +++ b/tests/expectations/compiler/futures/partial_type_specification.out @@ -75,7 +75,7 @@ finalize main: add r0[0u32] r1[0u32] into r5; set r5 into ayo[1u32]; """, errors = "", warnings = """ -Warning [WTYC0372000]: Not all paths through the function await all futures. 2/4 paths contain at least one future that is never awaited. +Warning [WSAZ0374000]: Not all paths through the function await all futures. 2/4 paths contain at least one future that is never awaited. --> compiler-test:17:5 | 17 | async function finalize_main(f: Future, f2: Future, a: u32) { From 87072d7958cab625833b0ed7e651a5ee9204d6b5 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:19:41 -0800 Subject: [PATCH 6/7] Fix and pass tests --- compiler/compiler/src/compiler.rs | 2 +- .../src/static_analysis/analyze_expression.rs | 18 ++++-- .../src/static_analysis/analyze_program.rs | 3 +- .../src/static_analysis/analyze_statement.rs | 7 +-- .../passes/src/static_analysis/analyzer.rs | 62 +++++++++---------- .../src/static_analysis/await_checker.rs | 2 +- .../src/symbol_table_creation/creator.rs | 31 +++++++++- .../src/type_checking/check_expressions.rs | 8 +-- .../compiler/futures/await_out_of_order.out | 47 ++++++++++++++ .../compiler/futures/explicit_type_simple.out | 2 +- .../compiler/futures/future_in_tuple.out | 2 +- .../expectations/compiler/futures/nested.out | 6 +- .../futures/non_async_after_complex_async.out | 58 +++++++++++++++++ .../futures/non_async_before_async.out | 52 ++++++++++++++++ .../non_async_before_complex_async.out | 25 ++++++++ .../futures/partial_type_specification.out | 6 +- .../compiler/futures/pass_in_out_of_order.out | 40 ++++++++++++ .../expectations/compiler/futures/simple.out | 2 +- .../mappings/read_external_mapping.out | 2 +- .../external_struct_in_async_function.out | 2 +- .../compiler/futures/await_out_of_order.leo | 31 ++++++++++ .../futures/non_async_after_complex_async.leo | 53 ++++++++++++++++ .../futures/non_async_before_async.leo | 47 ++++++++++++++ .../non_async_before_complex_async.leo | 61 ++++++++++++++++++ .../compiler/futures/pass_in_out_of_order.leo | 31 ++++++++++ 25 files changed, 532 insertions(+), 68 deletions(-) create mode 100644 tests/expectations/compiler/futures/await_out_of_order.out create mode 100644 tests/expectations/compiler/futures/non_async_after_complex_async.out create mode 100644 tests/expectations/compiler/futures/non_async_before_async.out create mode 100644 tests/expectations/compiler/futures/non_async_before_complex_async.out create mode 100644 tests/expectations/compiler/futures/pass_in_out_of_order.out create mode 100644 tests/tests/compiler/futures/await_out_of_order.leo create mode 100644 tests/tests/compiler/futures/non_async_after_complex_async.leo create mode 100644 tests/tests/compiler/futures/non_async_before_async.leo create mode 100644 tests/tests/compiler/futures/non_async_before_complex_async.leo create mode 100644 tests/tests/compiler/futures/pass_in_out_of_order.leo diff --git a/compiler/compiler/src/compiler.rs b/compiler/compiler/src/compiler.rs index b7900a6304..de4076b04c 100644 --- a/compiler/compiler/src/compiler.rs +++ b/compiler/compiler/src/compiler.rs @@ -168,7 +168,7 @@ impl<'a, N: Network> Compiler<'a, N> { StaticAnalyzer::::do_pass(( &self.ast, self.handler, - &symbol_table, + symbol_table, &self.type_table, self.compiler_options.build.conditional_block_max_depth, self.compiler_options.build.disable_conditional_branch_type_checking, diff --git a/compiler/passes/src/static_analysis/analyze_expression.rs b/compiler/passes/src/static_analysis/analyze_expression.rs index 6c827ea6a3..6326ce61ad 100644 --- a/compiler/passes/src/static_analysis/analyze_expression.rs +++ b/compiler/passes/src/static_analysis/analyze_expression.rs @@ -14,13 +14,12 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{StaticAnalyzer}; +use crate::StaticAnalyzer; use leo_ast::*; use snarkvm::console::network::Network; - impl<'a, N: Network> ExpressionVisitor<'a> for StaticAnalyzer<'a, N> { type AdditionalInput = (); type Output = (); @@ -46,13 +45,20 @@ impl<'a, N: Network> ExpressionVisitor<'a> for StaticAnalyzer<'a, N> { Expression::Identifier(ident) => { // If the function call is an external async transition, then for all async calls that follow a non-async call, // we must check that the async call is not an async function that takes a future as an argument. - if self.non_async_external_call_seen && self.variant == Some(Variant::AsyncTransition) && input.program.is_some() { + if self.non_async_external_call_seen + && self.variant == Some(Variant::AsyncTransition) + && input.program.is_some() + { // Note that this unwrap is safe since we check that `input.program` is `Some` above. self.assert_simple_async_transition_call(input.program.unwrap(), ident.name, input.span()); } - // Otherwise if the variant is a non-async external call, update the flag. - else if self.variant == Some(Variant::Transition) { - self.non_async_external_call_seen = true; + // Otherwise look up the function and check if it is a non-async call. + if let Some(function_symbol) = + self.symbol_table.lookup_fn_symbol(Location::new(input.program, ident.name)) + { + if function_symbol.variant == Variant::Transition { + self.non_async_external_call_seen = true; + } } } _ => 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 index cd6eada544..c97a670230 100644 --- a/compiler/passes/src/static_analysis/analyze_program.rs +++ b/compiler/passes/src/static_analysis/analyze_program.rs @@ -14,14 +14,13 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{StaticAnalyzer}; +use crate::StaticAnalyzer; use leo_ast::{Type, *}; use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning}; use snarkvm::console::network::Network; - impl<'a, N: Network> ProgramVisitor<'a> for StaticAnalyzer<'a, N> { fn visit_program_scope(&mut self, input: &'a ProgramScope) { // Set the current program name. diff --git a/compiler/passes/src/static_analysis/analyze_statement.rs b/compiler/passes/src/static_analysis/analyze_statement.rs index b6f0d717e4..f35295a9a2 100644 --- a/compiler/passes/src/static_analysis/analyze_statement.rs +++ b/compiler/passes/src/static_analysis/analyze_statement.rs @@ -15,12 +15,9 @@ // along with the Leo library. If not, see . use super::*; -use crate::{ConditionalTreeNode}; - -use leo_ast::{ - *, -}; +use crate::ConditionalTreeNode; +use leo_ast::*; impl<'a, N: Network> StatementVisitor<'a> for StaticAnalyzer<'a, N> { fn visit_conditional(&mut self, input: &'a ConditionalStatement) { diff --git a/compiler/passes/src/static_analysis/analyzer.rs b/compiler/passes/src/static_analysis/analyzer.rs index e90da6ca00..6527c34069 100644 --- a/compiler/passes/src/static_analysis/analyzer.rs +++ b/compiler/passes/src/static_analysis/analyzer.rs @@ -14,11 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{ - SymbolTable, - TypeTable, - static_analysis::await_checker::AwaitChecker, -}; +use crate::{SymbolTable, TypeTable, static_analysis::await_checker::AwaitChecker}; use leo_ast::*; use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning, emitter::Handler}; @@ -26,7 +22,7 @@ use leo_span::{Span, Symbol}; use snarkvm::console::network::Network; -use std::{marker::PhantomData}; +use std::marker::PhantomData; pub struct StaticAnalyzer<'a, N: Network> { /// The symbol table for the program. @@ -114,36 +110,34 @@ impl<'a, N: Network> StaticAnalyzer<'a, N> { /// Assert that an async call is a "simple" one. /// Simple is defined as an async transition function which does not return a `Future` that itself takes a `Future` as an argument. pub(crate) fn assert_simple_async_transition_call(&self, program: Symbol, function_name: Symbol, span: Span) { - // Note: The function symbol lookup is performed outside of the `if let Some(func) ...` block to avoid a RefCell lifetime bug in Rust. - // Do not move it into the `if let Some(func) ...` block or it will keep `self.symbol_table_creation` alive for the entire block and will be very memory inefficient! - if let Some(function) = self.symbol_table.lookup_fn_symbol(Location::new(Some(program), function_name)) - { - // Check that the function is an async function. - if function.variant != Variant::AsyncFunction { - return; + // Look up the function. + let function = match self.symbol_table.lookup_fn_symbol(Location::new(Some(program), function_name)) { + Some(function) => function, + None => { + unreachable!("Type checking guarantees that this function exists."); } - - // A helper function to check if a `Future` type takes a `Future` as an argument. - let check_future = |future_type: &FutureType| { - if future_type.inputs.iter().any(|input| matches!(input, Type::Future(_))) { - self.emit_err(StaticAnalyzerError::async_transition_call_with_future_argument(function_name, span)); - } - }; - - // Check the output type of the function. - match &function.output_type { - Type::Future(future_type) => check_future(&future_type), - Type::Tuple(tuple_type) => { - for element in tuple_type.elements() { - if let Type::Future(future_type) = element { - check_future(&future_type); - } - } - } - _ => (), // Do nothing. + }; + // If it is not an async transition, return. + if function.variant != Variant::AsyncTransition { + return; + } + // Otherwise, get the location of the finalize block. + let location = match &function.finalize { + Some(location) => location.clone(), + None => { + unreachable!("Typechecking guarantees that all async transitions have an associated `finalize` field."); } - } else { - unreachable!("Type checking guarantees that this function exists.") + }; + // Look up the async function. + let async_function = match self.symbol_table.lookup_fn_symbol(location) { + Some(function) => function, + None => { + unreachable!("Type checking guarantees that this function exists."); + } + }; + // If the async function takes a future as an argument, emit an error. + if !async_function.future_inputs.is_empty() { + self.emit_err(StaticAnalyzerError::async_transition_call_with_future_argument(function_name, span)); } } } diff --git a/compiler/passes/src/static_analysis/await_checker.rs b/compiler/passes/src/static_analysis/await_checker.rs index 7440223aeb..a8086e13d0 100644 --- a/compiler/passes/src/static_analysis/await_checker.rs +++ b/compiler/passes/src/static_analysis/await_checker.rs @@ -44,7 +44,7 @@ impl AwaitChecker { // Can assume in finalize block. let is_not_first = if self.enabled { // Remove from dynamic list. - self.to_await.iter_mut().fold(false, |is_not_first, node| is_not_first || node.remove_element(&id.name)) + self.to_await.iter_mut().any(|node| node.remove_element(&id.name)) } else { false }; diff --git a/compiler/passes/src/symbol_table_creation/creator.rs b/compiler/passes/src/symbol_table_creation/creator.rs index aac9ae6bda..603c28a473 100644 --- a/compiler/passes/src/symbol_table_creation/creator.rs +++ b/compiler/passes/src/symbol_table_creation/creator.rs @@ -114,11 +114,36 @@ impl<'a> ProgramVisitor<'a> for SymbolTableCreator<'a> { } fn visit_function_stub(&mut self, input: &'a FunctionStub) { - if let Err(err) = - self.symbol_table.insert_fn(Location::new(self.program_name, input.name()), &Function::from(input.clone())) - { + // Construct the location for the function. + let location = Location::new(self.program_name, input.name()); + // Initalize the function symbol. + if let Err(err) = self.symbol_table.insert_fn(location.clone(), &Function::from(input.clone())) { self.handler.emit_err(err); } + // If the `FunctionStub` is an async transition, attach the finalize logic to the function. + if matches!(input.variant, Variant::AsyncTransition) { + // This matches the logic in the disassembler. + let name = Symbol::intern(&format!("finalize/{}", input.name())); + if let Err(err) = self.symbol_table.attach_finalize(location, Location::new(self.program_name, name)) { + self.handler.emit_err(err); + } + } + // Otherwise is the `FunctionStub` is an async function, attach the future inputs. + else if matches!(input.variant, Variant::AsyncFunction) { + let future_inputs = input + .input + .iter() + .filter_map(|input| match &input.type_ { + Type::Future(future_type) => future_type.location.clone(), + _ => None, + }) + .collect(); + // Note that this unwrap is safe, because `self.program_name` is set before traversing the AST. + if let Err(err) = self.symbol_table.insert_futures(self.program_name.unwrap(), input.name(), future_inputs) + { + self.handler.emit_err(err); + } + } } fn visit_struct_stub(&mut self, input: &'a Composite) { diff --git a/compiler/passes/src/type_checking/check_expressions.rs b/compiler/passes/src/type_checking/check_expressions.rs index 45cb332775..488ba84241 100644 --- a/compiler/passes/src/type_checking/check_expressions.rs +++ b/compiler/passes/src/type_checking/check_expressions.rs @@ -119,11 +119,9 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> { } // Await futures here so that can use the argument variable names to lookup. - if core_instruction == CoreFunction::FutureAwait { - if access.arguments.len() != 1 { - self.emit_err(TypeCheckerError::can_only_await_one_future_at_a_time(access.span)); - return Some(Type::Unit); - } + if core_instruction == CoreFunction::FutureAwait && access.arguments.len() != 1 { + self.emit_err(TypeCheckerError::can_only_await_one_future_at_a_time(access.span)); + return Some(Type::Unit); } return return_type; } else { diff --git a/tests/expectations/compiler/futures/await_out_of_order.out b/tests/expectations/compiler/futures/await_out_of_order.out new file mode 100644 index 0000000000..a8f20ac29b --- /dev/null +++ b/tests/expectations/compiler/futures/await_out_of_order.out @@ -0,0 +1,47 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "c05e8ce7b527a0192d65e50963afd67a1fe92c837da97a5299c22af29f0275dd", type_checked_symbol_table = "8b2f1b5c22979924b6e08419e57fb515c05d3ce6932a9d1c1533ed4a2927aec2", unrolled_symbol_table = "8b2f1b5c22979924b6e08419e57fb515c05d3ce6932a9d1c1533ed4a2927aec2", initial_ast = "713c85bcb7d5fef43e4626a09e4fe3858f76de6025fcae475cb0398c26b5d123", unrolled_ast = "713c85bcb7d5fef43e4626a09e4fe3858f76de6025fcae475cb0398c26b5d123", ssa_ast = "5e2a213bd10e28dad299e0e48ac3336484fd2ffd894eeb63d15d67e6af65195e", flattened_ast = "11828763a38326b604155e1074699e6ca85205fbc83167d554624d0fe5d8bb2b", destructured_ast = "83c9eaa9aef53de6143980c9e001e59da64c291a7b2aa0693139b868339e589c", inlined_ast = "dda1aade2a50a9f25571004c8a68e3ff90efadd6a567ce25b1edbbd2e82b59d7", dce_ast = "dda1aade2a50a9f25571004c8a68e3ff90efadd6a567ce25b1edbbd2e82b59d7", bytecode = """ +program test.aleo; + +mapping foo: + key as u32.public; + value as u32.public; + +function main_inner: + input r0 as u32.public; + input r1 as u32.public; + async main_inner r0 r1 into r2; + output r2 as test.aleo/main_inner.future; + +finalize main_inner: + input r0 as u32.public; + input r1 as u32.public; + set r1 into foo[r0]; +""", errors = "", warnings = "" }, + { initial_symbol_table = "748f1d6d760d63c1eb80d518379d99323e13132bf77520f8442534c79c64895b", type_checked_symbol_table = "3c5970cf8251ed27741614785eb8871b1df48e9437da1cc425c9c85448f63ed3", unrolled_symbol_table = "3c5970cf8251ed27741614785eb8871b1df48e9437da1cc425c9c85448f63ed3", initial_ast = "c1dddbcc56c1c3e89c4633a45eac54ca710cbb206f002e3cc8a4312342d5e43e", unrolled_ast = "1717122f03eb439466cb78560d07bb79e508a7549ce60de07bb76b1c0ccefdad", ssa_ast = "c53df47cb4b84b067fea09e1b1dbadeb7751b57c6624961d5df4317f609eddd1", flattened_ast = "ba297f2543a11739a669f85af00f9bae6b9149c6860d895216f1e5bd96617642", destructured_ast = "8531973ac60ebf3c0d7884bd0aa36794242a6575c379113053c01ca8f11a805f", inlined_ast = "48b3da6f8d8a056bb2e18f2b1b3d83d8a00ed4d259d65ad86321b824390141e9", dce_ast = "48b3da6f8d8a056bb2e18f2b1b3d83d8a00ed4d259d65ad86321b824390141e9", bytecode = """ +import test.aleo; +program basic.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call test.aleo/main_inner 0u32 0u32 into r2; + call test.aleo/main_inner 1u32 1u32 into r3; + async main r2 r3 into r4; + output r4 as basic.aleo/main.future; + +finalize main: + input r0 as test.aleo/main_inner.future; + input r1 as test.aleo/main_inner.future; + await r1; + await r0; +""", errors = "", warnings = """ +Warning [WSAZ0374003]: The future `f2` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:13:9 + | + 13 | f2.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context.""" }, +] }]] diff --git a/tests/expectations/compiler/futures/explicit_type_simple.out b/tests/expectations/compiler/futures/explicit_type_simple.out index 89b6b4df68..909c621b46 100644 --- a/tests/expectations/compiler/futures/explicit_type_simple.out +++ b/tests/expectations/compiler/futures/explicit_type_simple.out @@ -20,7 +20,7 @@ finalize main_inner: input r0 as u32.public; set 1u32 into foo[r0]; """, errors = "", warnings = "" }, - { initial_symbol_table = "ad49aec92f87f1e65648a7ae10d5bfb563f50bb397a933a9852c979b4ed5e3f3", type_checked_symbol_table = "7a05bbb86250bee3f69eee0c1f46f9d506dcb57c74358c26d158bde771b29bd7", unrolled_symbol_table = "7a05bbb86250bee3f69eee0c1f46f9d506dcb57c74358c26d158bde771b29bd7", initial_ast = "f4b27c45b21e659b2b730a167dbbf8a309b19e71beded7108cb7267b06177417", unrolled_ast = "bdd7c6800831eebcb6a09cb05acd5be0ad83730e1d210eb4d9b4d6b968d0b326", ssa_ast = "e4441d4a0d42e1061d4481bce0113ebd8a6f258dc9e877adc5e52029d3f04991", flattened_ast = "82cca8f1537803acde719f029a4ac265e0c1c53fa6e8cd4e4e2800a4d840c871", destructured_ast = "aee30ce903740d4f39c7f88aae66ed0bca4affce5b51988699cc9167ff946494", inlined_ast = "f4292c099047c4d8e3c0fbdaf7f32a1273a3eb68c4a11b0eccff59bd7c804247", dce_ast = "406a8d3de9427c696512e49e8f7ab27d48616754516e535152dc13c15a3e1ee0", bytecode = """ + { initial_symbol_table = "4dd8afce563dd05c782ba0046cf84da0e161b0db932b993995bc119a0828174c", type_checked_symbol_table = "01e4b42297bfc584587cb7a0ebab2790e0fd87d7c645cdc55a8d6dcc6f48360f", unrolled_symbol_table = "01e4b42297bfc584587cb7a0ebab2790e0fd87d7c645cdc55a8d6dcc6f48360f", initial_ast = "f4b27c45b21e659b2b730a167dbbf8a309b19e71beded7108cb7267b06177417", unrolled_ast = "bdd7c6800831eebcb6a09cb05acd5be0ad83730e1d210eb4d9b4d6b968d0b326", ssa_ast = "e4441d4a0d42e1061d4481bce0113ebd8a6f258dc9e877adc5e52029d3f04991", flattened_ast = "82cca8f1537803acde719f029a4ac265e0c1c53fa6e8cd4e4e2800a4d840c871", destructured_ast = "aee30ce903740d4f39c7f88aae66ed0bca4affce5b51988699cc9167ff946494", inlined_ast = "f4292c099047c4d8e3c0fbdaf7f32a1273a3eb68c4a11b0eccff59bd7c804247", dce_ast = "406a8d3de9427c696512e49e8f7ab27d48616754516e535152dc13c15a3e1ee0", bytecode = """ import test.aleo; program basic.aleo; diff --git a/tests/expectations/compiler/futures/future_in_tuple.out b/tests/expectations/compiler/futures/future_in_tuple.out index 09ddba831c..e475a7a58c 100644 --- a/tests/expectations/compiler/futures/future_in_tuple.out +++ b/tests/expectations/compiler/futures/future_in_tuple.out @@ -19,7 +19,7 @@ function transfer_private_to_public: finalize transfer_private_to_public: assert.eq 1u8 1u8; """, errors = "", warnings = "" }, - { initial_symbol_table = "fd67d75af194fb6d6fee5a2b15b4b51ae5511e5d0546c6c6f83063611a168123", type_checked_symbol_table = "031e9fc89b17624e259bb154ca42385665d2cf4349bf1579347a2d2487305a1b", unrolled_symbol_table = "031e9fc89b17624e259bb154ca42385665d2cf4349bf1579347a2d2487305a1b", initial_ast = "fc9f1985c1e0441e9423e67cfd4cb8252178ccc236dfabae17187c5a5cc98ebe", unrolled_ast = "c6fdd37447ee674a058e7fe314096c0df8cf0c02f307ff499e0f08b76cdc6709", ssa_ast = "d26ea69b3993a2a3c4b2660a27706c51383f9b01357d27adf6275a5dfffe6e9d", flattened_ast = "5741efe1907a4da96fbad021b725a22e8c3365fa61b2413b06743c3ed01cda35", destructured_ast = "496bea9fd498c2d4ac9d93dd143beb403e13fdf59fc2ff842d8ff932883feda1", inlined_ast = "7c87cc964f8225fd91c634c8683ee0b09aaa301cb29ab85cadc4e4aea65253ba", dce_ast = "7c87cc964f8225fd91c634c8683ee0b09aaa301cb29ab85cadc4e4aea65253ba", bytecode = """ + { initial_symbol_table = "baa9875274a09ad91eb08326f18797401a6e98c32388e75b3b406a539acab343", type_checked_symbol_table = "610cf3eeddc2789f19854347864fbaae2dc10ecc0aae8034fe3eaaa2394ba89f", unrolled_symbol_table = "610cf3eeddc2789f19854347864fbaae2dc10ecc0aae8034fe3eaaa2394ba89f", initial_ast = "fc9f1985c1e0441e9423e67cfd4cb8252178ccc236dfabae17187c5a5cc98ebe", unrolled_ast = "c6fdd37447ee674a058e7fe314096c0df8cf0c02f307ff499e0f08b76cdc6709", ssa_ast = "d26ea69b3993a2a3c4b2660a27706c51383f9b01357d27adf6275a5dfffe6e9d", flattened_ast = "5741efe1907a4da96fbad021b725a22e8c3365fa61b2413b06743c3ed01cda35", destructured_ast = "496bea9fd498c2d4ac9d93dd143beb403e13fdf59fc2ff842d8ff932883feda1", inlined_ast = "7c87cc964f8225fd91c634c8683ee0b09aaa301cb29ab85cadc4e4aea65253ba", dce_ast = "7c87cc964f8225fd91c634c8683ee0b09aaa301cb29ab85cadc4e4aea65253ba", bytecode = """ import credits.aleo; program test_credits.aleo; diff --git a/tests/expectations/compiler/futures/nested.out b/tests/expectations/compiler/futures/nested.out index 79ca32cc39..6a6bd4abf5 100644 --- a/tests/expectations/compiler/futures/nested.out +++ b/tests/expectations/compiler/futures/nested.out @@ -24,7 +24,7 @@ finalize main_dep: input r1 as u32.public; set r1 into Yo[r0]; """, errors = "", warnings = "" }, - { initial_symbol_table = "837e6e9f7a93af9d92cb90208d54a4e55693939bccddf588c94102805a600ec2", type_checked_symbol_table = "c33e10eabb14d2d0dc8a7ffd7370dcda4d0467b46dc00d9a526c0cf7fc373906", unrolled_symbol_table = "c33e10eabb14d2d0dc8a7ffd7370dcda4d0467b46dc00d9a526c0cf7fc373906", initial_ast = "64089bd9ecc0ab9ce224328c7ba9b2ece577f585b2417b48eb0883ec8cec304c", unrolled_ast = "450bb73f7249477591a716a45cbd0fbb332d98a8765b2804ca919488cbc7e1bf", ssa_ast = "d445e67098ada41b7ada11f69a07acf107d1b8e6ab052e7bb3e8d1b6530c4371", flattened_ast = "b3e5d4d940f433b770b6acdd85c2a5f1de7327617f71783b75108c2a515c12a1", destructured_ast = "36361778b1d97dcde52548c1e082ad7382dbe6e6be4fd6be1fdc73bb213d0016", inlined_ast = "b358e9fa7f234ae1154b48cbd83c3e2029c1a83c5298470035729f78537e03a6", dce_ast = "4d6d5c792f8d7a9d83e0c1bee6efcf24470e92fd4746aa7a9d0afabc93ec8a19", bytecode = """ + { initial_symbol_table = "354ff959a0573f0946a92f8626af3fd47b9ee3165f9b3971d60040e00d1e078f", type_checked_symbol_table = "f532890dcb4613a7f23287cebd8594b9f053e1d80ceba9ca9566c40925df460b", unrolled_symbol_table = "f532890dcb4613a7f23287cebd8594b9f053e1d80ceba9ca9566c40925df460b", initial_ast = "64089bd9ecc0ab9ce224328c7ba9b2ece577f585b2417b48eb0883ec8cec304c", unrolled_ast = "450bb73f7249477591a716a45cbd0fbb332d98a8765b2804ca919488cbc7e1bf", ssa_ast = "d445e67098ada41b7ada11f69a07acf107d1b8e6ab052e7bb3e8d1b6530c4371", flattened_ast = "b3e5d4d940f433b770b6acdd85c2a5f1de7327617f71783b75108c2a515c12a1", destructured_ast = "36361778b1d97dcde52548c1e082ad7382dbe6e6be4fd6be1fdc73bb213d0016", inlined_ast = "b358e9fa7f234ae1154b48cbd83c3e2029c1a83c5298470035729f78537e03a6", dce_ast = "4d6d5c792f8d7a9d83e0c1bee6efcf24470e92fd4746aa7a9d0afabc93ec8a19", bytecode = """ import test_dep.aleo; program test.aleo; @@ -86,7 +86,7 @@ Warning [WSAZ0374000]: Not all paths through the function await all futures. 2/4 | ^ | = Ex: `f.await()` to await a future. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.""" }, - { initial_symbol_table = "11d73259b527776fa2019508fa961ca24850cc2bd0fbbcebfb7310c565289560", type_checked_symbol_table = "fb91e05612819b16dc6a1fb37cd80f776918dc1f502feca4d9428f42dc21754d", unrolled_symbol_table = "fb91e05612819b16dc6a1fb37cd80f776918dc1f502feca4d9428f42dc21754d", initial_ast = "05de2b0dcfd85ec6446f4507492e26b2093e771f44c497f92a24d6fff5e8c864", unrolled_ast = "4f09dae0678393afc3cbc5592159df83ca22b947084d3c8e779281724d07a2ca", ssa_ast = "0cb5c531ad471909089716ef6c7382fb3fcbb82dafb6edef541e4f7cff4fb8ba", flattened_ast = "46d54d4d9fe36538d34ac306780262ee1f54a6141aa2281ef7ae74ffcf4dddcf", destructured_ast = "88653b95656b6f56872d7ea452491322e4c122909879b72856b891c474aa8342", inlined_ast = "0f81029815dec13a526530eeea0e92e6eb61313421ce5a7b46ed3739d62beaf6", dce_ast = "6b852bcf601b323678eea14e096f49c72f8800d18ec811b00c31817daf630d63", bytecode = """ + { initial_symbol_table = "539fd89c0dea3feb2b2b3844532aacc032658f2546cd011dd1aa257eb18dc0af", type_checked_symbol_table = "b3785d02f513f6e1671e4c42f2674c7ecb8da3f2c8bd20d36024d7d2e1322217", unrolled_symbol_table = "b3785d02f513f6e1671e4c42f2674c7ecb8da3f2c8bd20d36024d7d2e1322217", initial_ast = "05de2b0dcfd85ec6446f4507492e26b2093e771f44c497f92a24d6fff5e8c864", unrolled_ast = "4f09dae0678393afc3cbc5592159df83ca22b947084d3c8e779281724d07a2ca", ssa_ast = "0cb5c531ad471909089716ef6c7382fb3fcbb82dafb6edef541e4f7cff4fb8ba", flattened_ast = "46d54d4d9fe36538d34ac306780262ee1f54a6141aa2281ef7ae74ffcf4dddcf", destructured_ast = "88653b95656b6f56872d7ea452491322e4c122909879b72856b891c474aa8342", inlined_ast = "0f81029815dec13a526530eeea0e92e6eb61313421ce5a7b46ed3739d62beaf6", dce_ast = "6b852bcf601b323678eea14e096f49c72f8800d18ec811b00c31817daf630d63", bytecode = """ import test_dep.aleo; import test.aleo; program wrapper.aleo; @@ -109,7 +109,7 @@ finalize main: await r1; await r2; """, errors = "", warnings = "" }, - { initial_symbol_table = "04a3a0ccbf4ed061d19da4e624725caff0e64ac838498cbd09df865f4f9044f2", type_checked_symbol_table = "69550e476553614e01dd39df0b3a8f682556cdf76982503af0e6a77d4916e027", unrolled_symbol_table = "69550e476553614e01dd39df0b3a8f682556cdf76982503af0e6a77d4916e027", initial_ast = "bf4f5dac2e3cac6f6c8b117a93b7bc9a4b9d31f66b3b0d946866da23003e6a69", unrolled_ast = "a1786c230d46f3b207f118aaaaea373cd1d9935aa7e63b99e403a8faf36df2fe", ssa_ast = "82581ca24afcd79d3e3c1346009981d4a9d3d227afc0540707b6c315ecdce107", flattened_ast = "2ff2d69c6199a5c70a8ffb96d8dc0529f6f1fbf631a1f690169d2d9162e91689", destructured_ast = "8da4c7c91fabf5edb6768e616f223e574b3415c848321f66ad9e587b76259210", inlined_ast = "a740025e070d37bd22f264e37dfd6802eb9e1b10c12c928a08acd14fbe9043d6", dce_ast = "e127a5223a49f123398009b927e96ebb44f266df7271feb7b1ff5f7f748e6ff5", bytecode = """ + { initial_symbol_table = "07bdfd403caa73ec17903694bb68a93e108011dc9d77e555fd2815e4da90a1de", type_checked_symbol_table = "9e2a9214686265f8ebc82b39d1102894360f03fd7e0f1cf3f8f8dc1cf463f0c6", unrolled_symbol_table = "9e2a9214686265f8ebc82b39d1102894360f03fd7e0f1cf3f8f8dc1cf463f0c6", initial_ast = "bf4f5dac2e3cac6f6c8b117a93b7bc9a4b9d31f66b3b0d946866da23003e6a69", unrolled_ast = "a1786c230d46f3b207f118aaaaea373cd1d9935aa7e63b99e403a8faf36df2fe", ssa_ast = "82581ca24afcd79d3e3c1346009981d4a9d3d227afc0540707b6c315ecdce107", flattened_ast = "2ff2d69c6199a5c70a8ffb96d8dc0529f6f1fbf631a1f690169d2d9162e91689", destructured_ast = "8da4c7c91fabf5edb6768e616f223e574b3415c848321f66ad9e587b76259210", inlined_ast = "a740025e070d37bd22f264e37dfd6802eb9e1b10c12c928a08acd14fbe9043d6", dce_ast = "e127a5223a49f123398009b927e96ebb44f266df7271feb7b1ff5f7f748e6ff5", bytecode = """ import test_dep.aleo; import test.aleo; import wrapper.aleo; diff --git a/tests/expectations/compiler/futures/non_async_after_complex_async.out b/tests/expectations/compiler/futures/non_async_after_complex_async.out new file mode 100644 index 0000000000..1a915a53a8 --- /dev/null +++ b/tests/expectations/compiler/futures/non_async_after_complex_async.out @@ -0,0 +1,58 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "6cd6500abf0e8651ba271e6aa7703e19da9c183c54cc5304ecbc59bf9336fce8", type_checked_symbol_table = "75075f8b61db1c78b075b1b7b042f351d4b406457277b05e2f4f531e19eed0d0", unrolled_symbol_table = "75075f8b61db1c78b075b1b7b042f351d4b406457277b05e2f4f531e19eed0d0", initial_ast = "242372b2feb8f6e98c77aa4bb4a891130a5ceb5c07ec724cce9c4dde4fd49208", unrolled_ast = "242372b2feb8f6e98c77aa4bb4a891130a5ceb5c07ec724cce9c4dde4fd49208", ssa_ast = "2787cc66971dcd0423755380ff9f4adc0a4ec03825407ff9fda2f2c731179c75", flattened_ast = "cf44ace46cbe7e60114c27ac8c6fbbdd2097f599134bdfe37822f3d4093b9f8a", destructured_ast = "72f55596cce2dab4e8fd8f2a8eff12931cd159f6eb5585dc13334ff20ad36f87", inlined_ast = "fc67b7df09f254e6c1fd19bc3005be38ce306eea369efa384ecba6b38639a638", dce_ast = "fc67b7df09f254e6c1fd19bc3005be38ce306eea369efa384ecba6b38639a638", bytecode = """ +program inner.aleo; + +mapping foo: + key as u32.public; + value as u32.public; + +function inner: + input r0 as u32.private; + async inner r0 into r1; + output r1 as inner.aleo/inner.future; + +finalize inner: + input r0 as u32.public; + set r0 into foo[0u32]; +""", errors = "", warnings = "" }, + { initial_symbol_table = "386cb81f5601365a2c421d1a78a1bcfaa78fd6c3172666c6a9d1c49b02233d6e", type_checked_symbol_table = "7ad49c58b4cdb6eadc16c5c55f66dbc6dacde2fb624e91b374e60ce5ad8b2425", unrolled_symbol_table = "7ad49c58b4cdb6eadc16c5c55f66dbc6dacde2fb624e91b374e60ce5ad8b2425", initial_ast = "f25d550819ceefd793a9711f96460c97c78ca1aeb7da21d3105dbcb06020ea8f", unrolled_ast = "b04609dab316adc708b481faf5241c5a67cb9fd3cbfa21531788b6bc01c9c8e6", ssa_ast = "e3f3822d24d05d2b393d2311ad1a115c69b816050fbd5c5b095f2713573d2030", flattened_ast = "ee6255715b49f761e5f12a23d7e25b968eb29821f9c2c6ee91a040ab7b7a0a0c", destructured_ast = "3d349126a6c352d726734cfcdd72affbe58584734f65c77e819ee1b171a135d3", inlined_ast = "cd9854c1633542668ffe6bf0e1f00de68f99ed21fddae01d161f2887e2e18180", dce_ast = "cd9854c1633542668ffe6bf0e1f00de68f99ed21fddae01d161f2887e2e18180", bytecode = """ +import inner.aleo; +program mid.aleo; + +function mid: + input r0 as u32.private; + call inner.aleo/inner 0u32 into r1; + call inner.aleo/inner 1u32 into r2; + async mid r2 r1 into r3; + output r3 as mid.aleo/mid.future; + +finalize mid: + input r0 as inner.aleo/inner.future; + input r1 as inner.aleo/inner.future; + await r0; + await r1; + +function dummy: +""", errors = "", warnings = "" }, + { initial_symbol_table = "f662e7a456e22a2d1b4b42fa3221de16e5671edc25c1d0b20610dfd0ab55c579", type_checked_symbol_table = "bc09a36414412397442b9bf24d95ddbb21d0bf2d1b70f7084df400bdd836e37a", unrolled_symbol_table = "bc09a36414412397442b9bf24d95ddbb21d0bf2d1b70f7084df400bdd836e37a", initial_ast = "5df535c99668c958f5649f0e5d24ae951023b165941ded5e9df3665a1c4bdd7d", unrolled_ast = "57200953ba2c83408d2dbc51c10e7c01143b6ed3f3dcf96616e7072ac99e2152", ssa_ast = "223b30eb9d800a33aab6105dea3b4dde8bc3435673b1be29ab4268e944406384", flattened_ast = "2ed49413ee703e36ee432a5f271fecb3327be45039477ee9bc2bc6ef77e25f41", destructured_ast = "26b81f6ad2dab39e3a9a5e1d73ebff1f5a165f794897fd770ed0f7927a34bf95", inlined_ast = "5dc7a113088ff1f7682c9b5618e381baa011609a7f2e1a876272c1fd79b6dfd3", dce_ast = "5dc7a113088ff1f7682c9b5618e381baa011609a7f2e1a876272c1fd79b6dfd3", bytecode = """ +import inner.aleo; +import mid.aleo; +program outer.aleo; + +function outer: + input r0 as u32.private; + call mid.aleo/mid 0u32 into r1; + call mid.aleo/mid 1u32 into r2; + call mid.aleo/dummy; + async outer r1 r2 into r3; + output r3 as outer.aleo/outer.future; + +finalize outer: + input r0 as mid.aleo/mid.future; + input r1 as mid.aleo/mid.future; + await r0; + await r1; +""", errors = "", warnings = "" }, +] }]] diff --git a/tests/expectations/compiler/futures/non_async_before_async.out b/tests/expectations/compiler/futures/non_async_before_async.out new file mode 100644 index 0000000000..45ddfae933 --- /dev/null +++ b/tests/expectations/compiler/futures/non_async_before_async.out @@ -0,0 +1,52 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "16e119df6ee0ccf0c2a2eaa55c81867664630b3468627f778098d1ff0c8cd5fa", type_checked_symbol_table = "f4fb482aeb7abb52f5ca3aae4f42a53b98f7403ec1d2c488996efb1ff11f213b", unrolled_symbol_table = "f4fb482aeb7abb52f5ca3aae4f42a53b98f7403ec1d2c488996efb1ff11f213b", initial_ast = "5a07ca97b09d15a8549692408a74444dc346fbd3c8d08b1e3fa4dc60d2a0a05c", unrolled_ast = "5a07ca97b09d15a8549692408a74444dc346fbd3c8d08b1e3fa4dc60d2a0a05c", ssa_ast = "50c140777b792e917824e9021e722774b3e037f2d97c9d0c59a14b2c5088c98b", flattened_ast = "f6f59f2f6e0f8b8c933ecdb0d2360cd9c53a2ba10486c2935f72140b48b68927", destructured_ast = "f5851f2b9ebf08030bf8a9778e0c52c85f61b0a32f3eed802897da99c48a29bd", inlined_ast = "8051736585fdd624f74052e44368eef86e1a7e9533152406503a5737939c4e1e", dce_ast = "8051736585fdd624f74052e44368eef86e1a7e9533152406503a5737939c4e1e", bytecode = """ +program test.aleo; + +mapping foo: + key as u32.public; + value as u32.public; + +function main_inner: + input r0 as u32.public; + input r1 as u32.public; + async main_inner r0 r1 into r2; + output r2 as test.aleo/main_inner.future; + +finalize main_inner: + input r0 as u32.public; + input r1 as u32.public; + set r1 into foo[r0]; + +function baz: + input r0 as u32.private; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; +""", errors = "", warnings = "" }, + { initial_symbol_table = "42d242bf08b1ff58a2640d65713dcb4783a7b59c9fe35af1863b819c33e6b9f2", type_checked_symbol_table = "f28e9e26827f070928d22fcd6ab9e070dec65d97d4e6b3333959e75ca91a9953", unrolled_symbol_table = "f28e9e26827f070928d22fcd6ab9e070dec65d97d4e6b3333959e75ca91a9953", initial_ast = "959ef8008a2cb837e0f067c6954356034892bed1a8dcda72224f08e360a1c791", unrolled_ast = "f5a2e49f992ab80a104ceae85eb1c0a34c094ee9012a7ca9d5d6406f050515c4", ssa_ast = "a70cc7f42605c3f72904723c4c8237e3eada556f617588f9bde00e07d58c2cd2", flattened_ast = "02f4563ed934754a6db1939c5b3017356e63e4deaeafb66091c46500a534d8ab", destructured_ast = "56153136bc984183c654e6a4fcc8bd607c801b0a92a502d4c008360e6b816c54", inlined_ast = "573187f8b898181cc4f1c59ca1dc960b4767fa818591ccd3189eee82fc698155", dce_ast = "573187f8b898181cc4f1c59ca1dc960b4767fa818591ccd3189eee82fc698155", bytecode = """ +import test.aleo; +program basic.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call test.aleo/baz r0 r1 into r2; + assert.eq r2 1u32; + call test.aleo/main_inner 0u32 0u32 into r3; + call test.aleo/baz r0 r1 into r4; + assert.eq r4 1u32; + call test.aleo/main_inner 1u32 1u32 into r5; + call test.aleo/baz r0 r1 into r6; + assert.eq r6 1u32; + async main r3 r5 into r7; + output r7 as basic.aleo/main.future; + +finalize main: + input r0 as test.aleo/main_inner.future; + input r1 as test.aleo/main_inner.future; + await r0; + await r1; +""", errors = "", warnings = "" }, +] }]] diff --git a/tests/expectations/compiler/futures/non_async_before_complex_async.out b/tests/expectations/compiler/futures/non_async_before_complex_async.out new file mode 100644 index 0000000000..f61b1045e3 --- /dev/null +++ b/tests/expectations/compiler/futures/non_async_before_complex_async.out @@ -0,0 +1,25 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ESAZ0374004]: The call to mid will result in failed executions on-chain. + --> compiler-test:8:26 + | + 8 | let f1: Future = mid.aleo/mid(0u32); + | ^^^^^^^^^^^^^^^^^^ + | + = There is a subtle error that occurs if an async transition call follows a non-async transition call, and the async call returns a `Future` that itself takes a `Future` as an input. See See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Error [ESAZ0374004]: The call to mid will result in failed executions on-chain. + --> compiler-test:9:26 + | + 9 | let f2: Future = mid.aleo/mid(1u32); + | ^^^^^^^^^^^^^^^^^^ + | + = There is a subtle error that occurs if an async transition call follows a non-async transition call, and the async call returns a `Future` that itself takes a `Future` as an input. See See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Error [ESAZ0374004]: The call to mid will result in failed executions on-chain. + --> compiler-test:17:26 + | + 17 | let f2: Future = mid.aleo/mid(1u32); + | ^^^^^^^^^^^^^^^^^^ + | + = There is a subtle error that occurs if an async transition call follows a non-async transition call, and the async call returns a `Future` that itself takes a `Future` as an input. See See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +"""] diff --git a/tests/expectations/compiler/futures/partial_type_specification.out b/tests/expectations/compiler/futures/partial_type_specification.out index 771b8b318b..e80a77be15 100644 --- a/tests/expectations/compiler/futures/partial_type_specification.out +++ b/tests/expectations/compiler/futures/partial_type_specification.out @@ -34,7 +34,7 @@ function main_dep_2: finalize main_dep_2: set 1u32 into Yo[1u32]; """, errors = "", warnings = "" }, - { initial_symbol_table = "1a537ce4873945cd8969e08fd2440d3d9dbf4175306e7a60a18f59305958366e", type_checked_symbol_table = "3c670b67da9da6028e642d487a1382f3de1b554c8c0d51fc531b71e36b5cdef5", unrolled_symbol_table = "3c670b67da9da6028e642d487a1382f3de1b554c8c0d51fc531b71e36b5cdef5", initial_ast = "bcfa98eafaf355e7313773fa4340b88d2530e3d2b279252fc1117327de42d77a", unrolled_ast = "01a9f5e11f5749b408619a513bf7f9eececfd83f9f87c883fcd8db53440babab", ssa_ast = "b6da9c41019a2af6cd137e29fe7b5041cc13a45d574b920101a69f7093c58980", flattened_ast = "7bddc7f16b5ef5baef1fc50ac2f45767844d05fc0de797d267c77306bc586dc5", destructured_ast = "df2c950dd52d4094ef1f2d364aa6dd57020f7ca431ead915353c2c33482ee05d", inlined_ast = "7dd0bb6eee84d038c01e43a8c7fdfd38ec3cbb269bf4990078a49e5202fe177e", dce_ast = "4378a2b09abc850959d98704efb7ec28bd6ad7962cc4ec761e26e57400cec8a0", bytecode = """ + { initial_symbol_table = "1efe1fca5533564cb10347a951f503433d841d861e7eae035d3a86912ac5e475", type_checked_symbol_table = "5c2b623a5f1f09b4bd46ab77d25c40a1966d596f6cc41f7c4d93f883094ad185", unrolled_symbol_table = "5c2b623a5f1f09b4bd46ab77d25c40a1966d596f6cc41f7c4d93f883094ad185", initial_ast = "bcfa98eafaf355e7313773fa4340b88d2530e3d2b279252fc1117327de42d77a", unrolled_ast = "01a9f5e11f5749b408619a513bf7f9eececfd83f9f87c883fcd8db53440babab", ssa_ast = "b6da9c41019a2af6cd137e29fe7b5041cc13a45d574b920101a69f7093c58980", flattened_ast = "7bddc7f16b5ef5baef1fc50ac2f45767844d05fc0de797d267c77306bc586dc5", destructured_ast = "df2c950dd52d4094ef1f2d364aa6dd57020f7ca431ead915353c2c33482ee05d", inlined_ast = "7dd0bb6eee84d038c01e43a8c7fdfd38ec3cbb269bf4990078a49e5202fe177e", dce_ast = "4378a2b09abc850959d98704efb7ec28bd6ad7962cc4ec761e26e57400cec8a0", bytecode = """ import test_dep.aleo; program test.aleo; @@ -96,7 +96,7 @@ Warning [WSAZ0374000]: Not all paths through the function await all futures. 2/4 | ^ | = Ex: `f.await()` to await a future. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.""" }, - { initial_symbol_table = "04f7d3a44d791763aec79b596224c653e682ab928bc0cba71a1cd6282198e885", type_checked_symbol_table = "d9d3363d1049a924bbae356d0f90ac3c9bfca7f6ae5ba51ad915d66e9d0b9a1e", unrolled_symbol_table = "d9d3363d1049a924bbae356d0f90ac3c9bfca7f6ae5ba51ad915d66e9d0b9a1e", initial_ast = "856e56d95eaf14f6e9241001763546b7d982402ac87521e2ec3b7ea476764692", unrolled_ast = "75b69748ca1e534c95cf084164773d471f51537b50b2d517dc4be26dddb06e1b", ssa_ast = "6d38bf225e9cf5af37b9d6c595c2973ec31a32d227ca65cb590d27400d442780", flattened_ast = "65fb4138701cad86a5fcd7e024645e833aeb6e88b3ea2a3a6b69269fd1d77620", destructured_ast = "85a81c23da7e97b057ddf4ef71f375781e1dfcb90d656d694a5aa0f0c176b497", inlined_ast = "a1b2367575e170a79ace2ac7ff071bc3c770476b37ee149310c3b2cfe67b1c7f", dce_ast = "f46fa7963b327b9c75c9f7a7569e350d7f62c21964cb5df140cd2186c2043697", bytecode = """ + { initial_symbol_table = "cd14d130e4f9b9b92f731cb8caee6237ae4477573ea636af8775e5e02966390e", type_checked_symbol_table = "387eabcc94db2a5021555b90e0dddcb7373aa3344fe7bbc86087f32f0893fa35", unrolled_symbol_table = "387eabcc94db2a5021555b90e0dddcb7373aa3344fe7bbc86087f32f0893fa35", initial_ast = "856e56d95eaf14f6e9241001763546b7d982402ac87521e2ec3b7ea476764692", unrolled_ast = "75b69748ca1e534c95cf084164773d471f51537b50b2d517dc4be26dddb06e1b", ssa_ast = "6d38bf225e9cf5af37b9d6c595c2973ec31a32d227ca65cb590d27400d442780", flattened_ast = "65fb4138701cad86a5fcd7e024645e833aeb6e88b3ea2a3a6b69269fd1d77620", destructured_ast = "85a81c23da7e97b057ddf4ef71f375781e1dfcb90d656d694a5aa0f0c176b497", inlined_ast = "a1b2367575e170a79ace2ac7ff071bc3c770476b37ee149310c3b2cfe67b1c7f", dce_ast = "f46fa7963b327b9c75c9f7a7569e350d7f62c21964cb5df140cd2186c2043697", bytecode = """ import test_dep.aleo; import test.aleo; program wrapper.aleo; @@ -119,7 +119,7 @@ finalize main: await r1; await r2; """, errors = "", warnings = "" }, - { initial_symbol_table = "11c1000ce2f1774ad382af12ba51e8b55d5a98ee0da67cb8620e686c1fcaebb1", type_checked_symbol_table = "9f27eb3f177ceb81d9b14cc85c07b7198eb67d0ee806c04cbbff1cfb18b997ab", unrolled_symbol_table = "9f27eb3f177ceb81d9b14cc85c07b7198eb67d0ee806c04cbbff1cfb18b997ab", initial_ast = "575e251f07e552c917ab36bc9877b13dd1638651c4023ade20701dd2a5fe27ff", unrolled_ast = "2a4969ad315e900b5a3f1eecd4e6508dc6946fb5f6c3861ee793961ce6bcc203", ssa_ast = "4a00e3d36cdd4ff4be1fc6a389aaf17cfb02b6c54fa84276fb5be66b8a78b124", flattened_ast = "885c5f8145aa1a82e5fe41abbabae12cbd15eb014b333b246c6c5401b5b6bfea", destructured_ast = "f3b5b961a498f9befec85b69b3012145a6e97774d37a8c8e354ec4e5eeb64f84", inlined_ast = "2bf37fc499b3eca18c8227e61f69f730d36e755d7879dde13bb9161936bafbfc", dce_ast = "390391c2098cf6a910eeec98fc92fdea31303a84a1d6fd6673c8dbd9d20180de", bytecode = """ + { initial_symbol_table = "29aefa9acac4488265303e8b3bd52a434ae4824211df7919f6b63cfe6e1ec51f", type_checked_symbol_table = "1d1a8312d73125086e436d6463fd554fbc3aa622a2e28efc8622ddf0adfb473f", unrolled_symbol_table = "1d1a8312d73125086e436d6463fd554fbc3aa622a2e28efc8622ddf0adfb473f", initial_ast = "575e251f07e552c917ab36bc9877b13dd1638651c4023ade20701dd2a5fe27ff", unrolled_ast = "2a4969ad315e900b5a3f1eecd4e6508dc6946fb5f6c3861ee793961ce6bcc203", ssa_ast = "4a00e3d36cdd4ff4be1fc6a389aaf17cfb02b6c54fa84276fb5be66b8a78b124", flattened_ast = "885c5f8145aa1a82e5fe41abbabae12cbd15eb014b333b246c6c5401b5b6bfea", destructured_ast = "f3b5b961a498f9befec85b69b3012145a6e97774d37a8c8e354ec4e5eeb64f84", inlined_ast = "2bf37fc499b3eca18c8227e61f69f730d36e755d7879dde13bb9161936bafbfc", dce_ast = "390391c2098cf6a910eeec98fc92fdea31303a84a1d6fd6673c8dbd9d20180de", bytecode = """ import test_dep.aleo; import test.aleo; import wrapper.aleo; diff --git a/tests/expectations/compiler/futures/pass_in_out_of_order.out b/tests/expectations/compiler/futures/pass_in_out_of_order.out new file mode 100644 index 0000000000..5a437678df --- /dev/null +++ b/tests/expectations/compiler/futures/pass_in_out_of_order.out @@ -0,0 +1,40 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "c05e8ce7b527a0192d65e50963afd67a1fe92c837da97a5299c22af29f0275dd", type_checked_symbol_table = "8b2f1b5c22979924b6e08419e57fb515c05d3ce6932a9d1c1533ed4a2927aec2", unrolled_symbol_table = "8b2f1b5c22979924b6e08419e57fb515c05d3ce6932a9d1c1533ed4a2927aec2", initial_ast = "713c85bcb7d5fef43e4626a09e4fe3858f76de6025fcae475cb0398c26b5d123", unrolled_ast = "713c85bcb7d5fef43e4626a09e4fe3858f76de6025fcae475cb0398c26b5d123", ssa_ast = "5e2a213bd10e28dad299e0e48ac3336484fd2ffd894eeb63d15d67e6af65195e", flattened_ast = "11828763a38326b604155e1074699e6ca85205fbc83167d554624d0fe5d8bb2b", destructured_ast = "83c9eaa9aef53de6143980c9e001e59da64c291a7b2aa0693139b868339e589c", inlined_ast = "dda1aade2a50a9f25571004c8a68e3ff90efadd6a567ce25b1edbbd2e82b59d7", dce_ast = "dda1aade2a50a9f25571004c8a68e3ff90efadd6a567ce25b1edbbd2e82b59d7", bytecode = """ +program test.aleo; + +mapping foo: + key as u32.public; + value as u32.public; + +function main_inner: + input r0 as u32.public; + input r1 as u32.public; + async main_inner r0 r1 into r2; + output r2 as test.aleo/main_inner.future; + +finalize main_inner: + input r0 as u32.public; + input r1 as u32.public; + set r1 into foo[r0]; +""", errors = "", warnings = "" }, + { initial_symbol_table = "748f1d6d760d63c1eb80d518379d99323e13132bf77520f8442534c79c64895b", type_checked_symbol_table = "3c5970cf8251ed27741614785eb8871b1df48e9437da1cc425c9c85448f63ed3", unrolled_symbol_table = "3c5970cf8251ed27741614785eb8871b1df48e9437da1cc425c9c85448f63ed3", initial_ast = "0614c4d1a71ead028505adbca60be45e21f8dfff3cac5c2d5825fdccb742599a", unrolled_ast = "3937cfdaeaeb310aed147b646aa466e86c2be135f08c34caba4c442d9287cc00", ssa_ast = "085e51180733890a2fa69926ff9aff285cbe30fbfb212363b7cde717f8fdb726", flattened_ast = "f8c1203193c1ede409ac0f76bdd99dcc3030182b0e04fcde92b21588e2130806", destructured_ast = "c2f3abb79321f123b41bf581e21b8abe73f799f1e3d4431fa68849d7ee4f2053", inlined_ast = "501b7b97d2873c583d12763895fa3d01a7fac7a7554d91b71850d9f8060413c8", dce_ast = "501b7b97d2873c583d12763895fa3d01a7fac7a7554d91b71850d9f8060413c8", bytecode = """ +import test.aleo; +program basic.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call test.aleo/main_inner 0u32 0u32 into r2; + call test.aleo/main_inner 1u32 1u32 into r3; + async main r3 r2 into r4; + output r4 as basic.aleo/main.future; + +finalize main: + input r0 as test.aleo/main_inner.future; + input r1 as test.aleo/main_inner.future; + await r0; + await r1; +""", errors = "", warnings = "" }, +] }]] diff --git a/tests/expectations/compiler/futures/simple.out b/tests/expectations/compiler/futures/simple.out index d8dd82936a..83a01d9e62 100644 --- a/tests/expectations/compiler/futures/simple.out +++ b/tests/expectations/compiler/futures/simple.out @@ -19,7 +19,7 @@ function main_inner: finalize main_inner: set 1u32 into foo[1u32]; """, errors = "", warnings = "" }, - { initial_symbol_table = "e68fd2fbfc3ff3832375c1c2df1e6a67787480498938fc77e766ca07ae751992", type_checked_symbol_table = "a3dbe89fee3c01d1a1798775bd34ee5e9a160d9a31bc223cf8d949ad08310b43", unrolled_symbol_table = "a3dbe89fee3c01d1a1798775bd34ee5e9a160d9a31bc223cf8d949ad08310b43", initial_ast = "90315edede362afca47bb3f8c861ab8bbbdb049ea56db7ebbbf8f20ce60aeb4a", unrolled_ast = "6541d8c338b4eeb027aedd7c9151f3eac30d61ab2986d22a008ef5bd4a67ffc7", ssa_ast = "80086e21c3779f9da4b57c755eedf9132709a1edc63644ef4ec574ce047b076f", flattened_ast = "a9988b6cbd9cb03bc49e6850084531888e0cc04e456496fe7eff390812d39611", destructured_ast = "a94ba575cc25982052a729a8a1b8fa3560a0043b305cf4dede91d17a71202fcb", inlined_ast = "7a6d98c84ce9a50bd944f11bca3d98f8262ab57b55fcc7f15537650b3d4bc6ef", dce_ast = "ef3d06f7a3ed3bba09c3fda4378aaa2f700384fc28e5d8c3751633bbc03f9f4e", bytecode = """ + { initial_symbol_table = "6c555c940588a42fbc942c59adb0a7d43f9423b5e08eea2ab43581fb8d979a41", type_checked_symbol_table = "bda27ed9363abf086753d5bb203fa4c10c8fb4168e6759f58e1eef1c5ae48dbf", unrolled_symbol_table = "bda27ed9363abf086753d5bb203fa4c10c8fb4168e6759f58e1eef1c5ae48dbf", initial_ast = "90315edede362afca47bb3f8c861ab8bbbdb049ea56db7ebbbf8f20ce60aeb4a", unrolled_ast = "6541d8c338b4eeb027aedd7c9151f3eac30d61ab2986d22a008ef5bd4a67ffc7", ssa_ast = "80086e21c3779f9da4b57c755eedf9132709a1edc63644ef4ec574ce047b076f", flattened_ast = "a9988b6cbd9cb03bc49e6850084531888e0cc04e456496fe7eff390812d39611", destructured_ast = "a94ba575cc25982052a729a8a1b8fa3560a0043b305cf4dede91d17a71202fcb", inlined_ast = "7a6d98c84ce9a50bd944f11bca3d98f8262ab57b55fcc7f15537650b3d4bc6ef", dce_ast = "ef3d06f7a3ed3bba09c3fda4378aaa2f700384fc28e5d8c3751633bbc03f9f4e", bytecode = """ import test.aleo; program basic.aleo; diff --git a/tests/expectations/compiler/mappings/read_external_mapping.out b/tests/expectations/compiler/mappings/read_external_mapping.out index 1e39ac93a4..5d999a9f76 100644 --- a/tests/expectations/compiler/mappings/read_external_mapping.out +++ b/tests/expectations/compiler/mappings/read_external_mapping.out @@ -24,7 +24,7 @@ finalize unregister: input r0 as address.public; set false into users[r0]; """, errors = "", warnings = "" }, - { initial_symbol_table = "23d4f67793776c110bfd4cc47d98dedde4495edb453c82f6b06718a8cdbc7f6d", type_checked_symbol_table = "f8c6d89c3ff7316d9a2e391c1a0d6c7f3f4ab2f45109b0dbd58b6ff424d854dd", unrolled_symbol_table = "f8c6d89c3ff7316d9a2e391c1a0d6c7f3f4ab2f45109b0dbd58b6ff424d854dd", initial_ast = "9cc519cc416b2f54ecf753c541196b337f359d42616e4f38b8d9a5a86746de41", unrolled_ast = "4f5beff4969ba9db8b429435d2a6a6133eed2e8718564073fefa76ed4db76381", ssa_ast = "012d0c07475a7e03d3898338aa2a91b56d77032978437b17c9337a5001ae5249", flattened_ast = "e391d1d2c6731ec8961afe91d8fa94fb9edb091b892ddecfa48ce3f5a6febe8e", destructured_ast = "26f202a3d6a24f0af49542d0f2c29c635314073b2d52ede163d3ab5e5bcc86fa", inlined_ast = "72e4121a823f91aeeb5b8433f03f07943d174353d55f58a3aae111bc1bab0798", dce_ast = "72e4121a823f91aeeb5b8433f03f07943d174353d55f58a3aae111bc1bab0798", bytecode = """ + { initial_symbol_table = "ad44035dc7b8fd88c75dae7732786222905037adcba313f1fa11964e3b6a3a18", type_checked_symbol_table = "2ae36cf87aea22ac6069d3d750ea3ed2b2ac03c0f9ee75f957edf106095aa036", unrolled_symbol_table = "2ae36cf87aea22ac6069d3d750ea3ed2b2ac03c0f9ee75f957edf106095aa036", initial_ast = "9cc519cc416b2f54ecf753c541196b337f359d42616e4f38b8d9a5a86746de41", unrolled_ast = "4f5beff4969ba9db8b429435d2a6a6133eed2e8718564073fefa76ed4db76381", ssa_ast = "012d0c07475a7e03d3898338aa2a91b56d77032978437b17c9337a5001ae5249", flattened_ast = "e391d1d2c6731ec8961afe91d8fa94fb9edb091b892ddecfa48ce3f5a6febe8e", destructured_ast = "26f202a3d6a24f0af49542d0f2c29c635314073b2d52ede163d3ab5e5bcc86fa", inlined_ast = "72e4121a823f91aeeb5b8433f03f07943d174353d55f58a3aae111bc1bab0798", dce_ast = "72e4121a823f91aeeb5b8433f03f07943d174353d55f58a3aae111bc1bab0798", bytecode = """ import registry.aleo; program relay.aleo; diff --git a/tests/expectations/compiler/structs/external_struct_in_async_function.out b/tests/expectations/compiler/structs/external_struct_in_async_function.out index a384590a60..b225b5672a 100644 --- a/tests/expectations/compiler/structs/external_struct_in_async_function.out +++ b/tests/expectations/compiler/structs/external_struct_in_async_function.out @@ -17,7 +17,7 @@ finalize init: input r0 as TestStruct.public; assert.eq 0u32 0u32; """, errors = "", warnings = "" }, - { initial_symbol_table = "c9f26fb8c18222d0819c01087efc4aae88ea8944dec03710d94c38c24e0d077a", type_checked_symbol_table = "ed3db1e139955da3a7df17d8abdf36ddcabf05e2cb0cc6af012cce4a4fc67fae", unrolled_symbol_table = "ed3db1e139955da3a7df17d8abdf36ddcabf05e2cb0cc6af012cce4a4fc67fae", initial_ast = "b1348090a951e00cbf76c62d734fa808bfceea5b4169aa6da15a08ff185cbc50", unrolled_ast = "f1c461c8b0f677d0954ff6d29ab29abb648b57c7c141ddaf116a28d837e2b546", ssa_ast = "39e50a1b965cf6d4c19750d75edd4b1a8f8c02c04bbcb361f4fa70cebdc39574", flattened_ast = "a5a1c8def04670f3c5177946811bd27dcae5b045fce181e5e3307d9964686341", destructured_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", inlined_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", dce_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", bytecode = """ + { initial_symbol_table = "df5db326efa87ea83dbafd0e06782435113131775dc2aa80754176a6433e34c3", type_checked_symbol_table = "4c659204bbe76e0bc59df1829ce5ae48f3bc830ae96ce5cf1b29cb69dfe0943b", unrolled_symbol_table = "4c659204bbe76e0bc59df1829ce5ae48f3bc830ae96ce5cf1b29cb69dfe0943b", initial_ast = "b1348090a951e00cbf76c62d734fa808bfceea5b4169aa6da15a08ff185cbc50", unrolled_ast = "f1c461c8b0f677d0954ff6d29ab29abb648b57c7c141ddaf116a28d837e2b546", ssa_ast = "39e50a1b965cf6d4c19750d75edd4b1a8f8c02c04bbcb361f4fa70cebdc39574", flattened_ast = "a5a1c8def04670f3c5177946811bd27dcae5b045fce181e5e3307d9964686341", destructured_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", inlined_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", dce_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", bytecode = """ import parent.aleo; program child.aleo; diff --git a/tests/tests/compiler/futures/await_out_of_order.leo b/tests/tests/compiler/futures/await_out_of_order.leo new file mode 100644 index 0000000000..d545c151fc --- /dev/null +++ b/tests/tests/compiler/futures/await_out_of_order.leo @@ -0,0 +1,31 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ +program test.aleo { + mapping foo: u32 => u32; + async transition main_inner(public a: u32, public b: u32) -> Future { + return finalize(a, b); + } + + async function finalize(a: u32, b: u32) { + Mapping::set(foo, a, b); + } +} + +// --- Next Program --- // + +import test.aleo; +program basic.aleo { + async transition main(public a: u32, b: u32) -> Future { + let f1: Future = test.aleo/main_inner(0u32, 0u32); + let f2: Future = test.aleo/main_inner(1u32, 1u32); + let f:Future = finalize(f1, f2); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f2.await(); + f1.await(); + } +} diff --git a/tests/tests/compiler/futures/non_async_after_complex_async.leo b/tests/tests/compiler/futures/non_async_after_complex_async.leo new file mode 100644 index 0000000000..6a205cd4e0 --- /dev/null +++ b/tests/tests/compiler/futures/non_async_after_complex_async.leo @@ -0,0 +1,53 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ +program inner.aleo { + mapping foo: u32 => u32; + + async transition inner(a: u32) -> Future { + return finalize(a); + } + + async function finalize(a: u32) { + Mapping::set(foo, 0u32, a); + } +} + +// --- Next Program --- // + +import inner.aleo; +program mid.aleo { + async transition mid(a: u32) -> Future { + let f1: Future = inner.aleo/inner(0u32); + let f2: Future = inner.aleo/inner(1u32); + let f:Future = finalize(f2, f1); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } + + transition dummy() {} +} + +// --- Next Program --- // + +import inner.aleo; +import mid.aleo; +program outer.aleo { + async transition outer(a: u32) -> Future { + let f1: Future = mid.aleo/mid(0u32); + let f2: Future = mid.aleo/mid(1u32); + mid.aleo/dummy(); + let f:Future = finalize(f1, f2); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} diff --git a/tests/tests/compiler/futures/non_async_before_async.leo b/tests/tests/compiler/futures/non_async_before_async.leo new file mode 100644 index 0000000000..d53ffe6e94 --- /dev/null +++ b/tests/tests/compiler/futures/non_async_before_async.leo @@ -0,0 +1,47 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ +program test.aleo { + mapping foo: u32 => u32; + + async transition main_inner(public a: u32, public b: u32) -> Future { + return finalize(a, b); + } + + async function finalize(a: u32, b: u32) { + Mapping::set(foo, a, b); + } + + transition baz(a: u32, b: u32) -> u32 { + return a + b; + } +} + +// --- Next Program --- // + +import test.aleo; +program basic.aleo { + async transition main(public a: u32, b: u32) -> Future { + let sum1: u32 = test.aleo/baz(a, b); + assert_eq(sum1, 1u32); + + let f1: Future = test.aleo/main_inner(0u32, 0u32); + + let sum2: u32 = test.aleo/baz(a, b); + assert_eq(sum2, 1u32); + + let f2: Future = test.aleo/main_inner(1u32, 1u32); + + let sum3: u32 = test.aleo/baz(a, b); + assert_eq(sum3, 1u32); + + let f:Future = finalize(f1, f2); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} diff --git a/tests/tests/compiler/futures/non_async_before_complex_async.leo b/tests/tests/compiler/futures/non_async_before_complex_async.leo new file mode 100644 index 0000000000..f31a36b20c --- /dev/null +++ b/tests/tests/compiler/futures/non_async_before_complex_async.leo @@ -0,0 +1,61 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ +program inner.aleo { + mapping foo: u32 => u32; + + async transition inner(a: u32) -> Future { + return finalize(a); + } + + async function finalize(a: u32) { + Mapping::set(foo, 0u32, a); + } +} + +// --- Next Program --- // + +import inner.aleo; +program mid.aleo { + async transition mid(a: u32) -> Future { + let f1: Future = inner.aleo/inner(0u32); + let f2: Future = inner.aleo/inner(1u32); + let f:Future = finalize(f2, f1); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } + + transition dummy() {} +} + +// --- Next Program --- // + +import inner.aleo; +import mid.aleo; +program outer.aleo { + async transition outer_1(a: u32) -> Future { + mid.aleo/dummy(); + let f1: Future = mid.aleo/mid(0u32); + let f2: Future = mid.aleo/mid(1u32); + let f:Future = finalize(f1, f2); + return f; + } + + async transition outer_2(a: u32) -> Future { + let f1: Future = mid.aleo/mid(0u32); + mid.aleo/dummy(); + let f2: Future = mid.aleo/mid(1u32); + let f:Future = finalize(f1, f2); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} diff --git a/tests/tests/compiler/futures/pass_in_out_of_order.leo b/tests/tests/compiler/futures/pass_in_out_of_order.leo new file mode 100644 index 0000000000..b3bcbf38cd --- /dev/null +++ b/tests/tests/compiler/futures/pass_in_out_of_order.leo @@ -0,0 +1,31 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ +program test.aleo { + mapping foo: u32 => u32; + async transition main_inner(public a: u32, public b: u32) -> Future { + return finalize(a, b); + } + + async function finalize(a: u32, b: u32) { + Mapping::set(foo, a, b); + } +} + +// --- Next Program --- // + +import test.aleo; +program basic.aleo { + async transition main(public a: u32, b: u32) -> Future { + let f1: Future = test.aleo/main_inner(0u32, 0u32); + let f2: Future = test.aleo/main_inner(1u32, 1u32); + let f:Future = finalize(f2, f1); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} From ec67f3d0ccfe710da1ace9093e5d9b12b02591e4 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:11:30 -0800 Subject: [PATCH 7/7] Regen expectations --- tests/expectations/execution/complex_finalization.out | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/expectations/execution/complex_finalization.out b/tests/expectations/execution/complex_finalization.out index 5f81a3d90c..dc04912086 100644 --- a/tests/expectations/execution/complex_finalization.out +++ b/tests/expectations/execution/complex_finalization.out @@ -35,7 +35,7 @@ finalize d: add r1 1u64 into r2; set r2 into counts[r0]; """, errors = "", warnings = "" }, - { initial_symbol_table = "6223f92c3bd5bbad5da2f567698b6e984ece97d3134095b26cc0b1d11079f60c", type_checked_symbol_table = "e22aa51f2a565205fe03a6f3b00552bd2e3442e100315dab5f2805a7f8b4cb69", unrolled_symbol_table = "e22aa51f2a565205fe03a6f3b00552bd2e3442e100315dab5f2805a7f8b4cb69", initial_ast = "2c14e776b891d7131858e07a8dba4dbf727b3d01dbf4d2e22415711d688dc7c3", unrolled_ast = "31db5dfbc43b124cb4780c1d629ee28de4a249a5aba21727a0dcb9726d4322f6", ssa_ast = "1f4225e1f83eb88bb3368544c3b2a077da163281476eaeb688334dac41bc0a9d", flattened_ast = "eba4b124fd3df6170a5cbfaad89f0e6d398cb2cba50d61b3c18f00381a6b3be9", destructured_ast = "c1e81066ab08a49915eaaed5b82b323ab1b7227157be6916832ff22eb658b15c", inlined_ast = "22f3f544c5331fee78a3b81381f6695bdaa06f437c4a56142b36da1e852d9840", dce_ast = "22f3f544c5331fee78a3b81381f6695bdaa06f437c4a56142b36da1e852d9840", bytecode = """ + { initial_symbol_table = "245c739fc70f8cdcd2b24f30d8aec16924fcab28c52d7c0043646622dbe75f57", type_checked_symbol_table = "8263d5899d205220dc81a56f9785ba66255b723f07d6203b6697708812831273", unrolled_symbol_table = "8263d5899d205220dc81a56f9785ba66255b723f07d6203b6697708812831273", initial_ast = "2c14e776b891d7131858e07a8dba4dbf727b3d01dbf4d2e22415711d688dc7c3", unrolled_ast = "31db5dfbc43b124cb4780c1d629ee28de4a249a5aba21727a0dcb9726d4322f6", ssa_ast = "1f4225e1f83eb88bb3368544c3b2a077da163281476eaeb688334dac41bc0a9d", flattened_ast = "eba4b124fd3df6170a5cbfaad89f0e6d398cb2cba50d61b3c18f00381a6b3be9", destructured_ast = "c1e81066ab08a49915eaaed5b82b323ab1b7227157be6916832ff22eb658b15c", inlined_ast = "22f3f544c5331fee78a3b81381f6695bdaa06f437c4a56142b36da1e852d9840", dce_ast = "22f3f544c5331fee78a3b81381f6695bdaa06f437c4a56142b36da1e852d9840", bytecode = """ import zero_program.aleo; import one_program.aleo; program two_program.aleo; @@ -60,7 +60,7 @@ finalize b: add r3 1u64 into r4; set r4 into counts[r2]; """, errors = "", warnings = "" }, - { initial_symbol_table = "7d0a0d54b673b8428f972bec8346ca6830248f69cb3fba4b42c32e1a72cc1b0f", type_checked_symbol_table = "ea10fb298006b83389a483e12f9b97b7e1f691dc0a1aee602e74e10d915e8b0c", unrolled_symbol_table = "ea10fb298006b83389a483e12f9b97b7e1f691dc0a1aee602e74e10d915e8b0c", initial_ast = "387aba043fde6ead4d99bf4eb5c817051491a7d16aecd6383411e3cbc6aaefd5", unrolled_ast = "f93e4fd19542c5af01a5e0aec60e9f6265491a0952cafabfb7cdcfac00bd81b9", ssa_ast = "0ad477f1c1bc42ebcd4098caf856428e5be9a0845972cbd2908dcf53c6ce45a0", flattened_ast = "3fa8070cfe4be62533fb8b3d899c490f940686a97ae01ee0c8f6f7743527d726", destructured_ast = "5407ddb3a931cde7e50dc466557108fde8f6ebfd8d446cdb44855542208f4056", inlined_ast = "8accc3977c89a2e948b39f6abc2c7f989e52313aac237bcb25469e4bc91fc4f1", dce_ast = "8accc3977c89a2e948b39f6abc2c7f989e52313aac237bcb25469e4bc91fc4f1", bytecode = """ + { initial_symbol_table = "ee9c47b21aa811094b66a4cef0eef7af32effb924a2f514094b5280b4d611987", type_checked_symbol_table = "bf3047bd773d692e484ef3bddcaca86ffc8c897f658afe2f85cb592c4b856ca3", unrolled_symbol_table = "bf3047bd773d692e484ef3bddcaca86ffc8c897f658afe2f85cb592c4b856ca3", initial_ast = "387aba043fde6ead4d99bf4eb5c817051491a7d16aecd6383411e3cbc6aaefd5", unrolled_ast = "f93e4fd19542c5af01a5e0aec60e9f6265491a0952cafabfb7cdcfac00bd81b9", ssa_ast = "0ad477f1c1bc42ebcd4098caf856428e5be9a0845972cbd2908dcf53c6ce45a0", flattened_ast = "3fa8070cfe4be62533fb8b3d899c490f940686a97ae01ee0c8f6f7743527d726", destructured_ast = "5407ddb3a931cde7e50dc466557108fde8f6ebfd8d446cdb44855542208f4056", inlined_ast = "8accc3977c89a2e948b39f6abc2c7f989e52313aac237bcb25469e4bc91fc4f1", dce_ast = "8accc3977c89a2e948b39f6abc2c7f989e52313aac237bcb25469e4bc91fc4f1", bytecode = """ import zero_program.aleo; import one_program.aleo; import two_program.aleo; @@ -89,7 +89,7 @@ finalize e: add r4 1u64 into r5; set r5 into counts[r3]; """, errors = "", warnings = "" }, - { initial_symbol_table = "8272b3774900302d111cc659f82a49e7df702875ceb4e54787c068bcac901a85", type_checked_symbol_table = "3b9ce08a512a197af239b00944b50298885603f4f723debc4ee96b281d28bc4c", unrolled_symbol_table = "3b9ce08a512a197af239b00944b50298885603f4f723debc4ee96b281d28bc4c", initial_ast = "f731cdda879e0134eb5b1cf0d64d3cf5abbee2fd2ce758d3afac05ee07fb885f", unrolled_ast = "79017a53e402d0c7aad500a44936f4e06e418407b4a2b40f2bf69a185c4865c0", ssa_ast = "8a4f2ea8f8118515b8843aad5a201824dc2c6b06046f68698dde622f5ace3c4f", flattened_ast = "35f966d0d86e1e38c2c6650d83e62d701a9b9440766b78919ee0b509c3255cf7", destructured_ast = "5677314a7b55bf523441d3c40029daedf97666fb7821159b0c88654776ea2932", inlined_ast = "9c779149583480acdca132daad34c2577ec0d09e28c36b11ecf91beb556cc7b5", dce_ast = "9c779149583480acdca132daad34c2577ec0d09e28c36b11ecf91beb556cc7b5", bytecode = """ + { initial_symbol_table = "044526dfe3dfe09fde9004db01d837c5fe566d508d6a6045b8b64c6921179e15", type_checked_symbol_table = "ad633121ad08786628d1ddd39eddae1004962f1f6dcb2370179c751fd460fcfe", unrolled_symbol_table = "ad633121ad08786628d1ddd39eddae1004962f1f6dcb2370179c751fd460fcfe", initial_ast = "f731cdda879e0134eb5b1cf0d64d3cf5abbee2fd2ce758d3afac05ee07fb885f", unrolled_ast = "79017a53e402d0c7aad500a44936f4e06e418407b4a2b40f2bf69a185c4865c0", ssa_ast = "8a4f2ea8f8118515b8843aad5a201824dc2c6b06046f68698dde622f5ace3c4f", flattened_ast = "35f966d0d86e1e38c2c6650d83e62d701a9b9440766b78919ee0b509c3255cf7", destructured_ast = "5677314a7b55bf523441d3c40029daedf97666fb7821159b0c88654776ea2932", inlined_ast = "9c779149583480acdca132daad34c2577ec0d09e28c36b11ecf91beb556cc7b5", dce_ast = "9c779149583480acdca132daad34c2577ec0d09e28c36b11ecf91beb556cc7b5", bytecode = """ import zero_program.aleo; import one_program.aleo; import two_program.aleo;