diff --git a/Cargo.lock b/Cargo.lock index 65c51a0a5c3..97e441a6a3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1924,6 +1924,7 @@ dependencies = [ "leo-disassembler", "leo-errors", "leo-parser", + "leo-passes", "leo-span", "leo-test-framework", "rand", @@ -1961,6 +1962,7 @@ dependencies = [ "leo-errors", "leo-interpreter", "leo-package", + "leo-passes", "leo-span", "libc", "nix", diff --git a/Cargo.toml b/Cargo.toml index 99dc2e57801..dd1fbc36e27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,6 +199,9 @@ workspace = true [dependencies.leo-package] workspace = true +[dependencies.leo-passes] +workspace = true + [dependencies.leo-span] workspace = true diff --git a/compiler/ast/src/expressions/mod.rs b/compiler/ast/src/expressions/mod.rs index c8b97e21d08..e724dce9588 100644 --- a/compiler/ast/src/expressions/mod.rs +++ b/compiler/ast/src/expressions/mod.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{CoreFunction, Identifier, IntegerType, Node, NodeBuilder, NodeID, Path, Type}; +use crate::{CoreFunction, Identifier, IntegerType, Location, Node, NodeBuilder, NodeID, Path, Type}; use leo_span::{Span, Symbol}; use serde::{Deserialize, Serialize}; @@ -400,7 +400,8 @@ impl Expression { ty: &Type, span: Span, node_builder: &NodeBuilder, - struct_lookup: &dyn Fn(&[Symbol]) -> Vec<(Symbol, Type)>, + current_program: Symbol, + struct_lookup: &dyn Fn(&Location) -> Vec<(Symbol, Type)>, ) -> Option { let id = node_builder.next_id(); @@ -444,13 +445,16 @@ impl Expression { // Structs (composite types) Type::Composite(composite_type) => { let path = &composite_type.path; - let members = struct_lookup(&path.absolute_path()); + let members = struct_lookup(&Location::new( + composite_type.program.unwrap_or(current_program), + path.absolute_path(), + )); let struct_members = members .into_iter() .map(|(symbol, member_type)| { let member_id = node_builder.next_id(); - let zero_expr = Self::zero(&member_type, span, node_builder, struct_lookup)?; + let zero_expr = Self::zero(&member_type, span, node_builder, current_program, struct_lookup)?; Some(StructVariableInitializer { span, @@ -474,7 +478,7 @@ impl Expression { Type::Array(array_type) => { let element_ty = &array_type.element_type; - let element_expr = Self::zero(element_ty, span, node_builder, struct_lookup)?; + let element_expr = Self::zero(element_ty, span, node_builder, current_program, struct_lookup)?; Some(Expression::Repeat( RepeatExpression { span, id, expr: element_expr, count: *array_type.length.clone() }.into(), diff --git a/compiler/ast/src/interpreter_value/value.rs b/compiler/ast/src/interpreter_value/value.rs index 6d5103ecfd7..4748c7fde0b 100644 --- a/compiler/ast/src/interpreter_value/value.rs +++ b/compiler/ast/src/interpreter_value/value.rs @@ -827,8 +827,9 @@ impl Value { &self, span: Span, node_builder: &NodeBuilder, + current_program: Symbol, ty: &Type, - struct_lookup: &dyn Fn(&[Symbol]) -> Vec<(Symbol, Type)>, + struct_lookup: &dyn Fn(&Location) -> Vec<(Symbol, Type)>, ) -> Option { use crate::{Literal, TupleExpression, UnitExpression}; @@ -850,7 +851,7 @@ impl Value { elements: vec .iter() .zip(tuple_type.elements()) - .map(|(val, ty)| val.to_expression(span, node_builder, ty, struct_lookup)) + .map(|(val, ty)| val.to_expression(span, node_builder, current_program, ty, struct_lookup)) .collect::>>()?, } .into() @@ -858,7 +859,7 @@ impl Value { ValueVariants::Unsuffixed(s) => Literal::unsuffixed(s.clone(), span, id).into(), ValueVariants::Svm(value) => match value { SvmValueParam::Plaintext(plaintext) => { - plaintext_to_expression(plaintext, span, node_builder, ty, &struct_lookup)? + plaintext_to_expression(plaintext, span, node_builder, current_program, ty, &struct_lookup)? } SvmValueParam::Record(..) => return None, SvmValueParam::Future(..) => return None, @@ -876,8 +877,9 @@ fn plaintext_to_expression( plaintext: &SvmPlaintext, span: Span, node_builder: &NodeBuilder, + current_program: Symbol, ty: &Type, - struct_lookup: &dyn Fn(&[Symbol]) -> Vec<(Symbol, Type)>, + struct_lookup: &dyn Fn(&Location) -> Vec<(Symbol, Type)>, ) -> Option { use crate::{ArrayExpression, Identifier, IntegerType, Literal, StructExpression, StructVariableInitializer}; @@ -925,7 +927,8 @@ fn plaintext_to_expression( return None; }; let symbols = composite_type.path.as_symbols(); - let iter_members = struct_lookup(&symbols); + let iter_members = + struct_lookup(&Location::new(composite_type.program.unwrap_or(current_program), symbols)); StructExpression { span, id, @@ -946,6 +949,7 @@ fn plaintext_to_expression( index_map.get(&svm_identifier)?, span, node_builder, + current_program, &ty, &struct_lookup, )?), @@ -964,7 +968,16 @@ fn plaintext_to_expression( id, elements: vec .iter() - .map(|pt| plaintext_to_expression(pt, span, node_builder, &array_ty.element_type, &struct_lookup)) + .map(|pt| { + plaintext_to_expression( + pt, + span, + node_builder, + current_program, + &array_ty.element_type, + &struct_lookup, + ) + }) .collect::>>()?, } .into() diff --git a/compiler/ast/src/passes/consumer.rs b/compiler/ast/src/passes/consumer.rs index 79fee072630..3c77fee4738 100644 --- a/compiler/ast/src/passes/consumer.rs +++ b/compiler/ast/src/passes/consumer.rs @@ -176,6 +176,12 @@ pub trait ProgramConsumer { fn consume_program(&mut self, input: Program) -> Self::Output; } +/// A Consumer trait for a stub in the the AST. +pub trait StubConsumer { + type Output; + fn consume_stub(&mut self, input: Stub) -> Self::Output; +} + /// A Consumer trait for modules in the AST. pub trait ModuleConsumer { type Output; diff --git a/compiler/ast/src/passes/reconstructor.rs b/compiler/ast/src/passes/reconstructor.rs index fe7bce82035..850f1de8cae 100644 --- a/compiler/ast/src/passes/reconstructor.rs +++ b/compiler/ast/src/passes/reconstructor.rs @@ -569,22 +569,16 @@ pub trait AstReconstructor { /// A Reconstructor trait for the program represented by the AST. pub trait ProgramReconstructor: AstReconstructor { fn reconstruct_program(&mut self, input: Program) -> Program { + let stubs = input.stubs.into_iter().map(|(id, stub)| (id, self.reconstruct_stub(stub))).collect(); let program_scopes = input.program_scopes.into_iter().map(|(id, scope)| (id, self.reconstruct_program_scope(scope))).collect(); - Program { - imports: input - .imports - .into_iter() - .map(|(id, import)| (id, (self.reconstruct_import(import.0), import.1))) - .collect(), - stubs: input.stubs.into_iter().map(|(id, stub)| (id, self.reconstruct_stub(stub))).collect(), - modules: input.modules.into_iter().map(|(id, module)| (id, self.reconstruct_module(module))).collect(), - program_scopes, - } + let modules = input.modules.into_iter().map(|(id, module)| (id, self.reconstruct_module(module))).collect(); + + Program { modules, imports: input.imports, stubs, program_scopes } } - fn reconstruct_stub(&mut self, input: Stub) -> Stub { - Stub { + fn reconstruct_aleo_program(&mut self, input: AleoProgram) -> AleoProgram { + AleoProgram { imports: input.imports, stub_id: input.stub_id, consts: input.consts, @@ -595,6 +589,15 @@ pub trait ProgramReconstructor: AstReconstructor { } } + fn reconstruct_stub(&mut self, input: Stub) -> Stub { + match input { + Stub::FromLeo { program, parents } => Stub::FromLeo { program: self.reconstruct_program(program), parents }, + Stub::FromAleo { program, parents } => { + Stub::FromAleo { program: self.reconstruct_aleo_program(program), parents } + } + } + } + fn reconstruct_program_scope(&mut self, input: ProgramScope) -> ProgramScope { ProgramScope { program_id: input.program_id, @@ -692,10 +695,6 @@ pub trait ProgramReconstructor: AstReconstructor { } } - fn reconstruct_import(&mut self, input: Program) -> Program { - self.reconstruct_program(input) - } - fn reconstruct_mapping(&mut self, input: Mapping) -> Mapping { Mapping { key_type: self.reconstruct_type(input.key_type).0, diff --git a/compiler/ast/src/passes/visitor.rs b/compiler/ast/src/passes/visitor.rs index e5720df6f93..37c1f7095d4 100644 --- a/compiler/ast/src/passes/visitor.rs +++ b/compiler/ast/src/passes/visitor.rs @@ -332,10 +332,18 @@ pub trait ProgramVisitor: AstVisitor { fn visit_program(&mut self, input: &Program) { input.program_scopes.values().for_each(|scope| self.visit_program_scope(scope)); input.modules.values().for_each(|module| self.visit_module(module)); - input.imports.values().for_each(|import| self.visit_import(&import.0)); input.stubs.values().for_each(|stub| self.visit_stub(stub)); } + fn visit_aleo_program(&mut self, _input: &AleoProgram) {} + + fn visit_stub(&mut self, input: &Stub) { + match input { + Stub::FromLeo { program, .. } => self.visit_program(program), + Stub::FromAleo { program, .. } => self.visit_aleo_program(program), + } + } + fn visit_program_scope(&mut self, input: &ProgramScope) { input.consts.iter().for_each(|(_, c)| self.visit_const(c)); input.structs.iter().for_each(|(_, c)| self.visit_struct(c)); @@ -353,12 +361,6 @@ pub trait ProgramVisitor: AstVisitor { input.functions.iter().for_each(|(_, c)| self.visit_function(c)); } - fn visit_stub(&mut self, _input: &Stub) {} - - fn visit_import(&mut self, input: &Program) { - self.visit_program(input) - } - fn visit_struct(&mut self, input: &Composite) { input.const_parameters.iter().for_each(|input| self.visit_type(&input.type_)); input.members.iter().for_each(|member| self.visit_type(&member.type_)); diff --git a/compiler/ast/src/program/mod.rs b/compiler/ast/src/program/mod.rs index 88a8d649a28..5a27f533ca7 100644 --- a/compiler/ast/src/program/mod.rs +++ b/compiler/ast/src/program/mod.rs @@ -35,7 +35,7 @@ pub struct Program { /// A map from module paths to module definitions. pub modules: IndexMap, Module>, /// A map from import names to import definitions. - pub imports: IndexMap, + pub imports: IndexMap, /// A map from program stub names to program stub scopes. pub stubs: IndexMap, /// A map from program names to program scopes. @@ -44,15 +44,15 @@ pub struct Program { impl fmt::Display for Program { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (_, stub) in self.stubs.iter() { + writeln!(f, "{stub}")?; + } for (_, module) in self.modules.iter() { writeln!(f, "{module}")?; } for (id, _import) in self.imports.iter() { writeln!(f, "import {id}.aleo;")?; } - for (_, stub) in self.stubs.iter() { - writeln!(f, "{stub}")?; - } for (_, program_scope) in self.program_scopes.iter() { writeln!(f, "{program_scope}")?; } diff --git a/compiler/ast/src/program/program_scope.rs b/compiler/ast/src/program/program_scope.rs index 3905fa2aae6..23bed1ff553 100644 --- a/compiler/ast/src/program/program_scope.rs +++ b/compiler/ast/src/program/program_scope.rs @@ -16,7 +16,7 @@ //! A Leo program scope consists of struct, function, and mapping definitions. -use crate::{Composite, ConstDeclaration, Constructor, Function, Indent, Mapping, ProgramId, StorageVariable, Stub}; +use crate::{Composite, ConstDeclaration, Constructor, Function, Indent, Mapping, ProgramId, StorageVariable}; use leo_span::{Span, Symbol}; use serde::{Deserialize, Serialize}; @@ -43,26 +43,6 @@ pub struct ProgramScope { pub span: Span, } -impl From for ProgramScope { - fn from(stub: Stub) -> Self { - Self { - program_id: stub.stub_id, - consts: stub.consts, - structs: stub.structs, - mappings: stub.mappings, - storage_variables: Vec::new(), // stubs don't have storage variables - functions: stub - .functions - .into_iter() - .map(|(symbol, function)| (symbol, Function::from(function))) - .collect(), - // A program scope constructed from a stub does not need a constructor, since they are not externally callable. - constructor: None, - span: stub.span, - } - } -} - impl fmt::Display for ProgramScope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "program {} {{", self.program_id)?; diff --git a/compiler/ast/src/stub/mod.rs b/compiler/ast/src/stub/mod.rs index 511ca91e0b7..2badf0c00ec 100644 --- a/compiler/ast/src/stub/mod.rs +++ b/compiler/ast/src/stub/mod.rs @@ -19,14 +19,70 @@ pub mod function_stub; pub use function_stub::*; -use crate::{Composite, ConstDeclaration, Identifier, Indent, Mapping, NodeID, ProgramId}; +use crate::{Composite, ConstDeclaration, Identifier, Indent, Mapping, NodeID, Program, ProgramId}; +use indexmap::IndexSet; use leo_span::{Span, Symbol}; use serde::{Deserialize, Serialize}; use std::fmt; +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum Stub { + /// A dependency that is a Leo program parsed into an AST. + FromLeo { + program: Program, + parents: IndexSet, // These are the names of all program that import this dependency. + }, + /// A dependency that is an Aleo program. + FromAleo { + program: AleoProgram, + parents: IndexSet, // These are the names of all program that import this dependency. + }, +} + +impl Stub { + /// Returns the programs that this stub imports. + pub fn imports(&self) -> Box + '_> { + match self { + Stub::FromLeo { program, .. } => Box::new(program.imports.keys()), + Stub::FromAleo { program, .. } => Box::new(program.imports.iter().map(|id| &id.name.name)), + } + } + + /// Inserts the given program name as a parent for this stub, implying that the stub is + /// imported by this parent. + pub fn add_parent(&mut self, parent: Symbol) { + match self { + Stub::FromLeo { parents, .. } | Stub::FromAleo { parents, .. } => { + parents.insert(parent); + } + } + } +} + +impl fmt::Display for Stub { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Stub::FromLeo { program, .. } => write!(f, "{program}"), + Stub::FromAleo { program, .. } => write!(f, "{program}"), + } + } +} + +impl From for Stub { + fn from(program: Program) -> Self { + Stub::FromLeo { program, parents: IndexSet::new() } + } +} + +impl From for Stub { + fn from(program: AleoProgram) -> Self { + Stub::FromAleo { program, parents: IndexSet::new() } + } +} + /// Stores the Leo stub abstract syntax tree. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct Stub { +pub struct AleoProgram { /// A vector of imported programs. pub imports: Vec, /// The stub id @@ -43,7 +99,7 @@ pub struct Stub { pub span: Span, } -impl Default for Stub { +impl Default for AleoProgram { /// Constructs an empty program stub fn default() -> Self { Self { @@ -61,7 +117,7 @@ impl Default for Stub { } } -impl fmt::Display for Stub { +impl fmt::Display for AleoProgram { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "stub {} {{", self.stub_id)?; for import in self.imports.iter() { diff --git a/compiler/ast/src/types/struct_type.rs b/compiler/ast/src/types/composite.rs similarity index 100% rename from compiler/ast/src/types/struct_type.rs rename to compiler/ast/src/types/composite.rs diff --git a/compiler/ast/src/types/mod.rs b/compiler/ast/src/types/mod.rs index 7ac00f53073..b6ce1d4980d 100644 --- a/compiler/ast/src/types/mod.rs +++ b/compiler/ast/src/types/mod.rs @@ -17,6 +17,9 @@ mod array; pub use array::*; +mod composite; +pub use composite::*; + mod core_constant; pub use core_constant::*; @@ -32,9 +35,6 @@ pub use optional::*; mod mapping; pub use mapping::*; -mod struct_type; -pub use struct_type::*; - mod tuple; pub use tuple::*; diff --git a/compiler/ast/src/types/type_.rs b/compiler/ast/src/types/type_.rs index 0ca4a2ba6b5..0c7d37f89ad 100644 --- a/compiler/ast/src/types/type_.rs +++ b/compiler/ast/src/types/type_.rs @@ -20,6 +20,7 @@ use crate::{ FutureType, Identifier, IntegerType, + Location, MappingType, OptionalType, Path, @@ -93,10 +94,13 @@ impl Type { /// if their element types match. This allows const propagation to potentially resolve the length before type /// checking is performed again. /// - /// Composite types are considered equal if their names and resolved program names match. If either side still has - /// const generic arguments, they are treated as equal unconditionally since monomorphization and other passes of - /// type-checking will handle mismatches later. - pub fn eq_user(&self, other: &Type) -> bool { + /// Composite types are considered equal if + /// - They are records and their absolute paths and resolved program names match. + /// - They are structs and their absolute paths match (program names are ignored for now). + /// + /// If either side still has const generic arguments, they are treated as equal unconditionally since + /// monomorphization and other passes of type-checking will handle mismatches later. + pub fn eq_user(&self, other: &Type, current_program: Symbol, is_record: &dyn Fn(&Location) -> bool) -> bool { match (self, other) { (Type::Err, _) | (_, Type::Err) @@ -116,40 +120,70 @@ impl Type { // equal to other arrays because their lengths _may_ eventually be proven equal. true } - }) && left.element_type().eq_user(right.element_type()) + }) && left.element_type().eq_user(right.element_type(), current_program, is_record) } (Type::Identifier(left), Type::Identifier(right)) => left.name == right.name, (Type::Integer(left), Type::Integer(right)) => left == right, (Type::Mapping(left), Type::Mapping(right)) => { - left.key.eq_user(&right.key) && left.value.eq_user(&right.value) + left.key.eq_user(&right.key, current_program, is_record) + && left.value.eq_user(&right.value, current_program, is_record) + } + (Type::Optional(left), Type::Optional(right)) => { + left.inner.eq_user(&right.inner, current_program, is_record) } - (Type::Optional(left), Type::Optional(right)) => left.inner.eq_user(&right.inner), (Type::Tuple(left), Type::Tuple(right)) if left.length() == right.length() => left .elements() .iter() .zip_eq(right.elements().iter()) - .all(|(left_type, right_type)| left_type.eq_user(right_type)), - (Type::Vector(left), Type::Vector(right)) => left.element_type.eq_user(&right.element_type), + .all(|(left_type, right_type)| left_type.eq_user(right_type, current_program, is_record)), + (Type::Vector(left), Type::Vector(right)) => { + left.element_type.eq_user(&right.element_type, current_program, is_record) + } + (Type::Composite(left), Type::Composite(right)) => { // If either composite still has const generic arguments, treat them as equal. - // Type checking will run again after monomorphization. if !left.const_arguments.is_empty() || !right.const_arguments.is_empty() { return true; } - // Two composite types are the same if their programs and their _absolute_ paths match. - (left.program == right.program) - && match (&left.path.try_absolute_path(), &right.path.try_absolute_path()) { - (Some(l), Some(r)) => l == r, - _ => false, + // Get absolute paths, return false immediately if any is None. + let Some(left_path) = left.path.try_absolute_path() else { + return false; + }; + let Some(right_path) = right.path.try_absolute_path() else { + return false; + }; + + // Use current_program if program is `None`. + let left_program = left.program.unwrap_or(current_program); + let right_program = right.program.unwrap_or(current_program); + + // Build locations for the composites. + let left_loc = Location::new(left_program, left_path.clone()); + let right_loc = Location::new(right_program, right_path.clone()); + + let left_is_record = is_record(&left_loc); + let right_is_record = is_record(&right_loc); + + match (left_is_record, right_is_record) { + (true, true) => { + // Both are records: programs and absolute paths must match. + left_program == right_program && left_path == right_path + } + (false, false) => { + // Both are structs: ignore programs, compare only absolute paths. + left_path == right_path } + _ => false, // mixed kinds + } } + (Type::Future(left), Type::Future(right)) if !left.is_explicit || !right.is_explicit => true, (Type::Future(left), Type::Future(right)) if left.inputs.len() == right.inputs.len() => left .inputs() .iter() .zip_eq(right.inputs().iter()) - .all(|(left_type, right_type)| left_type.eq_user(right_type)), + .all(|(left_type, right_type)| left_type.eq_user(right_type, current_program, is_record)), _ => false, } } @@ -290,15 +324,22 @@ impl Type { /// /// # Returns /// `true` if coercion is allowed; `false` otherwise. - pub fn can_coerce_to(&self, expected: &Type) -> bool { + pub fn can_coerce_to( + &self, + expected: &Type, + current_program: Symbol, + is_record: &dyn Fn(&Location) -> bool, + ) -> bool { use Type::*; match (self, expected) { // Allow Optional → Optional - (Optional(actual_opt), Optional(expected_opt)) => actual_opt.inner.can_coerce_to(&expected_opt.inner), + (Optional(actual_opt), Optional(expected_opt)) => { + actual_opt.inner.can_coerce_to(&expected_opt.inner, current_program, is_record) + } // Allow T → Optional - (a, Optional(opt)) => a.can_coerce_to(&opt.inner), + (a, Optional(opt)) => a.can_coerce_to(&opt.inner, current_program, is_record), // Allow [T; N] → [Optional; N] (Array(a_arr), Array(e_arr)) => { @@ -307,11 +348,11 @@ impl Type { _ => true, }; - lengths_equal && a_arr.element_type().can_coerce_to(e_arr.element_type()) + lengths_equal && a_arr.element_type().can_coerce_to(e_arr.element_type(), current_program, is_record) } // Fallback: check for exact match - _ => self.eq_user(expected), + _ => self.eq_user(expected, current_program, is_record), } } diff --git a/compiler/compiler/src/compiler.rs b/compiler/compiler/src/compiler.rs index cda6b000977..d9b075bbd7d 100644 --- a/compiler/compiler/src/compiler.rs +++ b/compiler/compiler/src/compiler.rs @@ -20,8 +20,8 @@ use crate::{AstSnapshots, CompilerOptions}; -pub use leo_ast::Ast; -use leo_ast::{NetworkName, Stub}; +pub use leo_ast::{Ast, Program}; +use leo_ast::{NetworkName, NodeBuilder, Stub}; use leo_errors::{CompilerError, Handler, Result}; use leo_passes::*; use leo_span::{Symbol, source_map::FileName, with_session_globals}; @@ -30,6 +30,7 @@ use std::{ ffi::OsStr, fs, path::{Path, PathBuf}, + rc::Rc, }; use indexmap::{IndexMap, IndexSet}; @@ -103,19 +104,39 @@ impl Compiler { Ok(()) } + /// Simple wrapper around `parse` that also returns the AST. + pub fn parse_and_return_ast( + &mut self, + source: &str, + filename: FileName, + modules: &[(&str, FileName)], + ) -> Result { + // Parse the program. + self.parse(source, filename, modules)?; + + Ok(self.state.ast.ast.clone()) + } + /// Returns a new Leo compiler. #[allow(clippy::too_many_arguments)] pub fn new( expected_program_name: Option, is_test: bool, handler: Handler, + node_builder: Rc, output_directory: PathBuf, compiler_options: Option, import_stubs: IndexMap, network: NetworkName, ) -> Self { Self { - state: CompilerState { handler, is_test, network, ..Default::default() }, + state: CompilerState { + handler, + node_builder: Rc::clone(&node_builder), + is_test, + network, + ..Default::default() + }, output_directory, program_name: expected_program_name, compiler_options: compiler_options.unwrap_or_default(), @@ -205,7 +226,12 @@ impl Compiler { /// /// * `Ok(String)` containing the generated bytecode if compilation succeeds. /// * `Err(CompilerError)` if any stage of the pipeline fails. - pub fn compile(&mut self, source: &str, filename: FileName, modules: &Vec<(&str, FileName)>) -> Result { + pub fn compile( + &mut self, + source: &str, + filename: FileName, + modules: &Vec<(&str, FileName)>, + ) -> Result { // Parse the program. self.parse(source, filename, modules)?; // Merge the stubs into the AST. @@ -213,35 +239,28 @@ impl Compiler { // Run the intermediate compiler stages. self.intermediate_passes()?; // Run code generation. - let bytecode = CodeGenerating::do_pass((), &mut self.state)?; - Ok(bytecode.to_string()) + CodeGenerating::do_pass((), &mut self.state) } - /// Compiles a program from a source file and its associated module files in the same directory tree. + /// Reads the main source file and all module files in the same directory tree. /// - /// This method reads the main source file and collects all other source files under the same - /// root directory (excluding the main file itself). It assumes a modular structure where additional - /// source files are compiled as modules, with deeper files (submodules) compiled first. + /// This helper walks all `.leo` files under `source_directory` (excluding the main file itself), + /// reads their contents, and returns: + /// - The main file’s source as a `String`. + /// - A vector of module tuples `(String, FileName)` suitable for compilation or parsing. /// /// # Arguments /// - /// * `source_file_path` - A path to the main source file to compile. It must have a parent directory, - /// which is used as the root for discovering additional module files. - /// - /// # Returns - /// - /// * `Ok(String)` containing the compiled output if successful. - /// * `Err(CompilerError)` if reading the main file fails or a compilation error occurs. + /// * `entry_file_path` - The main source file. + /// * `source_directory` - The directory root for discovering `.leo` module files. /// - /// # Panics + /// # Errors /// - /// * If the provided source file has no parent directory. - /// * If any discovered module file cannot be read (marked as a TODO). - pub fn compile_from_directory( - &mut self, + /// Returns `Err(CompilerError)` if reading any file fails. + fn read_sources_and_modules( entry_file_path: impl AsRef, source_directory: impl AsRef, - ) -> Result { + ) -> Result<(String, Vec<(String, FileName)>)> { // Read the contents of the main source file. let source = fs::read_to_string(&entry_file_path) .map_err(|e| CompilerError::file_read_error(entry_file_path.as_ref().display().to_string(), e))?; @@ -257,24 +276,48 @@ impl Compiler { }) .collect::>(); - let mut module_sources = Vec::new(); // Keep Strings alive for valid borrowing - let mut modules = Vec::new(); // Parsed (source, filename) tuples for compilation - - // Read all module files and store their contents + // Read all module files and pair with FileName immediately + let mut modules = Vec::new(); for file in &files { - let source = fs::read_to_string(file.path()) + let module_source = fs::read_to_string(file.path()) .map_err(|e| CompilerError::file_read_error(file.path().display().to_string(), e))?; - module_sources.push(source); // Keep the String alive + modules.push((module_source, FileName::Real(file.path().into()))); } - // Create tuples of (&str, FileName) for the compiler - for (i, file) in files.iter().enumerate() { - let source = &module_sources[i]; // Borrow from the alive String - modules.push((&source[..], FileName::Real(file.path().into()))); - } + Ok((source, modules)) + } + + /// Compiles a program from a source file and its associated module files in the same directory tree. + pub fn compile_from_directory( + &mut self, + entry_file_path: impl AsRef, + source_directory: impl AsRef, + ) -> Result { + let (source, modules_owned) = Self::read_sources_and_modules(&entry_file_path, &source_directory)?; - // Compile the main source along with all collected modules - self.compile(&source, FileName::Real(entry_file_path.as_ref().into()), &modules) + // Convert owned module sources into temporary (&str, FileName) tuples. + let module_refs: Vec<(&str, FileName)> = + modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect(); + + // Compile the main source along with all collected modules. + self.compile(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs) + } + + /// Parses a program from a source file and its associated module files in the same directory tree. + pub fn parse_from_directory( + &mut self, + entry_file_path: impl AsRef, + source_directory: impl AsRef, + ) -> Result { + let (source, modules_owned) = Self::read_sources_and_modules(&entry_file_path, &source_directory)?; + + // Convert owned module sources into temporary (&str, FileName) tuples. + let module_refs: Vec<(&str, FileName)> = + modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect(); + + // Parse the main source along with all collected modules. + self.parse(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?; + Ok(self.state.ast.ast.clone()) } /// Writes the AST to a JSON file. @@ -304,27 +347,62 @@ impl Compiler { Ok(()) } - /// Merge the imported stubs which are dependencies of the current program into the AST - /// in topological order. + /// Resolves and registers all import stubs for the current program. + /// + /// This method performs a graph traversal over the program’s import relationships to: + /// 1. Establish parent–child relationships between stubs based on imports. + /// 2. Collect all reachable stubs in traversal order. + /// 3. Store the explored stubs back into `self.state.ast.ast.stubs`. + /// + /// The traversal starts from the imports of the main program and recursively follows + /// their transitive dependencies. Any missing stub during traversal results in an error. + /// + /// # Returns + /// + /// * `Ok(())` if all imports are successfully resolved and stubs are collected. + /// * `Err(CompilerError)` if any imported program cannot be found. pub fn add_import_stubs(&mut self) -> Result<()> { + // Track which programs we've already processed. let mut explored = IndexSet::::new(); + + // Initialize the exploration queue with the main program’s direct imports. let mut to_explore: Vec = self.state.ast.ast.imports.keys().cloned().collect(); - while let Some(import) = to_explore.pop() { - explored.insert(import); - if let Some(stub) = self.import_stubs.get(&import) { - for new_import_id in stub.imports.iter() { - if !explored.contains(&new_import_id.name.name) { - to_explore.push(new_import_id.name.name); - } + // If this is a named program, set the main program as the parent of its direct imports. + if let Some(main_program_name) = self.program_name.clone() { + let main_symbol = Symbol::intern(&main_program_name); + for import in self.state.ast.ast.imports.keys() { + if let Some(child_stub) = self.import_stubs.get_mut(import) { + child_stub.add_parent(main_symbol); } - } else { + } + } + + // Traverse the import graph breadth-first, collecting dependencies. + while let Some(import_symbol) = to_explore.pop() { + // Mark this import as explored. + explored.insert(import_symbol); + + // Look up the corresponding stub. + let Some(stub) = self.import_stubs.get(&import_symbol) else { return Err(CompilerError::imported_program_not_found( self.program_name.as_ref().unwrap(), - import, - self.state.ast.ast.imports[&import].1, + import_symbol, + self.state.ast.ast.imports[&import_symbol], ) .into()); + }; + + for child_symbol in stub.imports().cloned().collect::>() { + // Record parent relationship. + if let Some(child_stub) = self.import_stubs.get_mut(&child_symbol) { + child_stub.add_parent(import_symbol); + } + + // Schedule child for exploration if not yet visited. + if explored.insert(child_symbol) { + to_explore.push(child_symbol); + } } } diff --git a/compiler/compiler/src/test_compiler.rs b/compiler/compiler/src/test_compiler.rs index 3ad4444188d..88900b21d54 100644 --- a/compiler/compiler/src/test_compiler.rs +++ b/compiler/compiler/src/test_compiler.rs @@ -14,8 +14,9 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use leo_disassembler::disassemble_from_str; +use leo_ast::NodeBuilder; use leo_errors::{BufferEmitter, Handler, LeoError}; +use leo_passes::Bytecode; use leo_span::{Symbol, create_session_if_not_set_then}; use snarkvm::{ @@ -26,50 +27,68 @@ use snarkvm::{ use indexmap::IndexMap; use itertools::Itertools as _; use serial_test::serial; -use std::str::FromStr; +use std::{rc::Rc, str::FromStr}; -fn run_test(test: &str, handler: &Handler) -> Result { - // Initialize a `Process`. This should always succeed. +fn run_test(test: &str, handler: &Handler, node_builder: &Rc) -> Result { let mut process = Process::::load().unwrap(); let mut import_stubs = IndexMap::new(); let mut bytecodes = Vec::::new(); - // Compile each source file separately. - for source in test.split(super::test_utils::PROGRAM_DELIMITER) { - let (bytecode, program_name) = - handler.extend_if_error(super::test_utils::whole_compile(source, handler, import_stubs.clone()))?; + let sources: Vec<&str> = test.split(super::test_utils::PROGRAM_DELIMITER).collect(); - // Parse the bytecode as an Aleo program. - // Note that this function checks that the bytecode is well-formed. - let aleo_program = handler.extend_if_error(ProgramCore::from_str(&bytecode).map_err(LeoError::Anyhow))?; + // Helper: parse and register Aleo program into `process`. + // Note that this performs an additional validity check on the bytecode. + let mut add_aleo_program = |code: &str| -> Result<(), ()> { + let program = handler.extend_if_error(ProgramCore::from_str(code).map_err(LeoError::Anyhow))?; + handler.extend_if_error(process.add_program(&program).map_err(LeoError::Anyhow))?; + Ok(()) + }; - // Add the program to the process. - // Note that this function performs an additional validity check on the bytecode. - handler.extend_if_error(process.add_program(&aleo_program).map_err(LeoError::Anyhow))?; + let (last, rest) = sources.split_last().expect("non-empty sources"); - // Add the bytecode to the import stubs. - let stub = handler - .extend_if_error(disassemble_from_str::(&program_name, &bytecode).map_err(|err| err.into()))?; - import_stubs.insert(Symbol::intern(&program_name), stub); + // Parse-only stage for intermediate programs. + for source in rest { + let (program, program_name) = + handler.extend_if_error(super::test_utils::parse(source, handler, node_builder, import_stubs.clone()))?; + + import_stubs.insert(Symbol::intern(&program_name), program.into()); - // Only error out if there are errors. Warnings are okay but we still want to print them later. if handler.err_count() != 0 { return Err(()); } + } + + // Full compile for final program. + let (compiled_programs, _program_name) = + handler.extend_if_error(super::test_utils::whole_compile(last, handler, node_builder, import_stubs.clone()))?; - bytecodes.push(bytecode); + // Only error out if there are errors. Warnings are okay but we still want to print them later. + if handler.err_count() != 0 { + return Err(()); } + // Add imports. + for Bytecode { bytecode, .. } in compiled_programs.import_bytecodes { + add_aleo_program(&bytecode)?; + bytecodes.push(bytecode.clone()); + } + + // Add main program. + let primary_bytecode = compiled_programs.primary_bytecode.clone(); + add_aleo_program(&primary_bytecode)?; + bytecodes.push(primary_bytecode); + Ok(bytecodes.iter().format(&format!("{}\n", super::test_utils::PROGRAM_DELIMITER)).to_string()) } fn runner(source: &str) -> String { let buf = BufferEmitter::new(); let handler = Handler::new(buf.clone()); + let node_builder = Rc::new(NodeBuilder::default()); - create_session_if_not_set_then(|_| match run_test(source, &handler) { + create_session_if_not_set_then(|_| match run_test(source, &handler, &node_builder) { Ok(x) => format!("{}{}", buf.extract_warnings(), x), Err(()) => format!("{}{}", buf.extract_errs(), buf.extract_warnings()), }) diff --git a/compiler/compiler/src/test_execution.rs b/compiler/compiler/src/test_execution.rs index ef6ae5df52e..218adc3b8b2 100644 --- a/compiler/compiler/src/test_execution.rs +++ b/compiler/compiler/src/test_execution.rs @@ -16,18 +16,15 @@ use crate::run_with_ledger; -use leo_disassembler::disassemble_from_str; +use leo_ast::NodeBuilder; use leo_errors::{BufferEmitter, Handler, Result}; +use leo_passes::Bytecode; use leo_span::{Symbol, create_session_if_not_set_then}; -use snarkvm::prelude::TestnetV0; - use indexmap::IndexMap; use itertools::Itertools as _; use serial_test::serial; -use std::fmt::Write as _; - -type CurrentNetwork = TestnetV0; +use std::{fmt::Write as _, rc::Rc}; // Execution test configuration. #[derive(Debug)] @@ -43,22 +40,39 @@ impl Default for Config { } } -fn execution_run_test(config: &Config, cases: &[run_with_ledger::Case], handler: &Handler) -> Result { +fn execution_run_test( + config: &Config, + cases: &[run_with_ledger::Case], + handler: &Handler, + node_builder: &Rc, +) -> Result { let mut import_stubs = IndexMap::new(); - let mut ledger_config = run_with_ledger::Config { seed: config.seed, start_height: config.start_height, programs: Vec::new() }; - // Compile each source file. - for source in &config.sources { - let (bytecode, name) = super::test_utils::whole_compile(source, handler, import_stubs.clone())?; + // We assume config.sources is non-empty. + let (last, rest) = config.sources.split_last().expect("non-empty sources"); - let stub = disassemble_from_str::(&name, &bytecode)?; - import_stubs.insert(Symbol::intern(&name), stub); + // Parse-only for intermediate programs. + for source in rest { + let (program, program_name) = super::test_utils::parse(source, handler, node_builder, import_stubs.clone())?; - ledger_config.programs.push(run_with_ledger::Program { bytecode, name }); + import_stubs.insert(Symbol::intern(&program_name), program.into()); } + // Full compile for the final program. + let (compiled_programs, program_name) = + super::test_utils::whole_compile(last, handler, node_builder, import_stubs.clone())?; + + // Add imports. + for Bytecode { program_name, bytecode } in compiled_programs.import_bytecodes { + ledger_config.programs.push(run_with_ledger::Program { bytecode, name: program_name }); + } + + // Add main program. + let primary_bytecode = compiled_programs.primary_bytecode.clone(); + ledger_config.programs.push(run_with_ledger::Program { bytecode: primary_bytecode, name: program_name }); + // Note: We wrap cases in a slice to run them all in one ledger instance. let outcomes = run_with_ledger::run_with_ledger(&ledger_config, &[cases.to_vec()])?.into_iter().flatten().collect::>(); @@ -91,6 +105,7 @@ fn execution_run_test(config: &Config, cases: &[run_with_ledger::Case], handler: fn execution_runner(source: &str) -> String { let buf = BufferEmitter::new(); let handler = Handler::new(buf.clone()); + let node_builder = Rc::new(NodeBuilder::default()); let mut config = Config::default(); let mut cases = Vec::::new(); @@ -120,7 +135,7 @@ fn execution_runner(source: &str) -> String { // Split the sources and add them to the config. config.sources = source.split(super::test_utils::PROGRAM_DELIMITER).map(|s| s.trim().to_string()).collect(); - create_session_if_not_set_then(|_| match execution_run_test(&config, &cases, &handler) { + create_session_if_not_set_then(|_| match execution_run_test(&config, &cases, &handler, &node_builder) { Ok(s) => s, Err(e) => { format!("Error while running execution tests:\n{e}\n\nErrors:\n{}", buf.extract_errs()) diff --git a/compiler/compiler/src/test_utils.rs b/compiler/compiler/src/test_utils.rs index b9c9d5d4918..ce2a4c251a2 100644 --- a/compiler/compiler/src/test_utils.rs +++ b/compiler/compiler/src/test_utils.rs @@ -16,43 +16,105 @@ use crate::Compiler; -use leo_ast::{NetworkName, Stub}; +use leo_ast::{NetworkName, NodeBuilder, Program, Stub}; use leo_errors::{Handler, LeoError}; +use leo_passes::CompiledPrograms; use leo_span::{Symbol, source_map::FileName}; -use std::path::PathBuf; +use std::{path::PathBuf, rc::Rc}; use indexmap::IndexMap; pub const PROGRAM_DELIMITER: &str = "// --- Next Program --- //"; pub const MODULE_DELIMITER: &str = "// --- Next Module:"; -/// Compiles a complete program from a single source string that may contain -/// embedded modules marked by a delimiter. +/// Fully compiles a Leo source string into bytecode. /// -/// The source string is expected to contain sections separated by the `MODULE_DELIMITER`, -/// each representing either the main source or a named module. The compiler parses each -/// section and compiles the full program, including any modules. +/// This performs the entire compilation pipeline: +/// - splits embedded modules, +/// - initializes a compiler with the given `handler`, `node_builder`, and `import_stubs`, +/// - compiles the main program and its modules, +/// - returns: +/// * `(main_bytecode, imported_bytecodes)` and +/// * the compiled program's name. +/// +/// Used when compiling the final (top-level) program in a test. +#[allow(clippy::type_complexity)] pub fn whole_compile( source: &str, handler: &Handler, + node_builder: &Rc, + import_stubs: IndexMap, +) -> Result<(CompiledPrograms, String), LeoError> { + let (main_source, modules) = split_modules(source); + + let mut compiler = Compiler::new( + None, + /* is_test */ false, + handler.clone(), + node_builder.clone(), + "/fakedirectory-wont-use".into(), + None, + import_stubs, + NetworkName::TestnetV0, + ); + + // Prepare module references + let module_refs: Vec<(&str, FileName)> = + modules.iter().map(|(src, path)| (src.as_str(), FileName::Custom(path.to_string_lossy().into()))).collect(); + + let filename = FileName::Custom("compiler-test".into()); + let bytecodes = compiler.compile(&main_source, filename, &module_refs)?; + + Ok((bytecodes, compiler.program_name.unwrap())) +} + +/// Parses a Leo source string into an AST `Program` without generating bytecode. +/// +/// This runs only the front-end portion of the pipeline: +/// - splits embedded modules, +/// - initializes a compiler with the given `handler`, `node_builder`, and `import_stubs`, +/// - parses the main program and its modules into an AST, +/// - returns the parsed `Program` and the program's name. +/// +/// Used for intermediate programs that are imported by the final one. +pub fn parse( + source: &str, + handler: &Handler, + node_builder: &Rc, import_stubs: IndexMap, -) -> Result<(String, String), LeoError> { +) -> Result<(Program, String), LeoError> { + let (main_source, modules) = split_modules(source); + let mut compiler = Compiler::new( None, /* is_test */ false, handler.clone(), + node_builder.clone(), "/fakedirectory-wont-use".into(), None, import_stubs, NetworkName::TestnetV0, ); + // Prepare module references + let module_refs: Vec<(&str, FileName)> = + modules.iter().map(|(src, path)| (src.as_str(), FileName::Custom(path.to_string_lossy().into()))).collect(); + + let filename = FileName::Custom("compiler-test".into()); + let program = compiler.parse_and_return_ast(&main_source, filename, &module_refs)?; + + Ok((program, compiler.program_name.unwrap())) +} + +/// Splits a single source string into a main source and a list of module +/// `(source, path)` pairs using the MODULE_DELIMITER protocol. +/// +/// Shared by both `whole_compile` and `parse`. +fn split_modules(source: &str) -> (String, Vec<(String, PathBuf)>) { + // Fast path — no modules at all if !source.contains(MODULE_DELIMITER) { - // Fast path: no modules - let filename = FileName::Custom("compiler-test".into()); - let bytecode = compiler.compile(source, filename.clone(), &Vec::new())?; - return Ok((bytecode, compiler.program_name.unwrap())); + return (source.to_string(), Vec::new()); } let mut main_source = String::new(); @@ -88,12 +150,5 @@ pub fn whole_compile( main_source = current_module_source; } - // Prepare module references for compiler - let module_refs: Vec<(&str, FileName)> = - modules.iter().map(|(src, path)| (src.as_str(), FileName::Custom(path.to_string_lossy().into()))).collect(); - - let filename = FileName::Custom("compiler-test".into()); - let bytecode = compiler.compile(&main_source, filename, &module_refs)?; - - Ok((bytecode, compiler.program_name.unwrap())) + (main_source, modules) } diff --git a/compiler/parser/src/conversions.rs b/compiler/parser/src/conversions.rs index b7db7121e14..f9e02517cb6 100644 --- a/compiler/parser/src/conversions.rs +++ b/compiler/parser/src/conversions.rs @@ -1281,7 +1281,7 @@ pub fn to_main(node: &SyntaxNode<'_>, builder: &NodeBuilder, handler: &Handler) .filter(|child| matches!(child.kind, SyntaxKind::Import)) .map(|child| { let name = Symbol::intern(child.children[1].text.strip_suffix(".aleo").unwrap()); - (name, (leo_ast::Program::default(), child.span)) + (name, child.span) }) .collect::>(); diff --git a/compiler/passes/src/code_generation/expression.rs b/compiler/passes/src/code_generation/expression.rs index b8e81d4217a..947c9d861a6 100644 --- a/compiler/passes/src/code_generation/expression.rs +++ b/compiler/passes/src/code_generation/expression.rs @@ -621,7 +621,7 @@ impl CodeGeneratingVisitor<'_> { let func_symbol = self .state .symbol_table - .lookup_function(&Location::new(callee_program, input.function.absolute_path().to_vec())) + .lookup_function(caller_program, &Location::new(callee_program, input.function.absolute_path().to_vec())) .expect("Type checking guarantees functions exist"); let mut instructions = vec![]; @@ -754,14 +754,19 @@ impl CodeGeneratingVisitor<'_> { } Type::Composite(comp_ty) => { + let current_program = self.program_id.unwrap().name.name; // We need to cast the old struct or record's members into the new one. - let program = comp_ty.program.unwrap_or(self.program_id.unwrap().name.name); + let program = comp_ty.program.unwrap_or(current_program); let location = Location::new(program, comp_ty.path.absolute_path().to_vec()); let comp = self .state .symbol_table - .lookup_record(&location) - .or_else(|| self.state.symbol_table.lookup_struct(&comp_ty.path.absolute_path())) + .lookup_record(current_program, &location) + .or_else(|| { + self.state + .symbol_table + .lookup_struct(current_program, &Location::new(program, comp_ty.path.absolute_path())) + }) .unwrap(); let elems = comp .members diff --git a/compiler/passes/src/code_generation/mod.rs b/compiler/passes/src/code_generation/mod.rs index 295b039fbde..ebf87453388 100644 --- a/compiler/passes/src/code_generation/mod.rs +++ b/compiler/passes/src/code_generation/mod.rs @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use std::fmt::Display; - -use crate::Pass; +use crate::{CompiledPrograms, Pass}; use itertools::Itertools; use leo_ast::{Literal, Mode, ProgramId}; use leo_errors::Result; +use std::fmt::Display; + mod expression; mod program; @@ -41,7 +41,7 @@ pub struct CodeGenerating; impl Pass for CodeGenerating { type Input = (); - type Output = AleoProgram; + type Output = CompiledPrograms; const NAME: &str = "CodeGenerating"; @@ -61,8 +61,8 @@ impl Pass for CodeGenerating { conditional_depth: 0, internal_record_inputs: Default::default(), }; - let code = visitor.visit_program(visitor.state.ast.as_repr()); - Ok(code) + + Ok(visitor.visit_package()) } } diff --git a/compiler/passes/src/code_generation/program.rs b/compiler/passes/src/code_generation/program.rs index 87a215a75a3..06ed734f1ee 100644 --- a/compiler/passes/src/code_generation/program.rs +++ b/compiler/passes/src/code_generation/program.rs @@ -57,10 +57,9 @@ impl<'a> CodeGeneratingVisitor<'a> { let this_program = self.program_id.unwrap().name.name; let lookup = |name: &[Symbol]| { - self.state - .symbol_table - .lookup_struct(name) - .or_else(|| self.state.symbol_table.lookup_record(&Location::new(this_program, name.to_vec()))) + self.state.symbol_table.lookup_struct(this_program, &Location::new(this_program, name.to_vec())).or_else( + || self.state.symbol_table.lookup_record(this_program, &Location::new(this_program, name.to_vec())), + ) }; // Add each `Struct` or `Record` in the post-ordering and produce an Aleo struct or record. @@ -90,10 +89,13 @@ impl<'a> CodeGeneratingVisitor<'a> { let finalize = &self .state .symbol_table - .lookup_function(&Location::new( - self.program_id.unwrap().name.name, - vec![function.identifier.name], // Guaranteed to live in program scope, not in any submodule - )) + .lookup_function( + this_program, + &Location::new( + this_program, + vec![function.identifier.name], // Guaranteed to live in program scope, not in any submodule + ), + ) .unwrap() .clone() .finalizer @@ -236,12 +238,12 @@ impl<'a> CodeGeneratingVisitor<'a> { // Track all internal record inputs. if let Type::Composite(comp) = &input.type_ { - let program = comp.program.unwrap_or(self.program_id.unwrap().name.name); - if let Some(record) = self - .state - .symbol_table - .lookup_record(&Location::new(program, comp.path.absolute_path().to_vec())) - && (record.external.is_none() || record.external == self.program_id.map(|id| id.name.name)) + let current_program = self.program_id.unwrap().name.name; + let program = comp.program.unwrap_or(current_program); + + let path = Location::new(program, comp.path.absolute_path().to_vec()); + if program == current_program + && self.state.symbol_table.lookup_record(current_program, &path).is_some() { self.internal_record_inputs.insert(AleoExpr::Reg(register_num.clone())); } diff --git a/compiler/passes/src/code_generation/type_.rs b/compiler/passes/src/code_generation/type_.rs index f75ab3e4e26..00fdd0803b4 100644 --- a/compiler/passes/src/code_generation/type_.rs +++ b/compiler/passes/src/code_generation/type_.rs @@ -81,7 +81,12 @@ impl CodeGeneratingVisitor<'_> { let name = composite.path.absolute_path(); let this_program_name = self.program_id.unwrap().name.name; let program_name = composite.program.unwrap_or(this_program_name); - if self.state.symbol_table.lookup_record(&Location::new(program_name, name.to_vec())).is_some() { + if self + .state + .symbol_table + .lookup_record(this_program_name, &Location::new(program_name, name.to_vec())) + .is_some() + { let [record_name] = &name[..] else { panic!("Absolute paths to records can only have a single segment at this stage.") }; diff --git a/compiler/passes/src/code_generation/visitor.rs b/compiler/passes/src/code_generation/visitor.rs index 5e7ae2b4a16..aac280ed358 100644 --- a/compiler/passes/src/code_generation/visitor.rs +++ b/compiler/passes/src/code_generation/visitor.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{AleoConstructor, AleoExpr, AleoReg, CompilerState}; +use crate::{AleoConstructor, AleoExpr, AleoReg, Bytecode, CompiledPrograms, CompilerState}; use leo_ast::{Function, Program, ProgramId, Variant}; use leo_span::Symbol; @@ -71,6 +71,53 @@ pub(crate) fn check_snarkvm_constructor(actual: &AleoConstructor) -> } impl CodeGeneratingVisitor<'_> { + pub(crate) fn visit_package(&mut self) -> CompiledPrograms { + let primary_bytecode = self.visit_program(self.state.ast.as_repr()).to_string(); + + let import_bytecodes = self + .state + .ast + .as_repr() + .stubs + .values() + .filter_map(|stub| { + if let leo_ast::Stub::FromLeo { program, .. } = stub { + let program_name = program + .program_scopes + .first() + .expect("programs must have a single program scope at this time.") + .0; + + // Get transitive imports for this program + let transitive_imports = self + .state + .symbol_table + .get_transitive_imports(program_name) + .into_iter() + .map(|sym| sym.to_string()) + .collect::>(); + + // Generate Aleo imports for those dependencies + let import_section = if transitive_imports.is_empty() { + String::new() + } else { + transitive_imports.iter().map(|name| format!("import {name}.aleo;\n")).collect::() + }; + + // Generate this stub’s Aleo program text + let mut bytecode = self.visit_program(program).to_string(); + bytecode = format!("{import_section}{bytecode}"); + + Some(Bytecode { program_name: program_name.to_string(), bytecode }) + } else { + None + } + }) + .collect(); + + CompiledPrograms { primary_bytecode, import_bytecodes } + } + pub(crate) fn next_register(&mut self) -> AleoReg { self.next_register += 1; AleoReg::R(self.next_register - 1) diff --git a/compiler/passes/src/common/block_to_function_rewriter/mod.rs b/compiler/passes/src/common/block_to_function_rewriter/mod.rs new file mode 100644 index 00000000000..3298562589d --- /dev/null +++ b/compiler/passes/src/common/block_to_function_rewriter/mod.rs @@ -0,0 +1,336 @@ +// Copyright (C) 2019-2025 Provable 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 . + +/// Transforms a captured `Block` into a standalone async `Function` plus a +/// corresponding call expression. +/// +/// This pass analyzes symbol accesses inside the block, determines which +/// variables must become parameters, and synthesizes the necessary `Input`s +/// and call-site arguments. Tuple and tuple-field accesses are normalized so +/// that each accessed element becomes a unique parameter, with full-tuple +/// reconstruction when needed. +/// +/// The original block is then reconstructed with all symbol references +/// replaced by these synthesized parameters. The result is a function +/// that encapsulates the block's logic and a call expression that invokes it. +/// +/// # Example +/// ```leo +/// // Original block +/// let a: i32 = 1; +/// let b: i32 = 2; +/// let c: (i32, i32) = (3, 4); +/// { +/// let x = a + b; +/// let y = c.0 + c.1; +/// .. +/// } +/// +/// // Rewritten as a function + call expression (assume `variant` is `AsyncFunction` here) +/// async function generated_async(a: i32, b: i32, "c.0": i32, "c.1": i32) { +/// let x = a + b; +/// let y = "c.0" + "c.1"; +/// .. +/// } +/// +/// // Call +/// generated_async(a, b, c.0, c.1); +/// ``` +use crate::{CompilerState, Replacer, SymbolAccessCollector}; + +use leo_ast::{ + AstReconstructor, + AstVisitor, + Block, + CallExpression, + Expression, + Function, + Identifier, + Input, + Location, + Node, + Path, + TupleAccess, + TupleExpression, + TupleType, + Type, + Variant, +}; +use leo_span::{Span, Symbol}; + +use indexmap::IndexMap; + +pub struct BlockToFunctionRewriter<'a> { + state: &'a mut CompilerState, + current_program: Symbol, +} + +impl<'a> BlockToFunctionRewriter<'a> { + pub fn new(state: &'a mut CompilerState, current_program: Symbol) -> Self { + Self { state, current_program } + } +} + +impl BlockToFunctionRewriter<'_> { + pub fn rewrite_block( + &mut self, + input: &Block, + function_name: Symbol, + function_variant: Variant, + ) -> (Function, Expression) { + // Collect all symbol accesses in the block. + let mut access_collector = SymbolAccessCollector::new(self.state); + access_collector.visit_block(input); + + // Stores mapping from accessed symbol (and optional index) to the expression used in replacement. + let mut replacements: IndexMap<(Symbol, Option), Expression> = IndexMap::new(); + + // Helper to create a fresh `Identifier`. + let make_identifier = |slf: &mut Self, symbol: Symbol| Identifier { + name: symbol, + span: Span::default(), + id: slf.state.node_builder.next_id(), + }; + + // Generates a set of `Input`s and corresponding call-site `Expression`s for a given symbol access. + // + // This function handles both: + // - Direct variable accesses (e.g., `foo`) + // - Tuple element accesses (e.g., `foo.0`) + // + // For tuple accesses: + // - If a single element (e.g. `foo.0`) is accessed, it generates a synthetic input like `"foo.0"`. + // - If the whole tuple (e.g. `foo`) is accessed, it ensures all elements are covered by: + // - Reusing existing inputs from `replacements` if already generated via prior field access. + // - Creating new inputs and arguments for any missing elements. + // - The entire tuple is reconstructed in `replacements` using the individual elements as a `TupleExpression`. + // + // This function also ensures deduplication by consulting the `replacements` map: + // - If a given `(symbol, index)` has already been processed, no duplicate input or argument is generated. + // - This prevents repeated parameters for accesses like both `foo` and `foo.0`. + // + // # Parameters + // - `symbol`: The symbol being accessed. + // - `var_type`: The type of the symbol (may be a tuple or base type). + // - `index_opt`: `Some(index)` for a tuple field (e.g., `.0`), or `None` for full-variable access. + // + // # Returns + // A `Vec<(Input, Expression)>`, where: + // - `Input` is a parameter for the generated function. + // - `Expression` is the call-site argument expression used to invoke that parameter. + let mut make_inputs_and_arguments = + |slf: &mut Self, symbol: Symbol, var_type: &Type, index_opt: Option| -> Vec<(Input, Expression)> { + if replacements.contains_key(&(symbol, index_opt)) { + return vec![]; // No new input needed; argument already exists + } + + match index_opt { + Some(index) => { + let Type::Tuple(TupleType { elements }) = var_type else { + panic!("Expected tuple type when accessing tuple field: {symbol}.{index}"); + }; + + let synthetic_name = format!("\"{symbol}.{index}\""); + let synthetic_symbol = Symbol::intern(&synthetic_name); + let identifier = make_identifier(slf, synthetic_symbol); + + let input = Input { + identifier, + mode: leo_ast::Mode::None, + type_: elements[index].clone(), + span: Span::default(), + id: slf.state.node_builder.next_id(), + }; + + replacements.insert((symbol, Some(index)), Path::from(identifier).into_absolute().into()); + + vec![( + input, + TupleAccess { + tuple: Path::from(make_identifier(slf, symbol)).into_absolute().into(), + index: index.into(), + span: Span::default(), + id: slf.state.node_builder.next_id(), + } + .into(), + )] + } + + None => match var_type { + Type::Tuple(TupleType { elements }) => { + let mut inputs_and_arguments = Vec::with_capacity(elements.len()); + let mut tuple_elements = Vec::with_capacity(elements.len()); + + for (i, element_type) in elements.iter().enumerate() { + let key = (symbol, Some(i)); + + // Skip if this field is already handled + if let Some(existing_expr) = replacements.get(&key) { + tuple_elements.push(existing_expr.clone()); + continue; + } + + // Otherwise, synthesize identifier and input + let synthetic_name = format!("\"{symbol}.{i}\""); + let synthetic_symbol = Symbol::intern(&synthetic_name); + let identifier = make_identifier(slf, synthetic_symbol); + + let input = Input { + identifier, + mode: leo_ast::Mode::None, + type_: element_type.clone(), + span: Span::default(), + id: slf.state.node_builder.next_id(), + }; + + let expr: Expression = Path::from(identifier).into_absolute().into(); + + replacements.insert(key, expr.clone()); + tuple_elements.push(expr.clone()); + inputs_and_arguments.push(( + input, + TupleAccess { + tuple: Path::from(make_identifier(slf, symbol)).into_absolute().into(), + index: i.into(), + span: Span::default(), + id: slf.state.node_builder.next_id(), + } + .into(), + )); + } + + // Now insert the full tuple (even if all fields were already there). + replacements.insert( + (symbol, None), + Expression::Tuple(TupleExpression { + elements: tuple_elements, + span: Span::default(), + id: slf.state.node_builder.next_id(), + }), + ); + + inputs_and_arguments + } + + _ => { + let identifier = make_identifier(slf, symbol); + let input = Input { + identifier, + mode: leo_ast::Mode::None, + type_: var_type.clone(), + span: Span::default(), + id: slf.state.node_builder.next_id(), + }; + + replacements.insert((symbol, None), Path::from(identifier).into_absolute().into()); + + let argument = Path::from(make_identifier(slf, symbol)).into_absolute().into(); + vec![(input, argument)] + } + }, + } + }; + + // Resolve symbol accesses into inputs and call arguments. + let (inputs, arguments): (Vec<_>, Vec<_>) = access_collector + .symbol_accesses + .iter() + .filter_map(|(path, index)| { + // Skip globals and variables that are local to this block or to one of its children. + + // Skip globals. + if self + .state + .symbol_table + .lookup_global(self.current_program, &Location::new(self.current_program, path.to_vec())) + .is_some() + { + return None; + } + + // Skip variables that are local to this block or to one of its children. + let local_var_name = *path.last().expect("all paths must have at least one segment."); + if self.state.symbol_table.is_local_to_or_in_child_scope(input.id(), local_var_name) { + return None; + } + + // All other variables become parameters to the function being built. + let var = self.state.symbol_table.lookup_local(local_var_name)?; + Some(make_inputs_and_arguments(self, local_var_name, &var.type_, *index)) + }) + .flatten() + .unzip(); + + // Replacement logic used to patch the block. + let replace_expr = |expr: &Expression| -> Expression { + match expr { + Expression::Path(path) => { + replacements.get(&(path.identifier().name, None)).cloned().unwrap_or_else(|| expr.clone()) + } + + Expression::TupleAccess(ta) => { + if let Expression::Path(path) = &ta.tuple { + replacements + .get(&(path.identifier().name, Some(ta.index.value()))) + .cloned() + .unwrap_or_else(|| expr.clone()) + } else { + expr.clone() + } + } + + _ => expr.clone(), + } + }; + + // Reconstruct the block with replaced references. + let mut replacer = Replacer::new(replace_expr, true /* refresh IDs */, self.state); + let new_block = replacer.reconstruct_block(input.clone()).0; + + // Define the new function. + let function = Function { + annotations: vec![], + variant: function_variant, + identifier: make_identifier(self, function_name), + const_parameters: vec![], + input: inputs, + output: vec![], // No returns supported yet. + output_type: Type::Unit, // No returns supported yet. + block: new_block, + span: input.span, + id: self.state.node_builder.next_id(), + }; + + // Create the call expression to invoke the function. + let call_to_function = CallExpression { + function: Path::new( + vec![], + make_identifier(self, function_name), + true, + Some(vec![function_name]), // the new function lives in the top level program scope for now. + Span::default(), + self.state.node_builder.next_id(), + ), + const_arguments: vec![], + arguments, + program: Some(self.current_program), + span: input.span, + id: self.state.node_builder.next_id(), + }; + + (function, call_to_function.into()) + } +} diff --git a/compiler/passes/src/common/mod.rs b/compiler/passes/src/common/mod.rs index 9d23fee0da2..0bfa010b9b4 100644 --- a/compiler/passes/src/common/mod.rs +++ b/compiler/passes/src/common/mod.rs @@ -17,12 +17,18 @@ mod assigner; pub use assigner::*; +mod block_to_function_rewriter; +pub use block_to_function_rewriter::*; + mod rename_table; pub use rename_table::*; mod replacer; pub use replacer::*; +mod symbol_access_collector; +pub use symbol_access_collector::*; + mod symbol_table; pub use symbol_table::*; diff --git a/compiler/passes/src/common/symbol_access_collector/mod.rs b/compiler/passes/src/common/symbol_access_collector/mod.rs new file mode 100644 index 00000000000..26c2cd07566 --- /dev/null +++ b/compiler/passes/src/common/symbol_access_collector/mod.rs @@ -0,0 +1,64 @@ +// Copyright (C) 2019-2025 Provable 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::CompilerState; + +use leo_ast::{AstVisitor, Expression, Node as _, Path, ProgramVisitor, TupleAccess, Type}; +use leo_span::Symbol; + +use indexmap::IndexSet; + +/// Collects all symbol accesses within an async block, +/// including both direct variable identifiers (`x`) and tuple field accesses (`x.0`, `x.1`, etc.). +/// Each access is recorded as a pair: (Symbol, Option). +/// - `None` means a direct variable access. +/// - `Some(index)` means a tuple field access. +pub struct SymbolAccessCollector<'a> { + state: &'a CompilerState, + pub symbol_accesses: IndexSet<(Vec, Option)>, +} + +impl<'a> SymbolAccessCollector<'a> { + pub fn new(state: &'a mut CompilerState) -> Self { + Self { state, symbol_accesses: IndexSet::new() } + } +} + +impl AstVisitor for SymbolAccessCollector<'_> { + type AdditionalInput = (); + type Output = (); + + fn visit_path(&mut self, input: &Path, _: &Self::AdditionalInput) -> Self::Output { + self.symbol_accesses.insert((input.absolute_path(), None)); + } + + fn visit_tuple_access(&mut self, input: &TupleAccess, _: &Self::AdditionalInput) -> Self::Output { + // Here we assume that we can't have nested tuples which is currently guaranteed by type + // checking. This may change in the future. + if let Expression::Path(path) = &input.tuple { + // Futures aren't accessed by field; treat the whole thing as a direct variable + if let Some(Type::Future(_)) = self.state.type_table.get(&input.tuple.id()) { + self.symbol_accesses.insert((path.absolute_path(), None)); + } else { + self.symbol_accesses.insert((path.absolute_path(), Some(input.index.value()))); + } + } else { + self.visit_expression(&input.tuple, &()); + } + } +} + +impl ProgramVisitor for SymbolAccessCollector<'_> {} diff --git a/compiler/passes/src/common/symbol_table/mod.rs b/compiler/passes/src/common/symbol_table/mod.rs index 2308bef5edd..9a83d3a6da2 100644 --- a/compiler/passes/src/common/symbol_table/mod.rs +++ b/compiler/passes/src/common/symbol_table/mod.rs @@ -18,7 +18,7 @@ use leo_ast::{Composite, Expression, Function, Location, NodeBuilder, NodeID, Ty use leo_errors::{AstError, Color, Label, LeoError, Result}; use leo_span::{Span, Symbol}; -use indexmap::IndexMap; +use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use std::{cell::RefCell, collections::HashMap, rc::Rc}; @@ -30,6 +30,9 @@ pub use symbols::*; /// Scopes are indexed by the NodeID of the function, block, or iteration. #[derive(Debug, Default)] pub struct SymbolTable { + /// Maps a program to the list if programs it imports + imports: IndexMap>, + /// Functions indexed by location. functions: IndexMap, @@ -37,7 +40,8 @@ pub struct SymbolTable { records: IndexMap, /// Structs indexed by a path. - structs: IndexMap, Composite>, + /// The `bool` indicates whether this struct is external. + structs: IndexMap, /// Consts that have been successfully evaluated. global_consts: IndexMap, @@ -144,6 +148,56 @@ impl LocalTable { } impl SymbolTable { + /// Record that `importer` imports `imported`. + pub fn add_import(&mut self, importer: Symbol, imported: Symbol) { + self.imports.entry(importer).or_default().insert(imported); + } + + /// Record that multiple importers import the same `imported` program. + pub fn add_imported_by(&mut self, imported: Symbol, importers: &IndexSet) { + for importer in importers { + self.add_import(*importer, imported); + } + } + + /// Returns all programs imported by a given program. + pub fn get_imports(&self, program: &Symbol) -> Option<&IndexSet> { + self.imports.get(program) + } + + /// Returns a mutable reference to the set of imports for a given program. + pub fn get_imports_mut(&mut self, program: &Symbol) -> Option<&mut IndexSet> { + self.imports.get_mut(program) + } + + /// Returns an iterator over all import relationships. + pub fn iter_imports(&self) -> impl Iterator)> { + self.imports.iter() + } + + /// Check if `target` program is visible from `current` program. + fn is_visible(&self, current: Symbol, target: &Symbol) -> bool { + current == *target || self.imports.get(¤t).map(|imports| imports.contains(target)).unwrap_or(false) + } + + /// Returns the transitive closure of imports for a given program. + pub fn get_transitive_imports(&self, program: &Symbol) -> IndexSet { + let mut visited = IndexSet::new(); + self.collect_transitive_imports(program, &mut visited); + visited + } + + fn collect_transitive_imports(&self, program: &Symbol, visited: &mut IndexSet) { + if let Some(direct_imports) = self.imports.get(program) { + for imported in direct_imports { + if visited.insert(*imported) { + // Recurse only if this is a new import + self.collect_transitive_imports(imported, visited); + } + } + } + } + /// Reset everything except leave consts that have been evaluated. pub fn reset_but_consts(&mut self) { self.functions.clear(); @@ -165,8 +219,8 @@ impl SymbolTable { } /// Iterator over all the structs (not records) in this program. - pub fn iter_structs(&self) -> impl Iterator, &Composite)> { - self.structs.iter() + pub fn iter_structs(&self) -> impl Iterator { + self.structs.iter().map(|(loc, (composite, _))| (loc, composite)) } /// Iterator over all the records in this program. @@ -179,22 +233,27 @@ impl SymbolTable { self.functions.iter() } - /// Access the struct by this name if it exists. - pub fn lookup_struct(&self, path: &[Symbol]) -> Option<&Composite> { - self.structs.get(path) + /// Access the struct by this name if it exists and is accessible from program named `current_program`. + pub fn lookup_struct(&self, current_program: Symbol, loc: &Location) -> Option<&Composite> { + if self.is_visible(current_program, &loc.program) { self.structs.get(loc).map(|(c, _)| c) } else { None } + } + + /// Checks if this location refers to an accessible external struct from program named `current_program`. + pub fn is_external_struct(&self, loc: &Location) -> bool { + *self.structs.get(loc).map(|(_, is_external)| is_external).unwrap_or(&false) } - /// Access the record at this location if it exists. - pub fn lookup_record(&self, location: &Location) -> Option<&Composite> { - self.records.get(location) + /// Access the record at this location if it exists and is accessible from program named `current_program`. + pub fn lookup_record(&self, current_program: Symbol, location: &Location) -> Option<&Composite> { + if self.is_visible(current_program, &location.program) { self.records.get(location) } else { None } } - /// Access the function at this location if it exists. - pub fn lookup_function(&self, location: &Location) -> Option<&FunctionSymbol> { - self.functions.get(location) + /// Access the struct by this name if it exists and is accessible from program named `current_program`. + pub fn lookup_function(&self, current_program: Symbol, location: &Location) -> Option<&FunctionSymbol> { + if self.is_visible(current_program, &location.program) { self.functions.get(location) } else { None } } - /// Attempts to look up a variable by a path. + /// Attempts to look up a variable by a path from program named `current_program`. /// /// First, it tries to resolve the symbol as a global using the full path under the given program. /// If that fails and the path is non-empty, it falls back to resolving the last component @@ -208,8 +267,8 @@ impl SymbolTable { /// # Returns /// /// An `Option` containing the resolved symbol if found, otherwise `None`. - pub fn lookup_path(&self, program: Symbol, path: &[Symbol]) -> Option { - self.lookup_global(&Location::new(program, path.to_vec())) + pub fn lookup_path(&self, current_program: Symbol, program: Symbol, path: &[Symbol]) -> Option { + self.lookup_global(current_program, &Location::new(program, path.to_vec())) .cloned() .or_else(|| path.last().copied().and_then(|name| self.lookup_local(name))) } @@ -354,20 +413,21 @@ impl SymbolTable { self.global_consts.get(&Location::new(program, path.to_vec())).cloned() } - /// Insert a struct at this name. - /// - /// Since structs are indexed only by name, the program is used only to check shadowing. - pub fn insert_struct(&mut self, program: Symbol, path: &[Symbol], composite: Composite) -> Result<()> { - if let Some(old_composite) = self.structs.get(path) { - if eq_struct(&composite, old_composite) { + /// Insert a struct at this location. + pub fn insert_struct(&mut self, location: Location, composite: Composite, is_external: bool) -> Result<()> { + if let Some((current_composite, current_is_external)) = self.structs.get(&location) + && (*current_is_external || is_external) + { + if eq_struct(&composite, current_composite) { + // Allow redefining external structs, if the definitions match. Ok(()) } else { - Err(AstError::redefining_external_struct(path.iter().format("::"), old_composite.span).into()) + Err(AstError::redefining_external_struct(location.path.iter().format("::"), current_composite.span) + .into()) } } else { - let location = Location::new(program, path.to_vec()); self.check_shadow_global(&location, composite.identifier.span)?; - self.structs.insert(path.to_vec(), composite); + self.structs.insert(location, (composite, is_external)); Ok(()) } } @@ -393,9 +453,10 @@ impl SymbolTable { Ok(()) } - /// Access the global at this location if it exists. - pub fn lookup_global(&self, location: &Location) -> Option<&VariableSymbol> { - self.globals.get(location) + /// Access the global variable at this location if it exists and is visible + /// from the given `current_program`. + pub fn lookup_global(&self, current_program: Symbol, location: &Location) -> Option<&VariableSymbol> { + if self.is_visible(current_program, &location.program) { self.globals.get(location) } else { None } } pub fn emit_shadow_error(name: Symbol, span: Span, prev_span: Span) -> LeoError { @@ -414,7 +475,7 @@ impl SymbolTable { .get(location) .map(|f| f.function.identifier.span) .or_else(|| self.records.get(location).map(|r| r.identifier.span)) - .or_else(|| self.structs.get(&location.path).map(|s| s.identifier.span)) + .or_else(|| self.structs.get(location).map(|s| s.0.identifier.span)) .or_else(|| self.globals.get(location).map(|g| g.span)) .map_or_else(|| Ok(()), |prev_span| Err(Self::emit_shadow_error(*name, span, prev_span))) } diff --git a/compiler/passes/src/const_propagation/visitor.rs b/compiler/passes/src/const_propagation/visitor.rs index bdde0c4d4b7..1ce66f67045 100644 --- a/compiler/passes/src/const_propagation/visitor.rs +++ b/compiler/passes/src/const_propagation/visitor.rs @@ -16,7 +16,7 @@ use crate::CompilerState; -use leo_ast::{Expression, Node, NodeID, interpreter_value::Value}; +use leo_ast::{Expression, Location, Node, NodeID, interpreter_value::Value}; use leo_errors::StaticAnalyzerError; use leo_span::{Span, Symbol}; @@ -64,16 +64,16 @@ impl ConstPropagationVisitor<'_> { pub fn value_to_expression(&self, value: &Value, span: Span, id: NodeID) -> Option { let ty = self.state.type_table.get(&id)?; let symbol_table = &self.state.symbol_table; - let struct_lookup = |sym: &[Symbol]| { + let struct_lookup = |loc: &Location| { symbol_table - .lookup_struct(sym) + .lookup_struct(self.program, loc) .unwrap() .members .iter() .map(|mem| (mem.identifier.name, mem.type_.clone())) .collect() }; - value.to_expression(span, &self.state.node_builder, &ty, &struct_lookup) + value.to_expression(span, &self.state.node_builder, self.program, &ty, &struct_lookup) } pub fn value_to_expression_node(&self, value: &Value, previous: &impl Node) -> Option { diff --git a/compiler/passes/src/flattening/ast.rs b/compiler/passes/src/flattening/ast.rs index 1007bf903ef..bd619ca1287 100644 --- a/compiler/passes/src/flattening/ast.rs +++ b/compiler/passes/src/flattening/ast.rs @@ -107,9 +107,11 @@ impl AstReconstructor for FlatteningVisitor<'_> { let if_true_type = self .state .symbol_table - .lookup_struct(&composite_path.absolute_path()) + .lookup_struct(self.program, &Location::new(program, composite_path.absolute_path())) .or_else(|| { - self.state.symbol_table.lookup_record(&Location::new(program, composite_path.absolute_path())) + self.state + .symbol_table + .lookup_record(self.program, &Location::new(program, composite_path.absolute_path())) }) .expect("This definition should exist") .clone(); diff --git a/compiler/passes/src/loop_unrolling/program.rs b/compiler/passes/src/loop_unrolling/program.rs index e212aa6ae74..251f65e47ef 100644 --- a/compiler/passes/src/loop_unrolling/program.rs +++ b/compiler/passes/src/loop_unrolling/program.rs @@ -19,10 +19,10 @@ use leo_ast::*; use super::UnrollingVisitor; impl ProgramReconstructor for UnrollingVisitor<'_> { - fn reconstruct_stub(&mut self, input: Stub) -> Stub { + fn reconstruct_aleo_program(&mut self, input: AleoProgram) -> AleoProgram { // Set the current program. self.program = input.stub_id.name.name; - Stub { + AleoProgram { functions: input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function_stub(f))).collect(), ..input } diff --git a/compiler/passes/src/monomorphization/ast.rs b/compiler/passes/src/monomorphization/ast.rs index 2e696c4a419..ab18edce0e6 100644 --- a/compiler/passes/src/monomorphization/ast.rs +++ b/compiler/passes/src/monomorphization/ast.rs @@ -23,6 +23,7 @@ use leo_ast::{ CompositeType, Expression, Identifier, + Location, Node as _, ProgramReconstructor, StructExpression, @@ -77,7 +78,11 @@ impl AstReconstructor for MonomorphizationVisitor<'_> { self.changed = true; ( Type::Composite(CompositeType { - path: self.monomorphize_struct(&input.path, &evaluated_const_args), + path: self.monomorphize_struct( + input.program.unwrap_or(self.program), + &input.path, + &evaluated_const_args, + ), const_arguments: vec![], // remove const arguments program: input.program, }), @@ -124,6 +129,8 @@ impl AstReconstructor for MonomorphizationVisitor<'_> { input_call: CallExpression, _additional: &(), ) -> (Expression, Self::AdditionalOutput) { + let callee_program = input_call.program.unwrap_or(self.program); + // Skip calls to functions from other programs. if input_call.program.is_some_and(|prog| prog != self.program) { return (input_call.into(), Default::default()); @@ -146,7 +153,7 @@ impl AstReconstructor for MonomorphizationVisitor<'_> { // Look up the already reconstructed function by name. let callee_fn = self .reconstructed_functions - .get(&input_call.function.absolute_path()) + .get(&Location::new(callee_program, input_call.function.absolute_path())) .expect("Callee should already be reconstructed (post-order traversal)."); // Proceed only if the function variant is `inline`. @@ -167,7 +174,7 @@ impl AstReconstructor for MonomorphizationVisitor<'_> { // Check if the new callee name is not already present in `reconstructed_functions`. This ensures that we do not // add a duplicate definition for the same function. - if self.reconstructed_functions.get(&new_callee_path.absolute_path()).is_none() { + if self.reconstructed_functions.get(&Location::new(callee_program, new_callee_path.absolute_path())).is_none() { // Build mapping from const parameters to const argument values. let const_param_map: IndexMap<_, _> = callee_fn .const_parameters @@ -203,10 +210,11 @@ impl AstReconstructor for MonomorphizationVisitor<'_> { function.id = self.state.node_builder.next_id(); // Keep track of the new function in case other functions need it. - self.reconstructed_functions.insert(new_callee_path.absolute_path(), function); + self.reconstructed_functions + .insert(Location::new(callee_program, new_callee_path.absolute_path()), function); // Now keep track of the function we just monomorphized - self.monomorphized_functions.insert(input_call.function.absolute_path()); + self.monomorphized_functions.insert(Location::new(callee_program, input_call.function.absolute_path())); } // At this stage, we know that we're going to modify the program @@ -267,7 +275,7 @@ impl AstReconstructor for MonomorphizationVisitor<'_> { // Finally, construct the updated struct expression that points to a monomorphized version and return it. ( StructExpression { - path: self.monomorphize_struct(&input.path, &evaluated_const_args), + path: self.monomorphize_struct(self.program, &input.path, &evaluated_const_args), members, const_arguments: vec![], // remove const arguments span: input.span, // Keep pointing to the original struct expression diff --git a/compiler/passes/src/monomorphization/program.rs b/compiler/passes/src/monomorphization/program.rs index 3229e158ddb..e3934d5be56 100644 --- a/compiler/passes/src/monomorphization/program.rs +++ b/compiler/passes/src/monomorphization/program.rs @@ -15,7 +15,7 @@ // along with the Leo library. If not, see . use super::MonomorphizationVisitor; -use leo_ast::{AstReconstructor, Module, Program, ProgramReconstructor, ProgramScope, Statement, Variant}; +use leo_ast::{AstReconstructor, Location, Module, Program, ProgramReconstructor, ProgramScope, Statement, Variant}; use leo_span::sym; impl ProgramReconstructor for MonomorphizationVisitor<'_> { @@ -29,11 +29,12 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { // Reconstruct structs in post-order. for struct_name in &struct_order { - if let Some(r#struct) = self.struct_map.swap_remove(struct_name) { + if let Some(r#struct) = self.struct_map.swap_remove(&Location::new(self.program, struct_name.clone())) { // Perform monomorphization or other reconstruction logic. let reconstructed_struct = self.reconstruct_struct(r#struct); // Store the reconstructed struct for inclusion in the output scope. - self.reconstructed_structs.insert(struct_name.clone(), reconstructed_struct); + self.reconstructed_structs + .insert(Location::new(self.program, struct_name.clone()), reconstructed_struct); } } @@ -61,7 +62,7 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { return true; } self.function_map - .get(&location.path) + .get(location) .map(|f| { matches!( f.variant, @@ -76,16 +77,17 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { for function_name in &order { // Reconstruct functions in post-order. - if let Some(function) = self.function_map.swap_remove(&function_name.path) { + if let Some(function) = + self.function_map.swap_remove(&Location::new(self.program, function_name.path.clone())) + { // Reconstruct the function. let reconstructed_function = self.reconstruct_function(function); // Add the reconstructed function to the mapping. - self.reconstructed_functions.insert(function_name.path.clone(), reconstructed_function); + self.reconstructed_functions + .insert(Location::new(self.program, function_name.path.clone()), reconstructed_function); } } - // Get any - // Now reconstruct mappings and storage variables let mappings = input.mappings.into_iter().map(|(id, mapping)| (id, self.reconstruct_mapping(mapping))).collect(); @@ -109,9 +111,12 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { let constructor = input.constructor.map(|c| self.reconstruct_constructor(c)); // Now retain only functions that are either not yet monomorphized or are still referenced by calls. - self.reconstructed_functions.retain(|f, _| { - let is_monomorphized = self.monomorphized_functions.contains(f); - let is_still_called = self.unresolved_calls.iter().any(|c| &c.function.absolute_path() == f); + self.reconstructed_functions.retain(|l, _| { + let is_monomorphized = self.monomorphized_functions.contains(l); + let is_still_called = self + .unresolved_calls + .iter() + .any(|c| c.function.absolute_path() == l.path && c.program.unwrap_or(self.program) == self.program); !is_monomorphized || is_still_called }); @@ -129,18 +134,24 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { structs: self .reconstructed_structs .iter() - .filter_map(|(path, c)| { - // only consider structs defined at program scope. The rest will be added to their parent module. - path.split_last().filter(|(_, rest)| rest.is_empty()).map(|(last, _)| (*last, c.clone())) + .filter_map(|(loc, c)| { + // Only consider structs defined at program scope within the same program. + loc.path + .split_last() + .filter(|(_, rest)| rest.is_empty() && loc.program == self.program) + .map(|(last, _)| (*last, c.clone())) }) .collect(), mappings, storage_variables, functions: all_functions .iter() - .filter_map(|(path, f)| { - // only consider functions defined at program scope. The rest will be added to their parent module. - path.split_last().filter(|(_, rest)| rest.is_empty()).map(|(last, _)| (*last, f.clone())) + .filter_map(|(loc, f)| { + // Only consider functions defined at program scope within the same program. + loc.path + .split_last() + .filter(|(_, rest)| rest.is_empty() && loc.program == self.program) + .map(|(last, _)| (*last, f.clone())) }) .collect(), constructor, @@ -150,6 +161,16 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { } fn reconstruct_program(&mut self, input: Program) -> Program { + let stubs = input.stubs.into_iter().map(|(id, stub)| (id, self.reconstruct_stub(stub))).collect(); + + // Set up for the next program. + // This is likely to change when we support cross-program monomorphization. + self.struct_map.clear(); + self.function_map.clear(); + + self.program = + *input.program_scopes.first().expect("a program must have a single program scope at this stage").0; + // Populate `self.function_map` using the functions in the program scopes and the modules input .modules @@ -166,7 +187,7 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { .flat_map(|(_, scope)| scope.functions.iter().map(|(name, f)| (vec![*name], f.clone()))), ) .for_each(|(full_name, f)| { - self.function_map.insert(full_name, f); + self.function_map.insert(Location::new(self.program, full_name), f); }); // Populate `self.struct_map` using the structs in the program scopes and the modules @@ -185,12 +206,13 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { .flat_map(|(_, scope)| scope.structs.iter().map(|(name, f)| (vec![*name], f.clone()))), ) .for_each(|(full_name, f)| { - self.struct_map.insert(full_name, f); + self.struct_map.insert(Location::new(self.program, full_name), f); }); // Reconstruct prrogram scopes first then reconstruct the modules after `self.reconstructed_structs` // and `self.reconstructed_functions` have been populated. Program { + stubs, program_scopes: input .program_scopes .into_iter() @@ -208,18 +230,25 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> { structs: self .reconstructed_structs .iter() - .filter_map(|(path, c)| path.split_last().map(|(last, rest)| (last, rest, c))) - .filter(|&(_, rest, _)| input.path == rest) - .map(|(last, _, c)| (*last, c.clone())) + .filter_map(|(loc, c)| { + loc.path + .split_last() + .filter(|(_, rest)| *rest == input.path && loc.program == self.program) + .map(|(last, _)| (*last, c.clone())) + }) .collect(), functions: self .reconstructed_functions .iter() - .filter_map(|(path, f)| path.split_last().map(|(last, rest)| (last, rest, f))) - .filter(|&(_, rest, _)| input.path == rest) - .map(|(last, _, f)| (*last, f.clone())) + .filter_map(|(loc, f)| { + loc.path + .split_last() + .filter(|(_, rest)| *rest == input.path && loc.program == self.program) + .map(|(last, _)| (*last, f.clone())) + }) .collect(), + ..input } } diff --git a/compiler/passes/src/monomorphization/visitor.rs b/compiler/passes/src/monomorphization/visitor.rs index b986aefa5f1..bde147d6567 100644 --- a/compiler/passes/src/monomorphization/visitor.rs +++ b/compiler/passes/src/monomorphization/visitor.rs @@ -24,6 +24,7 @@ use leo_ast::{ Expression, Function, Identifier, + Location, Path, ProgramReconstructor, StructExpression, @@ -35,19 +36,19 @@ pub struct MonomorphizationVisitor<'a> { /// The main program. pub program: Symbol, /// A map to provide faster lookup of functions. - pub function_map: IndexMap, Function>, + pub function_map: IndexMap, /// A map to provide faster lookup of structs. - pub struct_map: IndexMap, Composite>, + pub struct_map: IndexMap, /// A map of reconstructed functions in the current program scope. - pub reconstructed_functions: IndexMap, Function>, + pub reconstructed_functions: IndexMap, /// A set of all functions that have been monomorphized at least once. This keeps track of the _original_ names of /// the functions not the names of the monomorphized versions. - pub monomorphized_functions: IndexSet>, + pub monomorphized_functions: IndexSet, /// A map of reconstructed functions in the current program scope. - pub reconstructed_structs: IndexMap, Composite>, + pub reconstructed_structs: IndexMap, /// A set of all functions that have been monomorphized at least once. This keeps track of the _original_ names of /// the functions not the names of the monomorphized versions. - pub monomorphized_structs: IndexSet>, + pub monomorphized_structs: IndexSet, /// A vector of all the calls to const generic functions that have not been resolved. pub unresolved_calls: Vec, /// A vector of all the struct expressions of const generic structs that have not been resolved. @@ -71,7 +72,12 @@ impl MonomorphizationVisitor<'_> { /// * Returns a `Symbol` for the newly monomorphized struct. /// /// Note: this functions already assumes that all provided const arguments are literals. - pub(crate) fn monomorphize_struct(&mut self, path: &Path, const_arguments: &Vec) -> Path { + pub(crate) fn monomorphize_struct( + &mut self, + program: Symbol, + path: &Path, + const_arguments: &Vec, + ) -> Path { // Generate a unique name for the monomorphized struct based on const arguments. // // For `struct Foo::[x: u32, y: u32](..)`, the generated name would be `Foo::[1u32, 2u32]` for a struct @@ -87,12 +93,12 @@ impl MonomorphizationVisitor<'_> { // Check if the new struct name is not already present in `reconstructed_structs`. This ensures that we do not // add a duplicate definition for the same struct. - if self.reconstructed_structs.get(&new_struct_path.absolute_path()).is_none() { + if self.reconstructed_structs.get(&Location::new(program, new_struct_path.absolute_path())).is_none() { let full_name = path.absolute_path(); // Look up the already reconstructed struct by name. let struct_ = self .reconstructed_structs - .get(&full_name) + .get(&Location::new(program, full_name.clone())) .expect("Struct should already be reconstructed (post-order traversal)."); // Build mapping from const parameters to const argument values. @@ -123,10 +129,10 @@ impl MonomorphizationVisitor<'_> { struct_.id = self.state.node_builder.next_id(); // Keep track of the new struct in case other structs need it. - self.reconstructed_structs.insert(new_struct_path.absolute_path(), struct_); + self.reconstructed_structs.insert(Location::new(program, new_struct_path.absolute_path()), struct_); // Now keep track of the struct we just monomorphized - self.monomorphized_structs.insert(full_name); + self.monomorphized_structs.insert(Location::new(program, full_name)); } new_struct_path diff --git a/compiler/passes/src/name_validation/program.rs b/compiler/passes/src/name_validation/program.rs index 8dbb9d46b48..f783bef7c0a 100644 --- a/compiler/passes/src/name_validation/program.rs +++ b/compiler/passes/src/name_validation/program.rs @@ -39,7 +39,7 @@ impl ProgramVisitor for NameValidationVisitor<'_> { input.functions.iter().for_each(|(_, function)| self.visit_function(function)); } - fn visit_stub(&mut self, input: &Stub) { + fn visit_aleo_program(&mut self, input: &AleoProgram) { input.structs.iter().for_each(|(_, function)| self.visit_struct_stub(function)); input.functions.iter().for_each(|(_, function)| self.visit_function_stub(function)); } diff --git a/compiler/passes/src/option_lowering/ast.rs b/compiler/passes/src/option_lowering/ast.rs index 4e9a97becb7..570a7d5c8f3 100644 --- a/compiler/passes/src/option_lowering/ast.rs +++ b/compiler/passes/src/option_lowering/ast.rs @@ -62,8 +62,8 @@ impl leo_ast::AstReconstructor for OptionLoweringVisitor<'_> { ( Type::Composite(CompositeType { path: Path::from(Identifier::new(struct_name, self.state.node_builder.next_id())).into_absolute(), - const_arguments: vec![], // this is not a generic struct - program: None, // current program + const_arguments: vec![], // this is not a generic struct + program: Some(self.program), // current program }), Default::default(), ) @@ -115,7 +115,9 @@ impl leo_ast::AstReconstructor for OptionLoweringVisitor<'_> { "Type table must contain type for this expression ID; IDs are not modified during lowering", ); - if actual_expr_type.can_coerce_to(inner) { + if actual_expr_type.can_coerce_to(inner, self.program, &|loc: &Location| { + self.state.symbol_table.lookup_record(self.program, loc).is_some() + }) { return (self.wrap_optional_value(expr, *inner.clone()), stmts); } } @@ -358,7 +360,7 @@ impl leo_ast::AstReconstructor for OptionLoweringVisitor<'_> { let func_symbol = self .state .symbol_table - .lookup_function(&Location::new(callee_program, input.function.absolute_path())) + .lookup_function(callee_program, &Location::new(callee_program, input.function.absolute_path())) .expect("The symbol table creator should already have visited all functions.") .clone(); @@ -418,8 +420,8 @@ impl leo_ast::AstReconstructor for OptionLoweringVisitor<'_> { let struct_def = self .state .symbol_table - .lookup_record(&location) - .or_else(|| self.state.symbol_table.lookup_struct(&composite.path.absolute_path())) + .lookup_record(self.program, &location) + .or_else(|| self.state.symbol_table.lookup_struct(self.program, &location)) .or_else(|| self.new_structs.get(&composite.path.identifier().name)) .expect("guaranteed by type checking"); @@ -698,7 +700,7 @@ impl leo_ast::AstReconstructor for OptionLoweringVisitor<'_> { let func_symbol = self .state .symbol_table - .lookup_function(&Location::new(self.program, caller_path)) + .lookup_function(self.program, &Location::new(self.program, caller_path)) .expect("The symbol table creator should already have visited all functions."); let return_type = func_symbol.function.output_type.clone(); diff --git a/compiler/passes/src/option_lowering/program.rs b/compiler/passes/src/option_lowering/program.rs index c69683d9b6a..568edb1bde6 100644 --- a/compiler/passes/src/option_lowering/program.rs +++ b/compiler/passes/src/option_lowering/program.rs @@ -20,6 +20,7 @@ use leo_ast::{ ConstParameter, Function, Input, + Location, Module, Output, Program, @@ -31,35 +32,35 @@ use leo_span::Symbol; impl ProgramReconstructor for OptionLoweringVisitor<'_> { fn reconstruct_program(&mut self, input: Program) -> Program { + self.program = + *input.program_scopes.first().expect("a program must have a single program scope at this time.").0; + // Reconstruct all structs first and keep track of them in `self.reconstructed_structs`. for (_, scope) in &input.program_scopes { for (_, c) in &scope.structs { let new_struct = self.reconstruct_struct(c.clone()); - self.reconstructed_structs.insert(vec![new_struct.name()], new_struct); + self.reconstructed_structs + .insert(Location::new(scope.program_id.name.name, vec![new_struct.name()]), new_struct); } } for (module_path, module) in &input.modules { for (_, c) in &module.structs { let full_name = module_path.iter().cloned().chain(std::iter::once(c.name())).collect::>(); let new_struct = self.reconstruct_struct(c.clone()); - self.reconstructed_structs.insert(full_name, new_struct.clone()); + self.reconstructed_structs.insert(Location::new(module.program_name, full_name), new_struct.clone()); } } // Now we're ready to reconstruct everything else. Program { - imports: input - .imports - .into_iter() - .map(|(id, import)| (id, (self.reconstruct_import(import.0), import.1))) - .collect(), + modules: input.modules.into_iter().map(|(id, module)| (id, self.reconstruct_module(module))).collect(), + imports: input.imports, stubs: input.stubs.into_iter().map(|(id, stub)| (id, self.reconstruct_stub(stub))).collect(), program_scopes: input .program_scopes .into_iter() .map(|(id, scope)| (id, self.reconstruct_program_scope(scope))) .collect(), - modules: input.modules.into_iter().map(|(id, module)| (id, self.reconstruct_module(module))).collect(), } } @@ -75,13 +76,19 @@ impl ProgramReconstructor for OptionLoweringVisitor<'_> { _ => panic!("`reconstruct_const` can only return `Statement::Const`"), }) .collect(), + structs: self .reconstructed_structs .iter() - .filter_map(|(path, s)| { - path.split_last().filter(|(_, rest)| rest.is_empty()).map(|(last, _)| (*last, s.clone())) + .filter_map(|(loc, c)| { + // Only consider structs defined at program scope within the same program. + loc.path + .split_last() + .filter(|(_, rest)| rest.is_empty() && loc.program == self.program) + .map(|(last, _)| (*last, c.clone())) }) .collect(), + mappings: input.mappings.into_iter().map(|(id, m)| (id, self.reconstruct_mapping(m))).collect(), storage_variables: input .storage_variables @@ -108,13 +115,18 @@ impl ProgramReconstructor for OptionLoweringVisitor<'_> { _ => panic!("`reconstruct_const` can only return `Statement::Const`"), }) .collect(), + structs: slf .reconstructed_structs .iter() - .filter_map(|(path, c)| path.split_last().map(|(last, rest)| (last, rest, c))) - .filter(|&(_, rest, _)| input.path == rest) - .map(|(last, _, c)| (*last, c.clone())) + .filter_map(|(loc, c)| { + loc.path + .split_last() + .filter(|(_, rest)| *rest == input.path && loc.program == slf.program) + .map(|(last, _)| (*last, c.clone())) + }) .collect(), + functions: input.functions.into_iter().map(|(i, f)| (i, slf.reconstruct_function(f))).collect(), ..input }) diff --git a/compiler/passes/src/option_lowering/visitor.rs b/compiler/passes/src/option_lowering/visitor.rs index 0888f592286..75c24479cb2 100644 --- a/compiler/passes/src/option_lowering/visitor.rs +++ b/compiler/passes/src/option_lowering/visitor.rs @@ -33,7 +33,7 @@ pub struct OptionLoweringVisitor<'a> { // structs are to be inserted in the program scope. pub new_structs: IndexMap, // The reconstructed structs. These are the new versions of the existing structs in the program. - pub reconstructed_structs: IndexMap, Composite>, + pub reconstructed_structs: IndexMap, } impl OptionLoweringVisitor<'_> { @@ -125,12 +125,13 @@ impl OptionLoweringVisitor<'_> { // for each type. // Instead of relying on the symbol table (which does not get updated in this pass), we rely on the set of - // reconstructed structs which is produced for all program scopes and all modules before doing anything else. + // reconstructed structs (local to this program) which is produced for all program scopes and all modules before + // doing anything else. let reconstructed_structs = &self.reconstructed_structs; - let struct_lookup = |sym: &[Symbol]| { + let struct_lookup = |loc: &Location| { reconstructed_structs - .get(sym) // check the new version of existing structs - .or_else(|| self.new_structs.get(sym.last().unwrap())) // check the newly produced structs + .get(loc) // check the new version of existing structs + .or_else(|| self.new_structs.get(loc.path.last().unwrap())) // check the newly produced structs .expect("must exist by construction") .members .iter() @@ -138,8 +139,14 @@ impl OptionLoweringVisitor<'_> { .collect() }; - let zero_val_expr = - Expression::zero(&lowered_inner_type, Span::default(), &self.state.node_builder, &struct_lookup).expect(""); + let zero_val_expr = Expression::zero( + &lowered_inner_type, + Span::default(), + &self.state.node_builder, + self.program, + &struct_lookup, + ) + .expect("this must work if type checking was successful"); // Create or get an optional wrapper struct for `lowered_inner_type` let struct_name = self.insert_optional_wrapper_struct(&lowered_inner_type); diff --git a/compiler/passes/src/pass.rs b/compiler/passes/src/pass.rs index 7c3c2533c3d..e2e5e19e128 100644 --- a/compiler/passes/src/pass.rs +++ b/compiler/passes/src/pass.rs @@ -19,7 +19,7 @@ use crate::{Assigner, SymbolTable, TypeTable}; use leo_ast::{Ast, CallGraph, NetworkName, NodeBuilder, StructGraph}; use leo_errors::{Handler, LeoWarning, Result}; -use std::collections::HashSet; +use std::{collections::HashSet, rc::Rc}; /// Contains data shared by many compiler passes. #[derive(Default)] @@ -31,7 +31,7 @@ pub struct CompilerState { /// Maps node IDs to types. pub type_table: TypeTable, /// Creates incrementing node IDs. - pub node_builder: NodeBuilder, + pub node_builder: Rc, /// Creates unique symbols and definitions. pub assigner: Assigner, /// Contains data about the variables and other entities in the program. @@ -61,3 +61,15 @@ pub trait Pass { /// Runs the compiler pass. fn do_pass(input: Self::Input, state: &mut CompilerState) -> Result; } + +/// Produced set of bytecodes +pub struct CompiledPrograms { + pub primary_bytecode: String, + pub import_bytecodes: Vec, +} + +/// Bytecode for a single program. +pub struct Bytecode { + pub program_name: String, + pub bytecode: String, +} diff --git a/compiler/passes/src/path_resolution/ast.rs b/compiler/passes/src/path_resolution/ast.rs index 97b00b84679..81ff37225b8 100644 --- a/compiler/passes/src/path_resolution/ast.rs +++ b/compiler/passes/src/path_resolution/ast.rs @@ -43,7 +43,7 @@ impl AstReconstructor for PathResolutionVisitor<'_> { .into_iter() .map(|arg| self.reconstruct_expression(arg, &()).0) .collect(), - ..input + program: Some(input.program.unwrap_or(self.program)), }), Default::default(), ) diff --git a/compiler/passes/src/path_resolution/mod.rs b/compiler/passes/src/path_resolution/mod.rs index 52909699064..65a5c01a27b 100644 --- a/compiler/passes/src/path_resolution/mod.rs +++ b/compiler/passes/src/path_resolution/mod.rs @@ -51,6 +51,7 @@ use crate::Pass; use leo_ast::ProgramReconstructor as _; use leo_errors::Result; +use leo_span::Symbol; mod ast; @@ -69,7 +70,7 @@ impl Pass for PathResolution { fn do_pass(_input: Self::Input, state: &mut crate::CompilerState) -> Result { let mut ast = std::mem::take(&mut state.ast); - let mut visitor = PathResolutionVisitor { state, module: Vec::new() }; + let mut visitor = PathResolutionVisitor { state, program: Symbol::intern(""), module: Vec::new() }; ast.ast = visitor.reconstruct_program(ast.ast); visitor.state.handler.last_err()?; visitor.state.ast = ast; diff --git a/compiler/passes/src/path_resolution/program.rs b/compiler/passes/src/path_resolution/program.rs index 814af3d4c37..08c9c02b7c5 100644 --- a/compiler/passes/src/path_resolution/program.rs +++ b/compiler/passes/src/path_resolution/program.rs @@ -15,9 +15,35 @@ // along with the Leo library. If not, see . use super::PathResolutionVisitor; -use leo_ast::{AstReconstructor, Module, ProgramReconstructor, Statement}; +use leo_ast::{AstReconstructor, Module, ProgramReconstructor, ProgramScope, Statement}; impl ProgramReconstructor for PathResolutionVisitor<'_> { + fn reconstruct_program_scope(&mut self, input: ProgramScope) -> ProgramScope { + self.program = input.program_id.name.name; + + ProgramScope { + program_id: input.program_id, + consts: input + .consts + .into_iter() + .map(|(i, c)| match self.reconstruct_const(c) { + (Statement::Const(declaration), _) => (i, declaration), + _ => panic!("`reconstruct_const` can only return `Statement::Const`"), + }) + .collect(), + structs: input.structs.into_iter().map(|(i, c)| (i, self.reconstruct_struct(c))).collect(), + mappings: input.mappings.into_iter().map(|(id, mapping)| (id, self.reconstruct_mapping(mapping))).collect(), + storage_variables: input + .storage_variables + .into_iter() + .map(|(id, storage_variable)| (id, self.reconstruct_storage_variable(storage_variable))) + .collect(), + functions: input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function(f))).collect(), + constructor: input.constructor.map(|c| self.reconstruct_constructor(c)), + span: input.span, + } + } + fn reconstruct_module(&mut self, input: Module) -> Module { self.in_module_scope(&input.path.clone(), |slf| Module { program_name: input.program_name, diff --git a/compiler/passes/src/path_resolution/visitor.rs b/compiler/passes/src/path_resolution/visitor.rs index df1325d045c..e672c05701d 100644 --- a/compiler/passes/src/path_resolution/visitor.rs +++ b/compiler/passes/src/path_resolution/visitor.rs @@ -20,6 +20,8 @@ use leo_span::Symbol; pub struct PathResolutionVisitor<'a> { pub state: &'a mut CompilerState, + /// The current program. + pub program: Symbol, /// The current module. pub module: Vec, } diff --git a/compiler/passes/src/processing_async/ast.rs b/compiler/passes/src/processing_async/ast.rs index a38ba7748c1..0e8ad1fe7e5 100644 --- a/compiler/passes/src/processing_async/ast.rs +++ b/compiler/passes/src/processing_async/ast.rs @@ -15,67 +15,8 @@ // along with the Leo library. If not, see . use super::ProcessingAsyncVisitor; -use crate::{CompilerState, Replacer}; -use indexmap::{IndexMap, IndexSet}; -use leo_ast::{ - AstReconstructor, - AstVisitor, - AsyncExpression, - Block, - CallExpression, - Expression, - Function, - Identifier, - Input, - IterationStatement, - Location, - Node, - Path, - ProgramVisitor, - Statement, - TupleAccess, - TupleExpression, - TupleType, - Type, - Variant, -}; -use leo_span::{Span, Symbol}; - -/// Collects all symbol accesses within an async block, -/// including both direct variable identifiers (`x`) and tuple field accesses (`x.0`, `x.1`, etc.). -/// Each access is recorded as a pair: (Symbol, Option). -/// - `None` means a direct variable access. -/// - `Some(index)` means a tuple field access. -struct SymbolAccessCollector<'a> { - state: &'a CompilerState, - symbol_accesses: IndexSet<(Vec, Option)>, -} - -impl AstVisitor for SymbolAccessCollector<'_> { - type AdditionalInput = (); - type Output = (); - - fn visit_path(&mut self, input: &Path, _: &Self::AdditionalInput) -> Self::Output { - self.symbol_accesses.insert((input.absolute_path(), None)); - } - - fn visit_tuple_access(&mut self, input: &TupleAccess, _: &Self::AdditionalInput) -> Self::Output { - // Here we assume that we can't have nested tuples which is currently guaranteed by type - // checking. This may change in the future. - if let Expression::Path(path) = &input.tuple { - // Futures aren't accessed by field; treat the whole thing as a direct variable - if let Some(Type::Future(_)) = self.state.type_table.get(&input.tuple.id()) { - self.symbol_accesses.insert((path.absolute_path(), None)); - } else { - self.symbol_accesses.insert((path.absolute_path(), Some(input.index.value()))); - } - } else { - self.visit_expression(&input.tuple, &()); - } - } -} - -impl ProgramVisitor for SymbolAccessCollector<'_> {} +use crate::BlockToFunctionRewriter; +use leo_ast::{AstReconstructor, AsyncExpression, Block, Expression, IterationStatement, Node, Statement, Variant}; impl AstReconstructor for ProcessingAsyncVisitor<'_> { type AdditionalInput = (); @@ -83,265 +24,30 @@ impl AstReconstructor for ProcessingAsyncVisitor<'_> { /// Transforms an `AsyncExpression` into a standalone async `Function` and returns /// a call to this function. This process: - /// - Collects all referenced symbol accesses in the async block. - /// - Filters out mappings and constructs typed input parameters. - /// - Reconstructs an async function with those inputs and the original block. - /// - Builds and returns a `CallExpression` that invokes the new function. fn reconstruct_async(&mut self, input: AsyncExpression, _additional: &()) -> (Expression, Self::AdditionalOutput) { - // Step 1: Generate a unique name for the async function + // Generate a unique name for the async function. let finalize_fn_name = self.state.assigner.unique_symbol(self.current_function, "$"); - // Step 2: Collect all symbol accesses in the async block - let mut access_collector = SymbolAccessCollector { state: self.state, symbol_accesses: IndexSet::new() }; - access_collector.visit_async(&input, &()); - - // Stores mapping from accessed symbol (and optional index) to the expression used in replacement - let mut replacements: IndexMap<(Symbol, Option), Expression> = IndexMap::new(); - - // Helper to create a fresh `Identifier` - let make_identifier = |slf: &mut Self, symbol: Symbol| Identifier { - name: symbol, - span: Span::default(), - id: slf.state.node_builder.next_id(), - }; - - // Generates a set of `Input`s and corresponding call-site `Expression`s for a given symbol access. - // - // This function handles both: - // - Direct variable accesses (e.g., `foo`) - // - Tuple element accesses (e.g., `foo.0`) - // - // For tuple accesses: - // - If a single element (e.g. `foo.0`) is accessed, it generates a synthetic input like `"foo.0"`. - // - If the whole tuple (e.g. `foo`) is accessed, it ensures all elements are covered by: - // - Reusing existing inputs from `replacements` if already generated via prior field access. - // - Creating new inputs and arguments for any missing elements. - // - The entire tuple is reconstructed in `replacements` using the individual elements as a `TupleExpression`. - // - // This function also ensures deduplication by consulting the `replacements` map: - // - If a given `(symbol, index)` has already been processed, no duplicate input or argument is generated. - // - This prevents repeated parameters for accesses like both `foo` and `foo.0`. - // - // # Parameters - // - `symbol`: The symbol being accessed. - // - `var_type`: The type of the symbol (may be a tuple or base type). - // - `index_opt`: `Some(index)` for a tuple field (e.g., `.0`), or `None` for full-variable access. - // - // # Returns - // A `Vec<(Input, Expression)>`, where: - // - `Input` is a parameter for the generated async function. - // - `Expression` is the call-site argument expression used to invoke that parameter. - let mut make_inputs_and_arguments = - |slf: &mut Self, symbol: Symbol, var_type: &Type, index_opt: Option| -> Vec<(Input, Expression)> { - if replacements.contains_key(&(symbol, index_opt)) { - return vec![]; // No new input needed; argument already exists - } - - match index_opt { - Some(index) => { - let Type::Tuple(TupleType { elements }) = var_type else { - panic!("Expected tuple type when accessing tuple field: {symbol}.{index}"); - }; - - let synthetic_name = format!("\"{symbol}.{index}\""); - let synthetic_symbol = Symbol::intern(&synthetic_name); - let identifier = make_identifier(slf, synthetic_symbol); - - let input = Input { - identifier, - mode: leo_ast::Mode::None, - type_: elements[index].clone(), - span: Span::default(), - id: slf.state.node_builder.next_id(), - }; - - replacements.insert((symbol, Some(index)), Path::from(identifier).into_absolute().into()); - - vec![( - input, - TupleAccess { - tuple: Path::from(make_identifier(slf, symbol)).into_absolute().into(), - index: index.into(), - span: Span::default(), - id: slf.state.node_builder.next_id(), - } - .into(), - )] - } - - None => match var_type { - Type::Tuple(TupleType { elements }) => { - let mut inputs_and_arguments = Vec::with_capacity(elements.len()); - let mut tuple_elements = Vec::with_capacity(elements.len()); - - for (i, element_type) in elements.iter().enumerate() { - let key = (symbol, Some(i)); - - // Skip if this field is already handled - if let Some(existing_expr) = replacements.get(&key) { - tuple_elements.push(existing_expr.clone()); - continue; - } - - // Otherwise, synthesize identifier and input - let synthetic_name = format!("\"{symbol}.{i}\""); - let synthetic_symbol = Symbol::intern(&synthetic_name); - let identifier = make_identifier(slf, synthetic_symbol); + // Convert the block into a function and a function call. + let mut block_to_function_rewriter = BlockToFunctionRewriter::new(self.state, self.current_program); + let (function, call_to_finalize) = + block_to_function_rewriter.rewrite_block(&input.block, finalize_fn_name, Variant::AsyncFunction); - let input = Input { - identifier, - mode: leo_ast::Mode::None, - type_: element_type.clone(), - span: Span::default(), - id: slf.state.node_builder.next_id(), - }; - - let expr: Expression = Path::from(identifier).into_absolute().into(); - - replacements.insert(key, expr.clone()); - tuple_elements.push(expr.clone()); - inputs_and_arguments.push(( - input, - TupleAccess { - tuple: Path::from(make_identifier(slf, symbol)).into_absolute().into(), - index: i.into(), - span: Span::default(), - id: slf.state.node_builder.next_id(), - } - .into(), - )); - } - - // Now insert the full tuple (even if all fields were already there) - replacements.insert( - (symbol, None), - Expression::Tuple(TupleExpression { - elements: tuple_elements, - span: Span::default(), - id: slf.state.node_builder.next_id(), - }), - ); - - inputs_and_arguments - } - - _ => { - let identifier = make_identifier(slf, symbol); - let input = Input { - identifier, - mode: leo_ast::Mode::None, - type_: var_type.clone(), - span: Span::default(), - id: slf.state.node_builder.next_id(), - }; - - replacements.insert((symbol, None), Path::from(identifier).into_absolute().into()); - - let argument = Path::from(make_identifier(slf, symbol)).into_absolute().into(); - vec![(input, argument)] - } - }, - } - }; - - // Step 3: Resolve symbol accesses into inputs and call arguments - let (inputs, arguments): (Vec<_>, Vec<_>) = access_collector - .symbol_accesses - .iter() - .filter_map(|(path, index)| { - // Skip globals and variables that are local to this block or to one of its children. - - // Skip globals. - if self.state.symbol_table.lookup_global(&Location::new(self.current_program, path.to_vec())).is_some() - { - return None; - } - - // Skip variables that are local to this block or to one of its children. - let local_var_name = *path.last().expect("all paths must have at least one segment."); - if self.state.symbol_table.is_local_to_or_in_child_scope(input.block.id(), local_var_name) { - return None; - } - - // All other variables become parameters to the async function being built. - let var = self.state.symbol_table.lookup_local(local_var_name)?; - Some(make_inputs_and_arguments(self, local_var_name, &var.type_, *index)) - }) - .flatten() - .unzip(); - - // Step 4: Replacement logic used to patch the async block - let replace_expr = |expr: &Expression| -> Expression { - match expr { - Expression::Path(path) => { - replacements.get(&(path.identifier().name, None)).cloned().unwrap_or_else(|| expr.clone()) - } - - Expression::TupleAccess(ta) => { - if let Expression::Path(path) = &ta.tuple { - replacements - .get(&(path.identifier().name, Some(ta.index.value()))) - .cloned() - .unwrap_or_else(|| expr.clone()) - } else { - expr.clone() - } - } - - _ => expr.clone(), - } - }; - - // Step 5: Reconstruct the block with replaced references - let mut replacer = Replacer::new(replace_expr, true /* refresh IDs */, self.state); - let new_block = replacer.reconstruct_block(input.block.clone()).0; - - // Ensure we're not trying to capture too many variables - if inputs.len() > self.max_inputs { + // Ensure we're not trying to capture too many variables. + if function.input.len() > self.max_inputs { self.state.handler.emit_err(leo_errors::StaticAnalyzerError::async_block_capturing_too_many_vars( - inputs.len(), + function.input.len(), self.max_inputs, input.span, )); } - // Step 6: Define the new async function - let function = Function { - annotations: vec![], - variant: Variant::AsyncFunction, - identifier: make_identifier(self, finalize_fn_name), - const_parameters: vec![], - input: inputs, - output: vec![], // `async function`s can't have returns - output_type: Type::Unit, // Always the case for `async function`s - block: new_block, - span: input.span, - id: self.state.node_builder.next_id(), - }; - // Register the generated function self.new_async_functions.push((finalize_fn_name, function)); - // Step 7: Create the call expression to invoke the async function - let call_to_finalize = CallExpression { - function: Path::new( - vec![], - make_identifier(self, finalize_fn_name), - true, - Some(vec![finalize_fn_name]), // the finalize function lives in the top level program scope - Span::default(), - self.state.node_builder.next_id(), - ), - const_arguments: vec![], - arguments, - program: Some(self.current_program), - span: input.span, - id: self.state.node_builder.next_id(), - }; - self.modified = true; - (call_to_finalize.into(), ()) + (call_to_finalize, ()) } fn reconstruct_block(&mut self, input: Block) -> (Block, Self::AdditionalOutput) { diff --git a/compiler/passes/src/processing_script/ast.rs b/compiler/passes/src/processing_script/ast.rs index 59b9018a2c6..074bd7b6edc 100644 --- a/compiler/passes/src/processing_script/ast.rs +++ b/compiler/passes/src/processing_script/ast.rs @@ -28,11 +28,10 @@ impl AstReconstructor for ProcessingScriptVisitor<'_> { if !matches!(self.current_variant, Variant::Script) { let callee_program = input.program.unwrap_or(self.program_name); - let Some(func_symbol) = self - .state - .symbol_table - .lookup_function(&Location::new(callee_program, input.function.absolute_path().to_vec())) - else { + let Some(func_symbol) = self.state.symbol_table.lookup_function( + self.program_name, + &Location::new(callee_program, input.function.absolute_path().to_vec()), + ) else { panic!("Type checking should have prevented this."); }; diff --git a/compiler/passes/src/static_analysis/visitor.rs b/compiler/passes/src/static_analysis/visitor.rs index 8402307b364..33aceed87dc 100644 --- a/compiler/passes/src/static_analysis/visitor.rs +++ b/compiler/passes/src/static_analysis/visitor.rs @@ -38,7 +38,7 @@ impl StaticAnalyzingVisitor<'_> { } /// Emits a type checker warning - pub fn emit_warning(&self, warning: StaticAnalyzerWarning) { + pub fn emit_warning(&mut self, warning: StaticAnalyzerWarning) { self.state.handler.emit_warning(warning); } @@ -79,7 +79,7 @@ impl StaticAnalyzingVisitor<'_> { let func_symbol = self .state .symbol_table - .lookup_function(&Location::new(program, function_path.absolute_path().to_vec())) + .lookup_function(self.current_program, &Location::new(program, function_path.absolute_path().to_vec())) .expect("Type checking guarantees functions are present."); // If it is not an async transition, return. @@ -95,7 +95,7 @@ impl StaticAnalyzingVisitor<'_> { let async_function = self .state .symbol_table - .lookup_function(&finalizer.location) + .lookup_function(self.current_program, &finalizer.location) .expect("Type checking guarantees functions are present."); // If the async function takes a future as an argument, emit an error. @@ -140,7 +140,10 @@ impl AstVisitor for StaticAnalyzingVisitor<'_> { let func_symbol = self .state .symbol_table - .lookup_function(&Location::new(function_program, input.function.absolute_path().to_vec())) + .lookup_function( + self.current_program, + &Location::new(function_program, input.function.absolute_path().to_vec()), + ) .expect("Type checking guarantees functions exist."); if func_symbol.function.variant == Variant::Transition { diff --git a/compiler/passes/src/static_single_assignment/expression.rs b/compiler/passes/src/static_single_assignment/expression.rs index 4892ee9e744..55dc0d043e7 100644 --- a/compiler/passes/src/static_single_assignment/expression.rs +++ b/compiler/passes/src/static_single_assignment/expression.rs @@ -175,8 +175,12 @@ impl ExpressionConsumer for SsaFormingVisitor<'_> { let struct_definition: &Composite = self .state .symbol_table - .lookup_record(&Location::new(self.program, input.path.absolute_path())) - .or_else(|| self.state.symbol_table.lookup_struct(&input.path.absolute_path())) + .lookup_record(self.program, &Location::new(self.program, input.path.absolute_path())) + .or_else(|| { + self.state + .symbol_table + .lookup_struct(self.program, &Location::new(self.program, input.path.absolute_path())) + }) .expect("Type checking guarantees this definition exists."); // Initialize the list of reordered members. diff --git a/compiler/passes/src/static_single_assignment/program.rs b/compiler/passes/src/static_single_assignment/program.rs index cb355753e2e..5d0ef6365bb 100644 --- a/compiler/passes/src/static_single_assignment/program.rs +++ b/compiler/passes/src/static_single_assignment/program.rs @@ -34,6 +34,8 @@ use leo_ast::{ ProgramScopeConsumer, StatementConsumer, StructConsumer, + Stub, + StubConsumer, }; use leo_span::{Symbol, sym}; @@ -145,12 +147,8 @@ impl ProgramConsumer for SsaFormingVisitor<'_> { fn consume_program(&mut self, input: Program) -> Self::Output { Program { modules: input.modules.into_iter().map(|(path, module)| (path, self.consume_module(module))).collect(), - imports: input - .imports - .into_iter() - .map(|(name, (import, span))| (name, (self.consume_program(import), span))) - .collect(), - stubs: input.stubs, + imports: input.imports, + stubs: input.stubs.into_iter().map(|(name, stub)| (name, self.consume_stub(stub))).collect(), program_scopes: input .program_scopes .into_iter() @@ -160,10 +158,22 @@ impl ProgramConsumer for SsaFormingVisitor<'_> { } } +impl StubConsumer for SsaFormingVisitor<'_> { + type Output = Stub; + + fn consume_stub(&mut self, input: Stub) -> Self::Output { + match input { + Stub::FromLeo { program, .. } => self.consume_program(program).into(), + Stub::FromAleo { .. } => input, + } + } +} + impl ModuleConsumer for SsaFormingVisitor<'_> { type Output = Module; fn consume_module(&mut self, input: Module) -> Self::Output { + self.program = input.program_name; Module { path: input.path, program_name: self.program, diff --git a/compiler/passes/src/storage_lowering/ast.rs b/compiler/passes/src/storage_lowering/ast.rs index 0b6a3315637..e8e9331b93c 100644 --- a/compiler/passes/src/storage_lowering/ast.rs +++ b/compiler/passes/src/storage_lowering/ast.rs @@ -95,10 +95,7 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { // Reconstruct value let (value, stmts) = self.reconstruct_expression(value_expr.clone(), &()); - let (vec_values_mapping_name, vec_length_mapping_name) = - self.generate_mapping_names_for_vector(vector_expr); - let vec_path_expr = self.symbol_to_path_expr(vec_values_mapping_name); - let len_path_expr = self.symbol_to_path_expr(vec_length_mapping_name); + let (vec_path_expr, len_path_expr) = self.generate_vector_mapping_exprs(vector_expr); // let $len_var = Mapping::get_or_use(len_map, false, 0u32) let len_var_sym = self.state.assigner.unique_symbol("$len_var", "$"); @@ -145,9 +142,7 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { // Validate vector type assert!(matches!(self.state.type_table.get(&vector_expr.id()), Some(Type::Vector(_)))); - let (_vec_values_mapping_name, vec_length_mapping_name) = - self.generate_mapping_names_for_vector(vector_expr); - let len_path_expr = self.symbol_to_path_expr(vec_length_mapping_name); + let (_vec_path_expr, len_path_expr) = self.generate_vector_mapping_exprs(vector_expr); let get_len_expr = self.get_vector_len_expr(len_path_expr, input.span); (get_len_expr, vec![]) @@ -173,10 +168,7 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { panic!("argument to Vector::pop should be of type `Vector`."); }; - let (vec_values_mapping_name, vec_length_mapping_name) = - self.generate_mapping_names_for_vector(vector_expr); - let vec_path_expr = self.symbol_to_path_expr(vec_values_mapping_name); - let len_path_expr = self.symbol_to_path_expr(vec_length_mapping_name); + let (vec_path_expr, len_path_expr) = self.generate_vector_mapping_exprs(vector_expr); // let $len_var = Mapping::get_or_use(len_map, false, 0u32) let len_var_sym = self.state.assigner.unique_symbol("$len_var", "$"); @@ -250,10 +242,7 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { // ? Mapping::get_or_use(vec_map, index, zero_value) // : None - let (vec_values_mapping_name, vec_length_mapping_name) = - self.generate_mapping_names_for_vector(container_expr); - let vec_path_expr = self.symbol_to_path_expr(vec_values_mapping_name); - let len_path_expr = self.symbol_to_path_expr(vec_length_mapping_name); + let (vec_path_expr, len_path_expr) = self.generate_vector_mapping_exprs(container_expr); // let $len_var = Mapping::get_or_use(len_map, false, 0u32) let len_var_sym = self.state.assigner.unique_symbol("$len_var", "$"); @@ -329,10 +318,7 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { // assert(index < $len_var); // Mapping::set(vec_map, index, value); - let (vec_values_mapping_name, vec_length_mapping_name) = - self.generate_mapping_names_for_vector(container_expr); - let vec_path_expr = self.symbol_to_path_expr(vec_values_mapping_name); - let len_path_expr = self.symbol_to_path_expr(vec_length_mapping_name); + let (vec_path_expr, len_path_expr) = self.generate_vector_mapping_exprs(container_expr); // let $len_var = Mapping::get_or_use(len_map, false, 0u32) let len_var_sym = self.state.assigner.unique_symbol("$len_var", "$"); @@ -405,9 +391,7 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { // Validate vector type assert!(matches!(self.state.type_table.get(&vector_expr.id()), Some(Type::Vector(_)))); - let (_vec_values_mapping_name, vec_length_mapping_name) = - self.generate_mapping_names_for_vector(vector_expr); - let len_path_expr = self.symbol_to_path_expr(vec_length_mapping_name); + let (_vec_path_expr, len_path_expr) = self.generate_vector_mapping_exprs(vector_expr); // Mapping::set(len_map, false, 0u32) let literal_false = self.literal_false(); @@ -440,10 +424,7 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { let (reconstructed_index_expr, index_stmts) = self.reconstruct_expression(index_expr.clone(), &Default::default()); - let (vec_values_mapping_name, vec_length_mapping_name) = - self.generate_mapping_names_for_vector(vector_expr); - let vec_path_expr = self.symbol_to_path_expr(vec_values_mapping_name); - let len_path_expr = self.symbol_to_path_expr(vec_length_mapping_name); + let (vec_path_expr, len_path_expr) = self.generate_vector_mapping_exprs(vector_expr); // let $len_var = Mapping::get_or_use(len_map, false, 0u32) let len_var_sym = self.state.assigner.unique_symbol("$len_var", "$"); @@ -653,89 +634,15 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { } fn reconstruct_path(&mut self, input: Path, _additional: &()) -> (Expression, Self::AdditionalOutput) { - // Check if this path corresponds to a global symbol. - let Some(var) = self.state.symbol_table.lookup_global(&Location::new(self.program, input.absolute_path())) - else { - // Nothing to do - return (input.into(), vec![]); - }; - - match &var.type_ { - Type::Mapping(_) => { - // No transformation needed for mappings. - (input.into(), vec![]) - } - - Type::Optional(OptionalType { inner }) => { - // Input: - // storage x: field; - // ... - // let y = x; - // - // Lowered reconstruction: - // mapping x__: bool => field - // let y = x__.contains(false) - // ? x__.get_or_use(false, 0field) - // : None; - - let id = || self.state.node_builder.next_id(); - let var_name = input.identifier().name; - - // Path to the mapping backing the optional variable: `__` - let mapping_symbol = Symbol::intern(&format!("{var_name}__")); - let mapping_ident = Identifier::new(mapping_symbol, id()); - - // === Build expressions === - let mapping_expr: Expression = Path::from(mapping_ident).into_absolute().into(); - let false_literal: Expression = Literal::boolean(false, Span::default(), id()).into(); - - // `__.contains(false)` - let contains_expr: Expression = AssociatedFunctionExpression { - variant: Identifier::new(sym::Mapping, id()), - name: Identifier::new(Symbol::intern("contains"), id()), - type_parameters: vec![], - arguments: vec![mapping_expr.clone(), false_literal.clone()], - span: Span::default(), - id: id(), - } - .into(); - - // zero value for element type - let zero = self.zero(inner); - - // `__.get_or_use(false, zero_value)` - let get_or_use_expr: Expression = AssociatedFunctionExpression { - variant: Identifier::new(sym::Mapping, id()), - name: Identifier::new(Symbol::intern("get_or_use"), id()), - type_parameters: vec![], - arguments: vec![mapping_expr.clone(), false_literal, zero], - span: Span::default(), - id: id(), - } - .into(); - - // `None` - let none_expr = - Expression::Literal(Literal { variant: LiteralVariant::None, span: Span::default(), id: id() }); - - // Combine into ternary: - // `__.contains(false) ? __.get_or_use(false, zero_val) : None` - let ternary_expr: Expression = TernaryExpression { - condition: contains_expr, - if_true: get_or_use_expr, - if_false: none_expr, - span: Span::default(), - id: id(), - } - .into(); - - (ternary_expr, vec![]) - } + (self.reconstruct_path_or_locator(input.into()), vec![]) + } - _ => { - panic!("Expected a non-vector type in reconstruct_path, found {:?}", var.type_); - } - } + fn reconstruct_locator( + &mut self, + input: LocatorExpression, + _additional: &(), + ) -> (Expression, Self::AdditionalOutput) { + (self.reconstruct_path_or_locator(input.into()), vec![]) } fn reconstruct_ternary( @@ -817,7 +724,8 @@ impl leo_ast::AstReconstructor for StorageLoweringVisitor<'_> { // Check if `place` is a path if let Expression::Path(path) = &place { // Check if the path corresponds to a global storage variable - if let Some(var) = self.state.symbol_table.lookup_global(&Location::new(self.program, path.absolute_path())) + if let Some(var) = + self.state.symbol_table.lookup_global(self.program, &Location::new(self.program, path.absolute_path())) { // Storage variables that are not optional nor mappings are implicitly wrapped in an optional. assert!( diff --git a/compiler/passes/src/storage_lowering/program.rs b/compiler/passes/src/storage_lowering/program.rs index 30879e53d0f..45ac1b1dfe1 100644 --- a/compiler/passes/src/storage_lowering/program.rs +++ b/compiler/passes/src/storage_lowering/program.rs @@ -43,7 +43,12 @@ impl ProgramReconstructor for StorageLoweringVisitor<'_> { // Reconstruct old mappings and then append the new mappings to the final list. let mut mappings = input.mappings.into_iter().map(|(id, mapping)| (id, self.reconstruct_mapping(mapping))).collect::>(); - mappings.extend(self.new_mappings.clone().into_iter().collect::>()); + mappings.extend( + self.new_mappings + .iter() + .filter(|((program, _), _)| *program == self.program) + .map(|((program, _), mapping)| (*program, mapping.clone())), + ); ProgramScope { program_id: input.program_id, @@ -94,7 +99,7 @@ impl ProgramReconstructor for StorageLoweringVisitor<'_> { // 2. `__len__`: bool → length // Mapping for the vector’s contents - self.new_mappings.insert(mapping_name, Mapping { + self.new_mappings.insert((self.program, mapping_name), Mapping { identifier: Identifier::new(mapping_name, id()), key_type: Type::Integer(IntegerType::U32), value_type: *element_type.clone(), @@ -104,7 +109,7 @@ impl ProgramReconstructor for StorageLoweringVisitor<'_> { // Mapping for the vector’s length let len_name = Symbol::intern(&(name + "__len__")); - self.new_mappings.insert(len_name, Mapping { + self.new_mappings.insert((self.program, len_name), Mapping { identifier: Identifier::new(len_name, id()), key_type: Type::Boolean, value_type: Type::Integer(IntegerType::U32), @@ -121,7 +126,7 @@ impl ProgramReconstructor for StorageLoweringVisitor<'_> { // // The `bool` key acts as a presence indicator (typically `false`). - self.new_mappings.insert(mapping_name, Mapping { + self.new_mappings.insert((self.program, mapping_name), Mapping { identifier: Identifier::new(mapping_name, id()), key_type: Type::Boolean, value_type: input.type_.clone(), diff --git a/compiler/passes/src/storage_lowering/visitor.rs b/compiler/passes/src/storage_lowering/visitor.rs index fdf30d97ea5..1f1e2a29a32 100644 --- a/compiler/passes/src/storage_lowering/visitor.rs +++ b/compiler/passes/src/storage_lowering/visitor.rs @@ -25,26 +25,47 @@ pub struct StorageLoweringVisitor<'a> { pub state: &'a mut CompilerState, // The name of the current program scope pub program: Symbol, - pub new_mappings: IndexMap, + pub new_mappings: IndexMap<(Symbol, Symbol), Mapping>, } impl StorageLoweringVisitor<'_> { - /// Generate mapping names for a vector expression. Each vector is represented using two - /// mappings: a mapping for values and a mapping for the length. - pub fn generate_mapping_names_for_vector(&self, expr: &Expression) -> (Symbol, Symbol) { - let path = match expr { - Expression::Path(path) => path, - _ => panic!("Expected path expression for vector"), - }; - let base_sym = path.identifier().name; - let vec_values_mapping_name = Symbol::intern(&format!("{base_sym}__")); - let vec_length_mapping_name = Symbol::intern(&format!("{base_sym}__len__")); - (vec_values_mapping_name, vec_length_mapping_name) - } + /// Returns the two mapping expressions that back a vector: `__` (values) + /// and `__len__` (length). + /// + /// The returned expressions match the input form: + /// - `Path` → absolute `Path` expressions + /// - `Locator` → `LocatorExpression`s preserving the program name + /// + /// Panics if `expr` is not a `Path` or `Locator`. + pub fn generate_vector_mapping_exprs(&mut self, expr: &Expression) -> (Expression, Expression) { + match expr { + Expression::Path(path) => { + let base = path.identifier().name; + let val = Symbol::intern(&format!("{base}__")); + let len = Symbol::intern(&format!("{base}__len__")); + + ( + Path::from(Identifier::new(val, self.state.node_builder.next_id())).into_absolute().into(), + Path::from(Identifier::new(len, self.state.node_builder.next_id())).into_absolute().into(), + ) + } + + Expression::Locator(loc) => { + let base = loc.name; + let val = Symbol::intern(&format!("{base}__")); + let len = Symbol::intern(&format!("{base}__len__")); + let span = expr.span(); - /// Creates a path expression from a symbol - pub fn symbol_to_path_expr(&mut self, sym: Symbol) -> Expression { - Expression::Path(Path::from(Identifier::new(sym, self.state.node_builder.next_id())).into_absolute()) + ( + LocatorExpression { program: loc.program, name: val, span, id: self.state.node_builder.next_id() } + .into(), + LocatorExpression { program: loc.program, name: len, span, id: self.state.node_builder.next_id() } + .into(), + ) + } + + _ => panic!("Expected Path or Locator expression for vector mapping"), + } } /// Standard literal expressions used frequently @@ -144,16 +165,118 @@ impl StorageLoweringVisitor<'_> { pub fn zero(&self, ty: &Type) -> Expression { // zero value for element type (used as default in get_or_use) let symbol_table = &self.state.symbol_table; - let struct_lookup = |sym: &[Symbol]| { + let struct_lookup = |loc: &Location| { symbol_table - .lookup_struct(sym) + .lookup_struct(self.program, loc) .unwrap() .members .iter() .map(|mem| (mem.identifier.name, mem.type_.clone())) .collect() }; - Expression::zero(ty, Span::default(), &self.state.node_builder, &struct_lookup) + Expression::zero(ty, Span::default(), &self.state.node_builder, self.program, &struct_lookup) .expect("zero value generation failed") } + + pub fn reconstruct_path_or_locator(&self, input: Expression) -> Expression { + let location = match input { + Expression::Path(ref path) => Location::new(self.program, path.absolute_path()), + Expression::Locator(locator) => Location::new(locator.program.name.name, vec![locator.name]), + _ => panic!("unexpected expression type"), + }; + + // Check if this path corresponds to a global symbol. + let Some(var) = self.state.symbol_table.lookup_global(self.program, &location) else { + // Nothing to do + return input; + }; + + match &var.type_ { + Type::Mapping(_) => { + // No transformation needed for mappings. + input + } + + Type::Optional(OptionalType { inner }) => { + // Input: + // storage x: field; + // ... + // let y = x; + // + // Lowered reconstruction: + // mapping x__: bool => field + // let y = x__.contains(false) + // ? x__.get_or_use(false, 0field) + // : None; + + let id = || self.state.node_builder.next_id(); + let var_name = location.path.last().unwrap(); + + // Path to the mapping backing the optional variable: `__` + let mapping_symbol = Symbol::intern(&format!("{var_name}__")); + let mapping_ident = Identifier::new(mapping_symbol, id()); + + // === Build expressions === + let mapping_expr: Expression = match input { + Expression::Path(_) => Path::from(mapping_ident).into_absolute().into(), + Expression::Locator(locator) => LocatorExpression { + program: locator.program, + name: mapping_symbol, + span: locator.span, + id: id(), + } + .into(), + _ => panic!("unexpected expression type"), + }; + + let false_literal: Expression = Literal::boolean(false, Span::default(), id()).into(); + + // `__.contains(false)` + let contains_expr: Expression = AssociatedFunctionExpression { + variant: Identifier::new(sym::Mapping, id()), + name: Identifier::new(Symbol::intern("contains"), id()), + type_parameters: vec![], + arguments: vec![mapping_expr.clone(), false_literal.clone()], + span: Span::default(), + id: id(), + } + .into(); + + // zero value for element type + let zero = self.zero(inner); + + // `__.get_or_use(false, zero_value)` + let get_or_use_expr: Expression = AssociatedFunctionExpression { + variant: Identifier::new(sym::Mapping, id()), + name: Identifier::new(Symbol::intern("get_or_use"), id()), + type_parameters: vec![], + arguments: vec![mapping_expr.clone(), false_literal, zero], + span: Span::default(), + id: id(), + } + .into(); + + // `None` + let none_expr = + Expression::Literal(Literal { variant: LiteralVariant::None, span: Span::default(), id: id() }); + + // Combine into ternary: + // `__.contains(false) ? __.get_or_use(false, zero_val) : None` + let ternary_expr: Expression = TernaryExpression { + condition: contains_expr, + if_true: get_or_use_expr, + if_false: none_expr, + span: Span::default(), + id: id(), + } + .into(); + + ternary_expr + } + + _ => { + panic!("Expected an optional or a mapping, found {:?}", var.type_); + } + } + } } diff --git a/compiler/passes/src/symbol_table_creation/mod.rs b/compiler/passes/src/symbol_table_creation/mod.rs index e94e583d9b8..59a3ebfd714 100644 --- a/compiler/passes/src/symbol_table_creation/mod.rs +++ b/compiler/passes/src/symbol_table_creation/mod.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{CompilerState, Pass, SymbolTable, VariableSymbol, VariableType}; +use crate::{CompilerState, Pass, VariableSymbol, VariableType}; use leo_ast::{ + AleoProgram, AstVisitor, Composite, ConstDeclaration, @@ -27,7 +28,6 @@ use leo_ast::{ MappingType, Module, OptionalType, - Program, ProgramScope, ProgramVisitor, StorageVariable, @@ -36,9 +36,9 @@ use leo_ast::{ Variant, }; use leo_errors::Result; -use leo_span::{Span, Symbol}; +use leo_span::Symbol; -use indexmap::IndexMap; +use indexmap::IndexSet; /// A pass to fill the SymbolTable. /// @@ -55,10 +55,9 @@ impl Pass for SymbolTableCreation { let ast = std::mem::take(&mut state.ast); let mut visitor = SymbolTableCreationVisitor { state, - structs: IndexMap::new(), program_name: Symbol::intern(""), + parents: IndexSet::new(), module: vec![], - is_stub: false, }; visitor.visit_program(ast.as_repr()); visitor.state.handler.last_err()?; @@ -74,10 +73,8 @@ struct SymbolTableCreationVisitor<'a> { program_name: Symbol, /// The current module name. module: Vec, - /// Whether or not traversing stub. - is_stub: bool, - /// The set of local structs that have been successfully visited. - structs: IndexMap, Span>, + /// The set of programs that import the program we're visiting. + parents: IndexSet, } impl SymbolTableCreationVisitor<'_> { @@ -113,7 +110,9 @@ impl ProgramVisitor for SymbolTableCreationVisitor<'_> { fn visit_program_scope(&mut self, input: &ProgramScope) { // Set current program name self.program_name = input.program_id.name.name; - self.is_stub = false; + + // Update the `imports` map in the symbol table. + self.state.symbol_table.add_imported_by(self.program_name, &self.parents); // Visit the program scope input.consts.iter().for_each(|(_, c)| self.visit_const(c)); @@ -135,27 +134,10 @@ impl ProgramVisitor for SymbolTableCreationVisitor<'_> { }) } - fn visit_import(&mut self, input: &Program) { - self.visit_program(input) - } - fn visit_struct(&mut self, input: &Composite) { // Allow up to one local redefinition for each external struct. let full_name = self.module.iter().cloned().chain(std::iter::once(input.name())).collect::>(); - if !input.is_record { - if let Some(prev_span) = self.structs.get(&full_name) { - // The struct already existed - return self.state.handler.emit_err(SymbolTable::emit_shadow_error( - input.identifier.name, - input.identifier.span, - *prev_span, - )); - } - - self.structs.insert(full_name.clone(), input.identifier.span); - } - if input.is_record { // While records are not allowed in submodules, we stll use their full name in the records table. // We don't expect the full name to have more than a single Symbol though. @@ -165,8 +147,28 @@ impl ProgramVisitor for SymbolTableCreationVisitor<'_> { { self.state.handler.emit_err(err); } - } else if let Err(err) = self.state.symbol_table.insert_struct(self.program_name, &full_name, input.clone()) { - self.state.handler.emit_err(err); + } else { + // First, insert for the main program. + if let Err(err) = self.state.symbol_table.insert_struct( + Location::new(self.program_name, full_name.clone()), + input.clone(), + false, + ) { + self.state.handler.emit_err(err); + } + + // Then, insert for all parent programs. That's because structs live in a global scope + // for now. That is, by importing program `foo`, we also get all of its structs + // available without requiring the prefix `foo.aleo/`. + for parent_name in &self.parents { + if let Err(err) = self.state.symbol_table.insert_struct( + Location::new(*parent_name, full_name.clone()), + input.clone(), + true, + ) { + self.state.handler.emit_err(err); + } + } } } @@ -215,8 +217,24 @@ impl ProgramVisitor for SymbolTableCreationVisitor<'_> { } fn visit_stub(&mut self, input: &Stub) { - self.is_stub = true; + match input { + Stub::FromLeo { program, parents } => { + self.parents = parents.clone(); + self.visit_program(program); + } + Stub::FromAleo { program, parents } => { + self.parents = parents.clone(); + self.visit_aleo_program(program); + } + } + } + + fn visit_aleo_program(&mut self, input: &AleoProgram) { self.program_name = input.stub_id.name.name; + + // Update the `imports` map in the symbol table. + self.state.symbol_table.add_imported_by(self.program_name, &self.parents); + input.functions.iter().for_each(|(_, c)| self.visit_function_stub(c)); input.structs.iter().for_each(|(_, c)| self.visit_struct_stub(c)); input.mappings.iter().for_each(|(_, c)| self.visit_mapping(c)); @@ -260,9 +278,11 @@ impl ProgramVisitor for SymbolTableCreationVisitor<'_> { { self.state.handler.emit_err(err); } - } else if let Err(err) = - self.state.symbol_table.insert_struct(self.program_name, &[input.name()], input.clone()) - { + } else if let Err(err) = self.state.symbol_table.insert_struct( + Location::new(self.program_name, vec![input.name()]), + input.clone(), + true, + ) { self.state.handler.emit_err(err); } } diff --git a/compiler/passes/src/test_passes.rs b/compiler/passes/src/test_passes.rs index 05884d186c7..b9aacd8e819 100644 --- a/compiler/passes/src/test_passes.rs +++ b/compiler/passes/src/test_passes.rs @@ -85,6 +85,7 @@ use leo_errors::{BufferEmitter, Handler}; use leo_parser::parse_ast; use leo_span::{create_session_if_not_set_then, source_map::FileName, with_session_globals}; use serial_test::serial; +use std::rc::Rc; /// Table of all compiler passes and their runner names. /// Each entry is a tuple of `(runner_name, pass_struct, input)` @@ -109,14 +110,13 @@ macro_rules! compiler_passes { } /// Parse a Leo source program into an AST, returning errors via the handler. -fn parse_program(source: &str, handler: &Handler) -> Result { - let node_builder = NodeBuilder::default(); +fn parse_program(source: &str, node_builder: &Rc, handler: &Handler) -> Result { let filename = FileName::Custom("test".into()); // Add the source to the session's source map let source_file = with_session_globals(|s| s.source_map.new_source(source, filename)); - handler.extend_if_error(parse_ast(handler.clone(), &node_builder, &source_file, &[], NetworkName::TestnetV0)) + handler.extend_if_error(parse_ast(handler.clone(), node_builder, &source_file, &[], NetworkName::TestnetV0)) } /// Macro to generate a single runner function for a compiler pass. @@ -132,10 +132,16 @@ macro_rules! make_runner { let buf = BufferEmitter::new(); let handler = Handler::new(buf.clone()); + let node_builder = Rc::new(NodeBuilder::default()); create_session_if_not_set_then(|_| { // Parse program into AST - let mut state = match parse_program(source, &handler) { - Ok(ast) => CompilerState { ast, handler: handler.clone(), ..Default::default() }, + let mut state = match parse_program(source, &node_builder, &handler) { + Ok(ast) => CompilerState { + ast, + handler: handler.clone(), + node_builder: Rc::clone(&node_builder), + ..Default::default() + }, Err(()) => return format!("{}{}", buf.extract_errs(), buf.extract_warnings()), }; diff --git a/compiler/passes/src/type_checking/ast.rs b/compiler/passes/src/type_checking/ast.rs index 1a134ca15e0..d85c8ea76bc 100644 --- a/compiler/passes/src/type_checking/ast.rs +++ b/compiler/passes/src/type_checking/ast.rs @@ -15,7 +15,7 @@ // along with the Leo library. If not, see . use super::*; -use crate::{VariableSymbol, VariableType}; +use crate::{BlockToFunctionRewriter, SymbolTable, VariableSymbol, VariableType}; use leo_ast::{ Type::{Future, Tuple}, @@ -341,9 +341,9 @@ impl TypeCheckingVisitor<'_> { // Returns the type of the RHS of an assign statement if it's a `Path`. // Also returns whether the RHS is a storage location. pub fn visit_path_assign(&mut self, input: &Path) -> (Type, bool) { + let current_program = self.scope_state.program_name.unwrap(); // Lookup the variable in the symbol table and retrieve its type. - let Some(var) = - self.state.symbol_table.lookup_path(self.scope_state.program_name.unwrap(), &input.absolute_path()) + let Some(var) = self.state.symbol_table.lookup_path(current_program, current_program, &input.absolute_path()) else { self.emit_err(TypeCheckerError::unknown_sym("variable", input, input.span)); return (Type::Err, false); @@ -457,7 +457,18 @@ impl AstVisitor for TypeCheckingVisitor<'_> { for (expected, argument) in struct_.const_parameters.iter().zip(input.const_arguments.iter()) { self.visit_expression(argument, &Some(expected.type_().clone())); } + + // For now, do not allow external structs to be instantiated with generic arguments + if self + .state + .symbol_table + .is_external_struct(&Location::new(self.scope_state.program_name.unwrap(), input.path.absolute_path())) + && !input.const_arguments.is_empty() + { + self.emit_err(TypeCheckerError::unexpected_const_args(input, input.path.span)); + } } else if !input.const_arguments.is_empty() { + // This handles erroring out on all non-structs self.emit_err(TypeCheckerError::unexpected_const_args(input, input.path.span)); } } @@ -673,6 +684,23 @@ impl AstVisitor for TypeCheckingVisitor<'_> { // This scope now already has an async block self.scope_state.already_contains_an_async_block = true; + // Here we convert the async block to an async function using the helper + // `BlockToFunctionRewriter`. We do not actually replace anything in the original AST. We + // just inspect how the async block would look like as an async function in order to + // populate the map `async_function_input_types`. + let mut block_to_function_rewriter = + BlockToFunctionRewriter::new(self.state, self.scope_state.program_name.unwrap()); + let (new_function, _) = + block_to_function_rewriter.rewrite_block(&input.block, Symbol::intern("unused"), Variant::AsyncFunction); + let input_types = new_function.input.iter().map(|Input { type_, .. }| type_.clone()).collect(); + self.async_function_input_types.insert( + Location::new(self.scope_state.program_name.unwrap(), vec![Symbol::intern(&format!( + "finalize/{}", + self.scope_state.function.unwrap(), + ))]), + input_types, + ); + // Step out of the async block self.async_block_id = None; @@ -682,10 +710,17 @@ impl AstVisitor for TypeCheckingVisitor<'_> { } fn visit_binary(&mut self, input: &BinaryExpression, destination: &Self::AdditionalInput) -> Self::Output { + let current_program = self.scope_state.program_name.unwrap(); + fn is_record(current_program: Symbol, symbol_table: &SymbolTable, loc: &Location) -> bool { + symbol_table.lookup_record(current_program, loc).is_some() + } + let assert_same_type = |slf: &Self, t1: &Type, t2: &Type| -> Type { if t1 == &Type::Err || t2 == &Type::Err { Type::Err - } else if !t1.eq_user(t2) { + } else if !t1.eq_user(t2, current_program, &|loc: &Location| { + is_record(current_program, &slf.state.symbol_table, loc) + }) { slf.emit_err(TypeCheckerError::operation_types_mismatch(input.op, t1, t2, input.span())); Type::Err } else { @@ -1078,12 +1113,15 @@ impl AstVisitor for TypeCheckingVisitor<'_> { } fn visit_call(&mut self, input: &CallExpression, expected: &Self::AdditionalInput) -> Self::Output { + let current_program = self.scope_state.program_name.unwrap(); let callee_program = input.program.or(self.scope_state.program_name).unwrap(); let callee_path = input.function.absolute_path(); - let Some(func_symbol) = - self.state.symbol_table.lookup_function(&Location::new(callee_program, callee_path.clone())) + let Some(func_symbol) = self + .state + .symbol_table + .lookup_function(current_program, &Location::new(callee_program, callee_path.clone())) else { self.emit_err(TypeCheckerError::unknown_sym("function", input.function.clone(), input.function.span())); return Type::Err; @@ -1099,7 +1137,7 @@ impl AstVisitor for TypeCheckingVisitor<'_> { ), Variant::Transition | Variant::AsyncTransition if matches!(func.variant, Variant::Transition) - && input.program.is_none_or(|program| program == self.scope_state.program_name.unwrap()) => + && input.program.is_none_or(|program| program == current_program) => { self.emit_err(TypeCheckerError::cannot_invoke_call_to_local_transition_function(input.span)) } @@ -1307,7 +1345,7 @@ impl AstVisitor for TypeCheckingVisitor<'_> { self.state .symbol_table .attach_finalizer( - Location::new(callee_program, caller_path), + Location::new(callee_program, caller_path.clone()), Location::new(callee_program, callee_path.clone()), input_futures, inferred_finalize_inputs.clone(), @@ -1324,11 +1362,19 @@ impl AstVisitor for TypeCheckingVisitor<'_> { // Update ret to reflect fully inferred future type. ret = Type::Future(FutureType::new( - inferred_finalize_inputs, + inferred_finalize_inputs.clone(), Some(Location::new(callee_program, callee_path.clone())), true, )); + self.async_function_input_types.insert( + Location::new(callee_program, vec![Symbol::intern(&format!( + "finalize/{}", + caller_path.last().unwrap() + ))]), + inferred_finalize_inputs.clone(), + ); + // Type check in case the expected type is known. self.assert_and_return_type(ret.clone(), expected, input.span()); } @@ -1391,7 +1437,7 @@ impl AstVisitor for TypeCheckingVisitor<'_> { let type_ = Type::Composite(CompositeType { path: input.path.clone(), const_arguments: input.const_arguments.clone(), - program: None, + program: self.scope_state.program_name, }); self.maybe_assert_type(&type_, additional, input.path.span()); @@ -1477,7 +1523,8 @@ impl AstVisitor for TypeCheckingVisitor<'_> { } fn visit_path(&mut self, input: &Path, expected: &Self::AdditionalInput) -> Self::Output { - let var = self.state.symbol_table.lookup_path(self.scope_state.program_name.unwrap(), &input.absolute_path()); + let current_program = self.scope_state.program_name.unwrap(); + let var = self.state.symbol_table.lookup_path(current_program, current_program, &input.absolute_path()); if let Some(var) = var { if var.declaration == VariableType::Storage && !var.type_.is_vector() && !var.type_.is_mapping() { @@ -1578,8 +1625,14 @@ impl AstVisitor for TypeCheckingVisitor<'_> { } fn visit_locator(&mut self, input: &LocatorExpression, expected: &Self::AdditionalInput) -> Self::Output { - let maybe_var = - self.state.symbol_table.lookup_global(&Location::new(input.program.name.name, vec![input.name])).cloned(); + let maybe_var = self + .state + .symbol_table + .lookup_global( + self.scope_state.program_name.unwrap(), + &Location::new(input.program.name.name, vec![input.name]), + ) + .cloned(); if let Some(var) = maybe_var { self.maybe_assert_type(&var.type_, expected, input.span()); var.type_ @@ -1590,6 +1643,8 @@ impl AstVisitor for TypeCheckingVisitor<'_> { } fn visit_ternary(&mut self, input: &TernaryExpression, expected: &Self::AdditionalInput) -> Self::Output { + let current_program = self.scope_state.program_name.unwrap(); + self.visit_expression(&input.condition, &Some(Type::Boolean)); // We try to coerce one side to another in the ternary operator whenever possible and/or needed. @@ -1631,14 +1686,18 @@ impl AstVisitor for TypeCheckingVisitor<'_> { ) }; + let is_record = &|loc: &Location| self.state.symbol_table.lookup_record(current_program, loc).is_some(); + let typ = if t1 == Type::Err || t2 == Type::Err { Type::Err - } else if !t1.can_coerce_to(&t2) && !t2.can_coerce_to(&t1) { + } else if !t1.can_coerce_to(&t2, current_program, &is_record) + && !t2.can_coerce_to(&t1, current_program, &is_record) + { self.emit_err(TypeCheckerError::ternary_branch_mismatch(t1, t2, input.span())); Type::Err } else if let Some(expected) = expected { expected.clone() - } else if t1.can_coerce_to(&t2) { + } else if t1.can_coerce_to(&t2, current_program, &is_record) { t2 } else { t1 @@ -1870,6 +1929,7 @@ impl AstVisitor for TypeCheckingVisitor<'_> { } fn visit_assert(&mut self, input: &AssertStatement) { + let current_program = self.scope_state.program_name.unwrap(); match &input.variant { AssertVariant::Assert(expr) => { let _type = self.visit_expression(expr, &Some(Type::Boolean)); @@ -1878,10 +1938,15 @@ impl AstVisitor for TypeCheckingVisitor<'_> { let t1 = self.visit_expression_reject_numeric(left, &None); let t2 = self.visit_expression_reject_numeric(right, &None); - if t1 != Type::Err && t2 != Type::Err && !t1.eq_user(&t2) { + if t1 != Type::Err + && t2 != Type::Err + && !t1.eq_user(&t2, current_program, &|loc: &Location| { + self.state.symbol_table.lookup_record(current_program, loc).is_some() + }) + { let op = if matches!(input.variant, AssertVariant::AssertEq(..)) { "assert_eq" } else { "assert_neq" }; - self.emit_err(TypeCheckerError::operation_types_mismatch(op, t1, t2, input.span())); + self.emit_err(TypeCheckerError::operation_types_mismatch(op, &t1, &t2, input.span())); } } } @@ -2188,10 +2253,12 @@ impl AstVisitor for TypeCheckingVisitor<'_> { let caller_path = self.scope_state.module_name.iter().cloned().chain(std::iter::once(caller_name)).collect::>(); + let current_program = self.scope_state.program_name.unwrap(); + let func_symbol = self .state .symbol_table - .lookup_function(&Location::new(self.scope_state.program_name.unwrap(), caller_path.clone())) + .lookup_function(current_program, &Location::new(current_program, caller_path.clone())) .expect("The symbol table creator should already have visited all functions."); let mut return_type = func_symbol.function.output_type.clone(); @@ -2199,7 +2266,7 @@ impl AstVisitor for TypeCheckingVisitor<'_> { if self.scope_state.variant == Some(Variant::AsyncTransition) && self.scope_state.has_called_finalize { let inferred_future_type = Future(FutureType::new( if let Some(finalizer) = &func_symbol.finalizer { finalizer.inferred_inputs.clone() } else { vec![] }, - Some(Location::new(self.scope_state.program_name.unwrap(), caller_path)), + Some(Location::new(current_program, caller_path)), true, )); diff --git a/compiler/passes/src/type_checking/mod.rs b/compiler/passes/src/type_checking/mod.rs index a88e7bd0c3c..e5bd3382cbe 100644 --- a/compiler/passes/src/type_checking/mod.rs +++ b/compiler/passes/src/type_checking/mod.rs @@ -89,7 +89,7 @@ impl Pass for TypeChecking { .symbol_table .iter_records() .map(|(loc, _)| loc.path.clone()) - .chain(state.symbol_table.iter_structs().map(|(name, _)| name.clone())) + .chain(state.symbol_table.iter_structs().map(|(loc, _)| loc.path.clone())) .collect(); let function_names = state.symbol_table.iter_functions().map(|(loc, _)| loc.clone()).collect(); diff --git a/compiler/passes/src/type_checking/program.rs b/compiler/passes/src/type_checking/program.rs index 1fc9adb7830..87f47df4859 100644 --- a/compiler/passes/src/type_checking/program.rs +++ b/compiler/passes/src/type_checking/program.rs @@ -29,13 +29,15 @@ impl ProgramVisitor for TypeCheckingVisitor<'_> { fn visit_program(&mut self, input: &Program) { // Typecheck the program's stubs. input.stubs.iter().for_each(|(symbol, stub)| { - // Check that naming and ordering is consistent. - if symbol != &stub.stub_id.name.name { - self.emit_err(TypeCheckerError::stub_name_mismatch( - symbol, - stub.stub_id.name, - stub.stub_id.network.span, - )); + if let Stub::FromAleo { program, .. } = stub { + // Check that naming and ordering is consistent. + if symbol != &program.stub_id.name.name { + self.emit_err(TypeCheckerError::stub_name_mismatch( + symbol, + program.stub_id.name, + program.stub_id.network.span, + )); + } } self.visit_stub(stub) }); @@ -161,7 +163,7 @@ impl ProgramVisitor for TypeCheckingVisitor<'_> { self.scope_state.module_name = parent_module; } - fn visit_stub(&mut self, input: &Stub) { + fn visit_aleo_program(&mut self, input: &AleoProgram) { // Set the scope state. self.scope_state.program_name = Some(input.stub_id.name.name); self.scope_state.is_stub = true; @@ -573,7 +575,7 @@ impl ProgramVisitor for TypeCheckingVisitor<'_> { if let UpgradeVariant::Checksum { mapping, key, key_type } = &upgrade_variant { // Look up the mapping type. let Some(VariableSymbol { type_: Type::Mapping(mapping_type), .. }) = - self.state.symbol_table.lookup_global(mapping) + self.state.symbol_table.lookup_global(self.scope_state.program_name.unwrap(), mapping) else { self.emit_err(TypeCheckerError::custom( format!("The mapping '{mapping}' does not exist. Please ensure that it is imported or defined in your program."), diff --git a/compiler/passes/src/type_checking/visitor.rs b/compiler/passes/src/type_checking/visitor.rs index 2e61d2d222c..0a114c64474 100644 --- a/compiler/passes/src/type_checking/visitor.rs +++ b/compiler/passes/src/type_checking/visitor.rs @@ -114,7 +114,12 @@ impl TypeCheckingVisitor<'_> { } pub fn assert_type(&mut self, actual: &Type, expected: &Type, span: Span) { - if actual != &Type::Err && !actual.can_coerce_to(expected) { + let current_program = self.scope_state.program_name.unwrap(); + if actual != &Type::Err + && !actual.can_coerce_to(expected, current_program, &|loc: &Location| { + self.state.symbol_table.lookup_record(current_program, loc).is_some() + }) + { // If `actual` is Err, we will have already reported an error. self.emit_err(TypeCheckerError::type_should_be2(actual, format!("type `{expected}`"), span)); } @@ -136,7 +141,13 @@ impl TypeCheckingVisitor<'_> { // if destination is Optional and actual is T (not already Optional), wrap it (actual_type, Some(Type::Optional(opt_type))) if !matches!(actual_type, Type::Optional(_)) => { // only wrap if the inner type matches - if actual_type.can_coerce_to(&opt_type.inner) { + if actual_type.can_coerce_to( + &opt_type.inner, + self.scope_state.program_name.unwrap(), + &|loc: &Location| { + self.state.symbol_table.lookup_record(self.scope_state.program_name.unwrap(), loc).is_some() + }, + ) { Type::Optional(OptionalType { inner: Box::new(actual_type) }) } else { actual_type @@ -684,12 +695,34 @@ impl TypeCheckingVisitor<'_> { // Check that the operation is invoked in a `finalize` or `async` block. self.check_access_allowed("Vector::set", true, function_span); - Type::Unit - } else if let Type::Mapping(_) = &arguments[0].0 { + // argument 0 can only be a `Locator` or a `Path`. No other expression can be a vector + if let Expression::Locator(LocatorExpression { program, .. }) = arguments[0].1 + && program.name.name != self.scope_state.program_name.unwrap() + { + self.state.handler.emit_err(TypeCheckerError::cannot_modify_external_container( + "set", + "vector", + function_span, + )); + Type::Err + } else { + Type::Unit + } + } else if let Type::Mapping(mapping_type) = &arguments[0].0 { // Check that the operation is invoked in a `finalize` or `async` block. self.check_access_allowed("Mapping::set", true, function_span); - Type::Unit + // Cannot modify external mappings. + if mapping_type.program != self.scope_state.program_name.unwrap() { + self.state.handler.emit_err(TypeCheckerError::cannot_modify_external_container( + "set", + "mapping", + function_span, + )); + Type::Err + } else { + Type::Unit + } } else { self.assert_vector_or_mapping_type(&arguments[0].0, arguments[0].1.span()); Type::Err @@ -726,9 +759,11 @@ impl TypeCheckingVisitor<'_> { // Cannot modify external mappings. if mapping_type.program != self.scope_state.program_name.unwrap() { - self.state - .handler - .emit_err(TypeCheckerError::cannot_modify_external_mapping("remove", function_span)); + self.state.handler.emit_err(TypeCheckerError::cannot_modify_external_container( + "remove", + "mapping", + function_span, + )); } // Check that the second argument matches the key type of the mapping. @@ -782,7 +817,20 @@ impl TypeCheckingVisitor<'_> { Type::Vector(VectorType { element_type }) => { // Ensure that the element type and the type of the value to push are the same self.assert_type(&arguments[1].0, element_type, arguments[1].1.span()); - Type::Unit + + // argument 0 can only be a `Locator` or a `Path`. No other expression can be a vector + if let Expression::Locator(LocatorExpression { program, .. }) = arguments[0].1 + && program.name.name != self.scope_state.program_name.unwrap() + { + self.state.handler.emit_err(TypeCheckerError::cannot_modify_external_container( + "push", + "vector", + function_span, + )); + Type::Err + } else { + Type::Unit + } } _ => { self.assert_vector_type(&arguments[0].0, arguments[0].1.span()); @@ -804,7 +852,19 @@ impl TypeCheckingVisitor<'_> { self.check_access_allowed("Vector::pop", true, function_span); if let Type::Vector(VectorType { element_type }) = &arguments[0].0 { - Type::Optional(OptionalType { inner: Box::new(*element_type.clone()) }) + // argument 0 can only be a `Locator` or a `Path`. No other expression can be a vector + if let Expression::Locator(LocatorExpression { program, .. }) = arguments[0].1 + && program.name.name != self.scope_state.program_name.unwrap() + { + self.state.handler.emit_err(TypeCheckerError::cannot_modify_external_container( + "pop", + "vector", + function_span, + )); + Type::Err + } else { + Type::Optional(OptionalType { inner: Box::new(*element_type.clone()) }) + } } else { self.assert_vector_type(&arguments[0].0, arguments[0].1.span()); Type::Err @@ -814,7 +874,19 @@ impl TypeCheckingVisitor<'_> { self.check_access_allowed("Vector::swap_remove", true, function_span); if let Type::Vector(VectorType { element_type }) = &arguments[0].0 { - *element_type.clone() + // argument 0 can only be a `Locator` or a `Path`. No other expression can be a vector + if let Expression::Locator(LocatorExpression { program, .. }) = arguments[0].1 + && program.name.name != self.scope_state.program_name.unwrap() + { + self.state.handler.emit_err(TypeCheckerError::cannot_modify_external_container( + "swap_remove", + "vector", + function_span, + )); + Type::Err + } else { + *element_type.clone() + } } else { self.assert_vector_type(&arguments[0].0, arguments[0].1.span()); Type::Err @@ -822,7 +894,19 @@ impl TypeCheckingVisitor<'_> { } CoreFunction::VectorClear => { if arguments[0].0.is_vector() { - Type::Unit + // argument 0 can only be a `Locator` or a `Path`. No other expression can be a vector + if let Expression::Locator(LocatorExpression { program, .. }) = arguments[0].1 + && program.name.name != self.scope_state.program_name.unwrap() + { + self.state.handler.emit_err(TypeCheckerError::cannot_modify_external_container( + "clear", + "vector", + function_span, + )); + Type::Err + } else { + Type::Unit + } } else { self.assert_vector_type(&arguments[0].0, arguments[0].1.span()); Type::Err @@ -1376,7 +1460,7 @@ impl TypeCheckingVisitor<'_> { .iter() .flat_map(|caller| { let caller = Location::new(caller.program, caller.path.clone()); - self.state.symbol_table.lookup_function(&caller) + self.state.symbol_table.lookup_function(self.scope_state.program_name.unwrap(), &caller) }) .flat_map(|fn_symbol| fn_symbol.finalizer.clone()) }) @@ -1391,7 +1475,7 @@ impl TypeCheckingVisitor<'_> { for finalizer in caller_finalizers { assert_eq!(inferred_inputs.len(), finalizer.inferred_inputs.len()); for (t1, t2) in inferred_inputs.iter_mut().zip(finalizer.inferred_inputs.iter()) { - Self::merge_types(t1, t2); + self.merge_types(t1, t2); } } } else { @@ -1605,12 +1689,13 @@ impl TypeCheckingVisitor<'_> { /// That is, if `lhs` and `rhs` aren't equal, set `lhs` to Type::Err; /// or, if they're both futures, set any member of `lhs` that isn't /// equal to the equivalent member of `rhs` to `Type::Err`. - fn merge_types(lhs: &mut Type, rhs: &Type) { + fn merge_types(&self, lhs: &mut Type, rhs: &Type) { + let current_program = self.scope_state.program_name.unwrap(); if let Type::Future(f1) = lhs { if let Type::Future(f2) = rhs { for (i, type_) in f2.inputs.iter().enumerate() { if let Some(lhs_type) = f1.inputs.get_mut(i) { - Self::merge_types(lhs_type, type_); + self.merge_types(lhs_type, type_); } else { f1.inputs.push(Type::Err); } @@ -1618,16 +1703,24 @@ impl TypeCheckingVisitor<'_> { } else { *lhs = Type::Err; } - } else if !lhs.eq_user(rhs) { + } else if !lhs.eq_user(rhs, current_program, &|loc: &Location| { + self.state.symbol_table.lookup_record(current_program, loc).is_some() + }) { *lhs = Type::Err; } } /// Wrapper around lookup_struct that additionally records all structs that are used in the program. pub fn lookup_struct(&mut self, program: Option, name: &[Symbol]) -> Option { - let record_comp = - program.and_then(|prog| self.state.symbol_table.lookup_record(&Location::new(prog, name.to_vec()))); - let comp = record_comp.or_else(|| self.state.symbol_table.lookup_struct(name)); + let current_program = self.scope_state.program_name.unwrap(); + let record_comp = program.and_then(|prog| { + self.state.symbol_table.lookup_record(current_program, &Location::new(prog, name.to_vec())) + }); + let comp = record_comp.or_else(|| { + program.and_then(|prog| { + self.state.symbol_table.lookup_struct(current_program, &Location::new(prog, name.to_vec())) + }) + }); // Record the usage. if let Some(s) = comp { // If it's a struct or internal record, mark it used. @@ -1701,7 +1794,7 @@ impl TypeCheckingVisitor<'_> { && self .state .symbol_table - .lookup_record(&Location::new(program, typ.path.absolute_path().to_vec())) + .lookup_record(this_program, &Location::new(program, typ.path.absolute_path().to_vec())) .is_some() } else { false diff --git a/compiler/passes/src/write_transforming/visitor.rs b/compiler/passes/src/write_transforming/visitor.rs index 98e4ad67725..2b03f4d61f3 100644 --- a/compiler/passes/src/write_transforming/visitor.rs +++ b/compiler/passes/src/write_transforming/visitor.rs @@ -309,12 +309,15 @@ impl WriteTransformingFiller<'_> { .0 .state .symbol_table - .lookup_struct(&comp.path.absolute_path()) + .lookup_struct( + self.0.program, + &Location::new(comp.program.unwrap_or(self.0.program), comp.path.absolute_path()), + ) .or_else(|| { - self.0.state.symbol_table.lookup_record(&Location::new( - comp.program.unwrap_or(self.0.program), - comp.path.absolute_path(), - )) + self.0.state.symbol_table.lookup_record( + self.0.program, + &Location::new(comp.program.unwrap_or(self.0.program), comp.path.absolute_path()), + ) }) .unwrap(); struct_ diff --git a/errors/src/errors/type_checker/type_checker_error.rs b/errors/src/errors/type_checker/type_checker_error.rs index 87163c294d8..2152fbc1541 100644 --- a/errors/src/errors/type_checker/type_checker_error.rs +++ b/errors/src/errors/type_checker/type_checker_error.rs @@ -863,11 +863,20 @@ create_messages!( msg: "The output of an async function must be assigned to a `Future` type..".to_string(), help: None, } + @formatted - cannot_modify_external_mapping { - args: (operation: impl Display), - msg: format!("Cannot use operation `{operation}` on external mapping."), - help: Some("The only valid operations on external mappings are contains, get, and get_or_use.".to_string()), + cannot_modify_external_container { + args: (operation: impl Display, kind: impl Display), + msg: format!("Cannot use operation `{operation}` on external {kind}s."), + help: Some(format!("The only valid operations on external {kind}s are {}.", + if kind.to_string() == "vector" { + "`get` and `len`" + } else if kind.to_string() == "mapping" { + "`contains`, `get`, and `get_or_use`" + } else { + panic!("no other kinds expected here") + } + )), } @formatted @@ -1372,5 +1381,4 @@ create_messages!( msg: "A struct must have at least one member of non-zero size.".to_string(), help: None, } - ); diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml index aeec28546b8..ba9ce791e9f 100644 --- a/interpreter/Cargo.toml +++ b/interpreter/Cargo.toml @@ -29,6 +29,9 @@ workspace = true [dependencies.leo-parser] workspace = true +[dependencies.leo-passes] +workspace = true + [dependencies.leo-span] workspace = true diff --git a/interpreter/src/test_interpreter.rs b/interpreter/src/test_interpreter.rs index 461f3cdf7d3..2a226cd1d2d 100644 --- a/interpreter/src/test_interpreter.rs +++ b/interpreter/src/test_interpreter.rs @@ -16,10 +16,10 @@ //! These tests compare interpreter runs against ledger runs. -use leo_ast::{NetworkName, Stub, interpreter_value::Value}; +use leo_ast::{NetworkName, NodeBuilder, Program, Stub, interpreter_value::Value}; use leo_compiler::{Compiler, run_with_ledger}; -use leo_disassembler::disassemble_from_str; use leo_errors::{BufferEmitter, Handler, Result}; +use leo_passes::{Bytecode, CompiledPrograms}; use leo_span::{Symbol, create_session_if_not_set_then, source_map::FileName}; use snarkvm::prelude::{PrivateKey, TestnetV0}; @@ -29,6 +29,7 @@ use itertools::Itertools as _; use std::{ fs, path::{Path, PathBuf}, + rc::Rc, str::FromStr, }; use walkdir::WalkDir; @@ -39,11 +40,19 @@ const PROGRAM_DELIMITER: &str = "// --- Next Program --- //"; type CurrentNetwork = TestnetV0; -fn whole_compile(source: &str, handler: &Handler, import_stubs: IndexMap) -> Result<(String, String)> { +/// Fully compiles a Leo `source` with some stubs +#[allow(clippy::type_complexity)] +fn whole_compile( + source: &str, + handler: &Handler, + node_builder: &Rc, + import_stubs: IndexMap, +) -> Result<(CompiledPrograms, String)> { let mut compiler = Compiler::new( None, /* is_test (a Leo test) */ false, handler.clone(), + node_builder.clone(), "/fakedirectory-wont-use".into(), None, import_stubs, @@ -52,9 +61,29 @@ fn whole_compile(source: &str, handler: &Handler, import_stubs: IndexMap) -> Result<(Program, String)> { + let mut compiler = Compiler::new( + None, + /* is_test (a Leo test) */ false, + handler.clone(), + node_builder.clone(), + "/fakedirectory-wont-use".into(), + None, + IndexMap::new(), + NetworkName::TestnetV0, + ); + + let filename = FileName::Custom("execution-test".into()); + + let program = compiler.parse_and_return_ast(source, filename, &[])?; + + Ok((program, compiler.program_name.unwrap())) } fn parse_cases(source: &str) -> (Vec, Vec) { @@ -88,21 +117,39 @@ pub struct TestResult { interpreter_result: Vec, } -fn run_test(path: &Path, handler: &Handler, _buf: &BufferEmitter) -> Result { +fn run_test( + path: &Path, + handler: &Handler, + node_builder: &Rc, + _buf: &BufferEmitter, +) -> Result { let source = fs::read_to_string(path).unwrap_or_else(|e| panic!("Failed to read file {}: {e}.", path.display())); let (cases, sources) = parse_cases(&source); let mut import_stubs = IndexMap::new(); let mut ledger_config = run_with_ledger::Config { seed: 2, start_height: None, programs: Vec::new() }; - for source in &sources { - let (bytecode, name) = handler.extend_if_error(whole_compile(source, handler, import_stubs.clone()))?; - let stub = handler - .extend_if_error(disassemble_from_str::(&name, &bytecode).map_err(|err| err.into()))?; - import_stubs.insert(Symbol::intern(&name), stub); + // Split sources into intermediate and final. + let (last, rest) = sources.split_last().expect("sources cannot be empty"); - ledger_config.programs.push(run_with_ledger::Program { bytecode, name }); + // Parse-only stage for intermediate programs. + for source in rest { + let (program, program_name) = handler.extend_if_error(parse(source, handler, node_builder))?; + import_stubs.insert(Symbol::intern(&program_name), program.into()); } + // Full compile stage for the final program. + let (compiled_programs, program_name) = + handler.extend_if_error(whole_compile(last, handler, node_builder, import_stubs.clone()))?; + + // Add imported programs. + for Bytecode { program_name, bytecode } in compiled_programs.import_bytecodes { + ledger_config.programs.push(run_with_ledger::Program { bytecode, name: program_name }); + } + + // Add main program. + let primary_bytecode = compiled_programs.primary_bytecode.clone(); + ledger_config.programs.push(run_with_ledger::Program { bytecode: primary_bytecode, name: program_name }); + // Note. We wrap the cases in a slice to run them on a single ledger instance. // This is just to be consistent with previous semantics. let outcomes = handler @@ -183,7 +230,8 @@ fn test_interpreter() { let mut test_result = { let buf = BufferEmitter::new(); let handler = Handler::new(buf.clone()); - match run_test(path, &handler, &buf) { + let node_builder = Rc::new(NodeBuilder::default()); + match run_test(path, &handler, &node_builder, &buf) { Ok(result) => result, Err(..) => { let errs = buf.extract_errs(); diff --git a/leo/cli/commands/build.rs b/leo/cli/commands/build.rs index d723cf5c31e..a1e726b2a5e 100644 --- a/leo/cli/commands/build.rs +++ b/leo/cli/commands/build.rs @@ -16,16 +16,18 @@ use super::*; -use leo_ast::{NetworkName, Stub}; +use leo_ast::{NetworkName, NodeBuilder, Program, Stub}; use leo_compiler::{AstSnapshots, Compiler, CompilerOptions}; use leo_errors::{CliError, UtilError}; use leo_package::{Manifest, Package}; +use leo_passes::{Bytecode, CompiledPrograms}; use leo_span::Symbol; -use snarkvm::prelude::{CanaryV0, Itertools, MainnetV0, Program, TestnetV0}; +use snarkvm::prelude::{CanaryV0, MainnetV0, Program as SvmProgram, TestnetV0}; use indexmap::IndexMap; -use std::path::Path; +use itertools::Itertools; +use std::{path::Path, rc::Rc}; impl From for CompilerOptions { fn from(options: BuildOptions) -> Self { @@ -136,49 +138,84 @@ fn handle_build(command: &LeoBuild, context: Context) -> Result< = IndexMap::new(); - for program in package.programs.iter() { - let (bytecode, build_path) = match &program.data { + for program in &package.programs { + match &program.data { leo_package::ProgramData::Bytecode(bytecode) => { // This was a network dependency or local .aleo dependency, and we have its bytecode. - (bytecode.clone(), imports_directory.join(format!("{}.aleo", program.name))) + let build_path = imports_directory.join(format!("{}.aleo", program.name)); + + // Write the .aleo file. + std::fs::write(&build_path, bytecode).map_err(CliError::failed_to_load_instructions)?; + + // Track the Stub. + let stub = match network { + NetworkName::MainnetV0 => { + leo_disassembler::disassemble_from_str::(program.name, bytecode) + } + NetworkName::TestnetV0 => { + leo_disassembler::disassemble_from_str::(program.name, bytecode) + } + NetworkName::CanaryV0 => leo_disassembler::disassemble_from_str::(program.name, bytecode), + }?; + + stubs.insert(program.name, stub.into()); } + leo_package::ProgramData::SourcePath { directory, source } => { - // This is a local dependency, so we must compile it. - let build_path = if source == &main_source_path { - build_directory.join("main.aleo") - } else { - imports_directory.join(format!("{}.aleo", program.name)) - }; - // Load the manifest in local dependency. + // This is a local dependency, so we must compile or parse it. let source_dir = directory.join("src"); - let bytecode = compile_leo_source_directory( - source, // entry file + + if source == &main_source_path || program.is_test { + // Compile the program (main or test). + let compiled_programs = compile_leo_source_directory( + source, // entry file + &source_dir, + program.name, + program.is_test, + &outputs_directory, + &handler, + &node_builder, + command.options.clone(), + stubs.clone(), + network, + )?; + + // Where to write the primary bytecode? + let primary_path = if source == &main_source_path { + build_directory.join("main.aleo") + } else { + imports_directory.join(format!("{}.aleo", program.name)) + }; + + // Write the primary program bytecode. + std::fs::write(&primary_path, &compiled_programs.primary_bytecode) + .map_err(CliError::failed_to_load_instructions)?; + + // Write imports. + for Bytecode { program_name, bytecode } in compiled_programs.import_bytecodes { + let import_path = imports_directory.join(format!("{}.aleo", program_name)); + std::fs::write(&import_path, &bytecode).map_err(CliError::failed_to_load_instructions)?; + } + } + + // Parse intermediate dependencies only. + let leo_program = parse_leo_source_directory( + source, &source_dir, program.name, - program.is_test, - &outputs_directory, &handler, + &node_builder, command.options.clone(), - stubs.clone(), network, )?; - (bytecode, build_path) - } - }; - // Write the .aleo file. - std::fs::write(build_path, &bytecode).map_err(CliError::failed_to_load_instructions)?; - - // Track the Stub. - let stub = match network { - NetworkName::MainnetV0 => leo_disassembler::disassemble_from_str::(program.name, &bytecode), - NetworkName::TestnetV0 => leo_disassembler::disassemble_from_str::(program.name, &bytecode), - NetworkName::CanaryV0 => leo_disassembler::disassemble_from_str::(program.name, &bytecode), - }?; - stubs.insert(program.name, stub); + stubs.insert(program.name, leo_program.into()); + } + } } // SnarkVM expects to find a `program.json` file in the build directory, so make @@ -207,15 +244,17 @@ fn compile_leo_source_directory( is_test: bool, output_path: &Path, handler: &Handler, + node_builder: &Rc, options: BuildOptions, stubs: IndexMap, network: NetworkName, -) -> Result { +) -> Result { // Create a new instance of the Leo compiler. let mut compiler = Compiler::new( Some(program_name.to_string()), is_test, handler.clone(), + Rc::clone(node_builder), output_path.to_path_buf(), Some(options.into()), stubs, @@ -223,11 +262,12 @@ fn compile_leo_source_directory( ); // Compile the Leo program into Aleo instructions. - let bytecode = compiler.compile_from_directory(entry_file_path, source_directory)?; + let compiled_programs = compiler.compile_from_directory(entry_file_path, source_directory)?; + let primary_bytecode = compiled_programs.primary_bytecode.clone(); - // Check the program size limit. + // Check the program size limit for each bytecode. use leo_package::MAX_PROGRAM_SIZE; - let program_size = bytecode.len(); + let program_size = primary_bytecode.len(); if program_size > MAX_PROGRAM_SIZE { return Err(leo_errors::LeoError::UtilError(UtilError::program_size_limit_exceeded( @@ -239,9 +279,9 @@ fn compile_leo_source_directory( // Get the AVM bytecode. let checksum: String = match network { - NetworkName::MainnetV0 => Program::::from_str(&bytecode)?.to_checksum().iter().join(", "), - NetworkName::TestnetV0 => Program::::from_str(&bytecode)?.to_checksum().iter().join(", "), - NetworkName::CanaryV0 => Program::::from_str(&bytecode)?.to_checksum().iter().join(", "), + NetworkName::MainnetV0 => SvmProgram::::from_str(&primary_bytecode)?.to_checksum().iter().join(", "), + NetworkName::TestnetV0 => SvmProgram::::from_str(&primary_bytecode)?.to_checksum().iter().join(", "), + NetworkName::CanaryV0 => SvmProgram::::from_str(&primary_bytecode)?.to_checksum().iter().join(", "), }; tracing::info!(" {} statements before dead code elimination.", compiler.statements_before_dce); @@ -249,5 +289,44 @@ fn compile_leo_source_directory( tracing::info!(" The program checksum is: '[{checksum}]'."); tracing::info!("✅ Compiled '{program_name}.aleo' into Aleo instructions."); - Ok(bytecode) + + // Print checksums for all additional bytecodes (dependencies). + for Bytecode { program_name: dep_name, bytecode: dep_bytecode } in &compiled_programs.import_bytecodes { + // Compute checksum depending on network. + let dep_checksum: String = match network { + NetworkName::MainnetV0 => SvmProgram::::from_str(dep_bytecode)?.to_checksum().iter().join(", "), + NetworkName::TestnetV0 => SvmProgram::::from_str(dep_bytecode)?.to_checksum().iter().join(", "), + NetworkName::CanaryV0 => SvmProgram::::from_str(dep_bytecode)?.to_checksum().iter().join(", "), + }; + + tracing::info!(" Dependency '{dep_name}.aleo': checksum = '[{dep_checksum}]'"); + } + + Ok(compiled_programs) +} + +/// Compiles a Leo file. Writes and returns the compiled bytecode. +fn parse_leo_source_directory( + entry_file_path: &Path, + source_directory: &Path, + program_name: Symbol, + handler: &Handler, + node_builder: &Rc, + options: BuildOptions, + network: NetworkName, +) -> Result { + // Create a new instance of the Leo compiler. + let mut compiler = Compiler::new( + Some(program_name.to_string()), + false, + handler.clone(), + Rc::clone(node_builder), + std::path::PathBuf::default(), + Some(options.into()), + IndexMap::new(), + network, + ); + + // Compile the Leo program into Aleo instructions. + compiler.parse_from_directory(entry_file_path, source_directory) } diff --git a/tests/expectations/cli/local_aleo_dependency/COMMANDS b/tests/expectations/cli/local_aleo_dependency/COMMANDS new file mode 100755 index 00000000000..f4c781171ae --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/COMMANDS @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +LEO_BIN=${1} + +${LEO_BIN} build diff --git a/tests/expectations/cli/local_aleo_dependency/STDERR b/tests/expectations/cli/local_aleo_dependency/STDERR new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/expectations/cli/local_aleo_dependency/STDOUT b/tests/expectations/cli/local_aleo_dependency/STDOUT new file mode 100644 index 00000000000..0a86caef5e4 --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/STDOUT @@ -0,0 +1,6 @@ +⚠️ No network specified, defaulting to 'testnet'. +⚠️ No endpoint specified, defaulting to 'https://api.explorer.provable.com/v1'. + Leo 2 statements before dead code elimination. + Leo 2 statements after dead code elimination. + Leo The program checksum is: '[100u8, 207u8, 87u8, 107u8, 187u8, 153u8, 95u8, 22u8, 31u8, 75u8, 125u8, 95u8, 119u8, 144u8, 156u8, 19u8, 111u8, 156u8, 213u8, 202u8, 101u8, 112u8, 208u8, 87u8, 81u8, 218u8, 221u8, 118u8, 35u8, 125u8, 183u8, 157u8]'. + Leo ✅ Compiled 'complex.aleo' into Aleo instructions. diff --git a/tests/expectations/cli/local_aleo_dependency/contents/.gitignore b/tests/expectations/cli/local_aleo_dependency/contents/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/contents/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/expectations/cli/local_aleo_dependency/contents/build/imports/simple.aleo b/tests/expectations/cli/local_aleo_dependency/contents/build/imports/simple.aleo new file mode 100644 index 00000000000..55ec6ae3502 --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/contents/build/imports/simple.aleo @@ -0,0 +1,10 @@ +program simple.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/local_aleo_dependency/contents/build/main.aleo b/tests/expectations/cli/local_aleo_dependency/contents/build/main.aleo new file mode 100644 index 00000000000..da28bd6c16a --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/contents/build/main.aleo @@ -0,0 +1,11 @@ +import simple.aleo; +program complex.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call simple.aleo/main r0 r1 into r2; + output r2 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/local_aleo_dependency/contents/build/program.json b/tests/expectations/cli/local_aleo_dependency/contents/build/program.json new file mode 100644 index 00000000000..efd80a65169 --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/contents/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "complex.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "3.3.1", + "dependencies": null, + "dev_dependencies": null +} diff --git a/tests/expectations/cli/local_aleo_dependency/contents/program.json b/tests/expectations/cli/local_aleo_dependency/contents/program.json new file mode 100644 index 00000000000..5ed246a691f --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/contents/program.json @@ -0,0 +1,16 @@ +{ + "program": "complex.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.3.1", + "dependencies": [ + { + "name": "simple.aleo", + "location": "local", + "path": "simple.aleo", + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/tests/expectations/cli/local_aleo_dependency/contents/simple.aleo b/tests/expectations/cli/local_aleo_dependency/contents/simple.aleo new file mode 100644 index 00000000000..55ec6ae3502 --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/contents/simple.aleo @@ -0,0 +1,10 @@ +program simple.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/local_aleo_dependency/contents/src/main.leo b/tests/expectations/cli/local_aleo_dependency/contents/src/main.leo new file mode 100644 index 00000000000..15a4d510b68 --- /dev/null +++ b/tests/expectations/cli/local_aleo_dependency/contents/src/main.leo @@ -0,0 +1,11 @@ +// The 'simple' program. +import simple.aleo; + +program complex.aleo { + @noupgrade + async constructor() {} + + transition main(public a: u32, b: u32) -> u32 { + return simple.aleo/main(a, b); + } +} diff --git a/tests/expectations/cli/multiple_leo_deps/COMMANDS b/tests/expectations/cli/multiple_leo_deps/COMMANDS new file mode 100755 index 00000000000..47525daf034 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/COMMANDS @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +LEO_BIN=${1} + +cd parent || exit 1 +$LEO_BIN build diff --git a/tests/expectations/cli/multiple_leo_deps/STDERR b/tests/expectations/cli/multiple_leo_deps/STDERR new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/expectations/cli/multiple_leo_deps/STDOUT b/tests/expectations/cli/multiple_leo_deps/STDOUT new file mode 100644 index 00000000000..d600509219b --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/STDOUT @@ -0,0 +1,9 @@ +⚠️ No network specified, defaulting to 'testnet'. +⚠️ No endpoint specified, defaulting to 'https://api.explorer.provable.com/v1'. + Leo 9 statements before dead code elimination. + Leo 9 statements after dead code elimination. + Leo The program checksum is: '[215u8, 203u8, 66u8, 86u8, 222u8, 65u8, 57u8, 252u8, 117u8, 231u8, 155u8, 25u8, 45u8, 60u8, 202u8, 131u8, 149u8, 90u8, 30u8, 208u8, 156u8, 22u8, 227u8, 204u8, 30u8, 127u8, 166u8, 190u8, 70u8, 12u8, 210u8, 133u8]'. + Leo ✅ Compiled 'parent.aleo' into Aleo instructions. + Leo Dependency 'grandchild.aleo': checksum = '[9u8, 161u8, 225u8, 109u8, 140u8, 240u8, 34u8, 110u8, 197u8, 113u8, 9u8, 64u8, 14u8, 115u8, 105u8, 222u8, 56u8, 193u8, 21u8, 198u8, 195u8, 22u8, 18u8, 168u8, 64u8, 22u8, 193u8, 208u8, 158u8, 7u8, 94u8, 0u8]' + Leo Dependency 'child1.aleo': checksum = '[117u8, 228u8, 47u8, 107u8, 194u8, 194u8, 148u8, 143u8, 21u8, 99u8, 243u8, 112u8, 125u8, 105u8, 203u8, 69u8, 238u8, 73u8, 77u8, 220u8, 25u8, 81u8, 154u8, 247u8, 197u8, 230u8, 203u8, 180u8, 234u8, 50u8, 73u8, 16u8]' + Leo Dependency 'child2.aleo': checksum = '[198u8, 70u8, 203u8, 134u8, 248u8, 126u8, 166u8, 195u8, 11u8, 174u8, 43u8, 87u8, 169u8, 255u8, 198u8, 41u8, 185u8, 18u8, 154u8, 42u8, 108u8, 95u8, 21u8, 225u8, 85u8, 180u8, 75u8, 105u8, 197u8, 145u8, 148u8, 8u8]' diff --git a/tests/expectations/cli/multiple_leo_deps/contents/.gitignore b/tests/expectations/cli/multiple_leo_deps/contents/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/expectations/cli/multiple_leo_deps/contents/child1/.gitignore b/tests/expectations/cli/multiple_leo_deps/contents/child1/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/child1/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/expectations/cli/multiple_leo_deps/contents/child1/program.json b/tests/expectations/cli/multiple_leo_deps/contents/child1/program.json new file mode 100644 index 00000000000..4e35408121e --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/child1/program.json @@ -0,0 +1,16 @@ +{ + "program": "child1.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.3.1", + "dependencies": [ + { + "name": "grandchild.aleo", + "location": "local", + "path": "../grandchild", + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/tests/expectations/cli/multiple_leo_deps/contents/child1/src/main.leo b/tests/expectations/cli/multiple_leo_deps/contents/child1/src/main.leo new file mode 100644 index 00000000000..b8d28d6b751 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/child1/src/main.leo @@ -0,0 +1,15 @@ +import grandchild.aleo; + +program child1.aleo { + record R { + owner: address, + f1: field + } + + transition main(b: u32) -> u32 { + return grandchild.aleo/main(b); + } + + @noupgrade + async constructor() {} +} diff --git a/tests/expectations/cli/multiple_leo_deps/contents/child2/.gitignore b/tests/expectations/cli/multiple_leo_deps/contents/child2/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/child2/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/expectations/cli/multiple_leo_deps/contents/child2/program.json b/tests/expectations/cli/multiple_leo_deps/contents/child2/program.json new file mode 100644 index 00000000000..be91093ce05 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/child2/program.json @@ -0,0 +1,16 @@ +{ + "program": "child2.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.3.1", + "dependencies": [ + { + "name": "grandchild.aleo", + "location": "local", + "path": "../grandchild", + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/tests/expectations/cli/multiple_leo_deps/contents/child2/src/main.leo b/tests/expectations/cli/multiple_leo_deps/contents/child2/src/main.leo new file mode 100644 index 00000000000..e9dc101df1d --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/child2/src/main.leo @@ -0,0 +1,15 @@ +import grandchild.aleo; + +program child2.aleo { + record R { + owner: address, + f2: field + } + + transition main(b: u32) -> u32 { + return grandchild.aleo/main(b); + } + + @noupgrade + async constructor() {} +} diff --git a/tests/expectations/cli/multiple_leo_deps/contents/grandchild/.gitignore b/tests/expectations/cli/multiple_leo_deps/contents/grandchild/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/grandchild/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/expectations/cli/multiple_leo_deps/contents/grandchild/program.json b/tests/expectations/cli/multiple_leo_deps/contents/grandchild/program.json new file mode 100644 index 00000000000..96cc4e28430 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/grandchild/program.json @@ -0,0 +1,8 @@ +{ + "program": "grandchild.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "dependencies": null, + "dev_dependencies": null +} diff --git a/tests/expectations/cli/multiple_leo_deps/contents/grandchild/src/main.leo b/tests/expectations/cli/multiple_leo_deps/contents/grandchild/src/main.leo new file mode 100644 index 00000000000..60c071375bb --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/grandchild/src/main.leo @@ -0,0 +1,12 @@ +program grandchild.aleo { + record R { + owner: address, + } + + transition main(b: u32) -> u32 { + return b; + } + + @noupgrade + async constructor() {} +} diff --git a/tests/expectations/cli/multiple_leo_deps/contents/parent/.gitignore b/tests/expectations/cli/multiple_leo_deps/contents/parent/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/parent/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/child1.aleo b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/child1.aleo new file mode 100644 index 00000000000..30c3eb0d3ab --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/child1.aleo @@ -0,0 +1,10 @@ +import grandchild.aleo; +program child1.aleo; + +function main: + input r0 as u32.private; + call grandchild.aleo/main r0 into r1; + output r1 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/child2.aleo b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/child2.aleo new file mode 100644 index 00000000000..cbf3f34c2c2 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/child2.aleo @@ -0,0 +1,10 @@ +import grandchild.aleo; +program child2.aleo; + +function main: + input r0 as u32.private; + call grandchild.aleo/main r0 into r1; + output r1 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/grandchild.aleo b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/grandchild.aleo new file mode 100644 index 00000000000..bcf4efcc908 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/imports/grandchild.aleo @@ -0,0 +1,8 @@ +program grandchild.aleo; + +function main: + input r0 as u32.private; + output r0 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/multiple_leo_deps/contents/parent/build/main.aleo b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/main.aleo new file mode 100644 index 00000000000..a136b21708c --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/main.aleo @@ -0,0 +1,14 @@ +import grandchild.aleo; +import child1.aleo; +import child2.aleo; +program parent.aleo; + +function main: + input r0 as u32.private; + call child1.aleo/main r0 into r1; + call child2.aleo/main r0 into r2; + add r1 r2 into r3; + output r3 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/multiple_leo_deps/contents/parent/build/program.json b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/program.json new file mode 100644 index 00000000000..072da42b45d --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/parent/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "parent.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "3.3.1", + "dependencies": null, + "dev_dependencies": null +} diff --git a/tests/expectations/cli/multiple_leo_deps/contents/parent/program.json b/tests/expectations/cli/multiple_leo_deps/contents/parent/program.json new file mode 100644 index 00000000000..de9152677b2 --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/parent/program.json @@ -0,0 +1,22 @@ +{ + "program": "parent.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.3.1", + "dependencies": [ + { + "name": "child1.aleo", + "location": "local", + "path": "../child1", + "edition": null + }, + { + "name": "child2.aleo", + "location": "local", + "path": "../child2", + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/tests/expectations/cli/multiple_leo_deps/contents/parent/src/main.leo b/tests/expectations/cli/multiple_leo_deps/contents/parent/src/main.leo new file mode 100644 index 00000000000..36b3bf7876a --- /dev/null +++ b/tests/expectations/cli/multiple_leo_deps/contents/parent/src/main.leo @@ -0,0 +1,11 @@ +import child1.aleo; +import child2.aleo; + +program parent.aleo { + transition main(b: u32) -> u32 { + return child1.aleo/main(b) + child2.aleo/main(b); + } + + @noupgrade + async constructor() {} +} diff --git a/tests/expectations/cli/program_name_mismatch/contents/program.json b/tests/expectations/cli/program_name_mismatch/contents/program.json index a76eba3a335..e0d5a628f8f 100644 --- a/tests/expectations/cli/program_name_mismatch/contents/program.json +++ b/tests/expectations/cli/program_name_mismatch/contents/program.json @@ -4,8 +4,5 @@ "description": "", "license": "MIT", "dependencies": null, - "dev_dependencies": null, - "upgrade": { - "mode": "noupgrade" - } + "dev_dependencies": null } diff --git a/tests/expectations/cli/test_deploy/contents/program.json b/tests/expectations/cli/test_deploy/contents/program.json index f6b7caf5c08..93f916db11e 100644 --- a/tests/expectations/cli/test_deploy/contents/program.json +++ b/tests/expectations/cli/test_deploy/contents/program.json @@ -4,8 +4,5 @@ "description": "", "license": "MIT", "dependencies": null, - "dev_dependencies": null, - "upgrade": { - "mode": "noupgrade" - } + "dev_dependencies": null } diff --git a/tests/expectations/cli/test_simple_build/contents/program.json b/tests/expectations/cli/test_simple_build/contents/program.json index f6b7caf5c08..93f916db11e 100644 --- a/tests/expectations/cli/test_simple_build/contents/program.json +++ b/tests/expectations/cli/test_simple_build/contents/program.json @@ -4,8 +4,5 @@ "description": "", "license": "MIT", "dependencies": null, - "dev_dependencies": null, - "upgrade": { - "mode": "noupgrade" - } + "dev_dependencies": null } diff --git a/tests/expectations/cli/test_simple_test/COMMANDS b/tests/expectations/cli/test_simple_test/COMMANDS new file mode 100755 index 00000000000..2ee615b7deb --- /dev/null +++ b/tests/expectations/cli/test_simple_test/COMMANDS @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +LEO_BIN=${1} + +${LEO_BIN} test diff --git a/tests/expectations/cli/test_simple_test/STDERR b/tests/expectations/cli/test_simple_test/STDERR new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/expectations/cli/test_simple_test/STDOUT b/tests/expectations/cli/test_simple_test/STDOUT new file mode 100644 index 00000000000..95e063fa132 --- /dev/null +++ b/tests/expectations/cli/test_simple_test/STDOUT @@ -0,0 +1,15 @@ +⚠️ No network specified, defaulting to 'testnet'. +⚠️ No endpoint specified, defaulting to 'https://api.explorer.provable.com/v1'. + Leo 2 statements before dead code elimination. + Leo 2 statements after dead code elimination. + Leo The program checksum is: '[95u8, 99u8, 136u8, 243u8, 82u8, 169u8, 200u8, 72u8, 133u8, 227u8, 122u8, 161u8, 195u8, 178u8, 69u8, 37u8, 167u8, 114u8, 104u8, 192u8, 161u8, 175u8, 195u8, 180u8, 120u8, 4u8, 192u8, 16u8, 86u8, 199u8, 76u8, 235u8]'. + Leo ✅ Compiled 'some_sample_leo_program.aleo' into Aleo instructions. + Leo 6 statements before dead code elimination. + Leo 6 statements after dead code elimination. + Leo The program checksum is: '[74u8, 114u8, 9u8, 196u8, 119u8, 213u8, 43u8, 122u8, 92u8, 155u8, 60u8, 120u8, 169u8, 0u8, 41u8, 92u8, 14u8, 87u8, 204u8, 10u8, 34u8, 79u8, 168u8, 242u8, 102u8, 138u8, 132u8, 112u8, 157u8, 113u8, 231u8, 17u8]'. + Leo ✅ Compiled 'test_some_sample_leo_program.aleo' into Aleo instructions. + Leo Dependency 'some_sample_leo_program.aleo': checksum = '[95u8, 99u8, 136u8, 243u8, 82u8, 169u8, 200u8, 72u8, 133u8, 227u8, 122u8, 161u8, 195u8, 178u8, 69u8, 37u8, 167u8, 114u8, 104u8, 192u8, 161u8, 175u8, 195u8, 180u8, 120u8, 4u8, 192u8, 16u8, 86u8, 199u8, 76u8, 235u8]' + Leo Loading the ledger from storage... +2 / 2 tests passed. +PASSED: test_some_sample_leo_program.aleo/test_it +PASSED: test_some_sample_leo_program.aleo/do_nothing diff --git a/tests/expectations/cli/test_simple_test/contents/.gitignore b/tests/expectations/cli/test_simple_test/contents/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/expectations/cli/test_simple_test/contents/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/expectations/cli/test_simple_test/contents/build/imports/some_sample_leo_program.aleo b/tests/expectations/cli/test_simple_test/contents/build/imports/some_sample_leo_program.aleo new file mode 100644 index 00000000000..4cc28b0a97d --- /dev/null +++ b/tests/expectations/cli/test_simple_test/contents/build/imports/some_sample_leo_program.aleo @@ -0,0 +1,10 @@ +program some_sample_leo_program.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/test_simple_test/contents/build/imports/test_some_sample_leo_program.aleo b/tests/expectations/cli/test_simple_test/contents/build/imports/test_some_sample_leo_program.aleo new file mode 100644 index 00000000000..d6f8eb2cca2 --- /dev/null +++ b/tests/expectations/cli/test_simple_test/contents/build/imports/test_some_sample_leo_program.aleo @@ -0,0 +1,9 @@ +import some_sample_leo_program.aleo; +program test_some_sample_leo_program.aleo; + +function do_nothing: + call some_sample_leo_program.aleo/main 2u32 3u32 into r0; + assert.eq r0 3u32; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/test_simple_test/contents/build/main.aleo b/tests/expectations/cli/test_simple_test/contents/build/main.aleo new file mode 100644 index 00000000000..4cc28b0a97d --- /dev/null +++ b/tests/expectations/cli/test_simple_test/contents/build/main.aleo @@ -0,0 +1,10 @@ +program some_sample_leo_program.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/cli/test_simple_test/contents/build/program.json b/tests/expectations/cli/test_simple_test/contents/build/program.json new file mode 100644 index 00000000000..a847ce20759 --- /dev/null +++ b/tests/expectations/cli/test_simple_test/contents/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "some_sample_leo_program.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "3.3.1", + "dependencies": null, + "dev_dependencies": null +} diff --git a/tests/expectations/cli/test_simple_test/contents/program.json b/tests/expectations/cli/test_simple_test/contents/program.json new file mode 100644 index 00000000000..93f916db11e --- /dev/null +++ b/tests/expectations/cli/test_simple_test/contents/program.json @@ -0,0 +1,8 @@ +{ + "program": "some_sample_leo_program.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "dependencies": null, + "dev_dependencies": null +} diff --git a/tests/expectations/cli/test_simple_test/contents/src/main.leo b/tests/expectations/cli/test_simple_test/contents/src/main.leo new file mode 100644 index 00000000000..40acdafaa51 --- /dev/null +++ b/tests/expectations/cli/test_simple_test/contents/src/main.leo @@ -0,0 +1,10 @@ +// The 'some_sample_leo_program' program. +program some_sample_leo_program.aleo { + transition main(public a: u32, b: u32) -> u32 { + let c: u32 = a + b; + return c; + } + + @noupgrade + async constructor() {} +} diff --git a/tests/expectations/cli/test_simple_test/contents/tests/test_some_sample_leo_program.leo b/tests/expectations/cli/test_simple_test/contents/tests/test_some_sample_leo_program.leo new file mode 100644 index 00000000000..00528592861 --- /dev/null +++ b/tests/expectations/cli/test_simple_test/contents/tests/test_some_sample_leo_program.leo @@ -0,0 +1,19 @@ +// The 'test_some_sample_leo_program' test program. +import some_sample_leo_program.aleo; +program test_some_sample_leo_program.aleo { + @test + script test_it() { + let result: u32 = some_sample_leo_program.aleo/main(1u32, 2u32); + assert_eq(result, 3u32); + } + + @test + @should_fail + transition do_nothing() { + let result: u32 = some_sample_leo_program.aleo/main(2u32, 3u32); + assert_eq(result, 3u32); + } + + @noupgrade + async constructor() {} +} diff --git a/tests/expectations/compiler/async_blocks/future_in_tuple_check_fail.out b/tests/expectations/compiler/async_blocks/future_in_tuple_check_fail.out index 4123123208b..aa007a0ae03 100644 --- a/tests/expectations/compiler/async_blocks/future_in_tuple_check_fail.out +++ b/tests/expectations/compiler/async_blocks/future_in_tuple_check_fail.out @@ -1,7 +1,7 @@ Error [ETYC0372104]: Not all futures were consumed: result2 - --> compiler-test:9:27 + --> compiler-test:9:33 | 9 | return (result.0, async { result.1.await(); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ | = Make sure all futures are consumed exactly once. Consume by passing to an async function call or async block. diff --git a/tests/expectations/compiler/async_blocks/future_not_all_passed_to_async_block_fail.out b/tests/expectations/compiler/async_blocks/future_not_all_passed_to_async_block_fail.out index 0250f9e7433..d8e50261bb7 100644 --- a/tests/expectations/compiler/async_blocks/future_not_all_passed_to_async_block_fail.out +++ b/tests/expectations/compiler/async_blocks/future_not_all_passed_to_async_block_fail.out @@ -1,8 +1,8 @@ Error [ETYC0372104]: Not all futures were consumed: f1 - --> compiler-test:16:16 + --> compiler-test:16:22 | 16 | return async { - | ^^^^^^^ + | ^ 17 | f0.await(); | ^^^^^^^^^^^^ 18 | f3.await(); diff --git a/tests/expectations/compiler/async_blocks/nested.out b/tests/expectations/compiler/async_blocks/nested.out index 77b5ba2a3b0..4ebf7e72dc5 100644 --- a/tests/expectations/compiler/async_blocks/nested.out +++ b/tests/expectations/compiler/async_blocks/nested.out @@ -1,8 +1,8 @@ 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:14:26 + --> compiler-test:14:32 | 14 | let f3: Future = async { - | ^^^^^^^ + | ^ 15 | if b == 1u32 { | ^^^^^^^^^^^^^^ 16 | Future::await(f); @@ -90,8 +90,8 @@ finalize main: add r1[0u32] r2[0u32] into r5; set r5 into ayo[1u32]; // --- Next Program --- // -import test_dep.aleo; import test.aleo; +import test_dep.aleo; program wrapper.aleo; function main: diff --git a/tests/expectations/compiler/async_blocks/partial_type_specification.out b/tests/expectations/compiler/async_blocks/partial_type_specification.out index d523d747d70..91e35f585b4 100644 --- a/tests/expectations/compiler/async_blocks/partial_type_specification.out +++ b/tests/expectations/compiler/async_blocks/partial_type_specification.out @@ -1,8 +1,8 @@ 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:14:26 + --> compiler-test:14:32 | 14 | let f3: Future = async { - | ^^^^^^^ + | ^ 15 | // f.await(); | ^^^^^^^^^^^^^ 16 | if b == 1u32 { @@ -104,8 +104,8 @@ finalize main: add r1[0u32] r2[0u32] into r5; set r5 into ayo[1u32]; // --- Next Program --- // -import test_dep.aleo; import test.aleo; +import test_dep.aleo; program wrapper.aleo; function main: diff --git a/tests/expectations/compiler/const_generics/external_generic_struct.out b/tests/expectations/compiler/const_generics/external_generic_struct.out index 9e8f937d508..2842ba6d96e 100644 --- a/tests/expectations/compiler/const_generics/external_generic_struct.out +++ b/tests/expectations/compiler/const_generics/external_generic_struct.out @@ -26,4 +26,7 @@ struct Bar__DI7sPAg0NJ0: struct Bar__5Qh5JlRc8cY: arr as [u32; 3u32]; +struct Bar__IEGwqtYsUsS: + arr as [u32; 4u32]; + function main: diff --git a/tests/expectations/compiler/const_generics/external_struct_fail.out b/tests/expectations/compiler/const_generics/external_struct_fail.out index f6708500d16..812fea578e6 100644 --- a/tests/expectations/compiler/const_generics/external_struct_fail.out +++ b/tests/expectations/compiler/const_generics/external_struct_fail.out @@ -1,19 +1,7 @@ -Error [ETYC0372146]: unexpected generic const argment for Bar::[4]. +Error [ETYC0372146]: unexpected generic const argment for parent.aleo/Bar::[4]. --> compiler-test:5:24 | 5 | transition main(c: Bar::[4]) { | ^^^ | = If this is an external struct, consider using a resolved non-generic version of it instead. External structs can't be instantiated with const arguments -Error [ETYC0372017]: The type `Bar` is not found in the current scope. - --> compiler-test:5:21 - | - 5 | transition main(c: Bar::[4]) { - | ^^^^^^^^^^^ - | - = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372005]: Unknown struct or record `Bar` - --> compiler-test:6:17 - | - 6 | let b = Bar::[3] { - | ^^^ diff --git a/tests/expectations/compiler/const_generics/resolved_mismatched_fail.out b/tests/expectations/compiler/const_generics/resolved_mismatched_fail.out index d0bc0a719ed..1b19ee934f1 100644 --- a/tests/expectations/compiler/const_generics/resolved_mismatched_fail.out +++ b/tests/expectations/compiler/const_generics/resolved_mismatched_fail.out @@ -1,4 +1,4 @@ -Error [ETYC0372117]: Expected type `Foo::[0u32]` but type `Foo::[1u32]` was found. +Error [ETYC0372117]: Expected type `foo.aleo/Foo::[0u32]` but type `foo.aleo/Foo::[1u32]` was found. --> compiler-test:12:16 | 12 | return Foo::[2 * N + 1] { x: 0 }; diff --git a/tests/expectations/compiler/const_generics/unexpected_const_args_fail.out b/tests/expectations/compiler/const_generics/unexpected_const_args_fail.out index 641f60d962b..55b693b534a 100644 --- a/tests/expectations/compiler/const_generics/unexpected_const_args_fail.out +++ b/tests/expectations/compiler/const_generics/unexpected_const_args_fail.out @@ -1,11 +1,11 @@ -Error [ETYC0372146]: unexpected generic const argment for M::[6]. +Error [ETYC0372146]: unexpected generic const argment for test.aleo/M::[6]. --> compiler-test:7:12 | 7 | g: M::[6], | ^ | = If this is an external struct, consider using a resolved non-generic version of it instead. External structs can't be instantiated with const arguments -Error [ETYC0372146]: unexpected generic const argment for baz::[5]. +Error [ETYC0372146]: unexpected generic const argment for test.aleo/baz::[5]. --> compiler-test:8:12 | 8 | f: baz::[5], @@ -26,7 +26,7 @@ Error [ETYC0372017]: The type `baz` is not found in the current scope. | ^^^^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372146]: unexpected generic const argment for x::[5]. +Error [ETYC0372146]: unexpected generic const argment for test.aleo/x::[5]. --> compiler-test:13:16 | 13 | let y: x::[5] = [3]; diff --git a/tests/expectations/compiler/expression/cast_fail.out b/tests/expectations/compiler/expression/cast_fail.out index 64c43423a80..8a90bb81444 100644 --- a/tests/expectations/compiler/expression/cast_fail.out +++ b/tests/expectations/compiler/expression/cast_fail.out @@ -8,7 +8,7 @@ Error [ETYC0372117]: Expected an integer, bool, field, group, scalar, or address | 12 | let b: string = a as string; | ^^^^^^^^^^^ -Error [ETYC0372117]: Expected an integer, bool, field, group, scalar, or address but type `Foo` was found. +Error [ETYC0372117]: Expected an integer, bool, field, group, scalar, or address but type `test.aleo/Foo` was found. --> compiler-test:15:24 | 15 | let d: field = c as field; diff --git a/tests/expectations/compiler/finalize/get_or_incorrect_type_fail.out b/tests/expectations/compiler/finalize/get_or_incorrect_type_fail.out index 6d6b020d244..6a3bbd06b00 100644 --- a/tests/expectations/compiler/finalize/get_or_incorrect_type_fail.out +++ b/tests/expectations/compiler/finalize/get_or_incorrect_type_fail.out @@ -1,9 +1,9 @@ -Error [ETYC0372117]: Expected type `Token` but type `u128` was found. +Error [ETYC0372117]: Expected type `test.aleo/Token` but type `u128` was found. --> compiler-test:16:43 | 16 | Mapping::get_or_use(tokens, addr, amount); | ^^^^^^ -Error [ETYC0372117]: Expected type `Token` but type `u128` was found. +Error [ETYC0372117]: Expected type `test.aleo/Token` but type `u128` was found. --> compiler-test:17:33 | 17 | tokens.get_or_use(addr, amount); diff --git a/tests/expectations/compiler/finalize/mapping_fail.out b/tests/expectations/compiler/finalize/mapping_fail.out index 0204ae073c6..d124b3a54ea 100644 --- a/tests/expectations/compiler/finalize/mapping_fail.out +++ b/tests/expectations/compiler/finalize/mapping_fail.out @@ -10,7 +10,7 @@ Error [ETYC0372017]: The type `baz` is not found in the current scope. | ^^^^^^^^^^^^^^^^^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372017]: The type `baz` is not found in the current scope. +Error [ETYC0372017]: The type `test.aleo/baz` is not found in the current scope. --> compiler-test:5:5 | 5 | mapping floo: baz => u8; @@ -24,7 +24,7 @@ Error [ETYC0372017]: The type `foo` is not found in the current scope. | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372017]: The type `foo` is not found in the current scope. +Error [ETYC0372017]: The type `test.aleo/foo` is not found in the current scope. --> compiler-test:7:5 | 7 | mapping floop: foo => foo; @@ -38,7 +38,7 @@ Error [ETYC0372017]: The type `foo` is not found in the current scope. | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372017]: The type `foo` is not found in the current scope. +Error [ETYC0372017]: The type `test.aleo/foo` is not found in the current scope. --> compiler-test:7:5 | 7 | mapping floop: foo => foo; @@ -52,7 +52,7 @@ Error [ETYC0372017]: The type `foo` is not found in the current scope. | ^^^^^^^^^^^^^^^^^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372017]: The type `foo` is not found in the current scope. +Error [ETYC0372017]: The type `test.aleo/foo` is not found in the current scope. --> compiler-test:9:5 | 9 | mapping bar: foo => baz; @@ -66,7 +66,7 @@ Error [ETYC0372017]: The type `baz` is not found in the current scope. | ^^^^^^^^^^^^^^^^^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372017]: The type `baz` is not found in the current scope. +Error [ETYC0372017]: The type `test.aleo/baz` is not found in the current scope. --> compiler-test:9:5 | 9 | mapping bar: foo => baz; diff --git a/tests/expectations/compiler/finalize/set_incorrect_type_fail.out b/tests/expectations/compiler/finalize/set_incorrect_type_fail.out index 4168e249097..908ab02548e 100644 --- a/tests/expectations/compiler/finalize/set_incorrect_type_fail.out +++ b/tests/expectations/compiler/finalize/set_incorrect_type_fail.out @@ -1,9 +1,9 @@ -Error [ETYC0372117]: Expected type `Token` but type `u128` was found. +Error [ETYC0372117]: Expected type `test.aleo/Token` but type `u128` was found. --> compiler-test:16:36 | 16 | Mapping::set(tokens, addr, amount); | ^^^^^^ -Error [ETYC0372117]: Expected type `Token` but type `u128` was found. +Error [ETYC0372117]: Expected type `test.aleo/Token` but type `u128` was found. --> compiler-test:17:26 | 17 | tokens.set(addr, amount); diff --git a/tests/expectations/compiler/function/unknown_parameter_type_fail.out b/tests/expectations/compiler/function/unknown_parameter_type_fail.out index b4dcf913b2e..56f31b19bc8 100644 --- a/tests/expectations/compiler/function/unknown_parameter_type_fail.out +++ b/tests/expectations/compiler/function/unknown_parameter_type_fail.out @@ -12,7 +12,7 @@ Error [ETYC0372017]: The type `Foo` is not found in the current scope. | ^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372117]: Expected type `Foo` but type `u8` was found. +Error [ETYC0372117]: Expected type `test.aleo/Foo` but type `u8` was found. --> compiler-test:8:16 | 8 | return a; diff --git a/tests/expectations/compiler/futures/nested.out b/tests/expectations/compiler/futures/nested.out index 76df3b550b6..d031277a946 100644 --- a/tests/expectations/compiler/futures/nested.out +++ b/tests/expectations/compiler/futures/nested.out @@ -94,8 +94,8 @@ finalize main: add r0[0u32] r1[0u32] into r5; set r5 into ayo[1u32]; // --- Next Program --- // -import test_dep.aleo; import test.aleo; +import test_dep.aleo; program wrapper.aleo; function main: diff --git a/tests/expectations/compiler/futures/partial_type_specification.out b/tests/expectations/compiler/futures/partial_type_specification.out index 3efa5f90df9..b47893d621f 100644 --- a/tests/expectations/compiler/futures/partial_type_specification.out +++ b/tests/expectations/compiler/futures/partial_type_specification.out @@ -104,8 +104,8 @@ finalize main: add r0[0u32] r1[0u32] into r5; set r5 into ayo[1u32]; // --- Next Program --- // -import test_dep.aleo; import test.aleo; +import test_dep.aleo; program wrapper.aleo; function main: diff --git a/tests/expectations/compiler/mappings/modify_external_mapping_fail.out b/tests/expectations/compiler/mappings/modify_external_mapping_fail.out new file mode 100644 index 00000000000..3b7fe6cb116 --- /dev/null +++ b/tests/expectations/compiler/mappings/modify_external_mapping_fail.out @@ -0,0 +1,14 @@ +Error [ETYC0372108]: Cannot use operation `set` on external mappings. + --> compiler-test:11:13 + | + 11 | provider.aleo/balances.set(3u32, 300u32); // ❌ illegal external mutation + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = The only valid operations on external mappings are `contains`, `get`, and `get_or_use`. +Error [ETYC0372108]: Cannot use operation `remove` on external mappings. + --> compiler-test:12:13 + | + 12 | provider.aleo/balances.remove(1u32); // ❌ illegal external mutation + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = The only valid operations on external mappings are `contains`, `get`, and `get_or_use`. diff --git a/tests/expectations/compiler/option/bad_types_fail.out b/tests/expectations/compiler/option/bad_types_fail.out index 200df1e889e..34cf86dc20f 100644 --- a/tests/expectations/compiler/option/bad_types_fail.out +++ b/tests/expectations/compiler/option/bad_types_fail.out @@ -47,7 +47,7 @@ Error [ETYC0372117]: Expected type `u8?` but type `i8` was found. | 51 | let arr: [u8?; 3] = [1u8, none, 3i8]; // ERROR | ^^^ -Error [ETYC0372119]: Received different types `Foo` and `Foo?` for the operation `==`. +Error [ETYC0372119]: Received different types `bad_types.aleo/Foo` and `bad_types.aleo/Foo?` for the operation `==`. --> compiler-test:58:22 | 58 | let result = a == b; // ERROR diff --git a/tests/expectations/compiler/option/input_output_fail.out b/tests/expectations/compiler/option/input_output_fail.out index 14b5fb54f3b..aff388e1953 100644 --- a/tests/expectations/compiler/option/input_output_fail.out +++ b/tests/expectations/compiler/option/input_output_fail.out @@ -12,7 +12,7 @@ Error [ETYC0372164]: The input `y` has type `[u8?; 3]`, which is or contains an | ^^^^^^^^^^^ | = Inputs to `transition`, `async transition`, and `function` definitions cannot be optional or contain optionals. Consider moving the optionality outside the call site. -Error [ETYC0372164]: The input `f` has type `Foo`, which is or contains an optional type and is not allowed as an input to a `transition`, `async transition`, or `function`. +Error [ETYC0372164]: The input `f` has type `test.aleo/Foo`, which is or contains an optional type and is not allowed as an input to a `transition`, `async transition`, or `function`. --> compiler-test:5:41 | 5 | transition foo(x: u8?, y: [u8?; 3], f: Foo) -> (u8?, u8?, Foo) { @@ -33,7 +33,7 @@ Error [ETYC0372165]: This function has an output of type `u8?`, which is or cont | ^^^ | = Outputs of `transition`, `async transition`, and `function` definitions cannot be optional or contain optionals. Consider moving the optionality outside the function call. -Error [ETYC0372165]: This function has an output of type `Foo`, which is or contains an optional type and is not allowed as an output of a `transition`, `async transition`, or `function`. +Error [ETYC0372165]: This function has an output of type `test.aleo/Foo`, which is or contains an optional type and is not allowed as an output of a `transition`, `async transition`, or `function`. --> compiler-test:5:63 | 5 | transition foo(x: u8?, y: [u8?; 3], f: Foo) -> (u8?, u8?, Foo) { @@ -47,7 +47,7 @@ Error [ETYC0372164]: The input `x` has type `u8?`, which is or contains an optio | ^^^^^^ | = Inputs to `transition`, `async transition`, and `function` definitions cannot be optional or contain optionals. Consider moving the optionality outside the call site. -Error [ETYC0372164]: The input `f` has type `Foo`, which is or contains an optional type and is not allowed as an input to a `transition`, `async transition`, or `function`. +Error [ETYC0372164]: The input `f` has type `test.aleo/Foo`, which is or contains an optional type and is not allowed as an input to a `transition`, `async transition`, or `function`. --> compiler-test:9:34 | 9 | async transition bar(x: u8?, f: Foo) { diff --git a/tests/expectations/compiler/option/optional_in_mapping_fail.out b/tests/expectations/compiler/option/optional_in_mapping_fail.out index 8f8bc759154..26df25e7fe7 100644 --- a/tests/expectations/compiler/option/optional_in_mapping_fail.out +++ b/tests/expectations/compiler/option/optional_in_mapping_fail.out @@ -8,7 +8,7 @@ Error [ETYC0372161]: The type `address?` is or contains an optional type which c | 6 | mapping optional_balances: address? => Foo; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Error [ETYC0372161]: The type `Foo` is or contains an optional type which cannot be used as the value in a mapping +Error [ETYC0372161]: The type `st.aleo/Foo` is or contains an optional type which cannot be used as the value in a mapping --> compiler-test:6:5 | 6 | mapping optional_balances: address? => Foo; diff --git a/tests/expectations/compiler/option/optional_record_fail.out b/tests/expectations/compiler/option/optional_record_fail.out index 977922064f6..d271351fd28 100644 --- a/tests/expectations/compiler/option/optional_record_fail.out +++ b/tests/expectations/compiler/option/optional_record_fail.out @@ -1,4 +1,4 @@ -Error [ETYC0372159]: The type `Foo` cannot be wrapped in an optional because it is a record. +Error [ETYC0372159]: The type `optional_record_test.aleo/Foo` cannot be wrapped in an optional because it is a record. --> compiler-test:10:9 | 10 | let f: Foo? = Foo { diff --git a/tests/expectations/compiler/option/record_with_optional_fields_fail.out b/tests/expectations/compiler/option/record_with_optional_fields_fail.out index acb584c4236..da18056b830 100644 --- a/tests/expectations/compiler/option/record_with_optional_fields_fail.out +++ b/tests/expectations/compiler/option/record_with_optional_fields_fail.out @@ -12,7 +12,7 @@ Error [ETYC0372162]: The field `flags` in this record has type `[bool?; 4]`, whi | ^^^^^^^^^^^^^^^^^ | = Records cannot have fields that are optional or contain optionals. Consider moving the optionality outside the record. -Error [ETYC0372162]: The field `inner` in this record has type `InnerStruct`, which is or contains an optional type. +Error [ETYC0372162]: The field `inner` in this record has type `record_with_optional_fields.aleo/InnerStruct`, which is or contains an optional type. --> compiler-test:20:9 | 20 | inner: InnerStruct, // ERROR: contains optional via nested struct diff --git a/tests/expectations/compiler/records/return_record_instead_of_unit_fail.out b/tests/expectations/compiler/records/return_record_instead_of_unit_fail.out index 746553e35d2..4cf76c4980b 100644 --- a/tests/expectations/compiler/records/return_record_instead_of_unit_fail.out +++ b/tests/expectations/compiler/records/return_record_instead_of_unit_fail.out @@ -1,4 +1,4 @@ -Error [ETYC0372117]: Expected type `()` but type `test_credits` was found. +Error [ETYC0372117]: Expected type `()` but type `test.aleo/test_credits` was found. --> compiler-test:9:16 | 9 | return test_credits { diff --git a/tests/expectations/compiler/records/same_name_diff_type_fail.out b/tests/expectations/compiler/records/same_name_diff_type_fail.out index 78aa7581e25..566e9acab1f 100644 --- a/tests/expectations/compiler/records/same_name_diff_type_fail.out +++ b/tests/expectations/compiler/records/same_name_diff_type_fail.out @@ -1,4 +1,4 @@ -Error [ETYC0372119]: Received different types `R` and `child.aleo/R` for the operation `==`. +Error [ETYC0372119]: Received different types `parent.aleo/R` and `child.aleo/R` for the operation `==`. --> compiler-test:16:16 | 16 | return r1 == child.aleo/create(); diff --git a/tests/expectations/compiler/signature/signature_with_wrong_types_fail.out b/tests/expectations/compiler/signature/signature_with_wrong_types_fail.out index 574f422aa89..6b56bbd057b 100644 --- a/tests/expectations/compiler/signature/signature_with_wrong_types_fail.out +++ b/tests/expectations/compiler/signature/signature_with_wrong_types_fail.out @@ -18,12 +18,12 @@ Error [ETYC0372117]: Expected type `signature` but type `address` was found. | 16 | let first: bool = signature::verify(a, v, s); | ^ -Error [ETYC0372117]: Expected type `address` but type `foo` was found. +Error [ETYC0372117]: Expected type `address` but type `test.aleo/foo` was found. --> compiler-test:16:48 | 16 | let first: bool = signature::verify(a, v, s); | ^ -Error [ETYC0372117]: Expected type `address` but type `foo` was found. +Error [ETYC0372117]: Expected type `address` but type `test.aleo/foo` was found. --> compiler-test:17:37 | 17 | let second: bool = s.verify(v, a); diff --git a/tests/expectations/compiler/statements/unknown_type_in_definition_fail.out b/tests/expectations/compiler/statements/unknown_type_in_definition_fail.out index 4392d1ad331..8fe1fafe47c 100644 --- a/tests/expectations/compiler/statements/unknown_type_in_definition_fail.out +++ b/tests/expectations/compiler/statements/unknown_type_in_definition_fail.out @@ -5,7 +5,7 @@ Error [ETYC0372017]: The type `Foo` is not found in the current scope. | ^^^^^^^^^^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` -Error [ETYC0372117]: Expected type `Foo` but type `u8` was found. +Error [ETYC0372117]: Expected type `test.aleo/Foo` but type `u8` was found. --> compiler-test:4:19 | 4 | let b: Foo = 1u8; diff --git a/tests/expectations/compiler/storage/external_storage.out b/tests/expectations/compiler/storage/external_storage.out new file mode 100644 index 00000000000..492f37c4954 --- /dev/null +++ b/tests/expectations/compiler/storage/external_storage.out @@ -0,0 +1,903 @@ +program child.aleo; + +struct Point: + x as field; + y as field; + +struct Stats: + values as [u32; 3u32]; + active as boolean; + +mapping flag__: + key as boolean.public; + value as boolean.public; + +mapping scalar_val__: + key as boolean.public; + value as scalar.public; + +mapping field_val__: + key as boolean.public; + value as field.public; + +mapping group_val__: + key as boolean.public; + value as group.public; + +mapping point__: + key as boolean.public; + value as Point.public; + +mapping points__: + key as boolean.public; + value as [Point; 2u32].public; + +mapping stats__: + key as boolean.public; + value as Stats.public; + +mapping arr_u32__: + key as boolean.public; + value as [u32; 3u32].public; + +mapping arr_bool__: + key as boolean.public; + value as [boolean; 2u32].public; + +mapping nested__: + key as boolean.public; + value as [[u8; 2u32]; 2u32].public; + +mapping counter__: + key as boolean.public; + value as u8.public; + +mapping vec__: + key as u32.public; + value as u8.public; + +mapping vec__len__: + key as boolean.public; + value as u32.public; + +function initialize: + async initialize into r0; + output r0 as child.aleo/initialize.future; + +finalize initialize: + set true into flag__[false]; + set 1scalar into scalar_val__[false]; + set 42field into field_val__[false]; + set 0group into group_val__[false]; + cast 1field 2field into r0 as Point; + set r0 into point__[false]; + cast 10field 20field into r1 as Point; + cast 30field 40field into r2 as Point; + cast r1 r2 into r3 as [Point; 2u32]; + set r3 into points__[false]; + cast 5u32 10u32 15u32 into r4 as [u32; 3u32]; + cast r4 true into r5 as Stats; + set r5 into stats__[false]; + cast 7u32 8u32 9u32 into r6 as [u32; 3u32]; + set r6 into arr_u32__[false]; + cast true false into r7 as [boolean; 2u32]; + set r7 into arr_bool__[false]; + cast 1u8 2u8 into r8 as [u8; 2u32]; + cast 3u8 4u8 into r9 as [u8; 2u32]; + cast r8 r9 into r10 as [[u8; 2u32]; 2u32]; + set r10 into nested__[false]; + set 9u8 into counter__[false]; + get.or_use vec__len__[false] 0u32 into r11; + add r11 1u32 into r12; + set r12 into vec__len__[false]; + set 50u8 into vec__[r11]; + +function wipe: + async wipe into r0; + output r0 as child.aleo/wipe.future; + +finalize wipe: + remove flag__[false]; + remove scalar_val__[false]; + remove field_val__[false]; + remove group_val__[false]; + remove point__[false]; + remove points__[false]; + remove stats__[false]; + remove arr_u32__[false]; + remove arr_bool__[false]; + remove nested__[false]; + remove counter__[false]; + set 0u32 into vec__len__[false]; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +import child.aleo; +program test.aleo; + +struct Optional__DbbQYuLaHdi: + is_some as boolean; + val as boolean; + +struct Optional__JYP65FYVVJA: + is_some as boolean; + val as scalar; + +struct Optional__K4XRQPOk9yX: + is_some as boolean; + val as field; + +struct Optional__8aTicnNTOvk: + is_some as boolean; + val as group; + +struct Point: + x as field; + y as field; + +struct Optional__6Uxqmb23WYh: + is_some as boolean; + val as Point; + +struct Optional__BvQiU368h5e: + is_some as boolean; + val as [Point; 2u32]; + +struct Stats: + values as [u32; 3u32]; + active as boolean; + +struct Optional__L4AaGzlaCt7: + is_some as boolean; + val as Stats; + +struct Optional__EJpsIdnRUro: + is_some as boolean; + val as [u32; 3u32]; + +struct Optional__H28y6OQZ8v9: + is_some as boolean; + val as [boolean; 2u32]; + +struct Optional__3Jw8FxjrqtE: + is_some as boolean; + val as [[u8; 2u32]; 2u32]; + +struct Optional__8hhrPm4c3KB: + is_some as boolean; + val as u8; + +function check_initialized: + async check_initialized into r0; + output r0 as test.aleo/check_initialized.future; + +finalize check_initialized: + contains child.aleo/flag__[false] into r0; + get.or_use child.aleo/flag__[false] false into r1; + cast true r1 into r2 as Optional__DbbQYuLaHdi; + cast false false into r3 as Optional__DbbQYuLaHdi; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__DbbQYuLaHdi; + assert.eq r6.is_some true; + contains child.aleo/flag__[false] into r7; + get.or_use child.aleo/flag__[false] false into r8; + cast true r8 into r9 as Optional__DbbQYuLaHdi; + cast false false into r10 as Optional__DbbQYuLaHdi; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__DbbQYuLaHdi; + is.eq r13.val true into r14; + assert.eq r14 true; + contains child.aleo/scalar_val__[false] into r15; + get.or_use child.aleo/scalar_val__[false] 0scalar into r16; + cast true r16 into r17 as Optional__JYP65FYVVJA; + cast false 0scalar into r18 as Optional__JYP65FYVVJA; + ternary r15 r17.is_some r18.is_some into r19; + ternary r15 r17.val r18.val into r20; + cast r19 r20 into r21 as Optional__JYP65FYVVJA; + assert.eq r21.is_some true; + contains child.aleo/scalar_val__[false] into r22; + get.or_use child.aleo/scalar_val__[false] 0scalar into r23; + cast true r23 into r24 as Optional__JYP65FYVVJA; + cast false 0scalar into r25 as Optional__JYP65FYVVJA; + ternary r22 r24.is_some r25.is_some into r26; + ternary r22 r24.val r25.val into r27; + cast r26 r27 into r28 as Optional__JYP65FYVVJA; + is.eq r28.val 1scalar into r29; + assert.eq r29 true; + contains child.aleo/field_val__[false] into r30; + get.or_use child.aleo/field_val__[false] 0field into r31; + cast true r31 into r32 as Optional__K4XRQPOk9yX; + cast false 0field into r33 as Optional__K4XRQPOk9yX; + ternary r30 r32.is_some r33.is_some into r34; + ternary r30 r32.val r33.val into r35; + cast r34 r35 into r36 as Optional__K4XRQPOk9yX; + assert.eq r36.is_some true; + contains child.aleo/field_val__[false] into r37; + get.or_use child.aleo/field_val__[false] 0field into r38; + cast true r38 into r39 as Optional__K4XRQPOk9yX; + cast false 0field into r40 as Optional__K4XRQPOk9yX; + ternary r37 r39.is_some r40.is_some into r41; + ternary r37 r39.val r40.val into r42; + cast r41 r42 into r43 as Optional__K4XRQPOk9yX; + is.eq r43.val 42field into r44; + assert.eq r44 true; + contains child.aleo/group_val__[false] into r45; + get.or_use child.aleo/group_val__[false] 0group into r46; + cast true r46 into r47 as Optional__8aTicnNTOvk; + cast false 0group into r48 as Optional__8aTicnNTOvk; + ternary r45 r47.is_some r48.is_some into r49; + ternary r45 r47.val r48.val into r50; + cast r49 r50 into r51 as Optional__8aTicnNTOvk; + assert.eq r51.is_some true; + contains child.aleo/group_val__[false] into r52; + get.or_use child.aleo/group_val__[false] 0group into r53; + cast true r53 into r54 as Optional__8aTicnNTOvk; + cast false 0group into r55 as Optional__8aTicnNTOvk; + ternary r52 r54.is_some r55.is_some into r56; + ternary r52 r54.val r55.val into r57; + cast r56 r57 into r58 as Optional__8aTicnNTOvk; + is.eq r58.val 0group into r59; + assert.eq r59 true; + contains child.aleo/point__[false] into r60; + cast 0field 0field into r61 as Point; + get.or_use child.aleo/point__[false] r61 into r62; + cast true r62 into r63 as Optional__6Uxqmb23WYh; + cast 0field 0field into r64 as Point; + cast false r64 into r65 as Optional__6Uxqmb23WYh; + ternary r60 r63.val.x r65.val.x into r66; + ternary r60 r63.val.y r65.val.y into r67; + cast r66 r67 into r68 as Point; + ternary r60 r63.is_some r65.is_some into r69; + cast r69 r68 into r70 as Optional__6Uxqmb23WYh; + assert.eq r70.is_some true; + contains child.aleo/point__[false] into r71; + cast 0field 0field into r72 as Point; + get.or_use child.aleo/point__[false] r72 into r73; + cast true r73 into r74 as Optional__6Uxqmb23WYh; + cast 0field 0field into r75 as Point; + cast false r75 into r76 as Optional__6Uxqmb23WYh; + ternary r71 r74.val.x r76.val.x into r77; + ternary r71 r74.val.y r76.val.y into r78; + cast r77 r78 into r79 as Point; + ternary r71 r74.is_some r76.is_some into r80; + cast r80 r79 into r81 as Optional__6Uxqmb23WYh; + is.eq r81.val.x 1field into r82; + assert.eq r82 true; + is.eq r81.val.y 2field into r83; + assert.eq r83 true; + contains child.aleo/points__[false] into r84; + cast 0field 0field into r85 as Point; + cast r85 r85 into r86 as [Point; 2u32]; + get.or_use child.aleo/points__[false] r86 into r87; + cast true r87 into r88 as Optional__BvQiU368h5e; + cast 0field 0field into r89 as Point; + cast r89 r89 into r90 as [Point; 2u32]; + cast false r90 into r91 as Optional__BvQiU368h5e; + ternary r84 r88.val[0u32].x r91.val[0u32].x into r92; + ternary r84 r88.val[0u32].y r91.val[0u32].y into r93; + cast r92 r93 into r94 as Point; + ternary r84 r88.val[1u32].x r91.val[1u32].x into r95; + ternary r84 r88.val[1u32].y r91.val[1u32].y into r96; + cast r95 r96 into r97 as Point; + cast r94 r97 into r98 as [Point; 2u32]; + ternary r84 r88.is_some r91.is_some into r99; + cast r99 r98 into r100 as Optional__BvQiU368h5e; + assert.eq r100.is_some true; + contains child.aleo/points__[false] into r101; + cast 0field 0field into r102 as Point; + cast r102 r102 into r103 as [Point; 2u32]; + get.or_use child.aleo/points__[false] r103 into r104; + cast true r104 into r105 as Optional__BvQiU368h5e; + cast 0field 0field into r106 as Point; + cast r106 r106 into r107 as [Point; 2u32]; + cast false r107 into r108 as Optional__BvQiU368h5e; + ternary r101 r105.val[0u32].x r108.val[0u32].x into r109; + ternary r101 r105.val[0u32].y r108.val[0u32].y into r110; + cast r109 r110 into r111 as Point; + ternary r101 r105.val[1u32].x r108.val[1u32].x into r112; + ternary r101 r105.val[1u32].y r108.val[1u32].y into r113; + cast r112 r113 into r114 as Point; + cast r111 r114 into r115 as [Point; 2u32]; + ternary r101 r105.is_some r108.is_some into r116; + cast r116 r115 into r117 as Optional__BvQiU368h5e; + is.eq r117.val[0u32].x 10field into r118; + assert.eq r118 true; + is.eq r117.val[1u32].y 40field into r119; + assert.eq r119 true; + contains child.aleo/stats__[false] into r120; + cast 0u32 0u32 0u32 into r121 as [u32; 3u32]; + cast r121 false into r122 as Stats; + get.or_use child.aleo/stats__[false] r122 into r123; + cast true r123 into r124 as Optional__L4AaGzlaCt7; + cast r121 false into r125 as Stats; + cast false r125 into r126 as Optional__L4AaGzlaCt7; + ternary r120 r124.val.values[0u32] r126.val.values[0u32] into r127; + ternary r120 r124.val.values[1u32] r126.val.values[1u32] into r128; + ternary r120 r124.val.values[2u32] r126.val.values[2u32] into r129; + cast r127 r128 r129 into r130 as [u32; 3u32]; + ternary r120 r124.val.active r126.val.active into r131; + cast r130 r131 into r132 as Stats; + ternary r120 r124.is_some r126.is_some into r133; + cast r133 r132 into r134 as Optional__L4AaGzlaCt7; + assert.eq r134.is_some true; + contains child.aleo/stats__[false] into r135; + cast r121 false into r136 as Stats; + get.or_use child.aleo/stats__[false] r136 into r137; + cast true r137 into r138 as Optional__L4AaGzlaCt7; + cast r121 false into r139 as Stats; + cast false r139 into r140 as Optional__L4AaGzlaCt7; + ternary r135 r138.val.values[0u32] r140.val.values[0u32] into r141; + ternary r135 r138.val.values[1u32] r140.val.values[1u32] into r142; + ternary r135 r138.val.values[2u32] r140.val.values[2u32] into r143; + cast r141 r142 r143 into r144 as [u32; 3u32]; + ternary r135 r138.val.active r140.val.active into r145; + cast r144 r145 into r146 as Stats; + ternary r135 r138.is_some r140.is_some into r147; + cast r147 r146 into r148 as Optional__L4AaGzlaCt7; + is.eq r148.val.values[1u32] 10u32 into r149; + assert.eq r149 true; + is.eq r148.val.active true into r150; + assert.eq r150 true; + contains child.aleo/arr_u32__[false] into r151; + get.or_use child.aleo/arr_u32__[false] r121 into r152; + cast true r152 into r153 as Optional__EJpsIdnRUro; + cast false r121 into r154 as Optional__EJpsIdnRUro; + ternary r151 r153.val[0u32] r154.val[0u32] into r155; + ternary r151 r153.val[1u32] r154.val[1u32] into r156; + ternary r151 r153.val[2u32] r154.val[2u32] into r157; + cast r155 r156 r157 into r158 as [u32; 3u32]; + ternary r151 r153.is_some r154.is_some into r159; + cast r159 r158 into r160 as Optional__EJpsIdnRUro; + assert.eq r160.is_some true; + contains child.aleo/arr_u32__[false] into r161; + get.or_use child.aleo/arr_u32__[false] r121 into r162; + cast true r162 into r163 as Optional__EJpsIdnRUro; + cast false r121 into r164 as Optional__EJpsIdnRUro; + ternary r161 r163.val[0u32] r164.val[0u32] into r165; + ternary r161 r163.val[1u32] r164.val[1u32] into r166; + ternary r161 r163.val[2u32] r164.val[2u32] into r167; + cast r165 r166 r167 into r168 as [u32; 3u32]; + ternary r161 r163.is_some r164.is_some into r169; + cast r169 r168 into r170 as Optional__EJpsIdnRUro; + is.eq r170.val[2u32] 9u32 into r171; + assert.eq r171 true; + contains child.aleo/arr_bool__[false] into r172; + cast false false into r173 as [boolean; 2u32]; + get.or_use child.aleo/arr_bool__[false] r173 into r174; + cast true r174 into r175 as Optional__H28y6OQZ8v9; + cast false r173 into r176 as Optional__H28y6OQZ8v9; + ternary r172 r175.val[0u32] r176.val[0u32] into r177; + ternary r172 r175.val[1u32] r176.val[1u32] into r178; + cast r177 r178 into r179 as [boolean; 2u32]; + ternary r172 r175.is_some r176.is_some into r180; + cast r180 r179 into r181 as Optional__H28y6OQZ8v9; + assert.eq r181.is_some true; + contains child.aleo/arr_bool__[false] into r182; + get.or_use child.aleo/arr_bool__[false] r173 into r183; + cast true r183 into r184 as Optional__H28y6OQZ8v9; + cast false r173 into r185 as Optional__H28y6OQZ8v9; + ternary r182 r184.val[0u32] r185.val[0u32] into r186; + ternary r182 r184.val[1u32] r185.val[1u32] into r187; + cast r186 r187 into r188 as [boolean; 2u32]; + ternary r182 r184.is_some r185.is_some into r189; + cast r189 r188 into r190 as Optional__H28y6OQZ8v9; + is.eq r190.val[0u32] true into r191; + assert.eq r191 true; + contains child.aleo/nested__[false] into r192; + cast 0u8 0u8 into r193 as [u8; 2u32]; + cast r193 r193 into r194 as [[u8; 2u32]; 2u32]; + get.or_use child.aleo/nested__[false] r194 into r195; + cast true r195 into r196 as Optional__3Jw8FxjrqtE; + cast false r194 into r197 as Optional__3Jw8FxjrqtE; + ternary r192 r196.val[0u32][0u32] r197.val[0u32][0u32] into r198; + ternary r192 r196.val[0u32][1u32] r197.val[0u32][1u32] into r199; + cast r198 r199 into r200 as [u8; 2u32]; + ternary r192 r196.val[1u32][0u32] r197.val[1u32][0u32] into r201; + ternary r192 r196.val[1u32][1u32] r197.val[1u32][1u32] into r202; + cast r201 r202 into r203 as [u8; 2u32]; + cast r200 r203 into r204 as [[u8; 2u32]; 2u32]; + ternary r192 r196.is_some r197.is_some into r205; + cast r205 r204 into r206 as Optional__3Jw8FxjrqtE; + assert.eq r206.is_some true; + contains child.aleo/nested__[false] into r207; + get.or_use child.aleo/nested__[false] r194 into r208; + cast true r208 into r209 as Optional__3Jw8FxjrqtE; + cast false r194 into r210 as Optional__3Jw8FxjrqtE; + ternary r207 r209.val[0u32][0u32] r210.val[0u32][0u32] into r211; + ternary r207 r209.val[0u32][1u32] r210.val[0u32][1u32] into r212; + cast r211 r212 into r213 as [u8; 2u32]; + ternary r207 r209.val[1u32][0u32] r210.val[1u32][0u32] into r214; + ternary r207 r209.val[1u32][1u32] r210.val[1u32][1u32] into r215; + cast r214 r215 into r216 as [u8; 2u32]; + cast r213 r216 into r217 as [[u8; 2u32]; 2u32]; + ternary r207 r209.is_some r210.is_some into r218; + cast r218 r217 into r219 as Optional__3Jw8FxjrqtE; + is.eq r219.val[1u32][1u32] 4u8 into r220; + assert.eq r220 true; + contains child.aleo/counter__[false] into r221; + get.or_use child.aleo/counter__[false] 0u8 into r222; + cast true r222 into r223 as Optional__8hhrPm4c3KB; + cast false 0u8 into r224 as Optional__8hhrPm4c3KB; + ternary r221 r223.is_some r224.is_some into r225; + ternary r221 r223.val r224.val into r226; + cast r225 r226 into r227 as Optional__8hhrPm4c3KB; + assert.eq r227.is_some true; + contains child.aleo/counter__[false] into r228; + get.or_use child.aleo/counter__[false] 0u8 into r229; + cast true r229 into r230 as Optional__8hhrPm4c3KB; + cast false 0u8 into r231 as Optional__8hhrPm4c3KB; + ternary r228 r230.is_some r231.is_some into r232; + ternary r228 r230.val r231.val into r233; + cast r232 r233 into r234 as Optional__8hhrPm4c3KB; + is.eq r234.val 9u8 into r235; + assert.eq r235 true; + get.or_use child.aleo/vec__len__[false] 0u32 into r236; + lt 2u32 r236 into r237; + get.or_use child.aleo/vec__[2u32] 0u8 into r238; + cast true r238 into r239 as Optional__8hhrPm4c3KB; + cast false 0u8 into r240 as Optional__8hhrPm4c3KB; + ternary r237 r239.is_some r240.is_some into r241; + ternary r237 r239.val r240.val into r242; + cast r241 r242 into r243 as Optional__8hhrPm4c3KB; + assert.eq r243.is_some true; + is.eq r243.val 70u8 into r244; + assert.eq r244 true; + +function check_wiped: + async check_wiped into r0; + output r0 as test.aleo/check_wiped.future; + +finalize check_wiped: + contains child.aleo/flag__[false] into r0; + get.or_use child.aleo/flag__[false] false into r1; + cast true r1 into r2 as Optional__DbbQYuLaHdi; + cast false false into r3 as Optional__DbbQYuLaHdi; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__DbbQYuLaHdi; + cast false false into r7 as Optional__DbbQYuLaHdi; + is.eq r6 r7 into r8; + assert.eq r8 true; + contains child.aleo/scalar_val__[false] into r9; + get.or_use child.aleo/scalar_val__[false] 0scalar into r10; + cast true r10 into r11 as Optional__JYP65FYVVJA; + cast false 0scalar into r12 as Optional__JYP65FYVVJA; + ternary r9 r11.is_some r12.is_some into r13; + ternary r9 r11.val r12.val into r14; + cast r13 r14 into r15 as Optional__JYP65FYVVJA; + cast false 0scalar into r16 as Optional__JYP65FYVVJA; + is.eq r15 r16 into r17; + assert.eq r17 true; + contains child.aleo/field_val__[false] into r18; + get.or_use child.aleo/field_val__[false] 0field into r19; + cast true r19 into r20 as Optional__K4XRQPOk9yX; + cast false 0field into r21 as Optional__K4XRQPOk9yX; + ternary r18 r20.is_some r21.is_some into r22; + ternary r18 r20.val r21.val into r23; + cast r22 r23 into r24 as Optional__K4XRQPOk9yX; + cast false 0field into r25 as Optional__K4XRQPOk9yX; + is.eq r24 r25 into r26; + assert.eq r26 true; + contains child.aleo/group_val__[false] into r27; + get.or_use child.aleo/group_val__[false] 0group into r28; + cast true r28 into r29 as Optional__8aTicnNTOvk; + cast false 0group into r30 as Optional__8aTicnNTOvk; + ternary r27 r29.is_some r30.is_some into r31; + ternary r27 r29.val r30.val into r32; + cast r31 r32 into r33 as Optional__8aTicnNTOvk; + cast false 0group into r34 as Optional__8aTicnNTOvk; + is.eq r33 r34 into r35; + assert.eq r35 true; + contains child.aleo/point__[false] into r36; + cast 0field 0field into r37 as Point; + get.or_use child.aleo/point__[false] r37 into r38; + cast true r38 into r39 as Optional__6Uxqmb23WYh; + cast 0field 0field into r40 as Point; + cast false r40 into r41 as Optional__6Uxqmb23WYh; + ternary r36 r39.val.x r41.val.x into r42; + ternary r36 r39.val.y r41.val.y into r43; + cast r42 r43 into r44 as Point; + ternary r36 r39.is_some r41.is_some into r45; + cast r45 r44 into r46 as Optional__6Uxqmb23WYh; + cast 0field 0field into r47 as Point; + cast false r47 into r48 as Optional__6Uxqmb23WYh; + is.eq r46 r48 into r49; + assert.eq r49 true; + contains child.aleo/points__[false] into r50; + cast 0field 0field into r51 as Point; + cast r51 r51 into r52 as [Point; 2u32]; + get.or_use child.aleo/points__[false] r52 into r53; + cast true r53 into r54 as Optional__BvQiU368h5e; + cast 0field 0field into r55 as Point; + cast r55 r55 into r56 as [Point; 2u32]; + cast false r56 into r57 as Optional__BvQiU368h5e; + ternary r50 r54.val[0u32].x r57.val[0u32].x into r58; + ternary r50 r54.val[0u32].y r57.val[0u32].y into r59; + cast r58 r59 into r60 as Point; + ternary r50 r54.val[1u32].x r57.val[1u32].x into r61; + ternary r50 r54.val[1u32].y r57.val[1u32].y into r62; + cast r61 r62 into r63 as Point; + cast r60 r63 into r64 as [Point; 2u32]; + ternary r50 r54.is_some r57.is_some into r65; + cast r65 r64 into r66 as Optional__BvQiU368h5e; + cast 0field 0field into r67 as Point; + cast r67 r67 into r68 as [Point; 2u32]; + cast false r68 into r69 as Optional__BvQiU368h5e; + is.eq r66 r69 into r70; + assert.eq r70 true; + contains child.aleo/stats__[false] into r71; + cast 0u32 0u32 0u32 into r72 as [u32; 3u32]; + cast r72 false into r73 as Stats; + get.or_use child.aleo/stats__[false] r73 into r74; + cast true r74 into r75 as Optional__L4AaGzlaCt7; + cast r72 false into r76 as Stats; + cast false r76 into r77 as Optional__L4AaGzlaCt7; + ternary r71 r75.val.values[0u32] r77.val.values[0u32] into r78; + ternary r71 r75.val.values[1u32] r77.val.values[1u32] into r79; + ternary r71 r75.val.values[2u32] r77.val.values[2u32] into r80; + cast r78 r79 r80 into r81 as [u32; 3u32]; + ternary r71 r75.val.active r77.val.active into r82; + cast r81 r82 into r83 as Stats; + ternary r71 r75.is_some r77.is_some into r84; + cast r84 r83 into r85 as Optional__L4AaGzlaCt7; + cast r72 false into r86 as Stats; + cast false r86 into r87 as Optional__L4AaGzlaCt7; + is.eq r85 r87 into r88; + assert.eq r88 true; + contains child.aleo/arr_u32__[false] into r89; + get.or_use child.aleo/arr_u32__[false] r72 into r90; + cast true r90 into r91 as Optional__EJpsIdnRUro; + cast false r72 into r92 as Optional__EJpsIdnRUro; + ternary r89 r91.val[0u32] r92.val[0u32] into r93; + ternary r89 r91.val[1u32] r92.val[1u32] into r94; + ternary r89 r91.val[2u32] r92.val[2u32] into r95; + cast r93 r94 r95 into r96 as [u32; 3u32]; + ternary r89 r91.is_some r92.is_some into r97; + cast r97 r96 into r98 as Optional__EJpsIdnRUro; + cast false r72 into r99 as Optional__EJpsIdnRUro; + is.eq r98 r99 into r100; + assert.eq r100 true; + contains child.aleo/arr_bool__[false] into r101; + cast false false into r102 as [boolean; 2u32]; + get.or_use child.aleo/arr_bool__[false] r102 into r103; + cast true r103 into r104 as Optional__H28y6OQZ8v9; + cast false r102 into r105 as Optional__H28y6OQZ8v9; + ternary r101 r104.val[0u32] r105.val[0u32] into r106; + ternary r101 r104.val[1u32] r105.val[1u32] into r107; + cast r106 r107 into r108 as [boolean; 2u32]; + ternary r101 r104.is_some r105.is_some into r109; + cast r109 r108 into r110 as Optional__H28y6OQZ8v9; + cast false r102 into r111 as Optional__H28y6OQZ8v9; + is.eq r110 r111 into r112; + assert.eq r112 true; + contains child.aleo/nested__[false] into r113; + cast 0u8 0u8 into r114 as [u8; 2u32]; + cast r114 r114 into r115 as [[u8; 2u32]; 2u32]; + get.or_use child.aleo/nested__[false] r115 into r116; + cast true r116 into r117 as Optional__3Jw8FxjrqtE; + cast false r115 into r118 as Optional__3Jw8FxjrqtE; + ternary r113 r117.val[0u32][0u32] r118.val[0u32][0u32] into r119; + ternary r113 r117.val[0u32][1u32] r118.val[0u32][1u32] into r120; + cast r119 r120 into r121 as [u8; 2u32]; + ternary r113 r117.val[1u32][0u32] r118.val[1u32][0u32] into r122; + ternary r113 r117.val[1u32][1u32] r118.val[1u32][1u32] into r123; + cast r122 r123 into r124 as [u8; 2u32]; + cast r121 r124 into r125 as [[u8; 2u32]; 2u32]; + ternary r113 r117.is_some r118.is_some into r126; + cast r126 r125 into r127 as Optional__3Jw8FxjrqtE; + cast false r115 into r128 as Optional__3Jw8FxjrqtE; + is.eq r127 r128 into r129; + assert.eq r129 true; + contains child.aleo/counter__[false] into r130; + get.or_use child.aleo/counter__[false] 0u8 into r131; + cast true r131 into r132 as Optional__8hhrPm4c3KB; + cast false 0u8 into r133 as Optional__8hhrPm4c3KB; + ternary r130 r132.is_some r133.is_some into r134; + ternary r130 r132.val r133.val into r135; + cast r134 r135 into r136 as Optional__8hhrPm4c3KB; + cast false 0u8 into r137 as Optional__8hhrPm4c3KB; + is.eq r136 r137 into r138; + assert.eq r138 true; + get.or_use child.aleo/vec__len__[false] 0u32 into r139; + is.eq r139 0u32 into r140; + assert.eq r140 true; + +function check_fallback: + async check_fallback into r0; + output r0 as test.aleo/check_fallback.future; + +finalize check_fallback: + contains child.aleo/flag__[false] into r0; + get.or_use child.aleo/flag__[false] false into r1; + cast true r1 into r2 as Optional__DbbQYuLaHdi; + cast false false into r3 as Optional__DbbQYuLaHdi; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__DbbQYuLaHdi; + contains child.aleo/flag__[false] into r7; + get.or_use child.aleo/flag__[false] false into r8; + cast true r8 into r9 as Optional__DbbQYuLaHdi; + cast false false into r10 as Optional__DbbQYuLaHdi; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__DbbQYuLaHdi; + ternary r6.is_some r13.val false into r14; + is.eq r14 false into r15; + assert.eq r15 true; + contains child.aleo/scalar_val__[false] into r16; + get.or_use child.aleo/scalar_val__[false] 0scalar into r17; + cast true r17 into r18 as Optional__JYP65FYVVJA; + cast false 0scalar into r19 as Optional__JYP65FYVVJA; + ternary r16 r18.is_some r19.is_some into r20; + ternary r16 r18.val r19.val into r21; + cast r20 r21 into r22 as Optional__JYP65FYVVJA; + contains child.aleo/scalar_val__[false] into r23; + get.or_use child.aleo/scalar_val__[false] 0scalar into r24; + cast true r24 into r25 as Optional__JYP65FYVVJA; + cast false 0scalar into r26 as Optional__JYP65FYVVJA; + ternary r23 r25.is_some r26.is_some into r27; + ternary r23 r25.val r26.val into r28; + cast r27 r28 into r29 as Optional__JYP65FYVVJA; + ternary r22.is_some r29.val 0scalar into r30; + is.eq r30 0scalar into r31; + assert.eq r31 true; + contains child.aleo/field_val__[false] into r32; + get.or_use child.aleo/field_val__[false] 0field into r33; + cast true r33 into r34 as Optional__K4XRQPOk9yX; + cast false 0field into r35 as Optional__K4XRQPOk9yX; + ternary r32 r34.is_some r35.is_some into r36; + ternary r32 r34.val r35.val into r37; + cast r36 r37 into r38 as Optional__K4XRQPOk9yX; + contains child.aleo/field_val__[false] into r39; + get.or_use child.aleo/field_val__[false] 0field into r40; + cast true r40 into r41 as Optional__K4XRQPOk9yX; + cast false 0field into r42 as Optional__K4XRQPOk9yX; + ternary r39 r41.is_some r42.is_some into r43; + ternary r39 r41.val r42.val into r44; + cast r43 r44 into r45 as Optional__K4XRQPOk9yX; + ternary r38.is_some r45.val 0field into r46; + is.eq r46 0field into r47; + assert.eq r47 true; + contains child.aleo/group_val__[false] into r48; + get.or_use child.aleo/group_val__[false] 0group into r49; + cast true r49 into r50 as Optional__8aTicnNTOvk; + cast false 0group into r51 as Optional__8aTicnNTOvk; + ternary r48 r50.is_some r51.is_some into r52; + ternary r48 r50.val r51.val into r53; + cast r52 r53 into r54 as Optional__8aTicnNTOvk; + contains child.aleo/group_val__[false] into r55; + get.or_use child.aleo/group_val__[false] 0group into r56; + cast true r56 into r57 as Optional__8aTicnNTOvk; + cast false 0group into r58 as Optional__8aTicnNTOvk; + ternary r55 r57.is_some r58.is_some into r59; + ternary r55 r57.val r58.val into r60; + cast r59 r60 into r61 as Optional__8aTicnNTOvk; + ternary r54.is_some r61.val 0group into r62; + is.eq r62 0group into r63; + assert.eq r63 true; + contains child.aleo/point__[false] into r64; + cast 0field 0field into r65 as Point; + get.or_use child.aleo/point__[false] r65 into r66; + cast true r66 into r67 as Optional__6Uxqmb23WYh; + cast 0field 0field into r68 as Point; + cast false r68 into r69 as Optional__6Uxqmb23WYh; + ternary r64 r67.val.x r69.val.x into r70; + ternary r64 r67.val.y r69.val.y into r71; + cast r70 r71 into r72 as Point; + ternary r64 r67.is_some r69.is_some into r73; + cast r73 r72 into r74 as Optional__6Uxqmb23WYh; + contains child.aleo/point__[false] into r75; + cast 0field 0field into r76 as Point; + get.or_use child.aleo/point__[false] r76 into r77; + cast true r77 into r78 as Optional__6Uxqmb23WYh; + cast 0field 0field into r79 as Point; + cast false r79 into r80 as Optional__6Uxqmb23WYh; + ternary r75 r78.val.x r80.val.x into r81; + ternary r75 r78.val.y r80.val.y into r82; + cast r81 r82 into r83 as Point; + ternary r75 r78.is_some r80.is_some into r84; + cast r84 r83 into r85 as Optional__6Uxqmb23WYh; + cast 0field 0field into r86 as Point; + ternary r74.is_some r85.val.x r86.x into r87; + ternary r74.is_some r85.val.y r86.y into r88; + cast r87 r88 into r89 as Point; + is.eq r89.x 0field into r90; + assert.eq r90 true; + contains child.aleo/points__[false] into r91; + cast 0field 0field into r92 as Point; + cast r92 r92 into r93 as [Point; 2u32]; + get.or_use child.aleo/points__[false] r93 into r94; + cast true r94 into r95 as Optional__BvQiU368h5e; + cast 0field 0field into r96 as Point; + cast r96 r96 into r97 as [Point; 2u32]; + cast false r97 into r98 as Optional__BvQiU368h5e; + ternary r91 r95.val[0u32].x r98.val[0u32].x into r99; + ternary r91 r95.val[0u32].y r98.val[0u32].y into r100; + cast r99 r100 into r101 as Point; + ternary r91 r95.val[1u32].x r98.val[1u32].x into r102; + ternary r91 r95.val[1u32].y r98.val[1u32].y into r103; + cast r102 r103 into r104 as Point; + cast r101 r104 into r105 as [Point; 2u32]; + ternary r91 r95.is_some r98.is_some into r106; + cast r106 r105 into r107 as Optional__BvQiU368h5e; + contains child.aleo/points__[false] into r108; + cast 0field 0field into r109 as Point; + cast r109 r109 into r110 as [Point; 2u32]; + get.or_use child.aleo/points__[false] r110 into r111; + cast true r111 into r112 as Optional__BvQiU368h5e; + cast 0field 0field into r113 as Point; + cast r113 r113 into r114 as [Point; 2u32]; + cast false r114 into r115 as Optional__BvQiU368h5e; + ternary r108 r112.val[0u32].x r115.val[0u32].x into r116; + ternary r108 r112.val[0u32].y r115.val[0u32].y into r117; + cast r116 r117 into r118 as Point; + ternary r108 r112.val[1u32].x r115.val[1u32].x into r119; + ternary r108 r112.val[1u32].y r115.val[1u32].y into r120; + cast r119 r120 into r121 as Point; + cast r118 r121 into r122 as [Point; 2u32]; + ternary r108 r112.is_some r115.is_some into r123; + cast r123 r122 into r124 as Optional__BvQiU368h5e; + cast 0field 0field into r125 as Point; + cast 0field 0field into r126 as Point; + cast r125 r126 into r127 as [Point; 2u32]; + ternary r107.is_some r124.val[0u32].x r127[0u32].x into r128; + ternary r107.is_some r124.val[0u32].y r127[0u32].y into r129; + cast r128 r129 into r130 as Point; + ternary r107.is_some r124.val[1u32].x r127[1u32].x into r131; + ternary r107.is_some r124.val[1u32].y r127[1u32].y into r132; + cast r131 r132 into r133 as Point; + cast r130 r133 into r134 as [Point; 2u32]; + is.eq r134[0u32].x 0field into r135; + assert.eq r135 true; + contains child.aleo/stats__[false] into r136; + cast 0u32 0u32 0u32 into r137 as [u32; 3u32]; + cast r137 false into r138 as Stats; + get.or_use child.aleo/stats__[false] r138 into r139; + cast true r139 into r140 as Optional__L4AaGzlaCt7; + cast r137 false into r141 as Stats; + cast false r141 into r142 as Optional__L4AaGzlaCt7; + ternary r136 r140.val.values[0u32] r142.val.values[0u32] into r143; + ternary r136 r140.val.values[1u32] r142.val.values[1u32] into r144; + ternary r136 r140.val.values[2u32] r142.val.values[2u32] into r145; + cast r143 r144 r145 into r146 as [u32; 3u32]; + ternary r136 r140.val.active r142.val.active into r147; + cast r146 r147 into r148 as Stats; + ternary r136 r140.is_some r142.is_some into r149; + cast r149 r148 into r150 as Optional__L4AaGzlaCt7; + contains child.aleo/stats__[false] into r151; + cast r137 false into r152 as Stats; + get.or_use child.aleo/stats__[false] r152 into r153; + cast true r153 into r154 as Optional__L4AaGzlaCt7; + cast r137 false into r155 as Stats; + cast false r155 into r156 as Optional__L4AaGzlaCt7; + ternary r151 r154.val.values[0u32] r156.val.values[0u32] into r157; + ternary r151 r154.val.values[1u32] r156.val.values[1u32] into r158; + ternary r151 r154.val.values[2u32] r156.val.values[2u32] into r159; + cast r157 r158 r159 into r160 as [u32; 3u32]; + ternary r151 r154.val.active r156.val.active into r161; + cast r160 r161 into r162 as Stats; + ternary r151 r154.is_some r156.is_some into r163; + cast r163 r162 into r164 as Optional__L4AaGzlaCt7; + cast 0u32 0u32 0u32 into r165 as [u32; 3u32]; + cast r165 false into r166 as Stats; + ternary r150.is_some r164.val.values[0u32] r166.values[0u32] into r167; + ternary r150.is_some r164.val.values[1u32] r166.values[1u32] into r168; + ternary r150.is_some r164.val.values[2u32] r166.values[2u32] into r169; + cast r167 r168 r169 into r170 as [u32; 3u32]; + ternary r150.is_some r164.val.active r166.active into r171; + cast r170 r171 into r172 as Stats; + is.eq r172.active false into r173; + assert.eq r173 true; + contains child.aleo/arr_u32__[false] into r174; + get.or_use child.aleo/arr_u32__[false] r137 into r175; + cast true r175 into r176 as Optional__EJpsIdnRUro; + cast false r137 into r177 as Optional__EJpsIdnRUro; + ternary r174 r176.val[0u32] r177.val[0u32] into r178; + ternary r174 r176.val[1u32] r177.val[1u32] into r179; + ternary r174 r176.val[2u32] r177.val[2u32] into r180; + cast r178 r179 r180 into r181 as [u32; 3u32]; + ternary r174 r176.is_some r177.is_some into r182; + cast r182 r181 into r183 as Optional__EJpsIdnRUro; + contains child.aleo/arr_u32__[false] into r184; + get.or_use child.aleo/arr_u32__[false] r137 into r185; + cast true r185 into r186 as Optional__EJpsIdnRUro; + cast false r137 into r187 as Optional__EJpsIdnRUro; + ternary r184 r186.val[0u32] r187.val[0u32] into r188; + ternary r184 r186.val[1u32] r187.val[1u32] into r189; + ternary r184 r186.val[2u32] r187.val[2u32] into r190; + cast r188 r189 r190 into r191 as [u32; 3u32]; + ternary r184 r186.is_some r187.is_some into r192; + cast r192 r191 into r193 as Optional__EJpsIdnRUro; + ternary r183.is_some r193.val[0u32] r165[0u32] into r194; + ternary r183.is_some r193.val[1u32] r165[1u32] into r195; + ternary r183.is_some r193.val[2u32] r165[2u32] into r196; + cast r194 r195 r196 into r197 as [u32; 3u32]; + is.eq r197[1u32] 0u32 into r198; + assert.eq r198 true; + contains child.aleo/arr_bool__[false] into r199; + cast false false into r200 as [boolean; 2u32]; + get.or_use child.aleo/arr_bool__[false] r200 into r201; + cast true r201 into r202 as Optional__H28y6OQZ8v9; + cast false r200 into r203 as Optional__H28y6OQZ8v9; + ternary r199 r202.val[0u32] r203.val[0u32] into r204; + ternary r199 r202.val[1u32] r203.val[1u32] into r205; + cast r204 r205 into r206 as [boolean; 2u32]; + ternary r199 r202.is_some r203.is_some into r207; + cast r207 r206 into r208 as Optional__H28y6OQZ8v9; + contains child.aleo/arr_bool__[false] into r209; + get.or_use child.aleo/arr_bool__[false] r200 into r210; + cast true r210 into r211 as Optional__H28y6OQZ8v9; + cast false r200 into r212 as Optional__H28y6OQZ8v9; + ternary r209 r211.val[0u32] r212.val[0u32] into r213; + ternary r209 r211.val[1u32] r212.val[1u32] into r214; + cast r213 r214 into r215 as [boolean; 2u32]; + ternary r209 r211.is_some r212.is_some into r216; + cast r216 r215 into r217 as Optional__H28y6OQZ8v9; + cast false false into r218 as [boolean; 2u32]; + ternary r208.is_some r217.val[0u32] r218[0u32] into r219; + ternary r208.is_some r217.val[1u32] r218[1u32] into r220; + cast r219 r220 into r221 as [boolean; 2u32]; + is.eq r221[1u32] false into r222; + assert.eq r222 true; + contains child.aleo/nested__[false] into r223; + cast 0u8 0u8 into r224 as [u8; 2u32]; + cast r224 r224 into r225 as [[u8; 2u32]; 2u32]; + get.or_use child.aleo/nested__[false] r225 into r226; + cast true r226 into r227 as Optional__3Jw8FxjrqtE; + cast false r225 into r228 as Optional__3Jw8FxjrqtE; + ternary r223 r227.val[0u32][0u32] r228.val[0u32][0u32] into r229; + ternary r223 r227.val[0u32][1u32] r228.val[0u32][1u32] into r230; + cast r229 r230 into r231 as [u8; 2u32]; + ternary r223 r227.val[1u32][0u32] r228.val[1u32][0u32] into r232; + ternary r223 r227.val[1u32][1u32] r228.val[1u32][1u32] into r233; + cast r232 r233 into r234 as [u8; 2u32]; + cast r231 r234 into r235 as [[u8; 2u32]; 2u32]; + ternary r223 r227.is_some r228.is_some into r236; + cast r236 r235 into r237 as Optional__3Jw8FxjrqtE; + contains child.aleo/nested__[false] into r238; + get.or_use child.aleo/nested__[false] r225 into r239; + cast true r239 into r240 as Optional__3Jw8FxjrqtE; + cast false r225 into r241 as Optional__3Jw8FxjrqtE; + ternary r238 r240.val[0u32][0u32] r241.val[0u32][0u32] into r242; + ternary r238 r240.val[0u32][1u32] r241.val[0u32][1u32] into r243; + cast r242 r243 into r244 as [u8; 2u32]; + ternary r238 r240.val[1u32][0u32] r241.val[1u32][0u32] into r245; + ternary r238 r240.val[1u32][1u32] r241.val[1u32][1u32] into r246; + cast r245 r246 into r247 as [u8; 2u32]; + cast r244 r247 into r248 as [[u8; 2u32]; 2u32]; + ternary r238 r240.is_some r241.is_some into r249; + cast r249 r248 into r250 as Optional__3Jw8FxjrqtE; + cast 0u8 0u8 into r251 as [u8; 2u32]; + cast r251 r251 into r252 as [[u8; 2u32]; 2u32]; + ternary r237.is_some r250.val[0u32][0u32] r252[0u32][0u32] into r253; + ternary r237.is_some r250.val[0u32][1u32] r252[0u32][1u32] into r254; + cast r253 r254 into r255 as [u8; 2u32]; + ternary r237.is_some r250.val[1u32][0u32] r252[1u32][0u32] into r256; + ternary r237.is_some r250.val[1u32][1u32] r252[1u32][1u32] into r257; + cast r256 r257 into r258 as [u8; 2u32]; + cast r255 r258 into r259 as [[u8; 2u32]; 2u32]; + is.eq r259[0u32][0u32] 0u8 into r260; + assert.eq r260 true; + contains child.aleo/counter__[false] into r261; + get.or_use child.aleo/counter__[false] 0u8 into r262; + cast true r262 into r263 as Optional__8hhrPm4c3KB; + cast false 0u8 into r264 as Optional__8hhrPm4c3KB; + ternary r261 r263.is_some r264.is_some into r265; + ternary r261 r263.val r264.val into r266; + cast r265 r266 into r267 as Optional__8hhrPm4c3KB; + contains child.aleo/counter__[false] into r268; + get.or_use child.aleo/counter__[false] 0u8 into r269; + cast true r269 into r270 as Optional__8hhrPm4c3KB; + cast false 0u8 into r271 as Optional__8hhrPm4c3KB; + ternary r268 r270.is_some r271.is_some into r272; + ternary r268 r270.val r271.val into r273; + cast r272 r273 into r274 as Optional__8hhrPm4c3KB; + ternary r267.is_some r274.val 123u8 into r275; + is.eq r275 123u8 into r276; + assert.eq r276 true; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/compiler/storage/external_storage_multi_deps.out b/tests/expectations/compiler/storage/external_storage_multi_deps.out new file mode 100644 index 00000000000..85bc6e470f7 --- /dev/null +++ b/tests/expectations/compiler/storage/external_storage_multi_deps.out @@ -0,0 +1,324 @@ +program base.aleo; + +mapping val_u32__: + key as boolean.public; + value as u32.public; + +function init: + async init into r0; + output r0 as base.aleo/init.future; + +finalize init: + set 99u32 into val_u32__[false]; + +function wipe: + async wipe into r0; + output r0 as base.aleo/wipe.future; + +finalize wipe: + remove val_u32__[false]; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +import base.aleo; +program mid1.aleo; + +struct Optional__EsDmm5O6sv6: + is_some as boolean; + val as u32; + +mapping flag__: + key as boolean.public; + value as boolean.public; + +function init: + async init into r0; + output r0 as mid1.aleo/init.future; + +finalize init: + set true into flag__[false]; + contains base.aleo/val_u32__[false] into r0; + get.or_use base.aleo/val_u32__[false] 0u32 into r1; + cast true r1 into r2 as Optional__EsDmm5O6sv6; + cast false 0u32 into r3 as Optional__EsDmm5O6sv6; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__EsDmm5O6sv6; + assert.eq r6.is_some true; + contains base.aleo/val_u32__[false] into r7; + get.or_use base.aleo/val_u32__[false] 0u32 into r8; + cast true r8 into r9 as Optional__EsDmm5O6sv6; + cast false 0u32 into r10 as Optional__EsDmm5O6sv6; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__EsDmm5O6sv6; + is.eq r13.val 99u32 into r14; + assert.eq r14 true; + +function wipe: + async wipe into r0; + output r0 as mid1.aleo/wipe.future; + +finalize wipe: + remove flag__[false]; + contains base.aleo/val_u32__[false] into r0; + get.or_use base.aleo/val_u32__[false] 0u32 into r1; + cast true r1 into r2 as Optional__EsDmm5O6sv6; + cast false 0u32 into r3 as Optional__EsDmm5O6sv6; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__EsDmm5O6sv6; + cast false 0u32 into r7 as Optional__EsDmm5O6sv6; + is.eq r6 r7 into r8; + assert.eq r8 true; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +import base.aleo; +import mid1.aleo; +program mid2.aleo; + +struct Optional__EsDmm5O6sv6: + is_some as boolean; + val as u32; + +struct Optional__DbbQYuLaHdi: + is_some as boolean; + val as boolean; + +mapping counter__: + key as boolean.public; + value as u8.public; + +function init: + async init into r0; + output r0 as mid2.aleo/init.future; + +finalize init: + set 7u8 into counter__[false]; + contains base.aleo/val_u32__[false] into r0; + get.or_use base.aleo/val_u32__[false] 0u32 into r1; + cast true r1 into r2 as Optional__EsDmm5O6sv6; + cast false 0u32 into r3 as Optional__EsDmm5O6sv6; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__EsDmm5O6sv6; + assert.eq r6.is_some true; + contains base.aleo/val_u32__[false] into r7; + get.or_use base.aleo/val_u32__[false] 0u32 into r8; + cast true r8 into r9 as Optional__EsDmm5O6sv6; + cast false 0u32 into r10 as Optional__EsDmm5O6sv6; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__EsDmm5O6sv6; + is.eq r13.val 99u32 into r14; + assert.eq r14 true; + contains mid1.aleo/flag__[false] into r15; + get.or_use mid1.aleo/flag__[false] false into r16; + cast true r16 into r17 as Optional__DbbQYuLaHdi; + cast false false into r18 as Optional__DbbQYuLaHdi; + ternary r15 r17.is_some r18.is_some into r19; + ternary r15 r17.val r18.val into r20; + cast r19 r20 into r21 as Optional__DbbQYuLaHdi; + assert.eq r21.is_some true; + contains mid1.aleo/flag__[false] into r22; + get.or_use mid1.aleo/flag__[false] false into r23; + cast true r23 into r24 as Optional__DbbQYuLaHdi; + cast false false into r25 as Optional__DbbQYuLaHdi; + ternary r22 r24.is_some r25.is_some into r26; + ternary r22 r24.val r25.val into r27; + cast r26 r27 into r28 as Optional__DbbQYuLaHdi; + is.eq r28.val true into r29; + assert.eq r29 true; + +function wipe: + async wipe into r0; + output r0 as mid2.aleo/wipe.future; + +finalize wipe: + remove counter__[false]; + contains mid1.aleo/flag__[false] into r0; + get.or_use mid1.aleo/flag__[false] false into r1; + cast true r1 into r2 as Optional__DbbQYuLaHdi; + cast false false into r3 as Optional__DbbQYuLaHdi; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__DbbQYuLaHdi; + cast false false into r7 as Optional__DbbQYuLaHdi; + is.eq r6 r7 into r8; + assert.eq r8 true; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +import base.aleo; +import mid1.aleo; +import mid2.aleo; +program top.aleo; + +struct Optional__EsDmm5O6sv6: + is_some as boolean; + val as u32; + +struct Optional__DbbQYuLaHdi: + is_some as boolean; + val as boolean; + +struct Optional__8hhrPm4c3KB: + is_some as boolean; + val as u8; + +function test_initialized: + async test_initialized into r0; + output r0 as top.aleo/test_initialized.future; + +finalize test_initialized: + contains base.aleo/val_u32__[false] into r0; + get.or_use base.aleo/val_u32__[false] 0u32 into r1; + cast true r1 into r2 as Optional__EsDmm5O6sv6; + cast false 0u32 into r3 as Optional__EsDmm5O6sv6; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__EsDmm5O6sv6; + assert.eq r6.is_some true; + contains base.aleo/val_u32__[false] into r7; + get.or_use base.aleo/val_u32__[false] 0u32 into r8; + cast true r8 into r9 as Optional__EsDmm5O6sv6; + cast false 0u32 into r10 as Optional__EsDmm5O6sv6; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__EsDmm5O6sv6; + is.eq r13.val 99u32 into r14; + assert.eq r14 true; + contains mid1.aleo/flag__[false] into r15; + get.or_use mid1.aleo/flag__[false] false into r16; + cast true r16 into r17 as Optional__DbbQYuLaHdi; + cast false false into r18 as Optional__DbbQYuLaHdi; + ternary r15 r17.is_some r18.is_some into r19; + ternary r15 r17.val r18.val into r20; + cast r19 r20 into r21 as Optional__DbbQYuLaHdi; + assert.eq r21.is_some true; + contains mid1.aleo/flag__[false] into r22; + get.or_use mid1.aleo/flag__[false] false into r23; + cast true r23 into r24 as Optional__DbbQYuLaHdi; + cast false false into r25 as Optional__DbbQYuLaHdi; + ternary r22 r24.is_some r25.is_some into r26; + ternary r22 r24.val r25.val into r27; + cast r26 r27 into r28 as Optional__DbbQYuLaHdi; + is.eq r28.val true into r29; + assert.eq r29 true; + contains mid2.aleo/counter__[false] into r30; + get.or_use mid2.aleo/counter__[false] 0u8 into r31; + cast true r31 into r32 as Optional__8hhrPm4c3KB; + cast false 0u8 into r33 as Optional__8hhrPm4c3KB; + ternary r30 r32.is_some r33.is_some into r34; + ternary r30 r32.val r33.val into r35; + cast r34 r35 into r36 as Optional__8hhrPm4c3KB; + assert.eq r36.is_some true; + contains mid2.aleo/counter__[false] into r37; + get.or_use mid2.aleo/counter__[false] 0u8 into r38; + cast true r38 into r39 as Optional__8hhrPm4c3KB; + cast false 0u8 into r40 as Optional__8hhrPm4c3KB; + ternary r37 r39.is_some r40.is_some into r41; + ternary r37 r39.val r40.val into r42; + cast r41 r42 into r43 as Optional__8hhrPm4c3KB; + is.eq r43.val 7u8 into r44; + assert.eq r44 true; + +function test_fallback: + async test_fallback into r0; + output r0 as top.aleo/test_fallback.future; + +finalize test_fallback: + contains base.aleo/val_u32__[false] into r0; + get.or_use base.aleo/val_u32__[false] 0u32 into r1; + cast true r1 into r2 as Optional__EsDmm5O6sv6; + cast false 0u32 into r3 as Optional__EsDmm5O6sv6; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__EsDmm5O6sv6; + contains base.aleo/val_u32__[false] into r7; + get.or_use base.aleo/val_u32__[false] 0u32 into r8; + cast true r8 into r9 as Optional__EsDmm5O6sv6; + cast false 0u32 into r10 as Optional__EsDmm5O6sv6; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__EsDmm5O6sv6; + ternary r6.is_some r13.val 0u32 into r14; + is.eq r14 0u32 into r15; + assert.eq r15 true; + contains mid1.aleo/flag__[false] into r16; + get.or_use mid1.aleo/flag__[false] false into r17; + cast true r17 into r18 as Optional__DbbQYuLaHdi; + cast false false into r19 as Optional__DbbQYuLaHdi; + ternary r16 r18.is_some r19.is_some into r20; + ternary r16 r18.val r19.val into r21; + cast r20 r21 into r22 as Optional__DbbQYuLaHdi; + contains mid1.aleo/flag__[false] into r23; + get.or_use mid1.aleo/flag__[false] false into r24; + cast true r24 into r25 as Optional__DbbQYuLaHdi; + cast false false into r26 as Optional__DbbQYuLaHdi; + ternary r23 r25.is_some r26.is_some into r27; + ternary r23 r25.val r26.val into r28; + cast r27 r28 into r29 as Optional__DbbQYuLaHdi; + ternary r22.is_some r29.val false into r30; + is.eq r30 false into r31; + assert.eq r31 true; + contains mid2.aleo/counter__[false] into r32; + get.or_use mid2.aleo/counter__[false] 0u8 into r33; + cast true r33 into r34 as Optional__8hhrPm4c3KB; + cast false 0u8 into r35 as Optional__8hhrPm4c3KB; + ternary r32 r34.is_some r35.is_some into r36; + ternary r32 r34.val r35.val into r37; + cast r36 r37 into r38 as Optional__8hhrPm4c3KB; + contains mid2.aleo/counter__[false] into r39; + get.or_use mid2.aleo/counter__[false] 0u8 into r40; + cast true r40 into r41 as Optional__8hhrPm4c3KB; + cast false 0u8 into r42 as Optional__8hhrPm4c3KB; + ternary r39 r41.is_some r42.is_some into r43; + ternary r39 r41.val r42.val into r44; + cast r43 r44 into r45 as Optional__8hhrPm4c3KB; + ternary r38.is_some r45.val 11u8 into r46; + is.eq r46 11u8 into r47; + assert.eq r47 true; + +function test_none: + async test_none into r0; + output r0 as top.aleo/test_none.future; + +finalize test_none: + contains base.aleo/val_u32__[false] into r0; + get.or_use base.aleo/val_u32__[false] 0u32 into r1; + cast true r1 into r2 as Optional__EsDmm5O6sv6; + cast false 0u32 into r3 as Optional__EsDmm5O6sv6; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__EsDmm5O6sv6; + cast false 0u32 into r7 as Optional__EsDmm5O6sv6; + is.eq r6 r7 into r8; + assert.eq r8 true; + contains mid1.aleo/flag__[false] into r9; + get.or_use mid1.aleo/flag__[false] false into r10; + cast true r10 into r11 as Optional__DbbQYuLaHdi; + cast false false into r12 as Optional__DbbQYuLaHdi; + ternary r9 r11.is_some r12.is_some into r13; + ternary r9 r11.val r12.val into r14; + cast r13 r14 into r15 as Optional__DbbQYuLaHdi; + cast false false into r16 as Optional__DbbQYuLaHdi; + is.eq r15 r16 into r17; + assert.eq r17 true; + contains mid2.aleo/counter__[false] into r18; + get.or_use mid2.aleo/counter__[false] 0u8 into r19; + cast true r19 into r20 as Optional__8hhrPm4c3KB; + cast false 0u8 into r21 as Optional__8hhrPm4c3KB; + ternary r18 r20.is_some r21.is_some into r22; + ternary r18 r20.val r21.val into r23; + cast r22 r23 into r24 as Optional__8hhrPm4c3KB; + cast false 0u8 into r25 as Optional__8hhrPm4c3KB; + is.eq r24 r25 into r26; + assert.eq r26 true; + +constructor: + assert.eq edition 0u16; diff --git a/tests/expectations/compiler/storage/modify_external_vec_fail.out b/tests/expectations/compiler/storage/modify_external_vec_fail.out new file mode 100644 index 00000000000..d21f7a06fdd --- /dev/null +++ b/tests/expectations/compiler/storage/modify_external_vec_fail.out @@ -0,0 +1,35 @@ +Error [ETYC0372108]: Cannot use operation `push` on external vectors. + --> compiler-test:11:13 + | + 11 | provider.aleo/nums.push(3u32); // ❌ illegal external mutation + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = The only valid operations on external vectors are `get` and `len`. +Error [ETYC0372108]: Cannot use operation `pop` on external vectors. + --> compiler-test:12:13 + | + 12 | provider.aleo/nums.pop(); // ❌ illegal external mutation + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = The only valid operations on external vectors are `get` and `len`. +Error [ETYC0372108]: Cannot use operation `set` on external vectors. + --> compiler-test:13:13 + | + 13 | provider.aleo/nums.set(0u32, 99u32); // ❌ illegal external mutation + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = The only valid operations on external vectors are `get` and `len`. +Error [ETYC0372108]: Cannot use operation `clear` on external vectors. + --> compiler-test:14:13 + | + 14 | provider.aleo/nums.clear(); // ❌ illegal external mutation + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = The only valid operations on external vectors are `get` and `len`. +Error [ETYC0372108]: Cannot use operation `swap_remove` on external vectors. + --> compiler-test:15:13 + | + 15 | provider.aleo/nums.swap_remove(1u32); // ❌ illegal external mutation + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = The only valid operations on external vectors are `get` and `len`. diff --git a/tests/expectations/compiler/structs/external_struct.out b/tests/expectations/compiler/structs/external_struct.out index 3a96844336a..4855bb2e97a 100644 --- a/tests/expectations/compiler/structs/external_struct.out +++ b/tests/expectations/compiler/structs/external_struct.out @@ -87,6 +87,10 @@ import child.aleo; import parent.aleo; program grandparent.aleo; +struct Woo: + a as u32; + b as u32; + struct Two: val1 as u32; val2 as u32; @@ -103,10 +107,6 @@ struct Bar: struct Foo: bar as [Bar; 1u32]; -struct Woo: - a as u32; - b as u32; - function main: input r0 as u32.private; add 1u32 r0 into r1; diff --git a/tests/expectations/execution/external_storage.out b/tests/expectations/execution/external_storage.out new file mode 100644 index 00000000000..59e1752633c --- /dev/null +++ b/tests/expectations/execution/external_storage.out @@ -0,0 +1,237 @@ +program zero_program.aleo; + +struct Optional__DOnC9eGtnsJ: + is_some as boolean; + val as u64; + +mapping count__: + key as boolean.public; + value as u64.public; + +function c: + async c into r0; + output r0 as zero_program.aleo/c.future; + +finalize c: + contains count__[false] into r0; + get.or_use count__[false] 0u64 into r1; + cast true r1 into r2 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r3 as Optional__DOnC9eGtnsJ; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__DOnC9eGtnsJ; + contains count__[false] into r7; + get.or_use count__[false] 0u64 into r8; + cast true r8 into r9 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r10 as Optional__DOnC9eGtnsJ; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__DOnC9eGtnsJ; + ternary r6.is_some r13.val 0u64 into r14; + add r14 1u64 into r15; + set r15 into count__[false]; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +program one_program.aleo; + +struct Optional__DOnC9eGtnsJ: + is_some as boolean; + val as u64; + +mapping count__: + key as boolean.public; + value as u64.public; + +function d: + async d into r0; + output r0 as one_program.aleo/d.future; + +finalize d: + contains count__[false] into r0; + get.or_use count__[false] 0u64 into r1; + cast true r1 into r2 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r3 as Optional__DOnC9eGtnsJ; + ternary r0 r2.is_some r3.is_some into r4; + ternary r0 r2.val r3.val into r5; + cast r4 r5 into r6 as Optional__DOnC9eGtnsJ; + contains count__[false] into r7; + get.or_use count__[false] 0u64 into r8; + cast true r8 into r9 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r10 as Optional__DOnC9eGtnsJ; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__DOnC9eGtnsJ; + ternary r6.is_some r13.val 0u64 into r14; + add r14 2u64 into r15; + set r15 into count__[false]; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +import zero_program.aleo; +import one_program.aleo; +program two_program.aleo; + +struct Optional__DOnC9eGtnsJ: + is_some as boolean; + val as u64; + +mapping count__: + key as boolean.public; + value as u64.public; + +function b: + call zero_program.aleo/c into r0; + call one_program.aleo/d into r1; + async b r0 r1 into r2; + output r2 as two_program.aleo/b.future; + +finalize b: + input r0 as zero_program.aleo/c.future; + input r1 as one_program.aleo/d.future; + await r0; + await r1; + contains zero_program.aleo/count__[false] into r2; + get.or_use zero_program.aleo/count__[false] 0u64 into r3; + cast true r3 into r4 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r5 as Optional__DOnC9eGtnsJ; + ternary r2 r4.is_some r5.is_some into r6; + ternary r2 r4.val r5.val into r7; + cast r6 r7 into r8 as Optional__DOnC9eGtnsJ; + assert.eq r8.is_some true; + contains zero_program.aleo/count__[false] into r9; + get.or_use zero_program.aleo/count__[false] 0u64 into r10; + cast true r10 into r11 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r12 as Optional__DOnC9eGtnsJ; + ternary r9 r11.is_some r12.is_some into r13; + ternary r9 r11.val r12.val into r14; + cast r13 r14 into r15 as Optional__DOnC9eGtnsJ; + is.eq r15.val 1u64 into r16; + assert.eq r16 true; + contains one_program.aleo/count__[false] into r17; + get.or_use one_program.aleo/count__[false] 0u64 into r18; + cast true r18 into r19 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r20 as Optional__DOnC9eGtnsJ; + ternary r17 r19.is_some r20.is_some into r21; + ternary r17 r19.val r20.val into r22; + cast r21 r22 into r23 as Optional__DOnC9eGtnsJ; + assert.eq r23.is_some true; + contains one_program.aleo/count__[false] into r24; + get.or_use one_program.aleo/count__[false] 0u64 into r25; + cast true r25 into r26 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r27 as Optional__DOnC9eGtnsJ; + ternary r24 r26.is_some r27.is_some into r28; + ternary r24 r26.val r27.val into r29; + cast r28 r29 into r30 as Optional__DOnC9eGtnsJ; + is.eq r30.val 2u64 into r31; + assert.eq r31 true; + contains zero_program.aleo/count__[false] into r32; + get.or_use zero_program.aleo/count__[false] 0u64 into r33; + cast true r33 into r34 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r35 as Optional__DOnC9eGtnsJ; + ternary r32 r34.is_some r35.is_some into r36; + ternary r32 r34.val r35.val into r37; + cast r36 r37 into r38 as Optional__DOnC9eGtnsJ; + assert.eq r38.is_some true; + contains one_program.aleo/count__[false] into r39; + get.or_use one_program.aleo/count__[false] 0u64 into r40; + cast true r40 into r41 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r42 as Optional__DOnC9eGtnsJ; + ternary r39 r41.is_some r42.is_some into r43; + ternary r39 r41.val r42.val into r44; + cast r43 r44 into r45 as Optional__DOnC9eGtnsJ; + assert.eq r45.is_some true; + contains zero_program.aleo/count__[false] into r46; + get.or_use zero_program.aleo/count__[false] 0u64 into r47; + cast true r47 into r48 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r49 as Optional__DOnC9eGtnsJ; + ternary r46 r48.is_some r49.is_some into r50; + ternary r46 r48.val r49.val into r51; + cast r50 r51 into r52 as Optional__DOnC9eGtnsJ; + contains one_program.aleo/count__[false] into r53; + get.or_use one_program.aleo/count__[false] 0u64 into r54; + cast true r54 into r55 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r56 as Optional__DOnC9eGtnsJ; + ternary r53 r55.is_some r56.is_some into r57; + ternary r53 r55.val r56.val into r58; + cast r57 r58 into r59 as Optional__DOnC9eGtnsJ; + add r52.val r59.val into r60; + set r60 into count__[false]; + contains count__[false] into r61; + get.or_use count__[false] 0u64 into r62; + cast true r62 into r63 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r64 as Optional__DOnC9eGtnsJ; + ternary r61 r63.is_some r64.is_some into r65; + ternary r61 r63.val r64.val into r66; + cast r65 r66 into r67 as Optional__DOnC9eGtnsJ; + assert.eq r67.is_some true; + contains count__[false] into r68; + get.or_use count__[false] 0u64 into r69; + cast true r69 into r70 as Optional__DOnC9eGtnsJ; + cast false 0u64 into r71 as Optional__DOnC9eGtnsJ; + ternary r68 r70.is_some r71.is_some into r72; + ternary r68 r70.val r71.val into r73; + cast r72 r73 into r74 as Optional__DOnC9eGtnsJ; + is.eq r74.val 3u64 into r75; + assert.eq r75 true; + +constructor: + assert.eq edition 0u16; +verified: true +status: accepted +{ + "transitions": [ + { + "id": "au1dqha703ef4a02v4awqh92dtuflnz3neruz5x0uh0lqk382zu2cxqugvgm6", + "program": "zero_program.aleo", + "function": "c", + "inputs": [], + "outputs": [ + { + "type": "future", + "id": "4955974032316232838206317816100890804266278360331755670253599590258195674506field", + "value": "{\n program_id: zero_program.aleo,\n function_name: c,\n arguments: []\n}" + } + ], + "tpk": "7284880383293436353358238384845715609947232003607510196908264657027878680619group", + "tcm": "4794126194402330701419416714446177026000606473610827484550129439368086168762field", + "scm": "1471145396390555270435705139789333461488417867388379476549989636598964614294field" + }, + { + "id": "au16t3tvhaktpp87sj2hl0g8ywws4zzkn008ts7pt7229up3w8r6sxsvvvhs6", + "program": "one_program.aleo", + "function": "d", + "inputs": [], + "outputs": [ + { + "type": "future", + "id": "420721114927838987450066096615916210165839744001227281900302967346076605077field", + "value": "{\n program_id: one_program.aleo,\n function_name: d,\n arguments: []\n}" + } + ], + "tpk": "6432026667132427702017110202541920607742571991495452739877060033913903766556group", + "tcm": "7792855320117535180836916920858185103363428874326426137283500503899230508090field", + "scm": "1471145396390555270435705139789333461488417867388379476549989636598964614294field" + }, + { + "id": "au10stg7q3vllcu7f6z0ul4pnu3hwdzly8jfdfhc34uy9f5dd8k0gps6nspqz", + "program": "two_program.aleo", + "function": "b", + "inputs": [], + "outputs": [ + { + "type": "future", + "id": "5686728829604078181096270366596214002381795802346585259867918319308676393001field", + "value": "{\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: []\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: []\n }\n \n ]\n}" + } + ], + "tpk": "1245000968220076103449169778731689436929600417542470573272872321866493418828group", + "tcm": "2330233844147638998942866990173808736269466551588060338719692778780658655996field", + "scm": "1471145396390555270435705139789333461488417867388379476549989636598964614294field" + } + ], + "global_state_root": "sr1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gk0xu" +} + diff --git a/tests/expectations/execution/external_storage_vector.out b/tests/expectations/execution/external_storage_vector.out new file mode 100644 index 00000000000..c8da1bd97b1 --- /dev/null +++ b/tests/expectations/execution/external_storage_vector.out @@ -0,0 +1,307 @@ +program vec_program_a.aleo; + +mapping numbers__: + key as u32.public; + value as u32.public; + +mapping numbers__len__: + key as boolean.public; + value as u32.public; + +function push_a: + async push_a into r0; + output r0 as vec_program_a.aleo/push_a.future; + +finalize push_a: + get.or_use numbers__len__[false] 0u32 into r0; + add r0 1u32 into r1; + set r1 into numbers__len__[false]; + set 10u32 into numbers__[r0]; + get.or_use numbers__len__[false] 0u32 into r2; + add r2 1u32 into r3; + set r3 into numbers__len__[false]; + set 20u32 into numbers__[r2]; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +program vec_program_b.aleo; + +mapping numbers__: + key as u32.public; + value as u32.public; + +mapping numbers__len__: + key as boolean.public; + value as u32.public; + +function push_b: + async push_b into r0; + output r0 as vec_program_b.aleo/push_b.future; + +finalize push_b: + get.or_use numbers__len__[false] 0u32 into r0; + add r0 1u32 into r1; + set r1 into numbers__len__[false]; + set 1u32 into numbers__[r0]; + get.or_use numbers__len__[false] 0u32 into r2; + add r2 1u32 into r3; + set r3 into numbers__len__[false]; + set 2u32 into numbers__[r2]; + get.or_use numbers__len__[false] 0u32 into r4; + add r4 1u32 into r5; + set r5 into numbers__len__[false]; + set 3u32 into numbers__[r4]; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +import vec_program_a.aleo; +import vec_program_b.aleo; +program vec_program_c.aleo; + +struct Optional__EsDmm5O6sv6: + is_some as boolean; + val as u32; + +mapping sum_vec__: + key as u32.public; + value as u32.public; + +mapping sum_vec__len__: + key as boolean.public; + value as u32.public; + +function combine: + call vec_program_a.aleo/push_a into r0; + call vec_program_b.aleo/push_b into r1; + async combine r0 r1 into r2; + output r2 as vec_program_c.aleo/combine.future; + +finalize combine: + input r0 as vec_program_a.aleo/push_a.future; + input r1 as vec_program_b.aleo/push_b.future; + await r0; + await r1; + get.or_use vec_program_a.aleo/numbers__len__[false] 0u32 into r2; + get.or_use vec_program_b.aleo/numbers__len__[false] 0u32 into r3; + is.eq r2 2u32 into r4; + assert.eq r4 true; + is.eq r3 3u32 into r5; + assert.eq r5 true; + set 0u32 into sum_vec__len__[false]; + get.or_use vec_program_a.aleo/numbers__len__[false] 0u32 into r6; + lt 0u32 r6 into r7; + get.or_use vec_program_a.aleo/numbers__[0u32] 0u32 into r8; + cast true r8 into r9 as Optional__EsDmm5O6sv6; + cast false 0u32 into r10 as Optional__EsDmm5O6sv6; + ternary r7 r9.is_some r10.is_some into r11; + ternary r7 r9.val r10.val into r12; + cast r11 r12 into r13 as Optional__EsDmm5O6sv6; + get.or_use vec_program_a.aleo/numbers__[0u32] 0u32 into r14; + cast true r14 into r15 as Optional__EsDmm5O6sv6; + cast false 0u32 into r16 as Optional__EsDmm5O6sv6; + ternary r7 r15.is_some r16.is_some into r17; + ternary r7 r15.val r16.val into r18; + cast r17 r18 into r19 as Optional__EsDmm5O6sv6; + ternary r13.is_some r19.val 0u32 into r20; + get.or_use vec_program_b.aleo/numbers__len__[false] 0u32 into r21; + lt 0u32 r21 into r22; + get.or_use vec_program_b.aleo/numbers__[0u32] 0u32 into r23; + cast true r23 into r24 as Optional__EsDmm5O6sv6; + cast false 0u32 into r25 as Optional__EsDmm5O6sv6; + ternary r22 r24.is_some r25.is_some into r26; + ternary r22 r24.val r25.val into r27; + cast r26 r27 into r28 as Optional__EsDmm5O6sv6; + get.or_use vec_program_b.aleo/numbers__[0u32] 0u32 into r29; + cast true r29 into r30 as Optional__EsDmm5O6sv6; + cast false 0u32 into r31 as Optional__EsDmm5O6sv6; + ternary r22 r30.is_some r31.is_some into r32; + ternary r22 r30.val r31.val into r33; + cast r32 r33 into r34 as Optional__EsDmm5O6sv6; + ternary r28.is_some r34.val 0u32 into r35; + get.or_use sum_vec__len__[false] 0u32 into r36; + add r36 1u32 into r37; + set r37 into sum_vec__len__[false]; + add r20 r35 into r38; + set r38 into sum_vec__[r36]; + get.or_use vec_program_a.aleo/numbers__len__[false] 0u32 into r39; + lt 1u32 r39 into r40; + get.or_use vec_program_a.aleo/numbers__[1u32] 0u32 into r41; + cast true r41 into r42 as Optional__EsDmm5O6sv6; + cast false 0u32 into r43 as Optional__EsDmm5O6sv6; + ternary r40 r42.is_some r43.is_some into r44; + ternary r40 r42.val r43.val into r45; + cast r44 r45 into r46 as Optional__EsDmm5O6sv6; + get.or_use vec_program_a.aleo/numbers__[1u32] 0u32 into r47; + cast true r47 into r48 as Optional__EsDmm5O6sv6; + cast false 0u32 into r49 as Optional__EsDmm5O6sv6; + ternary r40 r48.is_some r49.is_some into r50; + ternary r40 r48.val r49.val into r51; + cast r50 r51 into r52 as Optional__EsDmm5O6sv6; + ternary r46.is_some r52.val 0u32 into r53; + get.or_use vec_program_b.aleo/numbers__len__[false] 0u32 into r54; + lt 1u32 r54 into r55; + get.or_use vec_program_b.aleo/numbers__[1u32] 0u32 into r56; + cast true r56 into r57 as Optional__EsDmm5O6sv6; + cast false 0u32 into r58 as Optional__EsDmm5O6sv6; + ternary r55 r57.is_some r58.is_some into r59; + ternary r55 r57.val r58.val into r60; + cast r59 r60 into r61 as Optional__EsDmm5O6sv6; + get.or_use vec_program_b.aleo/numbers__[1u32] 0u32 into r62; + cast true r62 into r63 as Optional__EsDmm5O6sv6; + cast false 0u32 into r64 as Optional__EsDmm5O6sv6; + ternary r55 r63.is_some r64.is_some into r65; + ternary r55 r63.val r64.val into r66; + cast r65 r66 into r67 as Optional__EsDmm5O6sv6; + ternary r61.is_some r67.val 0u32 into r68; + get.or_use sum_vec__len__[false] 0u32 into r69; + add r69 1u32 into r70; + set r70 into sum_vec__len__[false]; + add r53 r68 into r71; + set r71 into sum_vec__[r69]; + get.or_use vec_program_a.aleo/numbers__len__[false] 0u32 into r72; + lt 2u32 r72 into r73; + get.or_use vec_program_a.aleo/numbers__[2u32] 0u32 into r74; + cast true r74 into r75 as Optional__EsDmm5O6sv6; + cast false 0u32 into r76 as Optional__EsDmm5O6sv6; + ternary r73 r75.is_some r76.is_some into r77; + ternary r73 r75.val r76.val into r78; + cast r77 r78 into r79 as Optional__EsDmm5O6sv6; + get.or_use vec_program_a.aleo/numbers__[2u32] 0u32 into r80; + cast true r80 into r81 as Optional__EsDmm5O6sv6; + cast false 0u32 into r82 as Optional__EsDmm5O6sv6; + ternary r73 r81.is_some r82.is_some into r83; + ternary r73 r81.val r82.val into r84; + cast r83 r84 into r85 as Optional__EsDmm5O6sv6; + ternary r79.is_some r85.val 0u32 into r86; + get.or_use vec_program_b.aleo/numbers__len__[false] 0u32 into r87; + lt 2u32 r87 into r88; + get.or_use vec_program_b.aleo/numbers__[2u32] 0u32 into r89; + cast true r89 into r90 as Optional__EsDmm5O6sv6; + cast false 0u32 into r91 as Optional__EsDmm5O6sv6; + ternary r88 r90.is_some r91.is_some into r92; + ternary r88 r90.val r91.val into r93; + cast r92 r93 into r94 as Optional__EsDmm5O6sv6; + get.or_use vec_program_b.aleo/numbers__[2u32] 0u32 into r95; + cast true r95 into r96 as Optional__EsDmm5O6sv6; + cast false 0u32 into r97 as Optional__EsDmm5O6sv6; + ternary r88 r96.is_some r97.is_some into r98; + ternary r88 r96.val r97.val into r99; + cast r98 r99 into r100 as Optional__EsDmm5O6sv6; + ternary r94.is_some r100.val 0u32 into r101; + get.or_use sum_vec__len__[false] 0u32 into r102; + add r102 1u32 into r103; + set r103 into sum_vec__len__[false]; + add r86 r101 into r104; + set r104 into sum_vec__[r102]; + get.or_use sum_vec__len__[false] 0u32 into r105; + lt 0u32 r105 into r106; + get.or_use sum_vec__[0u32] 0u32 into r107; + cast true r107 into r108 as Optional__EsDmm5O6sv6; + cast false 0u32 into r109 as Optional__EsDmm5O6sv6; + ternary r106 r108.is_some r109.is_some into r110; + ternary r106 r108.val r109.val into r111; + cast r110 r111 into r112 as Optional__EsDmm5O6sv6; + assert.eq r112.is_some true; + get.or_use sum_vec__[0u32] 0u32 into r113; + cast true r113 into r114 as Optional__EsDmm5O6sv6; + cast false 0u32 into r115 as Optional__EsDmm5O6sv6; + ternary r106 r114.is_some r115.is_some into r116; + ternary r106 r114.val r115.val into r117; + cast r116 r117 into r118 as Optional__EsDmm5O6sv6; + is.eq r118.val 11u32 into r119; + assert.eq r119 true; + get.or_use sum_vec__len__[false] 0u32 into r120; + lt 1u32 r120 into r121; + get.or_use sum_vec__[1u32] 0u32 into r122; + cast true r122 into r123 as Optional__EsDmm5O6sv6; + cast false 0u32 into r124 as Optional__EsDmm5O6sv6; + ternary r121 r123.is_some r124.is_some into r125; + ternary r121 r123.val r124.val into r126; + cast r125 r126 into r127 as Optional__EsDmm5O6sv6; + assert.eq r127.is_some true; + get.or_use sum_vec__[1u32] 0u32 into r128; + cast true r128 into r129 as Optional__EsDmm5O6sv6; + cast false 0u32 into r130 as Optional__EsDmm5O6sv6; + ternary r121 r129.is_some r130.is_some into r131; + ternary r121 r129.val r130.val into r132; + cast r131 r132 into r133 as Optional__EsDmm5O6sv6; + is.eq r133.val 22u32 into r134; + assert.eq r134 true; + get.or_use sum_vec__len__[false] 0u32 into r135; + lt 2u32 r135 into r136; + get.or_use sum_vec__[2u32] 0u32 into r137; + cast true r137 into r138 as Optional__EsDmm5O6sv6; + cast false 0u32 into r139 as Optional__EsDmm5O6sv6; + ternary r136 r138.is_some r139.is_some into r140; + ternary r136 r138.val r139.val into r141; + cast r140 r141 into r142 as Optional__EsDmm5O6sv6; + assert.eq r142.is_some true; + get.or_use sum_vec__[2u32] 0u32 into r143; + cast true r143 into r144 as Optional__EsDmm5O6sv6; + cast false 0u32 into r145 as Optional__EsDmm5O6sv6; + ternary r136 r144.is_some r145.is_some into r146; + ternary r136 r144.val r145.val into r147; + cast r146 r147 into r148 as Optional__EsDmm5O6sv6; + is.eq r148.val 3u32 into r149; + assert.eq r149 true; + +constructor: + assert.eq edition 0u16; +verified: true +status: accepted +{ + "transitions": [ + { + "id": "au1npu5wqp22pjredu5emj2wdgt2d9xsqd9yxd5ma4vtse08zyedgzsdftfr5", + "program": "vec_program_a.aleo", + "function": "push_a", + "inputs": [], + "outputs": [ + { + "type": "future", + "id": "3318751779646558644956826855313786125061116275035427669036136588087011503796field", + "value": "{\n program_id: vec_program_a.aleo,\n function_name: push_a,\n arguments: []\n}" + } + ], + "tpk": "7284880383293436353358238384845715609947232003607510196908264657027878680619group", + "tcm": "4794126194402330701419416714446177026000606473610827484550129439368086168762field", + "scm": "1471145396390555270435705139789333461488417867388379476549989636598964614294field" + }, + { + "id": "au1583scara8pqhzgfhml5rtmu6r80m32cr48rzzr74pqpj2ttess9qye9038", + "program": "vec_program_b.aleo", + "function": "push_b", + "inputs": [], + "outputs": [ + { + "type": "future", + "id": "1354696960022919467638600402065783482351010712955073128278722273026833589489field", + "value": "{\n program_id: vec_program_b.aleo,\n function_name: push_b,\n arguments: []\n}" + } + ], + "tpk": "6432026667132427702017110202541920607742571991495452739877060033913903766556group", + "tcm": "7792855320117535180836916920858185103363428874326426137283500503899230508090field", + "scm": "1471145396390555270435705139789333461488417867388379476549989636598964614294field" + }, + { + "id": "au1t93ux3ugx2p7zaz5lgyqn60p6them5jwcpu9x06peqmaewkl0vzqez5hva", + "program": "vec_program_c.aleo", + "function": "combine", + "inputs": [], + "outputs": [ + { + "type": "future", + "id": "7242003649367231400984174271619617047981899615903029997163769342388495324689field", + "value": "{\n program_id: vec_program_c.aleo,\n function_name: combine,\n arguments: [\n {\n program_id: vec_program_a.aleo,\n function_name: push_a,\n arguments: []\n },\n {\n program_id: vec_program_b.aleo,\n function_name: push_b,\n arguments: []\n }\n \n ]\n}" + } + ], + "tpk": "1245000968220076103449169778731689436929600417542470573272872321866493418828group", + "tcm": "2330233844147638998942866990173808736269466551588060338719692778780658655996field", + "scm": "1471145396390555270435705139789333461488417867388379476549989636598964614294field" + } + ], + "global_state_root": "sr1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gk0xu" +} + diff --git a/tests/expectations/execution/optional_inputs_and_outputs_fail.out b/tests/expectations/execution/optional_inputs_and_outputs_fail.out index aabf9729af7..4721c077d7a 100644 --- a/tests/expectations/execution/optional_inputs_and_outputs_fail.out +++ b/tests/expectations/execution/optional_inputs_and_outputs_fail.out @@ -44,14 +44,14 @@ Error [ETYC0372165]: This function has an output of type `bool?`, which is or co | ^^^^^ | = Outputs of `transition`, `async transition`, and `function` definitions cannot be optional or contain optionals. Consider moving the optionality outside the function call. -Error [ETYC0372164]: The input `f` has type `Foo?`, which is or contains an optional type and is not allowed as an input to a `transition`, `async transition`, or `function`. +Error [ETYC0372164]: The input `f` has type `test.aleo/Foo?`, which is or contains an optional type and is not allowed as an input to a `transition`, `async transition`, or `function`. --> compiler-test:98:32 | 98 | transition struct_optional(f: Foo?) -> Foo? { | ^^^^^^^ | = Inputs to `transition`, `async transition`, and `function` definitions cannot be optional or contain optionals. Consider moving the optionality outside the call site. -Error [ETYC0372165]: This function has an output of type `Foo?`, which is or contains an optional type and is not allowed as an output of a `transition`, `async transition`, or `function`. +Error [ETYC0372165]: This function has an output of type `test.aleo/Foo?`, which is or contains an optional type and is not allowed as an output of a `transition`, `async transition`, or `function`. --> compiler-test:98:44 | 98 | transition struct_optional(f: Foo?) -> Foo? { diff --git a/tests/expectations/parser/program/external_mapping.out b/tests/expectations/parser/program/external_mapping.out index 80f981ce81d..7ff71c88913 100644 --- a/tests/expectations/parser/program/external_mapping.out +++ b/tests/expectations/parser/program/external_mapping.out @@ -1,18 +1,10 @@ { "modules": {}, "imports": { - "hello": [ - { - "modules": {}, - "imports": {}, - "stubs": {}, - "program_scopes": {} - }, - { - "lo": 0, - "hi": 18 - } - ] + "hello": { + "lo": 0, + "hi": 18 + } }, "stubs": {}, "program_scopes": { diff --git a/tests/expectations/parser/program/import.out b/tests/expectations/parser/program/import.out index 4f9fdac7722..e790d3e59fb 100644 --- a/tests/expectations/parser/program/import.out +++ b/tests/expectations/parser/program/import.out @@ -1,18 +1,10 @@ { "modules": {}, "imports": { - "hello": [ - { - "modules": {}, - "imports": {}, - "stubs": {}, - "program_scopes": {} - }, - { - "lo": 0, - "hi": 18 - } - ] + "hello": { + "lo": 0, + "hi": 18 + } }, "stubs": {}, "program_scopes": { diff --git a/tests/expectations/passes/option_lowering/option_none.out b/tests/expectations/passes/option_lowering/option_none.out index 77ccbff162a..261bf862e6d 100644 --- a/tests/expectations/passes/option_lowering/option_none.out +++ b/tests/expectations/passes/option_lowering/option_none.out @@ -4,7 +4,7 @@ program test.aleo { val: u32, } transition main(flag: bool) -> u32 { - let result: ::"u32?" = ::"u32?" { is_some: false, val: 0u32 }; + let result: test.aleo/::"u32?" = ::"u32?" { is_some: false, val: 0u32 }; assert(result.is_some); return result.val; } diff --git a/tests/expectations/passes/option_lowering/option_some.out b/tests/expectations/passes/option_lowering/option_some.out index fc9bbedd5a7..e3a24f1e439 100644 --- a/tests/expectations/passes/option_lowering/option_some.out +++ b/tests/expectations/passes/option_lowering/option_some.out @@ -4,7 +4,7 @@ program test.aleo { val: u32, } transition main(x: u32, flag: bool) -> u32 { - let result: ::"u32?" = ::"u32?" { is_some: true, val: 5u32 }; + let result: test.aleo/::"u32?" = ::"u32?" { is_some: true, val: 5u32 }; assert(result.is_some); return result.val; } diff --git a/tests/expectations/passes/storage_lowering/aggregates.out b/tests/expectations/passes/storage_lowering/aggregates.out index 018080d6ae1..e3997ec817c 100644 --- a/tests/expectations/passes/storage_lowering/aggregates.out +++ b/tests/expectations/passes/storage_lowering/aggregates.out @@ -10,9 +10,9 @@ program complex.aleo { values: [u32; 3], active: bool, } - mapping point__: bool => Point; - mapping points__: bool => [Point; 2]; - mapping stats__: bool => Stats; + mapping point__: bool => complex.aleo/Point; + mapping points__: bool => [complex.aleo/Point; 2]; + mapping stats__: bool => complex.aleo/Stats; mapping arr_u32__: bool => [u32; 3]; mapping arr_bool__: bool => [bool; 2]; mapping nested__: bool => [[u8; 2]; 2]; diff --git a/tests/tests/cli/local_aleo_dependency/COMMANDS b/tests/tests/cli/local_aleo_dependency/COMMANDS new file mode 100755 index 00000000000..f4c781171ae --- /dev/null +++ b/tests/tests/cli/local_aleo_dependency/COMMANDS @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +LEO_BIN=${1} + +${LEO_BIN} build diff --git a/tests/tests/cli/local_aleo_dependency/contents/.gitignore b/tests/tests/cli/local_aleo_dependency/contents/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/tests/cli/local_aleo_dependency/contents/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/tests/cli/local_aleo_dependency/contents/program.json b/tests/tests/cli/local_aleo_dependency/contents/program.json new file mode 100644 index 00000000000..5ed246a691f --- /dev/null +++ b/tests/tests/cli/local_aleo_dependency/contents/program.json @@ -0,0 +1,16 @@ +{ + "program": "complex.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.3.1", + "dependencies": [ + { + "name": "simple.aleo", + "location": "local", + "path": "simple.aleo", + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/tests/tests/cli/local_aleo_dependency/contents/simple.aleo b/tests/tests/cli/local_aleo_dependency/contents/simple.aleo new file mode 100644 index 00000000000..55ec6ae3502 --- /dev/null +++ b/tests/tests/cli/local_aleo_dependency/contents/simple.aleo @@ -0,0 +1,10 @@ +program simple.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; + +constructor: + assert.eq edition 0u16; diff --git a/tests/tests/cli/local_aleo_dependency/contents/src/main.leo b/tests/tests/cli/local_aleo_dependency/contents/src/main.leo new file mode 100644 index 00000000000..15a4d510b68 --- /dev/null +++ b/tests/tests/cli/local_aleo_dependency/contents/src/main.leo @@ -0,0 +1,11 @@ +// The 'simple' program. +import simple.aleo; + +program complex.aleo { + @noupgrade + async constructor() {} + + transition main(public a: u32, b: u32) -> u32 { + return simple.aleo/main(a, b); + } +} diff --git a/tests/tests/cli/multiple_leo_deps/COMMANDS b/tests/tests/cli/multiple_leo_deps/COMMANDS new file mode 100755 index 00000000000..47525daf034 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/COMMANDS @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +LEO_BIN=${1} + +cd parent || exit 1 +$LEO_BIN build diff --git a/tests/tests/cli/multiple_leo_deps/contents/.gitignore b/tests/tests/cli/multiple_leo_deps/contents/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/tests/cli/multiple_leo_deps/contents/child1/.gitignore b/tests/tests/cli/multiple_leo_deps/contents/child1/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/child1/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/tests/cli/multiple_leo_deps/contents/child1/program.json b/tests/tests/cli/multiple_leo_deps/contents/child1/program.json new file mode 100644 index 00000000000..4e35408121e --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/child1/program.json @@ -0,0 +1,16 @@ +{ + "program": "child1.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.3.1", + "dependencies": [ + { + "name": "grandchild.aleo", + "location": "local", + "path": "../grandchild", + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/tests/tests/cli/multiple_leo_deps/contents/child1/src/main.leo b/tests/tests/cli/multiple_leo_deps/contents/child1/src/main.leo new file mode 100644 index 00000000000..b8d28d6b751 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/child1/src/main.leo @@ -0,0 +1,15 @@ +import grandchild.aleo; + +program child1.aleo { + record R { + owner: address, + f1: field + } + + transition main(b: u32) -> u32 { + return grandchild.aleo/main(b); + } + + @noupgrade + async constructor() {} +} diff --git a/tests/tests/cli/multiple_leo_deps/contents/child2/.gitignore b/tests/tests/cli/multiple_leo_deps/contents/child2/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/child2/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/tests/cli/multiple_leo_deps/contents/child2/program.json b/tests/tests/cli/multiple_leo_deps/contents/child2/program.json new file mode 100644 index 00000000000..be91093ce05 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/child2/program.json @@ -0,0 +1,16 @@ +{ + "program": "child2.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.3.1", + "dependencies": [ + { + "name": "grandchild.aleo", + "location": "local", + "path": "../grandchild", + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/tests/tests/cli/multiple_leo_deps/contents/child2/src/main.leo b/tests/tests/cli/multiple_leo_deps/contents/child2/src/main.leo new file mode 100644 index 00000000000..e9dc101df1d --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/child2/src/main.leo @@ -0,0 +1,15 @@ +import grandchild.aleo; + +program child2.aleo { + record R { + owner: address, + f2: field + } + + transition main(b: u32) -> u32 { + return grandchild.aleo/main(b); + } + + @noupgrade + async constructor() {} +} diff --git a/tests/tests/cli/multiple_leo_deps/contents/grandchild/.gitignore b/tests/tests/cli/multiple_leo_deps/contents/grandchild/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/grandchild/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/tests/cli/multiple_leo_deps/contents/grandchild/program.json b/tests/tests/cli/multiple_leo_deps/contents/grandchild/program.json new file mode 100644 index 00000000000..96cc4e28430 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/grandchild/program.json @@ -0,0 +1,8 @@ +{ + "program": "grandchild.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "dependencies": null, + "dev_dependencies": null +} diff --git a/tests/tests/cli/multiple_leo_deps/contents/grandchild/src/main.leo b/tests/tests/cli/multiple_leo_deps/contents/grandchild/src/main.leo new file mode 100644 index 00000000000..60c071375bb --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/grandchild/src/main.leo @@ -0,0 +1,12 @@ +program grandchild.aleo { + record R { + owner: address, + } + + transition main(b: u32) -> u32 { + return b; + } + + @noupgrade + async constructor() {} +} diff --git a/tests/tests/cli/multiple_leo_deps/contents/parent/.gitignore b/tests/tests/cli/multiple_leo_deps/contents/parent/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/parent/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/tests/cli/multiple_leo_deps/contents/parent/program.json b/tests/tests/cli/multiple_leo_deps/contents/parent/program.json new file mode 100644 index 00000000000..de9152677b2 --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/parent/program.json @@ -0,0 +1,22 @@ +{ + "program": "parent.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.3.1", + "dependencies": [ + { + "name": "child1.aleo", + "location": "local", + "path": "../child1", + "edition": null + }, + { + "name": "child2.aleo", + "location": "local", + "path": "../child2", + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/tests/tests/cli/multiple_leo_deps/contents/parent/src/main.leo b/tests/tests/cli/multiple_leo_deps/contents/parent/src/main.leo new file mode 100644 index 00000000000..36b3bf7876a --- /dev/null +++ b/tests/tests/cli/multiple_leo_deps/contents/parent/src/main.leo @@ -0,0 +1,11 @@ +import child1.aleo; +import child2.aleo; + +program parent.aleo { + transition main(b: u32) -> u32 { + return child1.aleo/main(b) + child2.aleo/main(b); + } + + @noupgrade + async constructor() {} +} diff --git a/tests/tests/cli/program_name_mismatch/contents/program.json b/tests/tests/cli/program_name_mismatch/contents/program.json index a76eba3a335..e0d5a628f8f 100644 --- a/tests/tests/cli/program_name_mismatch/contents/program.json +++ b/tests/tests/cli/program_name_mismatch/contents/program.json @@ -4,8 +4,5 @@ "description": "", "license": "MIT", "dependencies": null, - "dev_dependencies": null, - "upgrade": { - "mode": "noupgrade" - } + "dev_dependencies": null } diff --git a/tests/tests/cli/test_deploy/contents/program.json b/tests/tests/cli/test_deploy/contents/program.json index f6b7caf5c08..93f916db11e 100644 --- a/tests/tests/cli/test_deploy/contents/program.json +++ b/tests/tests/cli/test_deploy/contents/program.json @@ -4,8 +4,5 @@ "description": "", "license": "MIT", "dependencies": null, - "dev_dependencies": null, - "upgrade": { - "mode": "noupgrade" - } + "dev_dependencies": null } diff --git a/tests/tests/cli/test_simple_build/contents/program.json b/tests/tests/cli/test_simple_build/contents/program.json index f6b7caf5c08..93f916db11e 100644 --- a/tests/tests/cli/test_simple_build/contents/program.json +++ b/tests/tests/cli/test_simple_build/contents/program.json @@ -4,8 +4,5 @@ "description": "", "license": "MIT", "dependencies": null, - "dev_dependencies": null, - "upgrade": { - "mode": "noupgrade" - } + "dev_dependencies": null } diff --git a/tests/tests/cli/test_simple_test/COMMANDS b/tests/tests/cli/test_simple_test/COMMANDS new file mode 100755 index 00000000000..2ee615b7deb --- /dev/null +++ b/tests/tests/cli/test_simple_test/COMMANDS @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +LEO_BIN=${1} + +${LEO_BIN} test diff --git a/tests/tests/cli/test_simple_test/contents/.gitignore b/tests/tests/cli/test_simple_test/contents/.gitignore new file mode 100644 index 00000000000..f721f7f6f45 --- /dev/null +++ b/tests/tests/cli/test_simple_test/contents/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/tests/tests/cli/test_simple_test/contents/program.json b/tests/tests/cli/test_simple_test/contents/program.json new file mode 100644 index 00000000000..93f916db11e --- /dev/null +++ b/tests/tests/cli/test_simple_test/contents/program.json @@ -0,0 +1,8 @@ +{ + "program": "some_sample_leo_program.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "dependencies": null, + "dev_dependencies": null +} diff --git a/tests/tests/cli/test_simple_test/contents/src/main.leo b/tests/tests/cli/test_simple_test/contents/src/main.leo new file mode 100644 index 00000000000..40acdafaa51 --- /dev/null +++ b/tests/tests/cli/test_simple_test/contents/src/main.leo @@ -0,0 +1,10 @@ +// The 'some_sample_leo_program' program. +program some_sample_leo_program.aleo { + transition main(public a: u32, b: u32) -> u32 { + let c: u32 = a + b; + return c; + } + + @noupgrade + async constructor() {} +} diff --git a/tests/tests/cli/test_simple_test/contents/tests/test_some_sample_leo_program.leo b/tests/tests/cli/test_simple_test/contents/tests/test_some_sample_leo_program.leo new file mode 100644 index 00000000000..00528592861 --- /dev/null +++ b/tests/tests/cli/test_simple_test/contents/tests/test_some_sample_leo_program.leo @@ -0,0 +1,19 @@ +// The 'test_some_sample_leo_program' test program. +import some_sample_leo_program.aleo; +program test_some_sample_leo_program.aleo { + @test + script test_it() { + let result: u32 = some_sample_leo_program.aleo/main(1u32, 2u32); + assert_eq(result, 3u32); + } + + @test + @should_fail + transition do_nothing() { + let result: u32 = some_sample_leo_program.aleo/main(2u32, 3u32); + assert_eq(result, 3u32); + } + + @noupgrade + async constructor() {} +} diff --git a/tests/tests/compiler/mappings/modify_external_mapping_fail.leo b/tests/tests/compiler/mappings/modify_external_mapping_fail.leo new file mode 100644 index 00000000000..fc1794bbadc --- /dev/null +++ b/tests/tests/compiler/mappings/modify_external_mapping_fail.leo @@ -0,0 +1,33 @@ +program provider.aleo { + mapping balances: u32 => u32; + + async transition init() -> Future { + return async { + balances.set(1u32, 100u32); + balances.set(2u32, 200u32); + }; + } + + @noupgrade + async constructor() {} +} + +// --- Next Program --- // + +import provider.aleo; + +program user.aleo { + async transition try_modify() -> Future { + return async { + // + // All of these must be rejected — external mapping mutation is illegal. + // + provider.aleo/balances.set(3u32, 300u32); // ❌ illegal external mutation + provider.aleo/balances.remove(1u32); // ❌ illegal external mutation + }; + } + + @noupgrade + async constructor() {} +} + diff --git a/tests/tests/compiler/storage/external_storage.leo b/tests/tests/compiler/storage/external_storage.leo new file mode 100644 index 00000000000..01359f18a7f --- /dev/null +++ b/tests/tests/compiler/storage/external_storage.leo @@ -0,0 +1,199 @@ +program child.aleo { + struct Point { + x: field, + y: field, + } + + struct Stats { + values: [u32; 3], + active: bool, + } + + storage flag: bool; + storage scalar_val: scalar; + storage field_val: field; + storage group_val: group; + + storage point: Point; + storage points: [Point; 2]; + storage stats: Stats; + storage arr_u32: [u32; 3]; + storage arr_bool: [bool; 2]; + storage nested: [[u8; 2]; 2]; + + storage counter: u8; + storage vec: [u8]; + + // Initialize everything + async transition initialize() -> Future { + return async { + flag = true; + scalar_val = 1; + field_val = 42field; + group_val = 0group; + + point = Point { x: 1field, y: 2field }; + points = [ + Point { x: 10field, y: 20field }, + Point { x: 30field, y: 40field }, + ]; + stats = Stats { values: [5u32, 10u32, 15u32], active: true }; + + arr_u32 = [7u32, 8u32, 9u32]; + arr_bool = [true, false]; + nested = [[1u8, 2u8], [3u8, 4u8]]; + + counter = 9u8; + vec.push(50u8); + }; + } + + // Wipes all values to none + async transition wipe() -> Future { + return async { + flag = none; + scalar_val = none; + field_val = none; + group_val = none; + + point = none; + points = none; + stats = none; + + arr_u32 = none; + arr_bool = none; + nested = none; + + counter = none; + vec.clear(); + }; + } + + @noupgrade + async constructor() {} +} + + +// --- Next Program --- // + + +import child.aleo; +program test.aleo { + // + // ──────────────────────────────────────────────────────────────── + // Case 1: Read initialized external storage + // ──────────────────────────────────────────────────────────────── + // + async transition check_initialized() -> Future { + return async { + // Primitive unwrapping + assert(child.aleo/flag.unwrap() == true); + assert(child.aleo/scalar_val.unwrap() == 1); + assert(child.aleo/field_val.unwrap() == 42field); + assert(child.aleo/group_val.unwrap() == 0group); + + // Struct + let p = child.aleo/point.unwrap(); + assert(p.x == 1field); + assert(p.y == 2field); + + // Array of structs + let ps = child.aleo/points.unwrap(); + assert(ps[0].x == 10field); + assert(ps[1].y == 40field); + + // Struct-with-array + let s = child.aleo/stats.unwrap(); + assert(s.values[1] == 10u32); + assert(s.active == true); + + // Arrays + let a = child.aleo/arr_u32.unwrap(); + assert(a[2] == 9u32); + + let b = child.aleo/arr_bool.unwrap(); + assert(b[0] == true); + + let n = child.aleo/nested.unwrap(); + assert(n[1][1] == 4u8); + + // Additional storage + assert(child.aleo/counter.unwrap() == 9u8); + + let v2 = child.aleo/vec.get(2); + assert(v2.unwrap() == 70u8); + }; + } + + + // + // ──────────────────────────────────────────────────────────────── + // Case 2: After wipe(), everything becomes none + // ──────────────────────────────────────────────────────────────── + // + async transition check_wiped() -> Future { + return async { + assert(child.aleo/flag == none); + assert(child.aleo/scalar_val == none); + assert(child.aleo/field_val == none); + assert(child.aleo/group_val == none); + + assert(child.aleo/point == none); + assert(child.aleo/points == none); + assert(child.aleo/stats == none); + + assert(child.aleo/arr_u32 == none); + assert(child.aleo/arr_bool == none); + assert(child.aleo/nested == none); + + assert(child.aleo/counter == none); + assert(child.aleo/vec.len() == 0); + }; + } + + + // + // ──────────────────────────────────────────────────────────────── + // Case 3: unwrap_or on wiped storage + // ──────────────────────────────────────────────────────────────── + // + async transition check_fallback() -> Future { + return async { + assert(child.aleo/flag.unwrap_or(false) == false); + assert(child.aleo/scalar_val.unwrap_or(0) == 0); + assert(child.aleo/field_val.unwrap_or(0field) == 0field); + assert(child.aleo/group_val.unwrap_or(0group) == 0group); + + let p = child.aleo/point.unwrap_or( + Point { x: 0field, y: 0field } + ); + assert(p.x == 0field); + + let ps = child.aleo/points.unwrap_or([ + Point { x: 0field, y: 0field }, + Point { x: 0field, y: 0field }, + ]); + assert(ps[0].x == 0field); + + let s = child.aleo/stats.unwrap_or( + Stats { values: [0u32, 0u32, 0u32], active: false } + ); + assert(s.active == false); + + let a = child.aleo/arr_u32.unwrap_or([0u32, 0u32, 0u32]); + assert(a[1] == 0u32); + + let b = child.aleo/arr_bool.unwrap_or([false, false]); + assert(b[1] == false); + + let n = child.aleo/nested.unwrap_or([[0u8, 0u8], [0u8, 0u8]]); + assert(n[0][0] == 0u8); + + assert(child.aleo/counter.unwrap_or(123u8) == 123u8); + }; + } + + @noupgrade + async constructor() {} + +} diff --git a/tests/tests/compiler/storage/external_storage_multi_deps.leo b/tests/tests/compiler/storage/external_storage_multi_deps.leo new file mode 100644 index 00000000000..dfaf4415283 --- /dev/null +++ b/tests/tests/compiler/storage/external_storage_multi_deps.leo @@ -0,0 +1,120 @@ +program base.aleo { + storage val_u32: u32; + + async transition init() -> Future { + return async { val_u32 = 99u32; }; + } + + async transition wipe() -> Future { + return async { val_u32 = none; }; + } + + @noupgrade + async constructor() {} +} + +// --- Next Program --- // + +import base.aleo; + +program mid1.aleo { + storage flag: bool; + + async transition init() -> Future { + return async { + flag = true; + assert(base.aleo/val_u32.unwrap() == 99u32); + }; + } + + async transition wipe() -> Future { + return async { + flag = none; + assert(base.aleo/val_u32 == none); + }; + } + + @noupgrade + async constructor() {} +} + +// --- Next Program --- // + +import base.aleo; +import mid1.aleo; + +program mid2.aleo { + storage counter: u8; + + async transition init() -> Future { + return async { + counter = 7u8; + + assert(base.aleo/val_u32.unwrap() == 99u32); + assert(mid1.aleo/flag.unwrap() == true); + }; + } + + async transition wipe() -> Future { + return async { + counter = none; + + assert(mid1.aleo/flag == none); + }; + } + + @noupgrade + async constructor() {} +} + +// --- Next Program --- // + +import base.aleo; +import mid1.aleo; +import mid2.aleo; + +program top.aleo { + + // + // ───────────────────────────────────────────── + // Check all external values are initialized + // ───────────────────────────────────────────── + // + async transition test_initialized() -> Future { + return async { + assert(base.aleo/val_u32.unwrap() == 99u32); + assert(mid1.aleo/flag.unwrap() == true); + assert(mid2.aleo/counter.unwrap() == 7u8); + }; + } + + // + // ───────────────────────────────────────────── + // Check fallback values after wipe + // ───────────────────────────────────────────── + // + async transition test_fallback() -> Future { + return async { + assert(base.aleo/val_u32.unwrap_or(0u32) == 0u32); + assert(mid1.aleo/flag.unwrap_or(false) == false); + assert(mid2.aleo/counter.unwrap_or(11u8) == 11u8); + }; + } + + // + // ───────────────────────────────────────────── + // None-checks + // ───────────────────────────────────────────── + // + async transition test_none() -> Future { + return async { + assert(base.aleo/val_u32 == none); + assert(mid1.aleo/flag == none); + assert(mid2.aleo/counter == none); + }; + } + + @noupgrade + async constructor() {} +} + diff --git a/tests/tests/compiler/storage/modify_external_vec_fail.leo b/tests/tests/compiler/storage/modify_external_vec_fail.leo new file mode 100644 index 00000000000..10c7a27c49d --- /dev/null +++ b/tests/tests/compiler/storage/modify_external_vec_fail.leo @@ -0,0 +1,35 @@ +program provider.aleo { + storage nums: [u32]; + + async transition init() -> Future { + return async { + nums.push(1u32); + nums.push(2u32); + }; + } + + @noupgrade + async constructor() {} +} + +// --- Next Program --- // + +import provider.aleo; + +program user.aleo { + async transition try_modify() -> Future { + return async { + // + // Every line below should be DISALLOWED on external storage. + // + provider.aleo/nums.push(3u32); // ❌ illegal external mutation + provider.aleo/nums.pop(); // ❌ illegal external mutation + provider.aleo/nums.set(0u32, 99u32); // ❌ illegal external mutation + provider.aleo/nums.clear(); // ❌ illegal external mutation + provider.aleo/nums.swap_remove(1u32); // ❌ illegal external mutation + }; + } + + @noupgrade + async constructor() {} +} diff --git a/tests/tests/compiler/symbols/illegal_names_in_library_fail.leo b/tests/tests/compiler/symbols/illegal_names_in_library_fail.leo index 6d64a4d619a..7810b66bf89 100644 --- a/tests/tests/compiler/symbols/illegal_names_in_library_fail.leo +++ b/tests/tests/compiler/symbols/illegal_names_in_library_fail.leo @@ -22,7 +22,7 @@ program childaleo.aleo { // --- Next Program --- // -import child.aleo; +import childaleo.aleo; program foo.aleo { transition foo () {} diff --git a/tests/tests/execution/external_storage.leo b/tests/tests/execution/external_storage.leo new file mode 100644 index 00000000000..7bbfc8200c2 --- /dev/null +++ b/tests/tests/execution/external_storage.leo @@ -0,0 +1,68 @@ +/* +seed = 123456789 +min_height = 16 + +[case] +program = "two_program.aleo" +function = "b" +input = [] +*/ + +program zero_program.aleo { + storage count: u64; + + async transition c() -> Future { + let f = async { + count = count.unwrap_or(0) + 1u64; + }; + return f; + } + + @noupgrade + async constructor() {} +} + + +// --- Next Program --- // + + +program one_program.aleo { + storage count: u64; + + async transition d() -> Future { + let f = async { + count = count.unwrap_or(0) + 2u64; + }; + return f; + } + + @noupgrade + async constructor() {} +} + +// --- Next Program --- // + + +import zero_program.aleo; +import one_program.aleo; + +program two_program.aleo { + storage count: u64; + + async transition b() -> Future { + let f0: Future = zero_program.aleo/c(); + let f1: Future = one_program.aleo/d(); + + return async { + f0.await(); + f1.await(); + assert(zero_program.aleo/count.unwrap() == 1u64); + assert(one_program.aleo/count.unwrap() == 2u64); + count = zero_program.aleo/count.unwrap() + one_program.aleo/count.unwrap(); + assert(count.unwrap() == 3u64); + }; + } + + @noupgrade + async constructor() {} +} diff --git a/tests/tests/execution/external_storage_vector.leo b/tests/tests/execution/external_storage_vector.leo new file mode 100644 index 00000000000..53d8c82d6db --- /dev/null +++ b/tests/tests/execution/external_storage_vector.leo @@ -0,0 +1,84 @@ +/* +seed = 123456789 +min_height = 16 + +[case] +program = "vec_program_c.aleo" +function = "combine" +input = [] +*/ + + +program vec_program_a.aleo { + storage numbers: [u32]; + + async transition push_a() -> Future { + return async { + numbers.push(10u32); + numbers.push(20u32); + }; + } + + @noupgrade + async constructor() {} +} + +// --- Next Program --- // + +program vec_program_b.aleo { + storage numbers: [u32]; + + async transition push_b() -> Future { + return async { + numbers.push(1u32); + numbers.push(2u32); + numbers.push(3u32); + }; + } + + @noupgrade + async constructor() {} +} + +// --- Next Program --- // + +import vec_program_a.aleo; +import vec_program_b.aleo; + +program vec_program_c.aleo { + storage sum_vec: [u32]; + + async transition combine() -> Future { + let f_a: Future = vec_program_a.aleo/push_a(); + let f_b: Future = vec_program_b.aleo/push_b(); + + return async { + f_a.await(); + f_b.await(); + + let len_a: u32 = vec_program_a.aleo/numbers.len(); + let len_b: u32 = vec_program_b.aleo/numbers.len(); + + assert(len_a == 2); + assert(len_b == 3); + + const max_len:u32 = 3; + + sum_vec.clear(); + + for i:u32 in 0..max_len { + let a_val = vec_program_a.aleo/numbers.get(i).unwrap_or(0u32); + let b_val = vec_program_b.aleo/numbers.get(i).unwrap_or(0u32); + sum_vec.push(a_val + b_val); + } + + // Assertions + assert(sum_vec.get(0).unwrap() == 10u32 + 1); + assert(sum_vec.get(1).unwrap() == 20u32 + 2); + assert(sum_vec.get(2).unwrap() == 0u32 + 3); + }; + } + + @noupgrade + async constructor() {} +} diff --git a/utils/disassembler/src/lib.rs b/utils/disassembler/src/lib.rs index 681e517d242..028cbd21d4b 100644 --- a/utils/disassembler/src/lib.rs +++ b/utils/disassembler/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use leo_ast::{Composite, FunctionStub, Identifier, Mapping, ProgramId, Stub}; +use leo_ast::{AleoProgram, Composite, FunctionStub, Identifier, Mapping, ProgramId}; use leo_errors::UtilError; use leo_span::Symbol; @@ -25,9 +25,9 @@ use snarkvm::{ use std::{fmt, str::FromStr}; -pub fn disassemble(program: ProgramCore) -> Stub { +pub fn disassemble(program: ProgramCore) -> AleoProgram { let program_id = ProgramId::from(program.id()); - Stub { + AleoProgram { imports: program.imports().into_iter().map(|(id, _)| ProgramId::from(id)).collect(), stub_id: program_id, consts: Vec::new(), @@ -84,7 +84,7 @@ pub fn disassemble(program: ProgramCore) -> Stub { } } -pub fn disassemble_from_str(name: impl fmt::Display, program: &str) -> Result { +pub fn disassemble_from_str(name: impl fmt::Display, program: &str) -> Result { match Program::::from_str(program) { Ok(p) => Ok(disassemble(p)), Err(_) => Err(UtilError::snarkvm_parsing_error(name)),