diff --git a/compiler/noirc_frontend/src/elaborator/enums.rs b/compiler/noirc_frontend/src/elaborator/enums.rs index 9917460dc87..d8faabf0e76 100644 --- a/compiler/noirc_frontend/src/elaborator/enums.rs +++ b/compiler/noirc_frontend/src/elaborator/enums.rs @@ -955,11 +955,18 @@ impl<'elab, 'ctx> MatchCompiler<'elab, 'ctx> { variable_types: Vec, location: Location, ) -> Vec { - vecmap(variable_types, |typ| self.fresh_match_variable(typ, location)) + vecmap(variable_types.into_iter().enumerate(), |(index, typ)| { + self.fresh_match_variable(index, typ, location) + }) } - fn fresh_match_variable(&mut self, variable_type: Type, location: Location) -> DefinitionId { - let name = "internal_match_variable".to_string(); + fn fresh_match_variable( + &mut self, + index: usize, + variable_type: Type, + location: Location, + ) -> DefinitionId { + let name = format!("internal_match_variable_{index}"); let kind = DefinitionKind::Local(None); let id = self.elaborator.interner.push_definition(name, false, false, kind, location); self.elaborator.interner.push_definition_type(id, variable_type); diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index cf648560db7..00f404a5552 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1573,10 +1573,12 @@ impl<'context> Elaborator<'context> { let resolved_trait_impl = Shared::new(TraitImpl { ident, + location, typ: self_type.clone(), trait_id, trait_generics, file: trait_impl.file_id, + crate_id: self.crate_id, where_clause, methods, }); diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index 921b79ca2a9..f7c3d224577 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -402,8 +402,12 @@ impl Elaborator<'_> { let tmp_value = HirLValue::Ident(ident, Type::Error); let lvalue = std::mem::replace(object_ref, Box::new(tmp_value)); - *object_ref = - Box::new(HirLValue::Dereference { lvalue, element_type, location }); + *object_ref = Box::new(HirLValue::Dereference { + lvalue, + element_type, + location, + implicitly_added: true, + }); *mutable_ref = true; }; @@ -442,8 +446,12 @@ impl Elaborator<'_> { // as needed to unwrap any `&` or `&mut` wrappers. while let Type::Reference(element, _) = lvalue_type.follow_bindings() { let element_type = element.as_ref().clone(); - lvalue = - HirLValue::Dereference { lvalue: Box::new(lvalue), element_type, location }; + lvalue = HirLValue::Dereference { + lvalue: Box::new(lvalue), + element_type, + location, + implicitly_added: true, + }; lvalue_type = *element; // We know this value to be mutable now since we found an `&mut` mutable = true; @@ -496,7 +504,12 @@ impl Elaborator<'_> { // Dereferences are always mutable since we already type checked against a &mut T let typ = element_type.clone(); - let lvalue = HirLValue::Dereference { lvalue, element_type, location }; + let lvalue = HirLValue::Dereference { + lvalue, + element_type, + location, + implicitly_added: false, + }; (lvalue, typ, true) } LValue::Interned(id, location) => { diff --git a/compiler/noirc_frontend/src/hir/comptime/display.rs b/compiler/noirc_frontend/src/hir/comptime/display.rs index f64263feb73..83d907288ee 100644 --- a/compiler/noirc_frontend/src/hir/comptime/display.rs +++ b/compiler/noirc_frontend/src/hir/comptime/display.rs @@ -63,8 +63,16 @@ impl Display for TokensPrettyPrinter<'_, '_> { } } -pub(super) fn tokens_to_string(tokens: &[LocatedToken], interner: &NodeInterner) -> String { - TokensPrettyPrinter { tokens, interner, indent: 0 }.to_string() +pub fn tokens_to_string(tokens: &[LocatedToken], interner: &NodeInterner) -> String { + tokens_to_string_with_indent(tokens, 0, interner) +} + +pub fn tokens_to_string_with_indent( + tokens: &[LocatedToken], + indent: usize, + interner: &NodeInterner, +) -> String { + TokensPrettyPrinter { tokens, interner, indent }.to_string() } /// Tries to print tokens in a way that it'll be easier for the user to understand a diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index 451cf14cea5..f95d4001dc9 100644 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -504,7 +504,7 @@ impl HirLValue { let index = index.to_display_ast(interner); LValue::Index { array, index, location: *location } } - HirLValue::Dereference { lvalue, element_type: _, location } => { + HirLValue::Dereference { lvalue, element_type: _, location, implicitly_added: _ } => { let lvalue = Box::new(lvalue.to_display_ast(interner)); LValue::Dereference(lvalue, *location) } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 0b34640d5c4..d6f3590df4d 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -1111,7 +1111,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn store_lvalue(&mut self, lvalue: HirLValue, rhs: Value) -> IResult<()> { match lvalue { HirLValue::Ident(ident, typ) => self.mutate(ident.id, rhs, ident.location), - HirLValue::Dereference { lvalue, element_type: _, location } => { + HirLValue::Dereference { lvalue, element_type: _, location, implicitly_added: _ } => { match self.evaluate_lvalue(&lvalue)? { Value::Pointer(value, _, _) => { *value.borrow_mut() = rhs; @@ -1172,7 +1172,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Value::Pointer(elem, true, _) => Ok(elem.borrow().clone()), other => Ok(other), }, - HirLValue::Dereference { lvalue, element_type, location } => { + HirLValue::Dereference { lvalue, element_type, location, implicitly_added: _ } => { match self.evaluate_lvalue(lvalue)? { Value::Pointer(value, _, _) => Ok(value.borrow().clone()), value => { diff --git a/compiler/noirc_frontend/src/hir/comptime/mod.rs b/compiler/noirc_frontend/src/hir/comptime/mod.rs index c4a987e5419..a5658ffbe2a 100644 --- a/compiler/noirc_frontend/src/hir/comptime/mod.rs +++ b/compiler/noirc_frontend/src/hir/comptime/mod.rs @@ -5,6 +5,7 @@ mod interpreter; mod tests; mod value; +pub use display::{tokens_to_string, tokens_to_string_with_indent}; pub use errors::{ComptimeError, InterpreterError}; pub use interpreter::Interpreter; pub use value::Value; diff --git a/compiler/noirc_frontend/src/hir_def/stmt.rs b/compiler/noirc_frontend/src/hir_def/stmt.rs index 4f5cce11f11..08a98ccf162 100644 --- a/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -150,6 +150,7 @@ pub enum HirLValue { Dereference { lvalue: Box, element_type: Type, + implicitly_added: bool, location: Location, }, } diff --git a/compiler/noirc_frontend/src/hir_def/traits.rs b/compiler/noirc_frontend/src/hir_def/traits.rs index a344b276913..ed39dd957cc 100644 --- a/compiler/noirc_frontend/src/hir_def/traits.rs +++ b/compiler/noirc_frontend/src/hir_def/traits.rs @@ -83,6 +83,7 @@ pub struct Trait { #[derive(Debug)] pub struct TraitImpl { pub ident: Ident, + pub location: Location, pub typ: Type, pub trait_id: TraitId, @@ -95,6 +96,7 @@ pub struct TraitImpl { pub trait_generics: Vec, pub file: FileId, + pub crate_id: CrateId, pub methods: Vec, // methods[i] is the implementation of trait.methods[i] for Type typ /// The where clause, if present, contains each trait requirement which must diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index 7cd5de02511..89482925e53 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -24,6 +24,7 @@ pub mod elaborator; pub mod graph; pub mod lexer; pub mod locations; +pub mod modules; pub mod monomorphization; pub mod node_interner; pub mod ownership; diff --git a/compiler/noirc_frontend/src/modules.rs b/compiler/noirc_frontend/src/modules.rs new file mode 100644 index 00000000000..b5b76714b00 --- /dev/null +++ b/compiler/noirc_frontend/src/modules.rs @@ -0,0 +1,211 @@ +use crate::{ + ast::Ident, + graph::{CrateId, Dependency}, + hir::def_map::{ModuleDefId, ModuleId}, + node_interner::{NodeInterner, ReferenceId}, +}; + +pub fn get_parent_module(interner: &NodeInterner, module_def_id: ModuleDefId) -> Option { + let reference_id = module_def_id_to_reference_id(module_def_id); + interner.reference_module(reference_id).copied() +} + +pub fn module_def_id_to_reference_id(module_def_id: ModuleDefId) -> ReferenceId { + match module_def_id { + ModuleDefId::ModuleId(id) => ReferenceId::Module(id), + ModuleDefId::FunctionId(id) => ReferenceId::Function(id), + ModuleDefId::TypeId(id) => ReferenceId::Type(id), + ModuleDefId::TypeAliasId(id) => ReferenceId::Alias(id), + ModuleDefId::TraitId(id) => ReferenceId::Trait(id), + ModuleDefId::GlobalId(id) => ReferenceId::Global(id), + } +} + +/// Returns the fully-qualified path of the given `ModuleDefId` relative to `current_module_id`: +/// - If `ModuleDefId` is a module, that module's path is returned +/// - Otherwise, that item's parent module's path is returned +pub fn relative_module_full_path( + module_def_id: ModuleDefId, + current_module_id: ModuleId, + current_module_parent_id: Option, + interner: &NodeInterner, +) -> Option { + let full_path; + if let ModuleDefId::ModuleId(module_id) = module_def_id { + full_path = relative_module_id_path( + module_id, + current_module_id, + current_module_parent_id, + interner, + ); + } else { + let parent_module = get_parent_module(interner, module_def_id)?; + + // If module_def_id is contained in the current module, the relative path is empty + if current_module_id == parent_module { + return None; + } + + full_path = relative_module_id_path( + parent_module, + current_module_id, + current_module_parent_id, + interner, + ); + } + Some(full_path) +} + +/// Returns the path to reach an item inside `target_module_id` from inside `current_module_id`. +/// Returns a relative path if possible. +pub fn relative_module_id_path( + target_module_id: ModuleId, + current_module_id: ModuleId, + current_module_parent_id: Option, + interner: &NodeInterner, +) -> String { + if Some(target_module_id) == current_module_parent_id { + return "super".to_string(); + } + + let mut segments: Vec<&str> = Vec::new(); + let mut is_relative = false; + + if let Some(module_attributes) = interner.try_module_attributes(&target_module_id) { + segments.push(&module_attributes.name); + + let mut current_attributes = module_attributes; + loop { + let Some(parent_local_id) = current_attributes.parent else { + break; + }; + + let parent_module_id = + &ModuleId { krate: target_module_id.krate, local_id: parent_local_id }; + + if current_module_id == *parent_module_id { + is_relative = true; + break; + } + + if current_module_parent_id == Some(*parent_module_id) { + segments.push("super"); + is_relative = true; + break; + } + + let Some(parent_attributes) = interner.try_module_attributes(parent_module_id) else { + break; + }; + + segments.push(&parent_attributes.name); + current_attributes = parent_attributes; + } + } + + if !is_relative { + // We don't record module attributes for the root module, + // so we handle that case separately + if target_module_id.krate.is_root() { + segments.push("crate"); + } + } + + segments.reverse(); + segments.join("::") +} + +pub fn module_full_path( + module: &ModuleId, + interner: &NodeInterner, + crate_id: CrateId, + crate_name: &str, + dependencies: &Vec, +) -> String { + let mut segments: Vec = Vec::new(); + + if let Some(module_attributes) = interner.try_module_attributes(module) { + segments.push(module_attributes.name.clone()); + + let mut current_attributes = module_attributes; + loop { + let Some(parent_local_id) = current_attributes.parent else { + break; + }; + + let Some(parent_attributes) = interner.try_module_attributes(&ModuleId { + krate: module.krate, + local_id: parent_local_id, + }) else { + break; + }; + + segments.push(parent_attributes.name.clone()); + current_attributes = parent_attributes; + } + } + + // We don't record module attributes for the root module, + // so we handle that case separately + if module.krate.is_root() { + if module.krate == crate_id { + segments.push(crate_name.to_string()); + } else { + for dep in dependencies { + if dep.crate_id == crate_id { + segments.push(dep.name.to_string()); + break; + } + } + } + }; + + segments.reverse(); + segments.join("::") +} + +/// Returns the relative path to reach `module_def_id` named `name` starting from `current_module_id`. +/// +/// - `defining_module` might be `Some` if the item is reexported from another module +/// - `intermediate_name` might be `Some` if the item's parent module is reexport from another module +/// (this will be the name of the reexport) +/// +/// Returns `None` if `module_def_id` isn't visible from the current module, neither directly, neither via +/// any of its reexports (or parent module reexports). +pub fn module_def_id_relative_path( + module_def_id: ModuleDefId, + name: &str, + current_module_id: ModuleId, + current_module_parent_id: Option, + defining_module: Option, + intermediate_name: &Option, + interner: &NodeInterner, +) -> Option { + let module_path = if let Some(defining_module) = defining_module { + relative_module_id_path( + defining_module, + current_module_id, + current_module_parent_id, + interner, + ) + } else { + relative_module_full_path( + module_def_id, + current_module_id, + current_module_parent_id, + interner, + )? + }; + + let path = if defining_module.is_some() || !matches!(module_def_id, ModuleDefId::ModuleId(..)) { + if let Some(reexport_name) = &intermediate_name { + format!("{}::{}::{}", module_path, reexport_name, name) + } else { + format!("{}::{}", module_path, name) + } + } else { + module_path.clone() + }; + + Some(path) +} diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 39175b2de30..0713eaf7165 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -1921,7 +1921,7 @@ impl<'interner> Monomorphizer<'interner> { let element_type = Self::convert_type(&typ, location)?; ast::LValue::Index { array, index, element_type, location } } - HirLValue::Dereference { lvalue, element_type, location } => { + HirLValue::Dereference { lvalue, element_type, location, implicitly_added: _ } => { let reference = Box::new(self.lvalue(*lvalue)?); let element_type = Self::convert_type(&element_type, location)?; ast::LValue::Dereference { reference, element_type } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 865c5bd28ba..2c0a69dc3a6 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::collections::HashSet; use std::fmt; use std::hash::Hash; use std::marker::Copy; @@ -1461,6 +1462,14 @@ impl NodeInterner { self.trait_implementations[&id].clone() } + pub fn get_trait_implementations_in_crate(&self, crate_id: CrateId) -> HashSet { + let trait_impls = self.trait_implementations.iter(); + let trait_impls = trait_impls.filter_map(|(id, trait_impl)| { + if trait_impl.borrow().crate_id == crate_id { Some(*id) } else { None } + }); + trait_impls.collect() + } + /// If the given function belongs to a trait impl, return its trait method id. /// Otherwise, return None. pub fn get_trait_method_id(&self, function: FuncId) -> Option { diff --git a/compiler/noirc_frontend/src/shared/visibility.rs b/compiler/noirc_frontend/src/shared/visibility.rs index 715732c3f8d..6a0b488ea1c 100644 --- a/compiler/noirc_frontend/src/shared/visibility.rs +++ b/compiler/noirc_frontend/src/shared/visibility.rs @@ -20,8 +20,8 @@ impl std::fmt::Display for Visibility { match self { Self::Public => write!(f, "pub"), Self::Private => write!(f, "priv"), - Self::CallData(id) => write!(f, "calldata{id}"), - Self::ReturnData => write!(f, "returndata"), + Self::CallData(id) => write!(f, "call_data({id})"), + Self::ReturnData => write!(f, "return_data"), } } } diff --git a/tooling/lsp/src/attribute_reference_finder.rs b/tooling/lsp/src/attribute_reference_finder.rs index 9f40d5cb3d2..8bd8bc5b827 100644 --- a/tooling/lsp/src/attribute_reference_finder.rs +++ b/tooling/lsp/src/attribute_reference_finder.rs @@ -16,14 +16,13 @@ use noirc_frontend::{ def_map::{CrateDefMap, LocalModuleId, ModuleId}, resolution::import::resolve_import, }, + modules::module_def_id_to_reference_id, node_interner::ReferenceId, parser::ParsedSubModule, token::{MetaAttribute, MetaAttributeName}, usage_tracker::UsageTracker, }; -use crate::modules::module_def_id_to_reference_id; - pub(crate) struct AttributeReferenceFinder<'a> { byte_index: usize, /// The module ID in scope. This might change as we traverse the AST diff --git a/tooling/lsp/src/modules.rs b/tooling/lsp/src/modules.rs index efad487100b..81d8d3f8eb1 100644 --- a/tooling/lsp/src/modules.rs +++ b/tooling/lsp/src/modules.rs @@ -4,168 +4,12 @@ use noirc_frontend::{ ast::{Ident, ItemVisibility}, graph::{CrateId, Dependency}, hir::def_map::{CrateDefMap, ModuleDefId, ModuleId}, - node_interner::{NodeInterner, Reexport, ReferenceId}, + modules::get_parent_module, + node_interner::{NodeInterner, Reexport}, }; use crate::visibility::module_def_id_is_visible; -pub(crate) fn get_parent_module( - interner: &NodeInterner, - module_def_id: ModuleDefId, -) -> Option { - let reference_id = module_def_id_to_reference_id(module_def_id); - interner.reference_module(reference_id).copied() -} - -pub(crate) fn module_def_id_to_reference_id(module_def_id: ModuleDefId) -> ReferenceId { - match module_def_id { - ModuleDefId::ModuleId(id) => ReferenceId::Module(id), - ModuleDefId::FunctionId(id) => ReferenceId::Function(id), - ModuleDefId::TypeId(id) => ReferenceId::Type(id), - ModuleDefId::TypeAliasId(id) => ReferenceId::Alias(id), - ModuleDefId::TraitId(id) => ReferenceId::Trait(id), - ModuleDefId::GlobalId(id) => ReferenceId::Global(id), - } -} - -/// Returns the fully-qualified path of the given `ModuleDefId` relative to `current_module_id`: -/// - If `ModuleDefId` is a module, that module's path is returned -/// - Otherwise, that item's parent module's path is returned -pub(crate) fn relative_module_full_path( - module_def_id: ModuleDefId, - current_module_id: ModuleId, - current_module_parent_id: Option, - interner: &NodeInterner, -) -> Option { - let full_path; - if let ModuleDefId::ModuleId(module_id) = module_def_id { - full_path = relative_module_id_path( - module_id, - current_module_id, - current_module_parent_id, - interner, - ); - } else { - let parent_module = get_parent_module(interner, module_def_id)?; - - full_path = relative_module_id_path( - parent_module, - current_module_id, - current_module_parent_id, - interner, - ); - } - Some(full_path) -} - -/// Returns the path to reach an item inside `target_module_id` from inside `current_module_id`. -/// Returns a relative path if possible. -pub(crate) fn relative_module_id_path( - target_module_id: ModuleId, - current_module_id: ModuleId, - current_module_parent_id: Option, - interner: &NodeInterner, -) -> String { - if Some(target_module_id) == current_module_parent_id { - return "super".to_string(); - } - - let mut segments: Vec<&str> = Vec::new(); - let mut is_relative = false; - - if let Some(module_attributes) = interner.try_module_attributes(&target_module_id) { - segments.push(&module_attributes.name); - - let mut current_attributes = module_attributes; - loop { - let Some(parent_local_id) = current_attributes.parent else { - break; - }; - - let parent_module_id = - &ModuleId { krate: target_module_id.krate, local_id: parent_local_id }; - - if current_module_id == *parent_module_id { - is_relative = true; - break; - } - - if current_module_parent_id == Some(*parent_module_id) { - segments.push("super"); - is_relative = true; - break; - } - - let Some(parent_attributes) = interner.try_module_attributes(parent_module_id) else { - break; - }; - - segments.push(&parent_attributes.name); - current_attributes = parent_attributes; - } - } - - if !is_relative { - // We don't record module attributes for the root module, - // so we handle that case separately - if target_module_id.krate.is_root() { - segments.push("crate"); - } - } - - segments.reverse(); - segments.join("::") -} - -pub(crate) fn module_full_path( - module: &ModuleId, - interner: &NodeInterner, - crate_id: CrateId, - crate_name: &str, - dependencies: &Vec, -) -> String { - let mut segments: Vec = Vec::new(); - - if let Some(module_attributes) = interner.try_module_attributes(module) { - segments.push(module_attributes.name.clone()); - - let mut current_attributes = module_attributes; - loop { - let Some(parent_local_id) = current_attributes.parent else { - break; - }; - - let Some(parent_attributes) = interner.try_module_attributes(&ModuleId { - krate: module.krate, - local_id: parent_local_id, - }) else { - break; - }; - - segments.push(parent_attributes.name.clone()); - current_attributes = parent_attributes; - } - } - - // We don't record module attributes for the root module, - // so we handle that case separately - if module.krate.is_root() { - if module.krate == crate_id { - segments.push(crate_name.to_string()); - } else { - for dep in dependencies { - if dep.crate_id == crate_id { - segments.push(dep.name.to_string()); - break; - } - } - } - }; - - segments.reverse(); - segments.join("::") -} - /// Finds a visible reexport for any ancestor module of the given ModuleDefId, pub(crate) fn get_ancestor_module_reexport( module_def_id: ModuleDefId, @@ -224,49 +68,3 @@ pub(crate) fn get_ancestor_module_reexport( Some(grandparent_module_reexport) } - -/// Returns the relative path to reach `module_def_id` named `name` starting from `current_module_id`. -/// -/// - `defining_module` might be `Some` if the item is reexported from another module -/// - `intermediate_name` might be `Some` if the item's parent module is reexport from another module -/// (this will be the name of the reexport) -/// -/// Returns `None` if `module_def_id` isn't visible from the current module, neither directly, neither via -/// any of its reexports (or parent module reexports). -pub(crate) fn module_def_id_relative_path( - module_def_id: ModuleDefId, - name: &str, - current_module_id: ModuleId, - current_module_parent_id: Option, - defining_module: Option, - intermediate_name: &Option, - interner: &NodeInterner, -) -> Option { - let module_path = if let Some(defining_module) = defining_module { - relative_module_id_path( - defining_module, - current_module_id, - current_module_parent_id, - interner, - ) - } else { - relative_module_full_path( - module_def_id, - current_module_id, - current_module_parent_id, - interner, - )? - }; - - let path = if defining_module.is_some() || !matches!(module_def_id, ModuleDefId::ModuleId(..)) { - if let Some(reexport_name) = &intermediate_name { - format!("{}::{}::{}", module_path, reexport_name, name) - } else { - format!("{}::{}", module_path, name) - } - } else { - module_path.clone() - }; - - Some(path) -} diff --git a/tooling/lsp/src/requests/code_action/import_or_qualify.rs b/tooling/lsp/src/requests/code_action/import_or_qualify.rs index efffacca727..2353e66d13d 100644 --- a/tooling/lsp/src/requests/code_action/import_or_qualify.rs +++ b/tooling/lsp/src/requests/code_action/import_or_qualify.rs @@ -1,10 +1,12 @@ use async_lsp::lsp_types::TextEdit; use noirc_errors::Location; -use noirc_frontend::ast::{Ident, Path}; +use noirc_frontend::{ + ast::{Ident, Path}, + modules::module_def_id_relative_path, +}; use crate::{ byte_span_to_range, - modules::module_def_id_relative_path, use_segment_positions::{ UseCompletionItemAdditionTextEditsRequest, use_completion_item_additional_text_edits, }, diff --git a/tooling/lsp/src/requests/code_action/import_trait.rs b/tooling/lsp/src/requests/code_action/import_trait.rs index 151c5d146ef..2950f499187 100644 --- a/tooling/lsp/src/requests/code_action/import_trait.rs +++ b/tooling/lsp/src/requests/code_action/import_trait.rs @@ -4,11 +4,11 @@ use noirc_errors::Location; use noirc_frontend::{ ast::MethodCallExpression, hir::def_map::ModuleDefId, + modules::module_def_id_relative_path, node_interner::{ReferenceId, TraitId}, }; use crate::{ - modules::module_def_id_relative_path, requests::TraitReexport, use_segment_positions::{ UseCompletionItemAdditionTextEditsRequest, use_completion_item_additional_text_edits, diff --git a/tooling/lsp/src/requests/completion/auto_import.rs b/tooling/lsp/src/requests/completion/auto_import.rs index cc80c8d8e01..8d1a4e21a5b 100644 --- a/tooling/lsp/src/requests/completion/auto_import.rs +++ b/tooling/lsp/src/requests/completion/auto_import.rs @@ -1,7 +1,10 @@ -use noirc_frontend::{ast::ItemVisibility, hir::def_map::ModuleDefId, node_interner::Reexport}; +use noirc_frontend::{ + ast::ItemVisibility, hir::def_map::ModuleDefId, modules::module_def_id_relative_path, + node_interner::Reexport, +}; use crate::{ - modules::{get_ancestor_module_reexport, module_def_id_relative_path}, + modules::get_ancestor_module_reexport, use_segment_positions::{ UseCompletionItemAdditionTextEditsRequest, use_completion_item_additional_text_edits, }, diff --git a/tooling/lsp/src/requests/completion/completion_items.rs b/tooling/lsp/src/requests/completion/completion_items.rs index 7ce4964d487..3c0a732c908 100644 --- a/tooling/lsp/src/requests/completion/completion_items.rs +++ b/tooling/lsp/src/requests/completion/completion_items.rs @@ -7,14 +7,12 @@ use noirc_frontend::{ ast::AttributeTarget, hir::def_map::{ModuleDefId, ModuleId}, hir_def::{function::FuncMeta, stmt::HirPattern}, + modules::{relative_module_full_path, relative_module_id_path}, node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId, TypeId}, }; -use crate::{ - modules::{relative_module_full_path, relative_module_id_path}, - use_segment_positions::{ - UseCompletionItemAdditionTextEditsRequest, use_completion_item_additional_text_edits, - }, +use crate::use_segment_positions::{ + UseCompletionItemAdditionTextEditsRequest, use_completion_item_additional_text_edits, }; use super::{ diff --git a/tooling/lsp/src/requests/hover/from_reference.rs b/tooling/lsp/src/requests/hover/from_reference.rs index ec33d2bb894..80978188a7a 100644 --- a/tooling/lsp/src/requests/hover/from_reference.rs +++ b/tooling/lsp/src/requests/hover/from_reference.rs @@ -11,6 +11,7 @@ use noirc_frontend::{ ast::ItemVisibility, hir::def_map::ModuleId, hir_def::{function::FuncMeta, stmt::HirPattern, traits::Trait}, + modules::module_full_path, node_interner::{ DefinitionId, DefinitionKind, FuncId, GlobalId, NodeInterner, ReferenceId, TraitId, TraitImplKind, TypeAliasId, TypeId, @@ -19,7 +20,6 @@ use noirc_frontend::{ use crate::{ attribute_reference_finder::AttributeReferenceFinder, - modules::module_full_path, requests::{ProcessRequestCallbackArgs, to_lsp_location}, utils, }; diff --git a/tooling/lsp/src/trait_impl_method_stub_generator.rs b/tooling/lsp/src/trait_impl_method_stub_generator.rs index e09aee79530..4aac1a1538e 100644 --- a/tooling/lsp/src/trait_impl_method_stub_generator.rs +++ b/tooling/lsp/src/trait_impl_method_stub_generator.rs @@ -9,11 +9,10 @@ use noirc_frontend::{ type_check::generics::TraitGenerics, }, hir_def::{function::FuncMeta, stmt::HirPattern, traits::Trait}, + modules::relative_module_id_path, node_interner::{FunctionModifiers, NodeInterner, ReferenceId}, }; -use crate::modules::relative_module_id_path; - pub(crate) struct TraitImplMethodStubGenerator<'a> { name: &'a str, func_meta: &'a FuncMeta, diff --git a/tooling/lsp/src/visibility.rs b/tooling/lsp/src/visibility.rs index d244bbba8cd..e09d7d95e8f 100644 --- a/tooling/lsp/src/visibility.rs +++ b/tooling/lsp/src/visibility.rs @@ -7,11 +7,10 @@ use noirc_frontend::{ def_map::{CrateDefMap, ModuleDefId, ModuleId}, resolution::visibility::item_in_module_is_visible, }, + modules::get_parent_module, node_interner::NodeInterner, }; -use crate::modules::get_parent_module; - /// Returns true if the given ModuleDefId is visible from the current module, given its visibility. /// This will in turn check if the ModuleDefId parent modules are visible from the current module. /// If `defining_module` is Some, it will be considered as the parent of the item to check diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 7d6e2a1b647..42ac578f3bc 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -41,7 +41,29 @@ fn main() { generate_compile_success_no_bug_tests(&mut test_file, &test_dir); generate_compile_success_with_bug_tests(&mut test_file, &test_dir); generate_compile_failure_tests(&mut test_file, &test_dir); + generate_fuzzing_failure_tests(&mut test_file, &test_dir); + + generate_nargo_expand_execution_success_tests(&mut test_file, &test_dir); + generate_nargo_expand_compile_tests_with_ignore_list( + "compile_success_empty", + &mut test_file, + &test_dir, + &IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_EMPTY_TESTS, + ); + generate_nargo_expand_compile_tests("compile_success_contract", &mut test_file, &test_dir); + generate_nargo_expand_compile_tests_with_ignore_list( + "compile_success_no_bug", + &mut test_file, + &test_dir, + &IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_NO_BUG_TESTS, + ); + generate_nargo_expand_compile_tests_with_ignore_list( + "compile_success_with_bug", + &mut test_file, + &test_dir, + &IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_WITH_BUG_TESTS, + ); } /// Some tests are explicitly ignored in brillig due to them failing. @@ -91,9 +113,94 @@ const TESTS_WITH_EXPECTED_WARNINGS: [&str; 4] = [ "comptime_enums", ]; +/// These tests are ignored because making them work involves a more complex test code that +/// might not be worth it. +/// Others are ignored because of existing bugs in `nargo expand`. +/// As the bugs are fixed these tests should be removed from this list. +const IGNORED_NARGO_EXPAND_EXECUTION_TESTS: [&str; 8] = [ + // There's nothing special about this program but making it work with a custom entry would involve + // having to parse the Nargo.toml file, etc., which is not worth it + "custom_entry", + // There's no "src/main.nr" here so it's trickier to make this work + "diamond_deps_0", + // There's no "src/main.nr" here so it's trickier to make this work + "overlapping_dep_and_mod", + // bug + "poseidonsponge_x5_254", + // bug + "regression_5045", + // bug + "regression_7744", + // There's no "src/main.nr" here so it's trickier to make this work + "workspace", + // There's no "src/main.nr" here so it's trickier to make this work + "workspace_default_member", +]; + /// Tests for which we don't check that stdout matches the expected output. const TESTS_WITHOUT_STDOUT_CHECK: [&str; 0] = []; +/// These tests are ignored because of existing bugs in `nargo expand`. +/// As the bugs are fixed these tests should be removed from this list. +/// (some are ignored on purpose for the same reason as `IGNORED_NARGO_EXPAND_EXECUTION_TESTS`) +const IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_EMPTY_TESTS: [&str; 15] = [ + // There's no "src/main.nr" here so it's trickier to make this work + "overlapping_dep_and_mod", + // bug + "reexports", + // bug + "regression_4436", + // bug + "regression_7038", + // bug + "regression_7038_2", + // bug + "regression_7038_3", + // bug + "regression_7038_4", + // bug + "serialize", + // bug + "trait_allowed_item_name_matches", + // bug + "trait_default_implementation", + // bug + "trait_function_calls", + // bug + "trait_method_mut_self", + // bug + "trait_override_implementation", + // bug + "trait_static_methods", + // There's no "src/main.nr" here so it's trickier to make this work + "workspace_reexport_bug", +]; + +/// These tests are ignored because of existing bugs in `nargo expand`. +/// As the bugs are fixed these tests should be removed from this list. +const IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_NO_BUG_TESTS: [&str; 17] = [ + "noirc_frontend_tests_arithmetic_generics_checked_casts_do_not_prevent_canonicalization", + "noirc_frontend_tests_check_trait_as_type_as_fn_parameter", + "noirc_frontend_tests_check_trait_as_type_as_two_fn_parameters", + "noirc_frontend_tests_enums_match_on_empty_enum", + "noirc_frontend_tests_resolves_generic_type_argument_via_self", + "noirc_frontend_tests_traits_calls_trait_function_if_it_is_in_scope", + "noirc_frontend_tests_traits_calls_trait_function_if_it_is_only_candidate_in_scope", + "noirc_frontend_tests_traits_calls_trait_function_if_it_is_only_candidate_in_scope_in_nested_module_using_super", + "noirc_frontend_tests_traits_passes_trait_with_associated_number_to_generic_function", + "noirc_frontend_tests_traits_passes_trait_with_associated_number_to_generic_function_inside_struct_impl", + "noirc_frontend_tests_traits_trait_alias_polymorphic_inheritance", + "noirc_frontend_tests_traits_trait_alias_single_member", + "noirc_frontend_tests_traits_trait_alias_two_members", + "noirc_frontend_tests_traits_trait_impl_with_where_clause_with_trait_with_associated_numeric", + "noirc_frontend_tests_traits_trait_impl_with_where_clause_with_trait_with_associated_type", + "noirc_frontend_tests_u32_globals_as_sizes_in_types", + "noirc_frontend_tests_unused_items_considers_struct_as_constructed_if_trait_method_is_called", +]; + +const IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_WITH_BUG_TESTS: [&str; 1] = + ["noirc_frontend_tests_cast_negative_one_to_u8_size_checks"]; + fn read_test_cases( test_data_dir: &Path, test_sub_dir: &str, @@ -568,3 +675,94 @@ fn generate_compile_failure_tests(test_file: &mut File, test_data_dir: &Path) { } writeln!(test_file, "}}").unwrap(); } + +/// Here we check, for every program in `test_programs/exeuction_success`, that: +/// 1. `nargo expand` works on it +/// 2. That the output of the original program is the same as the output of the expanded program +/// (that is, we run `nargo execute` on the original program and the expanded program and compare the output) +fn generate_nargo_expand_execution_success_tests(test_file: &mut File, test_data_dir: &Path) { + let test_type = "execution_success"; + let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + " +mod nargo_expand_{test_type} {{ + use super::*; + " + ) + .unwrap(); + + for (test_name, test_dir) in test_cases { + if IGNORED_NARGO_EXPAND_EXECUTION_TESTS.contains(&test_name.as_str()) { + continue; + } + + let test_dir = test_dir.display(); + + write!( + test_file, + r#" + #[test] + fn test_{test_name}() {{ + let test_program_dir = PathBuf::from("{test_dir}"); + nargo_expand_execute(test_program_dir); + }} + "# + ) + .unwrap(); + } + + writeln!(test_file, "}}").unwrap(); +} + +/// Here we check, for every program in `test_programs/{test_type}`, that: +/// 1. `nargo expand` works on it +/// 2. Compiling the output works fine +fn generate_nargo_expand_compile_tests( + test_type: &'static str, + test_file: &mut File, + test_data_dir: &Path, +) { + generate_nargo_expand_compile_tests_with_ignore_list(test_type, test_file, test_data_dir, &[]); +} + +fn generate_nargo_expand_compile_tests_with_ignore_list( + test_type: &'static str, + test_file: &mut File, + test_data_dir: &Path, + ignore: &[&str], +) { + let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + " +mod nargo_expand_{test_type} {{ + use super::*; + " + ) + .unwrap(); + + for (test_name, test_dir) in test_cases { + if ignore.contains(&test_name.as_str()) { + continue; + } + + let test_dir = test_dir.display(); + + write!( + test_file, + r#" + #[test] + fn test_{test_name}() {{ + let test_program_dir = PathBuf::from("{test_dir}"); + nargo_expand_compile(test_program_dir); + }} + "# + ) + .unwrap(); + } + + writeln!(test_file, "}}").unwrap(); +} diff --git a/tooling/nargo_cli/src/cli/expand_cmd.rs b/tooling/nargo_cli/src/cli/expand_cmd.rs new file mode 100644 index 00000000000..e16d07b2df9 --- /dev/null +++ b/tooling/nargo_cli/src/cli/expand_cmd.rs @@ -0,0 +1,105 @@ +use clap::Args; +use fm::FileManager; +use items::ItemBuilder; +use nargo::{ + errors::CompileError, insert_all_files_for_workspace_into_file_manager, + ops::check_crate_and_report_errors, package::Package, parse_all, prepare_package, + workspace::Workspace, +}; +use nargo_fmt::ImportsGranularity; +use nargo_toml::PackageSelection; +use noirc_driver::CompileOptions; +use noirc_frontend::{ + hir::{ParsedFiles, def_map::ModuleId}, + parse_program_with_dummy_file, +}; +use printer::ItemPrinter; + +use crate::errors::CliError; + +use super::{LockType, PackageOptions, WorkspaceCommand}; + +mod items; +mod printer; + +/// Show the result of macro expansion +#[derive(Debug, Clone, Args, Default)] +pub(crate) struct ExpandCommand { + #[clap(flatten)] + pub(super) package_options: PackageOptions, + + #[clap(flatten)] + compile_options: CompileOptions, +} + +impl WorkspaceCommand for ExpandCommand { + fn package_selection(&self) -> PackageSelection { + self.package_options.package_selection() + } + fn lock_type(&self) -> LockType { + // Creates a `Prover.toml` template if it doesn't exist, otherwise only writes if `allow_overwrite` is true, + // so it shouldn't lead to accidental conflicts. Doesn't produce compilation artifacts. + LockType::None + } +} + +pub(crate) fn run(args: ExpandCommand, workspace: Workspace) -> Result<(), CliError> { + let mut workspace_file_manager = workspace.new_file_manager(); + insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); + let parsed_files = parse_all(&workspace_file_manager); + + for package in &workspace { + expand_package(&workspace_file_manager, &parsed_files, package, &args.compile_options)?; + } + + Ok(()) +} + +fn expand_package( + file_manager: &FileManager, + parsed_files: &ParsedFiles, + package: &Package, + compile_options: &CompileOptions, +) -> Result<(), CompileError> { + let code = get_expanded_package(file_manager, parsed_files, package, compile_options)?; + println!("{code}"); + Ok(()) +} + +fn get_expanded_package( + file_manager: &FileManager, + parsed_files: &ParsedFiles, + package: &Package, + compile_options: &CompileOptions, +) -> Result { + let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); + + // Even though this isn't LSP, we need to active this to be able to go from a ModuleDefId to its parent module + context.activate_lsp_mode(); + + check_crate_and_report_errors(&mut context, crate_id, compile_options)?; + + let root_module_id = context.def_maps[&crate_id].root(); + let module_id = ModuleId { krate: crate_id, local_id: root_module_id }; + + let mut builder = ItemBuilder::new(crate_id, &context.def_interner, &context.def_maps); + let item = builder.build_module(module_id); + + let mut string = String::new(); + let mut printer = + ItemPrinter::new(crate_id, &context.def_interner, &context.def_maps, &mut string); + printer.show_item(item); + + let (parsed_module, errors) = parse_program_with_dummy_file(&string); + if errors.is_empty() { + let config = nargo_fmt::Config { + reorder_imports: true, + imports_granularity: ImportsGranularity::Crate, + ..Default::default() + }; + Ok(nargo_fmt::format(&string, parsed_module, &config)) + } else { + string.push_str("\n\n// Warning: the generated code has syntax errors"); + Ok(string) + } +} diff --git a/tooling/nargo_cli/src/cli/expand_cmd/items.rs b/tooling/nargo_cli/src/cli/expand_cmd/items.rs new file mode 100644 index 00000000000..e04077b0517 --- /dev/null +++ b/tooling/nargo_cli/src/cli/expand_cmd/items.rs @@ -0,0 +1,525 @@ +//! This module is responsible for building a list of items that represent the final +//! code that is going to be monomorphized and turned into SSA. +//! This final code has all macros expanded and is mainly gathered from data +//! inside `NodeInterner`, modules in `DefMaps` and function bodies from `HirExpression`s. + +use noirc_errors::Location; +use noirc_frontend::{ + Kind, NamedGeneric, Type, + ast::ItemVisibility, + hir::def_map::ModuleId, + modules::module_def_id_to_reference_id, + node_interner::{ + FuncId, GlobalId, ImplMethod, Methods, TraitId, TraitImplId, TypeAliasId, TypeId, + }, +}; +use std::collections::{HashMap, HashSet}; + +use noirc_driver::CrateId; +use noirc_frontend::{ + ast::Ident, + hir::def_map::{DefMaps, ModuleDefId}, + node_interner::NodeInterner, +}; + +pub(super) enum Item { + Module(Module), + DataType(DataType), + Trait(Trait), + TypeAlias(TypeAliasId), + Global(GlobalId), + Function(FuncId), +} + +impl Item { + pub(super) fn module_def_id(&self) -> ModuleDefId { + match self { + Item::Module(module) => ModuleDefId::ModuleId(module.id), + Item::DataType(data_type) => ModuleDefId::TypeId(data_type.id), + Item::Trait(trait_) => ModuleDefId::TraitId(trait_.id), + Item::TypeAlias(type_alias_id) => ModuleDefId::TypeAliasId(*type_alias_id), + Item::Global(global_id) => ModuleDefId::GlobalId(*global_id), + Item::Function(func_id) => ModuleDefId::FunctionId(*func_id), + } + } +} + +pub(super) struct Module { + pub(super) id: ModuleId, + pub(super) name: Option, + pub(super) is_contract: bool, + pub(super) imports: Vec, + pub(super) items: Vec<(ItemVisibility, Item)>, +} + +pub(super) struct DataType { + pub(super) id: TypeId, + pub(super) impls: Vec, + pub(super) trait_impls: Vec, +} + +pub(super) struct Trait { + pub(super) id: TraitId, + pub(super) methods: Vec, + pub(super) trait_impls: Vec, +} + +pub(super) struct Impl { + pub(super) generics: HashSet<(String, Kind)>, + pub(super) typ: Type, + pub(super) methods: Vec<(ItemVisibility, FuncId)>, +} + +pub(super) struct TraitImpl { + pub(super) generics: HashSet<(String, Kind)>, + pub(super) id: TraitImplId, + pub(super) methods: Vec, +} + +pub(super) struct Import { + pub(super) name: Ident, + pub(super) id: ModuleDefId, + pub(super) visibility: ItemVisibility, + pub(super) is_prelude: bool, +} + +pub(super) struct ItemBuilder<'interner, 'def_map> { + crate_id: CrateId, + interner: &'interner NodeInterner, + def_maps: &'def_map DefMaps, + /// This set is initially created with all the trait impls in the crate. + /// As we traverse traits, will gather trait impls associated to those traits + /// that aren't associated to types in the current crate. + /// As we find structs and enums, we'll gather trait impls associated to those types. + /// Because a trait impl might be associated to multiple types, once we link a trait + /// impl to a type we'll remove it from this set. + trait_impls: HashSet, +} + +impl<'interner, 'def_map> ItemBuilder<'interner, 'def_map> { + pub(super) fn new( + crate_id: CrateId, + interner: &'interner NodeInterner, + def_maps: &'def_map DefMaps, + ) -> Self { + let trait_impls = interner.get_trait_implementations_in_crate(crate_id); + Self { crate_id, interner, def_maps, trait_impls } + } + + pub(super) fn build_module(&mut self, module_id: ModuleId) -> Item { + let attributes = self.interner.try_module_attributes(&module_id); + let name = attributes.map(|attributes| attributes.name.clone()); + let module_data = &self.def_maps[&self.crate_id][module_id.local_id]; + let is_contract = module_data.is_contract; + + let definitions = module_data.definitions(); + + let mut definitions = definitions + .types() + .iter() + .chain(definitions.values()) + .flat_map(|(_name, scope)| scope.values()) + .map(|(module_def_id, visibility, _is_prelude)| { + let location = self.module_def_id_location(*module_def_id); + (*module_def_id, *visibility, location) + }) + .collect::>(); + + // Make sure definitions are sorted according to location so the output is more similar to the original code + definitions.sort_by_key(|(_module_def_id, _visibility, location)| *location); + + // Gather all ModuleDefId's for definitions so we can exclude them when we'll list imports now + let definitions_module_def_ids = + definitions.iter().map(|(module_def_id, ..)| *module_def_id).collect::>(); + + let scope = module_data.scope(); + let mut imports = scope + .types() + .iter() + .chain(scope.values()) + .flat_map(|(name, scope)| scope.values().map(|value| (name.clone(), value))) + .filter_map(|(name, (module_def_id, visibility, is_prelude))| { + if !definitions_module_def_ids.contains(module_def_id) { + Some(Import { + name, + id: *module_def_id, + visibility: *visibility, + is_prelude: *is_prelude, + }) + } else { + None + } + }) + .collect::>(); + + imports.sort_by_key(|import| import.name.location()); + + let items = definitions + .into_iter() + .map(|(module_def_id, visibility, _location)| { + let structure = self.build_module_def_id(module_def_id); + (visibility, structure) + }) + .collect(); + + Item::Module(Module { id: module_id, name, is_contract, imports, items }) + } + + fn module_def_id_location(&self, module_def_id: ModuleDefId) -> Location { + // We already have logic to go from a ReferenceId to a location, so we use that here + let reference_id = module_def_id_to_reference_id(module_def_id); + self.interner.reference_location(reference_id) + } + + fn build_module_def_id(&mut self, module_def_id: ModuleDefId) -> Item { + match module_def_id { + ModuleDefId::ModuleId(module_id) => self.build_module(module_id), + ModuleDefId::TypeId(type_id) => self.build_data_type(type_id), + ModuleDefId::TypeAliasId(type_alias_id) => Item::TypeAlias(type_alias_id), + ModuleDefId::TraitId(trait_id) => self.build_trait(trait_id), + ModuleDefId::GlobalId(global_id) => Item::Global(global_id), + ModuleDefId::FunctionId(func_id) => Item::Function(func_id), + } + } + + fn build_data_type(&mut self, type_id: TypeId) -> Item { + let data_type = self.interner.get_type(type_id); + + let impls = if let Some(methods) = + self.interner.get_type_methods(&Type::DataType(data_type.clone(), vec![])) + { + self.build_data_type_impls(methods.values()) + } else { + Vec::new() + }; + + let data_type = data_type.borrow(); + let trait_impls = self.build_data_type_trait_impls(&data_type); + + Item::DataType(DataType { id: type_id, impls, trait_impls }) + } + + fn build_data_type_impls<'a, 'b>( + &'a mut self, + methods: impl Iterator, + ) -> Vec { + // Gather all impl methods + // First split methods by impl methods and trait impl methods + let mut impl_methods = Vec::new(); + + for methods in methods { + impl_methods.extend(methods.direct.clone()); + } + + // Don't show enum variant functions + impl_methods.retain(|method| { + let meta = self.interner.function_meta(&method.method); + meta.enum_variant_index.is_none() + }); + + // Split them by the impl type. For example here we'll group + // all of `Foo` methods in one bucket, all of `Foo` in another, and + // all of `Foo` in another one. + #[allow(clippy::mutable_key_type)] + let mut impl_methods_by_type: HashMap> = HashMap::new(); + for method in impl_methods { + impl_methods_by_type.entry(method.typ.clone()).or_default().push(method); + } + + impl_methods_by_type + .into_iter() + .map(|(typ, methods)| self.build_impl(typ, methods)) + .collect() + } + + fn build_impl(&mut self, typ: Type, methods: Vec) -> Impl { + let mut generics = HashSet::new(); + gather_named_type_vars(&typ, &mut generics); + + let mut methods = methods + .into_iter() + .map(|method| { + let func_id = method.method; + let func_meta = self.interner.function_meta(&func_id); + let modifiers = self.interner.function_modifiers(&func_id); + let location = func_meta.name.location; + (modifiers.visibility, func_id, location) + }) + .collect::>(); + + methods.sort_by_key(|(_, _, location)| *location); + + let methods = + methods.into_iter().map(|(visibility, func_id, _)| (visibility, func_id)).collect(); + + Impl { generics, typ, methods } + } + + fn build_data_type_trait_impls( + &mut self, + data_type: &noirc_frontend::DataType, + ) -> Vec { + let mut trait_impls = self + .trait_impls + .iter() + .filter_map(|trait_impl_id| { + let trait_impl = self.interner.get_trait_implementation(*trait_impl_id); + let trait_impl = trait_impl.borrow(); + if type_mentions_data_type(&trait_impl.typ, data_type) { + Some((*trait_impl_id, trait_impl.location)) + } else { + None + } + }) + .collect::>(); + + trait_impls.sort_by_key(|(_trait_impl_id, location)| *location); + + trait_impls.into_iter().map(|(trait_impl, _)| self.build_trait_impl(trait_impl)).collect() + } + + /// Builds trait impls for traits, but only when those impls are + /// for types outside of the current crate as they are likely defined next + /// to the trait. + fn build_trait_impls_for_trait(&mut self, trait_id: TraitId) -> Vec { + let mut trait_impls = self + .trait_impls + .iter() + .filter_map(|trait_impl_id| { + let trait_impl = self.interner.get_trait_implementation(*trait_impl_id); + let trait_impl = trait_impl.borrow(); + if trait_impl.trait_id == trait_id + && self.type_only_mention_types_outside_current_crate(&trait_impl.typ) + { + Some((*trait_impl_id, trait_impl.location)) + } else { + None + } + }) + .collect::>(); + + trait_impls.sort_by_key(|(_trait_impl_id, location)| *location); + + trait_impls.into_iter().map(|(trait_impl, _)| self.build_trait_impl(trait_impl)).collect() + } + + fn build_trait_impl(&mut self, trait_impl_id: TraitImplId) -> TraitImpl { + // Remove the trait impl from the set so we don't show it again + self.trait_impls.remove(&trait_impl_id); + + let trait_impl = self.interner.get_trait_implementation(trait_impl_id); + let trait_impl = trait_impl.borrow(); + + let mut type_var_names = HashSet::new(); + for generic in &trait_impl.trait_generics { + gather_named_type_vars(generic, &mut type_var_names); + } + gather_named_type_vars(&trait_impl.typ, &mut type_var_names); + + TraitImpl { + generics: type_var_names, + id: trait_impl_id, + methods: trait_impl.methods.clone(), + } + } + + fn build_trait(&mut self, trait_id: TraitId) -> Item { + let trait_ = self.interner.get_trait(trait_id); + + let mut func_ids = trait_ + .method_ids + .values() + .map(|func_id| { + let location = self.interner.function_meta(func_id).location; + (func_id, location) + }) + .collect::>(); + + // Make sure functions are shown in the same order they were defined + func_ids.sort_by_key(|(_func_id, location)| *location); + + let methods = func_ids.into_iter().map(|(func_id, _)| *func_id).collect(); + let trait_impls = self.build_trait_impls_for_trait(trait_id); + + Item::Trait(Trait { id: trait_id, methods, trait_impls }) + } + + fn type_only_mention_types_outside_current_crate(&self, typ: &Type) -> bool { + match typ { + Type::Array(length, typ) => { + self.type_only_mention_types_outside_current_crate(length) + && self.type_only_mention_types_outside_current_crate(typ) + } + Type::Slice(typ) => self.type_only_mention_types_outside_current_crate(typ), + Type::FmtString(length, typ) => { + self.type_only_mention_types_outside_current_crate(length) + && self.type_only_mention_types_outside_current_crate(typ) + } + Type::Tuple(types) => { + types.iter().all(|typ| self.type_only_mention_types_outside_current_crate(typ)) + } + Type::DataType(data_type, generics) => { + let data_type = data_type.borrow(); + data_type.id.krate() != self.crate_id + && generics + .iter() + .all(|typ| self.type_only_mention_types_outside_current_crate(typ)) + } + Type::Alias(_type_alias, generics) => { + generics.iter().all(|typ| self.type_only_mention_types_outside_current_crate(typ)) + } + Type::TraitAsType(trait_id, _, generics) => { + let trait_ = self.interner.get_trait(*trait_id); + trait_.id.0.krate != self.crate_id + && generics + .ordered + .iter() + .all(|typ| self.type_only_mention_types_outside_current_crate(typ)) + && generics.named.iter().all(|named_type| { + self.type_only_mention_types_outside_current_crate(&named_type.typ) + }) + } + Type::CheckedCast { from, to: _ } => { + self.type_only_mention_types_outside_current_crate(from) + } + Type::Function(args, ret, env, _) => { + args.iter().all(|typ| self.type_only_mention_types_outside_current_crate(typ)) + && self.type_only_mention_types_outside_current_crate(ret) + && self.type_only_mention_types_outside_current_crate(env) + } + Type::Reference(typ, _) => self.type_only_mention_types_outside_current_crate(typ), + Type::Forall(_, typ) => self.type_only_mention_types_outside_current_crate(typ), + Type::InfixExpr(lhs, _, rhs, _) => { + self.type_only_mention_types_outside_current_crate(lhs) + && self.type_only_mention_types_outside_current_crate(rhs) + } + Type::Unit + | Type::Bool + | Type::Integer(..) + | Type::FieldElement + | Type::String(_) + | Type::Quoted(_) + | Type::Constant(..) + | Type::TypeVariable(..) + | Type::NamedGeneric(..) + | Type::Error => true, + } + } +} + +fn gather_named_type_vars(typ: &Type, type_vars: &mut HashSet<(String, Kind)>) { + match typ { + Type::Array(length, typ) => { + gather_named_type_vars(length, type_vars); + gather_named_type_vars(typ, type_vars); + } + Type::Slice(typ) => { + gather_named_type_vars(typ, type_vars); + } + Type::FmtString(length, typ) => { + gather_named_type_vars(length, type_vars); + gather_named_type_vars(typ, type_vars); + } + Type::Tuple(types) => { + for typ in types { + gather_named_type_vars(typ, type_vars); + } + } + Type::DataType(_, generics) | Type::Alias(_, generics) => { + for typ in generics { + gather_named_type_vars(typ, type_vars); + } + } + Type::TraitAsType(_, _, trait_generics) => { + for typ in &trait_generics.ordered { + gather_named_type_vars(typ, type_vars); + } + for named_type in &trait_generics.named { + gather_named_type_vars(&named_type.typ, type_vars); + } + } + Type::NamedGeneric(NamedGeneric { type_var, name, .. }) => { + type_vars.insert((name.to_string(), type_var.kind())); + } + Type::CheckedCast { from, to: _ } => { + gather_named_type_vars(from, type_vars); + } + Type::Function(args, ret, env, _) => { + for typ in args { + gather_named_type_vars(typ, type_vars); + } + gather_named_type_vars(ret, type_vars); + gather_named_type_vars(env, type_vars); + } + Type::Reference(typ, _) => { + gather_named_type_vars(typ, type_vars); + } + Type::Forall(_, typ) => { + gather_named_type_vars(typ, type_vars); + } + Type::InfixExpr(lhs, _, rhs, _) => { + gather_named_type_vars(lhs, type_vars); + gather_named_type_vars(rhs, type_vars); + } + Type::String(typ) => { + gather_named_type_vars(typ, type_vars); + } + Type::Unit + | Type::FieldElement + | Type::Integer(..) + | Type::Bool + | Type::Quoted(_) + | Type::Constant(..) + | Type::TypeVariable(_) + | Type::Error => (), + } +} + +fn type_mentions_data_type(typ: &Type, data_type: &noirc_frontend::DataType) -> bool { + match typ { + Type::Array(length, typ) => { + type_mentions_data_type(length, data_type) || type_mentions_data_type(typ, data_type) + } + Type::Slice(typ) => type_mentions_data_type(typ, data_type), + Type::FmtString(length, typ) => { + type_mentions_data_type(length, data_type) || type_mentions_data_type(typ, data_type) + } + Type::Tuple(types) => types.iter().any(|typ| type_mentions_data_type(typ, data_type)), + Type::DataType(other_data_type, generics) => { + let other_data_type = other_data_type.borrow(); + data_type.id == other_data_type.id + || generics.iter().any(|typ| type_mentions_data_type(typ, data_type)) + } + Type::Alias(_type_alias, generics) => { + generics.iter().any(|typ| type_mentions_data_type(typ, data_type)) + } + Type::TraitAsType(_, _, generics) => { + generics.ordered.iter().any(|typ| type_mentions_data_type(typ, data_type)) + || generics + .named + .iter() + .any(|named_type| type_mentions_data_type(&named_type.typ, data_type)) + } + Type::CheckedCast { from: _, to } => type_mentions_data_type(to, data_type), + Type::Function(args, ret, env, _) => { + args.iter().any(|typ| type_mentions_data_type(typ, data_type)) + || type_mentions_data_type(ret, data_type) + || type_mentions_data_type(env, data_type) + } + Type::Reference(typ, _) => type_mentions_data_type(typ, data_type), + Type::Forall(_, typ) => type_mentions_data_type(typ, data_type), + Type::InfixExpr(lhs, _, rhs, _) => { + type_mentions_data_type(lhs, data_type) || type_mentions_data_type(rhs, data_type) + } + Type::Unit + | Type::Bool + | Type::Integer(..) + | Type::FieldElement + | Type::String(_) + | Type::Quoted(_) + | Type::Constant(..) + | Type::TypeVariable(..) + | Type::NamedGeneric(..) + | Type::Error => false, + } +} diff --git a/tooling/nargo_cli/src/cli/expand_cmd/printer.rs b/tooling/nargo_cli/src/cli/expand_cmd/printer.rs new file mode 100644 index 00000000000..08defd0cb89 --- /dev/null +++ b/tooling/nargo_cli/src/cli/expand_cmd/printer.rs @@ -0,0 +1,1111 @@ +use std::collections::{HashMap, HashSet}; + +use noirc_driver::CrateId; +use noirc_frontend::{ + DataType, Generics, Kind, NamedGeneric, Type, + ast::{Ident, ItemVisibility}, + hir::{ + comptime::{Value, tokens_to_string_with_indent}, + def_map::{DefMaps, ModuleDefId, ModuleId}, + type_check::generics::TraitGenerics, + }, + hir_def::{ + expr::HirExpression, + stmt::{HirLetStatement, HirPattern}, + traits::{ResolvedTraitBound, TraitConstraint}, + }, + modules::{module_def_id_to_reference_id, relative_module_full_path}, + node_interner::{FuncId, GlobalId, GlobalValue, NodeInterner, ReferenceId, TypeAliasId}, + shared::Visibility, + token::{FunctionAttributeKind, LocatedToken, SecondaryAttribute, SecondaryAttributeKind}, +}; + +use super::items::{Impl, Import, Item, Module, TraitImpl}; + +mod hir; +mod types; + +pub(super) struct ItemPrinter<'interner, 'def_map, 'string> { + crate_id: CrateId, + interner: &'interner NodeInterner, + def_maps: &'def_map DefMaps, + string: &'string mut String, + indent: usize, + module_id: ModuleId, + imports: HashMap, + self_type: Option, +} + +impl<'interner, 'def_map, 'string> ItemPrinter<'interner, 'def_map, 'string> { + pub(super) fn new( + crate_id: CrateId, + interner: &'interner NodeInterner, + def_maps: &'def_map DefMaps, + string: &'string mut String, + ) -> Self { + let root_id = def_maps[&crate_id].root(); + let module_id = ModuleId { krate: crate_id, local_id: root_id }; + let imports = HashMap::new(); + Self { + crate_id, + interner, + def_maps, + string, + indent: 0, + module_id, + imports, + self_type: None, + } + } + + pub(super) fn show_item(&mut self, item: Item) { + match item { + Item::Module(module) => self.show_module(module), + Item::DataType(data_type) => self.show_data_type(data_type), + Item::Trait(trait_) => self.show_trait(trait_), + Item::TypeAlias(type_alias_id) => self.show_type_alias(type_alias_id), + Item::Global(global_id) => self.show_global(global_id), + Item::Function(func_id) => self.show_function(func_id), + } + } + + fn show_module(&mut self, module: Module) { + let module_id = module.id; + + if let Some(name) = &module.name { + if module.is_contract { + self.push_str("contract "); + } else { + self.push_str("mod "); + } + self.push_str(name); + self.push_str(" {"); + self.increase_indent(); + } + + let previous_module_id = self.module_id; + self.module_id = module_id; + + let previous_imports = std::mem::take(&mut self.imports); + + self.imports = + module.imports.iter().map(|import| (import.id, import.name.clone())).collect(); + + self.show_imports(module.imports); + + for (index, (visibility, item)) in module.items.into_iter().enumerate() { + if index == 0 { + self.push_str("\n"); + } else { + self.push_str("\n\n"); + } + self.write_indent(); + self.show_item_with_visibility(item, visibility); + } + + self.module_id = previous_module_id; + self.imports = previous_imports; + + if module.name.is_some() { + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push_str("}"); + } + } + + fn show_item_with_visibility(&mut self, item: Item, visibility: ItemVisibility) { + let module_def_id = item.module_def_id(); + let reference_id = module_def_id_to_reference_id(module_def_id); + self.show_doc_comments(reference_id); + self.show_module_def_id_attributes(module_def_id); + self.show_item_visibility(visibility); + self.show_item(item); + } + + fn show_doc_comments(&mut self, reference_id: ReferenceId) { + let Some(doc_comments) = self.interner.doc_comments(reference_id) else { + return; + }; + + for comment in doc_comments { + if comment.contains('\n') { + let ends_with_newline = comment.ends_with('\n'); + + self.push_str("/**"); + for (index, line) in comment.lines().enumerate() { + if index != 0 { + self.push('\n'); + self.write_indent(); + } + self.push_str(line); + } + + if ends_with_newline { + self.push('\n'); + self.write_indent(); + } + + self.push_str("*/"); + } else { + self.push_str("///"); + self.push_str(comment); + } + self.push('\n'); + self.write_indent(); + } + } + + fn show_module_def_id_attributes(&mut self, module_def_id: ModuleDefId) { + match module_def_id { + ModuleDefId::FunctionId(func_id) => { + let modifiers = self.interner.function_modifiers(&func_id); + if let Some(attribute) = modifiers.attributes.function() { + self.push_str(&attribute.to_string()); + self.push('\n'); + self.write_indent(); + } + self.show_secondary_attributes(&modifiers.attributes.secondary); + } + ModuleDefId::TypeId(type_id) => { + self.show_secondary_attributes(self.interner.type_attributes(&type_id)); + } + ModuleDefId::GlobalId(global_id) => { + self.show_secondary_attributes(self.interner.global_attributes(&global_id)); + } + ModuleDefId::ModuleId(..) | ModuleDefId::TypeAliasId(..) | ModuleDefId::TraitId(..) => { + } + } + } + + fn show_secondary_attributes(&mut self, attributes: &[SecondaryAttribute]) { + for attribute in attributes { + if !matches!(attribute.kind, SecondaryAttributeKind::Meta(..)) { + self.push_str(&attribute.to_string()); + self.push('\n'); + self.write_indent(); + } + } + } + + fn show_item_visibility(&mut self, visibility: ItemVisibility) { + if visibility != ItemVisibility::Private { + self.push_str(&visibility.to_string()); + self.push(' '); + }; + } + + fn show_visibility(&mut self, visibility: Visibility) { + if visibility != Visibility::Private { + self.push_str(&visibility.to_string()); + self.push(' '); + } + } + + fn show_data_type(&mut self, item_data_type: super::items::DataType) { + let type_id = item_data_type.id; + let shared_data_type = self.interner.get_type(type_id); + let data_type = shared_data_type.borrow(); + if data_type.is_struct() { + self.show_struct(&data_type); + } else if data_type.is_enum() { + self.show_enum(&data_type); + } else { + unreachable!("DataType should either be a struct or an enum") + } + drop(data_type); + + self.show_data_type_impls(item_data_type.impls); + self.show_trait_impls(item_data_type.trait_impls); + } + + fn show_struct(&mut self, data_type: &DataType) { + self.push_str("struct "); + self.push_str(&data_type.name.to_string()); + self.show_generics(&data_type.generics); + self.push_str(" {\n"); + self.increase_indent(); + for (index, field) in data_type.get_fields_as_written().unwrap().into_iter().enumerate() { + self.write_indent(); + self.show_doc_comments(ReferenceId::StructMember(data_type.id, index)); + self.push_str(&field.name.to_string()); + self.push_str(": "); + self.show_type(&field.typ); + self.push_str(",\n"); + } + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + + fn show_enum(&mut self, data_type: &DataType) { + self.push_str("enum "); + self.push_str(&data_type.name.to_string()); + self.show_generics(&data_type.generics); + self.push_str(" {\n"); + self.increase_indent(); + for (index, variant) in data_type.get_variants_as_written().unwrap().into_iter().enumerate() + { + self.write_indent(); + self.show_doc_comments(ReferenceId::EnumVariant(data_type.id, index)); + self.push_str(&variant.name.to_string()); + if variant.is_function { + self.push('('); + for (index, typ) in variant.params.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_type(typ); + } + self.push(')'); + } + self.push_str(",\n"); + } + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + + fn show_data_type_impls(&mut self, impls: Vec) { + for impl_ in impls { + self.push_str("\n\n"); + self.write_indent(); + self.show_impl(impl_); + } + } + + fn show_impl(&mut self, impl_: Impl) { + let typ = impl_.typ; + + self.push_str("impl"); + self.show_generic_type_variables(&impl_.generics); + self.push(' '); + self.show_type(&typ); + self.push_str(" {\n"); + self.increase_indent(); + + self.self_type = Some(typ.clone()); + + for (index, (visibility, func_id)) in impl_.methods.iter().enumerate() { + if index != 0 { + self.push_str("\n\n"); + } + self.write_indent(); + + let item = Item::Function(*func_id); + self.show_item_with_visibility(item, *visibility); + } + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + + self.self_type = None; + } + + fn show_trait_impls(&mut self, trait_impls: Vec) { + for trait_impl in trait_impls { + self.push_str("\n\n"); + self.write_indent(); + self.show_trait_impl(trait_impl); + } + } + + fn show_type_alias(&mut self, type_alias_id: TypeAliasId) { + let type_alias = self.interner.get_type_alias(type_alias_id); + let type_alias = type_alias.borrow(); + + self.push_str("type "); + self.push_str(&type_alias.name.to_string()); + self.show_generics(&type_alias.generics); + self.push_str(" = "); + self.show_type(&type_alias.typ); + self.push(';'); + } + + fn show_trait(&mut self, item_trait: super::items::Trait) { + let trait_id = item_trait.id; + let trait_ = self.interner.get_trait(trait_id); + + self.push_str("trait "); + self.push_str(&trait_.name.to_string()); + self.show_generics(&trait_.generics); + + if !trait_.trait_bounds.is_empty() { + self.push_str(": "); + for (index, trait_bound) in trait_.trait_bounds.iter().enumerate() { + if index != 0 { + self.push_str(" + "); + } + self.show_trait_bound(trait_bound); + } + } + + self.show_where_clause(&trait_.where_clause); + self.push_str(" {\n"); + self.increase_indent(); + + let mut printed_type_or_function = false; + + for associated_type in &trait_.associated_types { + if printed_type_or_function { + self.push_str("\n\n"); + } + + self.write_indent(); + self.push_str("type "); + self.push_str(&associated_type.name); + self.push_str(";"); + printed_type_or_function = true; + } + + for func_id in item_trait.methods { + if printed_type_or_function { + self.push_str("\n\n"); + } + + self.write_indent(); + + let item = Item::Function(func_id); + let visibility = ItemVisibility::Private; + self.show_item_with_visibility(item, visibility); + printed_type_or_function = true; + } + + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + + self.show_trait_impls(item_trait.trait_impls); + } + + fn show_trait_impl(&mut self, item_trait_impl: TraitImpl) { + let trait_impl_id = item_trait_impl.id; + + let trait_impl = self.interner.get_trait_implementation(trait_impl_id); + let trait_impl = trait_impl.borrow(); + let trait_ = self.interner.get_trait(trait_impl.trait_id); + + self.push_str("impl"); + self.show_generic_type_variables(&item_trait_impl.generics); + self.push(' '); + + let use_import = true; + self.show_reference_to_module_def_id(ModuleDefId::TraitId(trait_.id), use_import); + + let use_colons = false; + self.show_generic_types(&trait_impl.trait_generics, use_colons); + + self.push_str(" for "); + self.show_type(&trait_impl.typ); + self.show_where_clause(&trait_impl.where_clause); + self.push_str(" {\n"); + self.increase_indent(); + + self.self_type = Some(trait_impl.typ.clone()); + + let mut printed_item = false; + + let named = self.interner.get_associated_types_for_impl(trait_impl_id); + for named_type in named { + if printed_item { + self.push_str("\n\n"); + } + + self.write_indent(); + self.push_str("type "); + self.push_str(&named_type.name.to_string()); + self.push_str(" = "); + self.show_type(&named_type.typ); + self.push_str(";"); + + printed_item = true; + } + + for method in item_trait_impl.methods { + if printed_item { + self.push_str("\n\n"); + } + self.write_indent(); + + let item = Item::Function(method); + let visibility = ItemVisibility::Private; + self.show_item_with_visibility(item, visibility); + + printed_item = true; + } + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + + self.self_type = None; + } + + fn show_global(&mut self, global_id: GlobalId) { + let global_info = self.interner.get_global(global_id); + let definition_id = global_info.definition_id; + let definition = self.interner.definition(definition_id); + let typ = self.interner.definition_type(definition_id); + + if let Some(HirLetStatement { comptime: true, .. }) = + self.interner.get_global_let_statement(global_id) + { + self.push_str("comptime "); + } + if definition.mutable { + self.push_str("mut "); + } + self.push_str("global "); + self.push_str(&global_info.ident.to_string()); + self.push_str(": "); + self.show_type(&typ); + if let GlobalValue::Resolved(value) = &global_info.value { + self.push_str(" = "); + self.show_value(value); + }; + self.push_str(";"); + } + + fn show_function(&mut self, func_id: FuncId) { + let modifiers = self.interner.function_modifiers(&func_id); + let func_meta = self.interner.function_meta(&func_id); + + if modifiers.is_unconstrained { + self.push_str("unconstrained "); + } + if modifiers.is_comptime { + self.push_str("comptime "); + } + + self.push_str("fn "); + self.push_str(&modifiers.name); + + self.show_generics(&func_meta.direct_generics); + + self.push('('); + let parameters = &func_meta.parameters; + for (index, (pattern, typ, visibility)) in parameters.iter().enumerate() { + let is_self = self.pattern_is_self(pattern); + + // `&mut self` is represented as a mutable reference type, not as a mutable pattern + if is_self && matches!(typ, Type::Reference(..)) { + self.push_str("&mut "); + } + + self.show_pattern(pattern); + + // Don't add type for `self` param + if !is_self { + self.push_str(": "); + if matches!(visibility, Visibility::Public) { + self.push_str("pub "); + } + self.show_type(typ); + } + + if index != parameters.len() - 1 { + self.push_str(", "); + } + } + self.push(')'); + + let return_type = func_meta.return_type(); + match return_type { + Type::Unit => (), + _ => { + self.push_str(" -> "); + self.show_visibility(func_meta.return_visibility); + self.show_type(return_type); + } + } + + self.show_where_clause(&func_meta.trait_constraints); + + let hir_function = self.interner.function(&func_id); + if let Some(expr) = hir_function.try_as_expr() { + let hir_expr = self.interner.expression(&expr); + if let HirExpression::Block(_) = &hir_expr { + self.push(' '); + self.show_hir_expression(hir_expr); + } else { + self.push_str(" {\n"); + self.increase_indent(); + self.write_indent(); + self.show_hir_expression(hir_expr); + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + } else { + match &modifiers.attributes.function { + Some((attribute, _)) => match attribute.kind { + FunctionAttributeKind::Foreign(_) + | FunctionAttributeKind::Builtin(_) + | FunctionAttributeKind::Oracle(_) => { + self.push_str(" {}"); + } + FunctionAttributeKind::Test(..) + | FunctionAttributeKind::FuzzingHarness(..) + | FunctionAttributeKind::Fold + | FunctionAttributeKind::NoPredicates + | FunctionAttributeKind::InlineAlways => { + self.push(';'); + } + }, + None => { + self.push(';'); + } + } + } + } + + fn show_generic_types(&mut self, types: &[Type], use_colons: bool) { + if types.is_empty() { + return; + } + if use_colons { + self.push_str("::"); + } + self.push('<'); + self.show_types_separated_by_comma(types); + self.push('>'); + } + + fn show_generics(&mut self, generics: &Generics) { + if generics.is_empty() { + return; + } + + self.push('<'); + for (index, generic) in generics.iter().enumerate() { + if index > 0 { + self.push_str(", "); + } + self.show_generic_kind(&generic.name, &generic.kind()); + } + self.push('>'); + } + + fn show_generic_kind(&mut self, name: &str, kind: &Kind) { + match kind { + Kind::Any | Kind::Normal => { + self.push_str(name); + } + Kind::IntegerOrField | Kind::Integer => { + self.push_str("let "); + self.push_str(name); + self.push_str(": u32"); + } + Kind::Numeric(typ) => { + self.push_str("let "); + self.push_str(name); + self.push_str(": "); + self.show_type(typ); + } + } + } + + fn show_trait_generics(&mut self, generics: &TraitGenerics) { + let ordered = &generics.ordered; + + // Exclude named generics that are unbound because it's the same as not including them + let named = generics + .named + .iter() + .filter(|named| { + if let Type::NamedGeneric(NamedGeneric { type_var, .. }) = &named.typ { + if type_var.borrow().is_unbound() { + return false; + } + } + + true + }) + .collect::>(); + + if ordered.is_empty() && named.is_empty() { + return; + } + + let mut printed_type = false; + + self.push('<'); + + for typ in ordered { + if printed_type { + self.push_str(", "); + } + + self.show_type(typ); + printed_type = true; + } + + for named_type in named { + if printed_type { + self.push_str(", "); + } + + self.push_str(&named_type.name.to_string()); + self.push_str(" = "); + self.show_type(&named_type.typ); + printed_type = true; + } + + self.push('>'); + } + + fn show_generic_type_variables(&mut self, generics: &HashSet<(String, Kind)>) { + if generics.is_empty() { + return; + } + + self.push('<'); + for (index, (name, kind)) in generics.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_generic_kind(name, kind); + } + self.push('>'); + } + + fn show_where_clause(&mut self, constraints: &[TraitConstraint]) { + if constraints.is_empty() { + return; + } + + self.push_str(" where "); + for (index, constraint) in constraints.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_type(&constraint.typ); + self.push_str(": "); + self.show_trait_bound(&constraint.trait_bound); + } + } + + fn show_trait_bound(&mut self, bound: &ResolvedTraitBound) { + let trait_ = self.interner.get_trait(bound.trait_id); + self.push_str(&trait_.name.to_string()); + self.show_trait_generics(&bound.trait_generics); + } + + fn show_pattern(&mut self, pattern: &HirPattern) { + match pattern { + HirPattern::Identifier(ident) => { + let definition = self.interner.definition(ident.id); + self.push_str(&definition.name); + } + HirPattern::Mutable(pattern, _) => { + self.push_str("mut "); + self.show_pattern(pattern); + } + HirPattern::Tuple(patterns, _) => { + let len = patterns.len(); + self.push('('); + for (index, pattern) in patterns.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_pattern(pattern); + } + if len == 1 { + self.push(','); + } + self.push(')'); + } + HirPattern::Struct(typ, fields, _) => { + self.show_type_name_as_data_type(typ); + self.push_str(" { "); + for (index, (name, pattern)) in fields.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.push_str(name.as_str()); + self.push_str(": "); + self.show_pattern(pattern); + } + + self.push_str(" }"); + } + } + } + + fn show_value(&mut self, value: &Value) { + match value { + Value::Unit => self.push_str("()"), + Value::Bool(bool) => self.push_str(&bool.to_string()), + Value::Field(value) => self.push_str(&value.to_string()), + Value::I8(value) => self.push_str(&value.to_string()), + Value::I16(value) => self.push_str(&value.to_string()), + Value::I32(value) => self.push_str(&value.to_string()), + Value::I64(value) => self.push_str(&value.to_string()), + Value::U1(value) => self.push_str(&value.to_string()), + Value::U8(value) => self.push_str(&value.to_string()), + Value::U16(value) => self.push_str(&value.to_string()), + Value::U32(value) => self.push_str(&value.to_string()), + Value::U64(value) => self.push_str(&value.to_string()), + Value::U128(value) => self.push_str(&value.to_string()), + Value::String(string) => self.push_str(&format!("{:?}", string)), + Value::FormatString(string, _typ) => { + // Note: at this point the format string was already expanded so we can't recover the original + // interpolation and this will result in a compile-error. But... the expanded code is meant + // to be browsed, not compiled. + self.push_str(&format!("f{:?}", string)); + } + Value::CtString(string) => { + let std = if self.crate_id.is_stdlib() { "std" } else { "crate" }; + self.push_str(&format!( + "{}::meta::ctstring::AsCtString::as_ctstring({:?})", + std, string + )); + } + Value::Function(func_id, ..) => { + let use_import = true; + self.show_reference_to_module_def_id(ModuleDefId::FunctionId(*func_id), use_import); + } + Value::Tuple(values) => { + self.push('('); + for (index, value) in values.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_value(value); + } + self.push(')'); + } + Value::Struct(fields, typ) => { + self.show_type_name_as_data_type(typ); + + if fields.is_empty() { + self.push_str(" {}"); + } else { + self.push_str(" {\n"); + self.increase_indent(); + for (name, value) in fields { + self.write_indent(); + self.push_str(name); + self.push_str(": "); + self.show_value(value); + self.push_str(",\n"); + } + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + } + Value::Enum(index, args, typ) => { + self.show_type_name_as_data_type(typ); + + let Type::DataType(data_type, _generics) = typ.follow_bindings() else { + panic!("Expected typ to be a data type"); + }; + let data_type = data_type.borrow(); + + let variant = data_type.variant_at(*index); + self.push_str("::"); + self.push_str(&variant.name.to_string()); + if variant.is_function { + self.push('('); + for (index, arg) in args.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_value(arg); + } + self.push(')'); + } + } + Value::Array(values, _) => { + self.push('['); + for (index, value) in values.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_value(value); + } + self.push(']'); + } + Value::Slice(values, _) => { + self.push_str("&["); + for (index, value) in values.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_value(value); + } + self.push(']'); + } + Value::Quoted(tokens) => { + self.show_quoted(tokens); + } + Value::Pointer(value, ..) => { + self.show_value(&value.borrow()); + } + Value::Zeroed(_) => { + let std = if self.crate_id.is_stdlib() { "std" } else { "crate" }; + self.push_str(&format!("{std}::mem::zeroed()")); + } + Value::Closure(closure) => { + self.show_hir_lambda(closure.lambda.clone()); + } + Value::TypeDefinition(_) + | Value::TraitConstraint(..) + | Value::TraitDefinition(_) + | Value::TraitImpl(_) + | Value::FunctionDefinition(_) + | Value::ModuleDefinition(_) + | Value::Type(_) + | Value::Expr(_) + | Value::TypedExpr(_) + | Value::UnresolvedType(_) => { + if self.crate_id.is_stdlib() { + self.push_str( + "crate::panic(f\"comptime value that cannot be represented with code\")", + ); + } else { + self.push_str( + "panic(f\"comptime value that cannot be represented with code\")", + ); + } + } + } + } + + fn show_type_name_as_data_type(&mut self, typ: &Type) { + if self.self_type.as_ref() == Some(typ) { + self.push_str("Self"); + return; + } + + let Type::DataType(data_type, generics) = typ.follow_bindings() else { + panic!("Expected a data type, got: {typ:?}"); + }; + + let data_type = data_type.borrow(); + let use_import = true; + self.show_reference_to_module_def_id(ModuleDefId::TypeId(data_type.id), use_import); + + let use_colons = true; + self.show_generic_types(&generics, use_colons); + } + + fn show_imports(&mut self, imports: Vec) { + let mut first = true; + + for import in imports { + if import.is_prelude { + continue; + } + + if first { + self.push('\n'); + first = false; + } + self.write_indent(); + self.show_item_visibility(import.visibility); + self.push_str("use "); + let use_import = false; + let name = self.show_reference_to_module_def_id(import.id, use_import); + + if name != import.name.as_str() { + self.push_str(" as "); + self.push_str(import.name.as_str()); + } + self.push(';'); + self.push('\n'); + } + } + + fn show_reference_to_module_def_id( + &mut self, + module_def_id: ModuleDefId, + use_import: bool, + ) -> String { + if let ModuleDefId::FunctionId(func_id) = module_def_id { + let func_meta = self.interner.function_meta(&func_id); + + if let Some(trait_impl_id) = func_meta.trait_impl { + let trait_impl = self.interner.get_trait_implementation(trait_impl_id); + let trait_impl = trait_impl.borrow(); + self.show_reference_to_module_def_id( + ModuleDefId::TraitId(trait_impl.trait_id), + use_import, + ); + + let use_colons = true; + self.show_generic_types(&trait_impl.trait_generics, use_colons); + + self.push_str("::"); + + let name = self.interner.function_name(&func_id).to_string(); + self.push_str(&name); + return name; + } + + if let Some(trait_id) = func_meta.trait_id { + self.show_reference_to_module_def_id(ModuleDefId::TraitId(trait_id), use_import); + self.push_str("::"); + + let name = self.interner.function_name(&func_id).to_string(); + self.push_str(&name); + return name; + } + + if let Some(type_id) = func_meta.type_id { + self.show_reference_to_module_def_id(ModuleDefId::TypeId(type_id), use_import); + self.push_str("::"); + + let name = self.interner.function_name(&func_id).to_string(); + self.push_str(&name); + return name; + } + + if let Some(self_type) = &func_meta.self_type { + if self_type.is_primitive() { + // Type path, like `Field::method(...)` + self.show_type(self_type); + self.push_str("::"); + + let name = self.interner.function_name(&func_id).to_string(); + self.push_str(&name); + return name; + } + } + } + + if use_import { + if let Some(name) = self.imports.get(&module_def_id) { + let name = name.to_string(); + self.push_str(&name); + return name; + } + } + + let current_module_parent_id = self.module_id.parent(self.def_maps); + if let Some(full_path) = relative_module_full_path( + module_def_id, + self.module_id, + current_module_parent_id, + self.interner, + ) { + if !full_path.is_empty() { + // `relative_module_full_path` for a module returns the full path to that module + // so we need to remove the last segment + if matches!(module_def_id, ModuleDefId::ModuleId(..)) { + let mut full_path = full_path.split("::").collect::>(); + full_path.pop(); + let full_path = full_path.join("::"); + if !full_path.is_empty() { + self.push_str(&full_path); + self.push_str("::"); + } + } else { + self.push_str(&full_path); + self.push_str("::"); + } + } + }; + + let name = self.module_def_id_name(module_def_id); + self.push_str(&name); + name + } + + fn show_quoted(&mut self, tokens: &[LocatedToken]) { + self.push_str("quote {"); + let string = tokens_to_string_with_indent(tokens, self.indent + 1, self.interner); + if string.contains('\n') { + self.push('\n'); + self.increase_indent(); + self.write_indent(); + self.push_str(string.trim()); + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + } else { + self.push(' '); + self.push_str(&string); + self.push(' '); + } + self.push_str("}"); + } + + fn pattern_is_self(&self, pattern: &HirPattern) -> bool { + match pattern { + HirPattern::Identifier(ident) => { + let definition = self.interner.definition(ident.id); + definition.name == "self" + } + HirPattern::Mutable(pattern, _) => self.pattern_is_self(pattern), + HirPattern::Tuple(..) | HirPattern::Struct(..) => false, + } + } + + fn module_def_id_name(&self, module_def_id: ModuleDefId) -> String { + match module_def_id { + ModuleDefId::ModuleId(module_id) => { + let attributes = self.interner.try_module_attributes(&module_id); + let name = attributes.map(|attributes| &attributes.name); + name.cloned().expect("All modules should have a name") + } + ModuleDefId::FunctionId(func_id) => self.interner.function_name(&func_id).to_string(), + ModuleDefId::TypeId(type_id) => { + let data_type = self.interner.get_type(type_id); + let data_type = data_type.borrow(); + data_type.name.to_string() + } + ModuleDefId::TypeAliasId(type_alias_id) => { + let type_alias = self.interner.get_type_alias(type_alias_id); + let type_alias = type_alias.borrow(); + type_alias.name.to_string() + } + ModuleDefId::TraitId(trait_id) => { + let trait_ = self.interner.get_trait(trait_id); + trait_.name.to_string() + } + ModuleDefId::GlobalId(global_id) => { + let global_info = self.interner.get_global(global_id); + global_info.ident.to_string() + } + } + } + + fn show_separated_by_comma(&mut self, items: &[Item], f: F) + where + F: Fn(&mut Self, &Item), + { + for (index, item) in items.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + f(self, item); + } + } + + fn increase_indent(&mut self) { + self.indent += 1; + } + + fn decrease_indent(&mut self) { + self.indent -= 1; + } + + fn write_indent(&mut self) { + for _ in 0..self.indent { + self.push_str(" "); + } + } + + fn push_str(&mut self, str: &str) { + self.string.push_str(str); + } + + fn push(&mut self, char: char) { + self.string.push(char); + } +} diff --git a/tooling/nargo_cli/src/cli/expand_cmd/printer/hir.rs b/tooling/nargo_cli/src/cli/expand_cmd/printer/hir.rs new file mode 100644 index 00000000000..5bac2653a74 --- /dev/null +++ b/tooling/nargo_cli/src/cli/expand_cmd/printer/hir.rs @@ -0,0 +1,763 @@ +use noirc_frontend::{ + NamedGeneric, Type, TypeBindings, + ast::UnaryOp, + hir::def_map::ModuleDefId, + hir_def::{ + expr::{ + Constructor, HirArrayLiteral, HirBlockExpression, HirCallExpression, HirExpression, + HirIdent, HirLambda, HirLiteral, HirMatch, ImplKind, + }, + stmt::{HirLValue, HirPattern, HirStatement}, + }, + node_interner::{DefinitionId, DefinitionKind, ExprId, StmtId}, + token::FmtStrFragment, +}; + +use super::ItemPrinter; + +impl ItemPrinter<'_, '_, '_> { + fn show_hir_expression_id(&mut self, expr_id: ExprId) { + let hir_expr = self.interner.expression(&expr_id); + self.show_hir_expression(hir_expr); + } + + fn dereference_hir_expression_id(&self, expr_id: ExprId) -> ExprId { + let hir_expr = self.interner.expression(&expr_id); + let HirExpression::Prefix(prefix) = &hir_expr else { + return expr_id; + }; + + match prefix.operator { + UnaryOp::Reference { .. } | UnaryOp::Dereference { implicitly_added: true } => { + prefix.rhs + } + UnaryOp::Minus | UnaryOp::Not | UnaryOp::Dereference { implicitly_added: false } => { + expr_id + } + } + } + + fn show_hir_expression_id_maybe_inside_parens(&mut self, expr_id: ExprId) { + let hir_expr = self.interner.expression(&expr_id); + let parens = hir_expression_needs_parentheses(&hir_expr); + if parens { + self.push('('); + } + self.show_hir_expression(hir_expr); + if parens { + self.push(')'); + } + } + + fn show_hir_expression_id_maybe_inside_curlies(&mut self, expr_id: ExprId) { + let hir_expr = self.interner.expression(&expr_id); + let curlies = hir_expression_needs_parentheses(&hir_expr); + if curlies { + self.push('{'); + } + self.show_hir_expression(hir_expr); + if curlies { + self.push('}'); + } + } + + pub(super) fn show_hir_expression(&mut self, hir_expr: HirExpression) { + match hir_expr { + HirExpression::Ident(hir_ident, generics) => { + self.show_hir_ident(hir_ident); + if let Some(generics) = generics { + let use_colons = true; + self.show_generic_types(&generics, use_colons); + } + } + HirExpression::Literal(hir_literal) => { + self.show_hir_literal(hir_literal); + } + HirExpression::Block(hir_block_expression) => { + self.show_hir_block_expression(hir_block_expression); + } + HirExpression::Prefix(hir_prefix_expression) => match hir_prefix_expression.operator { + UnaryOp::Minus => { + self.push('-'); + self.show_hir_expression_id_maybe_inside_parens(hir_prefix_expression.rhs); + } + UnaryOp::Not => { + self.push('!'); + self.show_hir_expression_id_maybe_inside_parens(hir_prefix_expression.rhs); + } + UnaryOp::Reference { mutable } => { + if mutable { + self.push_str("&mut "); + } else { + self.push_str("&"); + } + self.show_hir_expression_id(hir_prefix_expression.rhs); + } + UnaryOp::Dereference { implicitly_added } => { + if implicitly_added { + self.show_hir_expression_id(hir_prefix_expression.rhs); + } else { + self.push('*'); + self.show_hir_expression_id_maybe_inside_parens(hir_prefix_expression.rhs); + } + } + }, + HirExpression::Infix(hir_infix_expression) => { + self.show_hir_expression_id_maybe_inside_parens(hir_infix_expression.lhs); + self.push(' '); + self.push_str(&hir_infix_expression.operator.kind.to_string()); + self.push(' '); + self.show_hir_expression_id_maybe_inside_parens(hir_infix_expression.rhs); + } + HirExpression::Index(hir_index_expression) => { + self.show_hir_expression_id_maybe_inside_parens(hir_index_expression.collection); + self.push('['); + self.show_hir_expression_id(hir_index_expression.index); + self.push(']'); + } + HirExpression::Constructor(hir_constructor_expression) => { + // let data_type = hir_constructor_expression.r#type.borrow(); + let typ = Type::DataType( + hir_constructor_expression.r#type.clone(), + hir_constructor_expression.struct_generics.clone(), + ); + if self.self_type.as_ref() == Some(&typ) { + self.push_str("Self"); + } else { + let data_type = hir_constructor_expression.r#type.borrow(); + + let use_import = true; + self.show_reference_to_module_def_id( + ModuleDefId::TypeId(data_type.id), + use_import, + ); + + let use_colons = true; + self.show_generic_types( + &hir_constructor_expression.struct_generics, + use_colons, + ); + } + + self.push_str(" { "); + self.show_separated_by_comma( + &hir_constructor_expression.fields, + |this, (name, value)| { + this.push_str(&name.to_string()); + this.push_str(": "); + this.show_hir_expression_id(*value); + }, + ); + self.push('}'); + } + HirExpression::EnumConstructor(constructor) => { + let data_type = constructor.r#type.borrow(); + let use_import = true; + self.show_reference_to_module_def_id(ModuleDefId::TypeId(data_type.id), use_import); + + let variant = data_type.variant_at(constructor.variant_index); + self.push_str("::"); + self.push_str(&variant.name.to_string()); + if variant.is_function { + self.push('('); + self.show_hir_expression_ids_separated_by_comma(&constructor.arguments); + self.push(')'); + } + } + HirExpression::MemberAccess(hir_member_access) => { + self.show_hir_expression_id_maybe_inside_parens(hir_member_access.lhs); + self.push('.'); + self.push_str(&hir_member_access.rhs.to_string()); + } + HirExpression::Call(hir_call_expression) => { + if self.try_show_hir_call_as_method(&hir_call_expression) { + return; + } + + let func = self.interner.expression(&hir_call_expression.func); + + // Special case: a call on a member access must have parentheses around it + if matches!(func, HirExpression::MemberAccess(..)) { + self.push('('); + self.show_hir_expression_id(hir_call_expression.func); + self.push(')'); + } else { + self.show_hir_expression_id_maybe_inside_parens(hir_call_expression.func); + } + + if hir_call_expression.is_macro_call { + self.push('!'); + } + self.push('('); + self.show_hir_expression_ids_separated_by_comma(&hir_call_expression.arguments); + self.push(')'); + } + HirExpression::Constrain(hir_constrain_expression) => { + self.push_str("assert("); + self.show_hir_expression_id(hir_constrain_expression.0); + if let Some(message_id) = hir_constrain_expression.2 { + self.push_str(", "); + self.show_hir_expression_id(message_id); + } + self.push(')'); + } + HirExpression::Cast(hir_cast_expression) => { + self.show_hir_expression_id_maybe_inside_parens(hir_cast_expression.lhs); + self.push_str(" as "); + self.show_type(&hir_cast_expression.r#type); + } + HirExpression::If(hir_if_expression) => { + self.push_str("if "); + self.show_hir_expression_id(hir_if_expression.condition); + self.push(' '); + self.show_hir_expression_id(hir_if_expression.consequence); + if let Some(alternative) = hir_if_expression.alternative { + self.push_str(" else "); + self.show_hir_expression_id(alternative); + } + } + HirExpression::Match(hir_match) => self.show_hir_match(hir_match), + HirExpression::Tuple(expr_ids) => { + let len = expr_ids.len(); + self.push('('); + self.show_hir_expression_ids_separated_by_comma(&expr_ids); + if len == 1 { + self.push(','); + } + self.push(')'); + } + HirExpression::Lambda(hir_lambda) => self.show_hir_lambda(hir_lambda), + HirExpression::Quote(tokens) => { + self.show_quoted(&tokens.0); + } + HirExpression::Unsafe(hir_block_expression) => { + self.push_str("// Safety: comment added by `nargo expand`\n"); + self.write_indent(); + self.push_str("unsafe "); + self.show_hir_block_expression(hir_block_expression); + } + HirExpression::Error => unreachable!("error nodes should not happen"), + HirExpression::Unquote(_) => unreachable!("unquote should not happen"), + } + } + + pub(super) fn show_hir_lambda(&mut self, hir_lambda: HirLambda) { + self.push('|'); + self.show_separated_by_comma(&hir_lambda.parameters, |this, (parameter, typ)| { + this.show_hir_pattern(parameter.clone()); + this.push_str(": "); + this.show_type(typ); + }); + self.push_str("| "); + if hir_lambda.return_type != Type::Unit { + self.push_str("-> "); + self.show_type(&hir_lambda.return_type); + self.push_str(" "); + } + self.show_hir_expression_id_maybe_inside_curlies(hir_lambda.body); + } + + fn show_hir_match(&mut self, hir_match: HirMatch) { + match hir_match { + HirMatch::Success(expr_id) => self.show_hir_expression_id(expr_id), + HirMatch::Failure { .. } => { + unreachable!("At this point code should not have errors") + } + HirMatch::Guard { cond, body, otherwise } => { + self.push_str("if "); + self.show_hir_expression_id(cond); + self.push(' '); + self.show_hir_expression_id(body); + self.push_str(" else "); + self.show_hir_match(*otherwise); + } + HirMatch::Switch(variable, cases, default) => { + self.push_str("match "); + self.show_definition_id(variable); + self.push_str(" {\n"); + self.increase_indent(); + for case in cases { + let typ = self.interner.definition_type(variable).follow_bindings(); + self.write_indent(); + + if !matches!(typ, Type::Tuple(..)) { + self.show_constructor(case.constructor); + } + + if !case.arguments.is_empty() { + if let Some(fields) = get_type_fields(&typ) { + self.push('{'); + self.show_separated_by_comma( + &case.arguments.into_iter().zip(fields).collect::>(), + |this, (argument, (name, _))| { + this.push_str(name); + this.push_str(": "); + this.show_definition_id(*argument); + }, + ); + self.push('}'); + } else { + self.push('('); + self.show_separated_by_comma(&case.arguments, |this, argument| { + this.show_definition_id(*argument); + }); + self.push(')'); + } + } + self.push_str(" => "); + self.show_hir_match(case.body); + self.push(','); + self.push('\n'); + } + + if let Some(default) = default { + self.write_indent(); + self.push_str("_ => "); + self.show_hir_match(*default); + self.push(','); + self.push('\n'); + } + + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + } + } + + fn show_constructor(&mut self, constructor: Constructor) { + match constructor { + Constructor::True => self.push_str("true"), + Constructor::False => self.push_str("false"), + Constructor::Unit => self.push_str("()"), + Constructor::Int(signed_field) => self.push_str(&signed_field.to_string()), + Constructor::Tuple(items) => { + let len = items.len(); + self.push('('); + self.show_types_separated_by_comma(&items); + if len == 1 { + self.push(','); + } + self.push(')'); + } + Constructor::Variant(typ, index) => { + self.show_type_name_as_data_type(&typ); + + let Type::DataType(data_type, _) = typ.follow_bindings() else { + panic!("Expected data type") + }; + let data_type = data_type.borrow(); + if data_type.is_enum() { + let variant = data_type.variant_at(index); + self.push_str("::"); + self.push_str(&variant.name.to_string()); + } + } + Constructor::Range(from, to) => { + self.push_str(&from.to_string()); + self.push_str(".."); + self.push_str(&to.to_string()); + } + } + } + + fn try_show_hir_call_as_method(&mut self, hir_call_expression: &HirCallExpression) -> bool { + let arguments = &hir_call_expression.arguments; + + // If there are no arguments this is definitely not a method call + if arguments.is_empty() { + return false; + } + + // A method call must have `func` be a HirIdent + let HirExpression::Ident(hir_ident, generics) = + self.interner.expression(&hir_call_expression.func) + else { + return false; + }; + + // That HirIdent must be a function reference + let definition = self.interner.definition(hir_ident.id); + let DefinitionKind::Function(func_id) = definition.kind else { + return false; + }; + + // Is this `self.foo()` where `self` is currently a trait? + // If so, show it as `self.foo()` instead of `Self::foo(self)`. + let mut method_on_trait_self = false; + + // Special case: assumed trait method + if let ImplKind::TraitMethod(trait_method) = hir_ident.impl_kind { + if trait_method.assumed { + if let Type::NamedGeneric(NamedGeneric { name, .. }) = &trait_method.constraint.typ + { + if name.to_string() == "Self" { + method_on_trait_self = true; + } + } + + if !method_on_trait_self { + self.show_type(&trait_method.constraint.typ); + self.push_str("::"); + self.push_str(self.interner.function_name(&func_id)); + if let Some(generics) = generics { + let use_colons = true; + self.show_generic_types(&generics, use_colons); + } + self.push('('); + self.show_hir_expression_ids_separated_by_comma(arguments); + self.push(')'); + return true; + } + } + } + + // The function must have a self type + let func_meta = self.interner.function_meta(&func_id); + + // Don't do this for trait methods (refer to the trait name instead) + if func_meta.trait_id.is_some() && !method_on_trait_self { + return false; + } + + let Some(self_type) = &func_meta.self_type else { + return false; + }; + + // And it must have parameters + if func_meta.parameters.is_empty() { + return false; + } + + // The first parameter must unify with the self type (as-is or after removing `&mut`) + let param_type = func_meta.parameters.0[0].1.follow_bindings(); + let param_type = if let Type::Reference(typ, ..) = param_type { *typ } else { param_type }; + + let mut bindings = TypeBindings::new(); + if self_type.try_unify(¶m_type, &mut bindings).is_err() { + return false; + } + + let first_argument = self.dereference_hir_expression_id(arguments[0]); + self.show_hir_expression_id_maybe_inside_parens(first_argument); + self.push('.'); + self.push_str(self.interner.function_name(&func_id)); + if let Some(generics) = generics { + let use_colons = true; + self.show_generic_types(&generics, use_colons); + } + self.push('('); + self.show_hir_expression_ids_separated_by_comma(&arguments[1..]); + self.push(')'); + + true + } + + fn show_hir_block_expression(&mut self, block: HirBlockExpression) { + self.push_str("{\n"); + self.increase_indent(); + let len = block.statements.len(); + for (index, statement) in block.statements.into_iter().enumerate() { + self.write_indent(); + self.show_hir_statement_id(statement); + + // For some reason some statements in the middle of a block end up being `Expression` + // and not `Semi`, so we add a semicolon, if needed, to produce valid syntax. + if index != len - 1 && !self.string.ends_with(';') { + self.push(';'); + } + + self.push_str("\n"); + } + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + + fn show_hir_expression_ids_separated_by_comma(&mut self, expr_ids: &[ExprId]) { + self.show_separated_by_comma(expr_ids, |this, expr_id| { + this.show_hir_expression_id(*expr_id); + }); + } + + fn show_hir_statement_id(&mut self, stmt_id: StmtId) { + let statement = self.interner.statement(&stmt_id); + self.show_hir_statement(statement); + } + + fn show_hir_statement(&mut self, statement: HirStatement) { + match statement { + HirStatement::Let(hir_let_statement) => { + // If this is `let ... = unsafe { }` then show the unsafe comment on top of `let` + if let HirExpression::Unsafe(_) = + self.interner.expression(&hir_let_statement.expression) + { + self.push_str("// Safety: comment added by `nargo expand`\n"); + self.write_indent(); + } + + self.push_str("let "); + self.show_hir_pattern(hir_let_statement.pattern); + self.push_str(": "); + self.show_type(&hir_let_statement.r#type); + self.push_str(" = "); + + if let HirExpression::Unsafe(block_expression) = + self.interner.expression(&hir_let_statement.expression) + { + self.push_str("unsafe "); + self.show_hir_block_expression(block_expression); + } else { + self.show_hir_expression_id(hir_let_statement.expression); + } + + self.push(';'); + } + HirStatement::Assign(hir_assign_statement) => { + self.show_hir_lvalue(hir_assign_statement.lvalue); + self.push_str(" = "); + self.show_hir_expression_id(hir_assign_statement.expression); + self.push(';'); + } + HirStatement::For(hir_for_statement) => { + self.push_str("for "); + self.show_hir_ident(hir_for_statement.identifier); + self.push_str(" in "); + self.show_hir_expression_id(hir_for_statement.start_range); + self.push_str(".."); + self.show_hir_expression_id(hir_for_statement.end_range); + self.push(' '); + self.show_hir_expression_id(hir_for_statement.block); + } + HirStatement::Loop(expr_id) => { + self.push_str("loop "); + self.show_hir_expression_id(expr_id); + } + HirStatement::While(condition, body) => { + self.push_str("while "); + self.show_hir_expression_id(condition); + self.push(' '); + self.show_hir_expression_id(body); + } + HirStatement::Break => { + self.push_str("break;"); + } + HirStatement::Continue => { + self.push_str("continue;"); + } + HirStatement::Expression(expr_id) => { + self.show_hir_expression_id(expr_id); + } + HirStatement::Semi(expr_id) => { + self.show_hir_expression_id(expr_id); + self.push(';'); + } + HirStatement::Comptime(_) => unreachable!("comptime should not happen"), + HirStatement::Error => unreachable!("error should not happen"), + } + } + + fn show_hir_literal(&mut self, literal: HirLiteral) { + match literal { + HirLiteral::Array(hir_array_literal) => { + self.push_str("["); + self.show_hir_array_literal(hir_array_literal); + self.push(']'); + } + HirLiteral::Slice(hir_array_literal) => { + self.push_str("&["); + self.show_hir_array_literal(hir_array_literal); + self.push(']'); + } + HirLiteral::Bool(value) => { + self.push_str(&value.to_string()); + } + HirLiteral::Integer(signed_field) => { + self.push_str(&signed_field.to_string()); + } + HirLiteral::Str(string) => { + self.push_str(&format!("{:?}", string)); + } + HirLiteral::FmtStr(fmt_str_fragments, _expr_ids, _) => { + self.push_str("f\""); + for fragment in fmt_str_fragments { + match fragment { + FmtStrFragment::String(string) => { + let string = string + .replace('\\', "\\\\") + .replace('\n', "\\n") + .replace('\t', "\\t") + .replace('{', "{{") + .replace('}', "}}"); + self.push_str(&string); + } + FmtStrFragment::Interpolation(string, _) => { + self.push('{'); + self.push_str(&string); + self.push('}'); + } + } + } + self.push('"'); + } + HirLiteral::Unit => { + self.push_str("()"); + } + } + } + + fn show_hir_array_literal(&mut self, array: HirArrayLiteral) { + match array { + HirArrayLiteral::Standard(expr_ids) => { + self.show_hir_expression_ids_separated_by_comma(&expr_ids); + } + HirArrayLiteral::Repeated { repeated_element, length } => { + self.show_hir_expression_id(repeated_element); + self.push_str("; "); + self.show_type(&length); + } + } + } + + fn show_hir_lvalue(&mut self, lvalue: HirLValue) { + match lvalue { + HirLValue::Ident(hir_ident, _) => { + self.show_hir_ident(hir_ident); + } + HirLValue::MemberAccess { object, field_name, field_index: _, typ: _, location: _ } => { + self.show_hir_lvalue(*object); + self.push('.'); + self.push_str(&field_name.to_string()); + } + HirLValue::Index { array, index, typ: _, location: _ } => { + self.show_hir_lvalue(*array); + self.push('['); + self.show_hir_expression_id(index); + self.push(']'); + } + HirLValue::Dereference { lvalue, implicitly_added, element_type: _, location: _ } => { + if implicitly_added { + self.show_hir_lvalue(*lvalue); + } else { + // Even though parentheses aren't always required, it's tricky to + // figure out exactly when so we always include them. + self.push_str("*("); + self.show_hir_lvalue(*lvalue); + self.push(')'); + } + } + } + } + + fn show_hir_pattern(&mut self, pattern: HirPattern) { + match pattern { + HirPattern::Identifier(hir_ident) => self.show_hir_ident(hir_ident), + HirPattern::Mutable(hir_pattern, _) => { + self.push_str("mut "); + self.show_hir_pattern(*hir_pattern); + } + HirPattern::Tuple(hir_patterns, _location) => { + let len = hir_patterns.len(); + self.push('('); + self.show_separated_by_comma(&hir_patterns, |this, pattern| { + this.show_hir_pattern(pattern.clone()); + }); + if len == 1 { + self.push(','); + } + self.push(')'); + } + HirPattern::Struct(typ, items, _location) => { + self.show_type_name_as_data_type(&typ); + self.push_str(" {\n"); + self.increase_indent(); + self.show_separated_by_comma(&items, |this, (name, pattern)| { + this.push_str(&name.to_string()); + this.push_str(": "); + this.show_hir_pattern(pattern.clone()); + }); + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + } + } + + fn show_definition_id(&mut self, definition_id: DefinitionId) { + let location = self.interner.definition(definition_id).location; + let ident = HirIdent::non_trait_method(definition_id, location); + self.show_hir_ident(ident); + } + + fn show_hir_ident(&mut self, ident: HirIdent) { + let definition = self.interner.definition(ident.id); + match definition.kind { + DefinitionKind::Function(func_id) => { + let use_import = true; + self.show_reference_to_module_def_id(ModuleDefId::FunctionId(func_id), use_import); + } + DefinitionKind::Global(global_id) => { + let global_info = self.interner.get_global(global_id); + let typ = self.interner.definition_type(global_info.definition_id); + + // Special case: the global is an enum value + let typ = if let Type::Forall(_, typ) = typ { *typ } else { typ }; + if let Type::DataType(data_type, _generics) = &typ { + let data_type = data_type.borrow(); + if data_type.is_enum() { + self.show_type_name_as_data_type(&typ); + self.push_str("::"); + } + } + let use_import = true; + self.show_reference_to_module_def_id(ModuleDefId::GlobalId(global_id), use_import); + } + DefinitionKind::Local(..) | DefinitionKind::NumericGeneric(..) => { + let name = self.interner.definition_name(ident.id); + + // The compiler uses '$' for some internal identifiers. + // We replace them with "___" to make sure they have valid syntax, even though + // there's a tiny change they might collide with user code (unlikely, really). + // + // In other cases these internal names have spaces. + let name = name.replace(['$', ' '], "___"); + + self.push_str(&name); + } + } + } +} + +fn hir_expression_needs_parentheses(hir_expr: &HirExpression) -> bool { + match hir_expr { + HirExpression::Infix(..) | HirExpression::Cast(..) | HirExpression::Lambda(..) => true, + HirExpression::Ident(..) + | HirExpression::Literal(..) + | HirExpression::Block(..) + | HirExpression::Prefix(..) + | HirExpression::Index(..) + | HirExpression::Constructor(..) + | HirExpression::EnumConstructor(..) + | HirExpression::MemberAccess(..) + | HirExpression::Call(..) + | HirExpression::Constrain(..) + | HirExpression::If(..) + | HirExpression::Match(..) + | HirExpression::Tuple(..) + | HirExpression::Quote(..) + | HirExpression::Unquote(..) + | HirExpression::Unsafe(..) + | HirExpression::Error => false, + } +} + +fn get_type_fields(typ: &Type) -> Option> { + match typ.follow_bindings() { + Type::DataType(data_type, generics) => { + let data_type = data_type.borrow(); + data_type.get_fields(&generics) + } + _ => None, + } +} diff --git a/tooling/nargo_cli/src/cli/expand_cmd/printer/types.rs b/tooling/nargo_cli/src/cli/expand_cmd/printer/types.rs new file mode 100644 index 00000000000..db966895f93 --- /dev/null +++ b/tooling/nargo_cli/src/cli/expand_cmd/printer/types.rs @@ -0,0 +1,176 @@ +use noirc_frontend::{NamedGeneric, Type, TypeBinding, hir::def_map::ModuleDefId}; + +use super::ItemPrinter; + +impl ItemPrinter<'_, '_, '_> { + pub(super) fn show_types_separated_by_comma(&mut self, types: &[Type]) { + self.show_separated_by_comma(types, |this, typ| { + this.show_type(typ); + }); + } + + pub(super) fn show_type(&mut self, typ: &Type) { + if self.self_type.as_ref() == Some(typ) { + self.push_str("Self"); + return; + } + + match typ { + Type::Array(length, typ) => { + self.push('['); + self.show_type(typ); + self.push_str("; "); + self.show_type(length); + self.push(']'); + } + Type::Slice(typ) => { + self.push('['); + self.show_type(typ); + self.push(']'); + } + Type::FmtString(length, typ) => { + self.push_str("fmtstr<"); + self.show_type(length); + self.push_str(", "); + self.show_type(typ); + self.push('>'); + } + Type::Tuple(types) => { + let len = types.len(); + self.push('('); + self.show_types_separated_by_comma(types); + if len == 1 { + self.push(','); + } + self.push(')'); + } + Type::DataType(data_type, generics) => { + let data_type = data_type.borrow(); + let use_import = true; + self.show_reference_to_module_def_id(ModuleDefId::TypeId(data_type.id), use_import); + if !generics.is_empty() { + self.push_str("<"); + self.show_types_separated_by_comma(generics); + self.push('>'); + } + } + Type::Alias(type_alias, generics) => { + let type_alias = type_alias.borrow(); + let use_import = true; + self.show_reference_to_module_def_id( + ModuleDefId::TypeAliasId(type_alias.id), + use_import, + ); + if !generics.is_empty() { + self.push_str("<"); + self.show_types_separated_by_comma(generics); + self.push('>'); + } + } + Type::TypeVariable(type_variable) => match &*type_variable.borrow() { + TypeBinding::Bound(typ) => { + self.show_type(typ); + } + TypeBinding::Unbound(..) => { + self.push('_'); + } + }, + Type::TraitAsType(trait_id, _, generics) => { + let trait_ = self.interner.get_trait(*trait_id); + self.push_str("impl "); + self.push_str(trait_.name.as_str()); + self.show_trait_generics(generics); + } + Type::NamedGeneric(NamedGeneric { name, .. }) => { + self.push_str(name); + } + Type::CheckedCast { from: _, to } => { + self.show_type(to); + } + Type::Function(args, ret, env, unconstrained) => { + if *unconstrained { + self.push_str("unconstrained "); + } + self.push_str("fn"); + if **env != Type::Unit { + self.push('['); + self.show_type(env); + self.push(']'); + } + self.push('('); + self.show_types_separated_by_comma(args); + self.push(')'); + if **ret != Type::Unit { + self.push_str(" -> "); + self.show_type(ret); + } + } + Type::Reference(typ, mutable) => { + if *mutable { + self.push_str("&mut "); + } else { + self.push('&'); + } + self.show_type(typ); + } + Type::Forall(..) => { + panic!("Should not need to print Type::Forall") + } + Type::Constant(field_element, _) => { + self.push_str(&field_element.to_string()); + } + Type::InfixExpr(lhs, op, rhs, _) => { + self.show_type_maybe_in_parentheses(lhs); + self.push(' '); + self.push_str(&op.to_string()); + self.push(' '); + self.show_type_maybe_in_parentheses(rhs); + } + Type::Unit + | Type::Bool + | Type::Integer(..) + | Type::FieldElement + | Type::String(_) + | Type::Quoted(..) + | Type::Error => self.push_str(&typ.to_string()), + } + } + + fn show_type_maybe_in_parentheses(&mut self, typ: &Type) { + if type_needs_parentheses(typ) { + self.push('('); + self.show_type(typ); + self.push(')'); + } else { + self.show_type(typ); + } + } +} + +fn type_needs_parentheses(typ: &Type) -> bool { + match typ { + Type::InfixExpr(..) | Type::Function(..) | Type::TraitAsType(..) => true, + Type::TypeVariable(type_variable) => match &*type_variable.borrow() { + TypeBinding::Bound(typ) => type_needs_parentheses(typ), + TypeBinding::Unbound(..) => false, + }, + Type::CheckedCast { from: _, to } => type_needs_parentheses(to), + Type::FieldElement + | Type::Array(..) + | Type::Slice(..) + | Type::Integer(..) + | Type::Bool + | Type::String(..) + | Type::FmtString(..) + | Type::Unit + | Type::Tuple(..) + | Type::DataType(..) + | Type::Alias(..) + | Type::NamedGeneric(..) + | Type::Reference(..) + | Type::Forall(..) + | Type::Constant(..) + | Type::Quoted(..) + | Type::Error => false, + } +} diff --git a/tooling/nargo_cli/src/cli/mod.rs b/tooling/nargo_cli/src/cli/mod.rs index ec7d050206f..fbb5774977c 100644 --- a/tooling/nargo_cli/src/cli/mod.rs +++ b/tooling/nargo_cli/src/cli/mod.rs @@ -19,6 +19,7 @@ mod compile_cmd; mod dap_cmd; mod debug_cmd; mod execute_cmd; +mod expand_cmd; mod export_cmd; mod fmt_cmd; mod fuzz_cmd; @@ -107,6 +108,7 @@ enum NargoCommand { Lsp(lsp_cmd::LspCommand), #[command(hide = true)] Dap(dap_cmd::DapCommand), + Expand(expand_cmd::ExpandCommand), GenerateCompletionScript(generate_completion_script_cmd::GenerateCompletionScriptCommand), } @@ -149,6 +151,7 @@ pub(crate) fn start_cli() -> eyre::Result<()> { NargoCommand::Lsp(_) => lsp_cmd::run(), NargoCommand::Dap(args) => dap_cmd::run(args), NargoCommand::Fmt(args) => with_workspace(args, config, fmt_cmd::run), + NargoCommand::Expand(args) => with_workspace(args, config, expand_cmd::run), NargoCommand::GenerateCompletionScript(args) => generate_completion_script_cmd::run(args), }?; diff --git a/tooling/nargo_cli/tests/execute.rs b/tooling/nargo_cli/tests/execute.rs index deb61e99897..f0784ce3faf 100644 --- a/tooling/nargo_cli/tests/execute.rs +++ b/tooling/nargo_cli/tests/execute.rs @@ -311,6 +311,123 @@ mod tests { } } + fn nargo_expand_execute(test_program_dir: PathBuf) { + // First run `nargo execute` on the original code to get the output + let mut nargo = Command::cargo_bin("nargo").unwrap(); + nargo.arg("--program-dir").arg(test_program_dir.clone()); + nargo.arg("execute").arg("--force").arg("--disable-comptime-printing"); + + // Enable enums as an unstable feature + nargo.arg("-Zenums"); + + // Enable pedantic solving + nargo.arg("--pedantic-solving"); + + nargo.assert().success(); + + let original_output = nargo.output().unwrap(); + let original_output: String = String::from_utf8(original_output.stdout).unwrap(); + + let mut nargo = Command::cargo_bin("nargo").unwrap(); + nargo.arg("--program-dir").arg(test_program_dir.clone()); + nargo.arg("expand").arg("--force").arg("--disable-comptime-printing"); + + // Enable enums as an unstable feature + nargo.arg("-Zenums"); + + // Enable pedantic solving + nargo.arg("--pedantic-solving"); + + nargo.assert().success(); + + let expanded_code = nargo.output().unwrap(); + let expanded_code: String = String::from_utf8(expanded_code.stdout).unwrap(); + + // Create a new directory where we'll put the expanded code + let temp_dir = tempfile::tempdir().unwrap().into_path(); + + // Copy everything from the original directory to the new directory + // (because some depdendencies might be there and might be needed for the expanded code to work) + copy_dir_all(test_program_dir.clone(), temp_dir.clone()).unwrap(); + + // Create a main file for the expanded code + fs::write(temp_dir.join("src").join("main.nr"), expanded_code).unwrap(); + + // First check if `nargo fmt` works on the expanded code. If not, it means the code is not valid. + run_nargo_fmt(temp_dir.clone()); + + // Now we can run `nargo execute` on the expanded code + let mut nargo = Command::cargo_bin("nargo").unwrap(); + nargo.arg("--program-dir").arg(temp_dir); + nargo.arg("execute").arg("--force").arg("--disable-comptime-printing"); + + // Enable enums as an unstable feature + nargo.arg("-Zenums"); + + // Enable pedantic solving + nargo.arg("--pedantic-solving"); + + nargo.assert().success(); + + let expanded_output = nargo.output().unwrap(); + let expanded_output: String = String::from_utf8(expanded_output.stdout).unwrap(); + + let original_output = remove_noise_lines(original_output); + let expanded_output = remove_noise_lines(expanded_output); + + assert_eq!(original_output, expanded_output); + } + + fn nargo_expand_compile(test_program_dir: PathBuf) { + let mut nargo = Command::cargo_bin("nargo").unwrap(); + nargo.arg("--program-dir").arg(test_program_dir.clone()); + nargo.arg("expand").arg("--force").arg("--disable-comptime-printing"); + + // Enable enums as an unstable feature + nargo.arg("-Zenums"); + + // Enable pedantic solving + nargo.arg("--pedantic-solving"); + + nargo.assert().success(); + + let expanded_code = nargo.output().unwrap(); + let expanded_code: String = String::from_utf8(expanded_code.stdout).unwrap(); + + // Create a new directory where we'll put the expanded code + let temp_dir = tempfile::tempdir().unwrap().into_path(); + + // Copy everything from the original directory to the new directory + // (because some depdendencies might be there and might be needed for the expanded code to work) + copy_dir_all(test_program_dir.clone(), temp_dir.clone()).unwrap(); + + // Create a main file for the expanded code + fs::write(temp_dir.join("src").join("main.nr"), expanded_code).unwrap(); + + // First check if `nargo fmt` works on the expanded code. If not, it means the code is not valid. + run_nargo_fmt(temp_dir.clone()); + + // Now we can run `nargo compile` on the expanded code + let mut nargo = Command::cargo_bin("nargo").unwrap(); + nargo.arg("--program-dir").arg(temp_dir); + nargo.arg("compile").arg("--force"); + + // Enable enums as an unstable feature + nargo.arg("-Zenums"); + + // Enable pedantic solving + nargo.arg("--pedantic-solving"); + + nargo.assert().success(); + } + + fn run_nargo_fmt(target_dir: PathBuf) { + let mut nargo = Command::cargo_bin("nargo").unwrap(); + nargo.arg("--program-dir").arg(target_dir); + nargo.arg("fmt"); + nargo.assert().success(); + } + fn check_program_artifact( prefix: &'static str, test_program_dir: &Path, @@ -375,6 +492,22 @@ mod tests { None } + fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + { + fs::create_dir_all(&dst)?; + for entry in fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else { + fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) + } + } + // include tests generated by `build.rs` include!(concat!(env!("OUT_DIR"), "/execute.rs")); } diff --git a/tooling/nargo_fmt/src/lib.rs b/tooling/nargo_fmt/src/lib.rs index bf3cf48184d..6492f0e1ad6 100644 --- a/tooling/nargo_fmt/src/lib.rs +++ b/tooling/nargo_fmt/src/lib.rs @@ -42,7 +42,7 @@ mod formatter; use formatter::Formatter; use noirc_frontend::ParsedModule; -pub use config::Config; +pub use config::{Config, ImportsGranularity}; pub fn format(source: &str, parsed_module: ParsedModule, config: &Config) -> String { let mut formatter = Formatter::new(source, config);