diff --git a/Cargo.lock b/Cargo.lock index 7a64f0ec49e..69382069829 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3088,6 +3088,7 @@ dependencies = [ "prettytable-rs", "proptest", "rayon", + "rustc-hash 1.1.0", "serde", "serde_json", "sha2", diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index f39832e22b3..80756de297f 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -607,8 +607,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/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 1fde7e1ffbc..1e65165d3e2 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1511,10 +1511,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/hir/comptime/display.rs b/compiler/noirc_frontend/src/hir/comptime/display.rs index bc2e0574086..1a482ff352b 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 @@ -285,7 +293,6 @@ impl<'interner> TokenPrettyPrinter<'interner> { | Token::RawStr(..) | Token::FmtStr(..) | Token::Whitespace(_) - | Token::LineComment(..) | Token::BlockComment(..) | Token::AttributeStart { .. } | Token::Invalid(_) => { @@ -294,6 +301,10 @@ impl<'interner> TokenPrettyPrinter<'interner> { } write!(f, "{token}") } + Token::LineComment(..) => { + writeln!(f, "{token}")?; + self.write_indent(f) + } Token::EOF => Ok(()), } } 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/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 88a32b2717c..164f14febec 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -18,6 +18,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 parser; 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/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 12ef39f6b18..165f135db42 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; @@ -1450,6 +1451,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/parser/parser/expression.rs b/compiler/noirc_frontend/src/parser/parser/expression.rs index 672d9428d9a..4b51c6219ff 100644 --- a/compiler/noirc_frontend/src/parser/parser/expression.rs +++ b/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -147,6 +147,25 @@ impl Parser<'_> { self.parse_index(atom, start_location) } + pub(super) fn parse_member_accesses_or_method_calls_after_expression( + &mut self, + mut atom: Expression, + start_location: Location, + ) -> Expression { + let mut parsed; + + loop { + (atom, parsed) = self.parse_member_access_or_method_call(atom, start_location); + if parsed { + continue; + } else { + break; + } + } + + atom + } + /// CallExpression = Atom CallArguments fn parse_call(&mut self, atom: Expression, start_location: Location) -> (Expression, bool) { if let Some(call_arguments) = self.parse_call_arguments() { diff --git a/compiler/noirc_frontend/src/parser/parser/function.rs b/compiler/noirc_frontend/src/parser/parser/function.rs index caf2cdeb1c3..b9271f36d5c 100644 --- a/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/compiler/noirc_frontend/src/parser/parser/function.rs @@ -570,4 +570,16 @@ mod tests { UnresolvedTypeData::Integer(Signedness::Signed, IntegerBitSize::SixtyFour) ); } + + #[test] + fn parses_block_followed_by_call() { + let src = "fn foo() { { 1 }.bar() }"; + let _ = parse_function_no_error(src); + } + + #[test] + fn parses_if_followed_by_call() { + let src = "fn foo() { if 1 { 2 } else { 3 }.bar() }"; + let _ = parse_function_no_error(src); + } } diff --git a/compiler/noirc_frontend/src/parser/parser/statement.rs b/compiler/noirc_frontend/src/parser/parser/statement.rs index 600ddec43c9..873f921f12f 100644 --- a/compiler/noirc_frontend/src/parser/parser/statement.rs +++ b/compiler/noirc_frontend/src/parser/parser/statement.rs @@ -148,19 +148,26 @@ impl Parser<'_> { if let Some(kind) = self.parse_if_expr() { let location = self.location_since(start_location); - return Some(StatementKind::Expression(Expression { kind, location })); + let expression = Expression { kind, location }; + let expression = self + .parse_member_accesses_or_method_calls_after_expression(expression, start_location); + return Some(StatementKind::Expression(expression)); } if let Some(kind) = self.parse_match_expr() { let location = self.location_since(start_location); - return Some(StatementKind::Expression(Expression { kind, location })); + let expression = Expression { kind, location }; + let expression = self + .parse_member_accesses_or_method_calls_after_expression(expression, start_location); + return Some(StatementKind::Expression(expression)); } if let Some(block) = self.parse_block() { - return Some(StatementKind::Expression(Expression { - kind: ExpressionKind::Block(block), - location: self.location_since(start_location), - })); + let location = self.location_since(start_location); + let expression = Expression { kind: ExpressionKind::Block(block), location }; + let expression = self + .parse_member_accesses_or_method_calls_after_expression(expression, start_location); + return Some(StatementKind::Expression(expression)); } if let Some(token) = self.eat_kind(TokenKind::InternedLValue) { diff --git a/tooling/lsp/src/attribute_reference_finder.rs b/tooling/lsp/src/attribute_reference_finder.rs index 384c575f0c6..a727fe1eede 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, 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 4e7ef64b2c0..180e921ad9c 100644 --- a/tooling/lsp/src/modules.rs +++ b/tooling/lsp/src/modules.rs @@ -1,171 +1,15 @@ use std::collections::BTreeMap; use noirc_frontend::{ - ast::{Ident, ItemVisibility}, + ast::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, @@ -222,49 +66,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 070dcfcde90..451a200954a 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 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 e29558c617b..5dccef61a91 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 4478b975a2a..2c024b78263 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 a8ed06c0896..d0416fda879 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::{ stmt::HirPattern, traits::Trait, }, + modules::module_full_path, node_interner::{ DefinitionId, DefinitionKind, ExprId, 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 b24f4dd7d87..a0a370a0c23 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 7366684a859..09c4f12cb34 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/Cargo.toml b/tooling/nargo_cli/Cargo.toml index a80828356ff..33793a0d515 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -67,6 +67,7 @@ color-eyre.workspace = true tokio = { version = "1.0", features = ["io-std", "rt"] } dap.workspace = true clap-markdown = { git = "https://github.com/noir-lang/clap-markdown", rev = "450d759532c88f0dba70891ceecdbc9ff8f25d2b", optional = true } +rustc-hash = "1.1.0" notify = "6.1.1" notify-debouncer-full = "0.3.1" 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..29f5825009b --- /dev/null +++ b/tooling/nargo_cli/src/cli/expand_cmd.rs @@ -0,0 +1,94 @@ +use clap::Args; +use fm::FileManager; +use nargo::{ + errors::CompileError, insert_all_files_for_workspace_into_file_manager, 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::Printer; + +use crate::errors::CliError; + +use super::{LockType, PackageOptions, WorkspaceCommand, check_cmd::check_crate_and_report_errors}; + +mod printer; + +/// Expands macros +#[derive(Debug, Clone, Args)] +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 (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 def_map = &context.def_maps[&crate_id]; + let root_module_id = def_map.root(); + let module_id = ModuleId { krate: crate_id, local_id: root_module_id }; + + let mut string = String::new(); + let mut printer = + Printer::new(crate_id, &context.def_interner, &context.def_maps, def_map, &mut string); + printer.show_module(module_id); + printer.show_stray_trait_impls(); + + 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() + }; + let code = nargo_fmt::format(&string, parsed_module, &config); + println!("{code}"); + } else { + println!("{string}"); + println!(); + println!("// Warning: the generated code has syntax errors"); + } + + Ok(()) +} 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..fd2bb68e7e4 --- /dev/null +++ b/tooling/nargo_cli/src/cli/expand_cmd/printer.rs @@ -0,0 +1,1975 @@ +use std::collections::{HashMap, HashSet}; + +use noirc_driver::CrateId; +use noirc_errors::Location; +use noirc_frontend::{ + DataType, Generics, Type, TypeBinding, TypeBindings, + ast::{Ident, ItemVisibility, UnaryOp, Visibility}, + hir::{ + comptime::{Value, tokens_to_string_with_indent}, + def_map::{CrateDefMap, DefMaps, ModuleDefId, ModuleId}, + type_check::generics::TraitGenerics, + }, + hir_def::{ + expr::{ + HirArrayLiteral, HirBlockExpression, HirCallExpression, HirExpression, HirIdent, + HirLiteral, HirMatch, + }, + stmt::{HirLValue, HirLetStatement, HirPattern, HirStatement}, + traits::{ResolvedTraitBound, TraitConstraint}, + }, + modules::relative_module_full_path, + node_interner::{ + DefinitionKind, ExprId, FuncId, GlobalId, GlobalValue, ImplMethod, Methods, NodeInterner, + ReferenceId, StmtId, TraitId, TraitImplId, TypeAliasId, TypeId, + }, + token::{FmtStrFragment, FunctionAttribute, SecondaryAttribute}, +}; + +pub(super) struct Printer<'interner, 'def_map, 'string> { + crate_id: CrateId, + interner: &'interner NodeInterner, + def_maps: &'def_map DefMaps, + def_map: &'def_map CrateDefMap, + string: &'string mut String, + indent: usize, + module_id: ModuleId, + imports: HashMap, + pub(super) trait_impls: HashSet, +} + +impl<'interner, 'def_map, 'string> Printer<'interner, 'def_map, 'string> { + pub(super) fn new( + crate_id: CrateId, + interner: &'interner NodeInterner, + def_maps: &'def_map DefMaps, + def_map: &'def_map CrateDefMap, + string: &'string mut String, + ) -> Self { + let module_id = ModuleId { krate: crate_id, local_id: def_map.root() }; + let trait_impls = interner.get_trait_implementations_in_crate(crate_id); + let imports = HashMap::new(); + Self { + crate_id, + interner, + def_maps, + def_map, + string, + indent: 0, + module_id, + imports, + trait_impls, + } + } + + pub(super) fn show_module(&mut self, module_id: ModuleId) { + let attributes = self.interner.try_module_attributes(&module_id); + let name = attributes.map(|attributes| &attributes.name); + let module_data = &self.def_map.modules()[module_id.local_id.0]; + let is_contract = module_data.is_contract; + + if let Some(name) = name { + if 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 = HashMap::new(); + + 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 scope = 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((name, *module_def_id, *visibility, *is_prelude)) + } else { + None + } + }) + .collect::>(); + + scope.sort_by_key(|(name, ..)| name.location()); + + self.imports = scope + .iter() + .map(|(name, module_def_id, ..)| (*module_def_id, name.clone())) + .collect::>(); + + self.show_imports(scope); + + for (index, (module_def_id, visibility, _location)) in definitions.iter().enumerate() { + if index == 0 { + self.push_str("\n"); + } else { + self.push_str("\n\n"); + } + self.write_indent(); + self.show_module_def_id(*module_def_id, *visibility); + } + + self.module_id = previous_module_id; + self.imports = previous_imports; + + if name.is_some() { + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push_str("}"); + } + } + + fn show_module_def_id(&mut self, module_def_id: ModuleDefId, visibility: ItemVisibility) { + 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); + + match module_def_id { + ModuleDefId::ModuleId(module_id) => { + self.show_module(module_id); + } + ModuleDefId::TypeId(type_id) => self.show_data_type(type_id), + ModuleDefId::TypeAliasId(type_alias_id) => self.show_type_alias(type_alias_id), + ModuleDefId::TraitId(trait_id) => { + self.show_trait(trait_id); + self.show_trait_impls_for_trait(trait_id); + } + ModuleDefId::GlobalId(global_id) => self.show_global(global_id), + ModuleDefId::FunctionId(func_id) => self.show_function(func_id), + } + } + + 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') { + self.push_str("/**"); + self.push_str(comment); + 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, SecondaryAttribute::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, type_id: TypeId) { + 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); + + if let Some(methods) = + self.interner.get_type_methods(&Type::DataType(shared_data_type.clone(), vec![])) + { + self.show_data_type_impls(methods); + } + + let data_type = shared_data_type.borrow(); + self.show_data_type_trait_impls(&data_type); + } + + 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, methods: &rustc_hash::FxHashMap) { + // Gather all impl methods + // First split methods by impl methods and trait impl methods + let mut impl_methods = Vec::new(); + + for methods in methods.values() { + 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); + } + + for (typ, methods) in impl_methods_by_type { + self.push_str("\n\n"); + self.write_indent(); + self.show_impl(typ, methods); + } + } + + fn show_impl(&mut self, typ: Type, methods: Vec) { + self.push_str("impl"); + + let mut type_var_names = HashSet::new(); + gather_named_type_vars(&typ, &mut type_var_names); + + if !type_var_names.is_empty() { + self.push('<'); + for (index, name) in type_var_names.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.push_str(name); + } + self.push('>'); + } + + self.push(' '); + self.show_type(&typ); + self.push_str(" {\n"); + self.increase_indent(); + for (index, method) in methods.iter().enumerate() { + if index != 0 { + self.push_str("\n\n"); + } + self.write_indent(); + self.show_function(method.method); + } + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + + fn show_data_type_trait_impls(&mut self, data_type: &DataType) { + 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); + + 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, trait_id: TraitId) { + 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; + } + + 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); + + for (func_id, _location) in func_ids { + if printed_type_or_function { + self.push_str("\n\n"); + } + + self.write_indent(); + self.show_function(*func_id); + printed_type_or_function = true; + } + + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + + /// Shows trait impls for traits, but only when those impls are + /// only for primitive types, or combination of primitive types + /// (like `[Field; 3]`, [T; 2], etc.) as they are likely defined next + /// to the trait. + fn show_trait_impls_for_trait(&mut self, trait_id: TraitId) { + 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); + + for (trait_impl, _) in trait_impls { + self.push_str("\n\n"); + self.write_indent(); + self.show_trait_impl(trait_impl); + } + } + + pub(super) fn show_stray_trait_impls(&mut self) { + let trait_impls = std::mem::take(&mut self.trait_impls); + for trait_impl in trait_impls { + self.push_str("\n\n"); + self.write_indent(); + self.show_trait_impl(trait_impl); + } + } + + fn show_trait_impl(&mut self, trait_impl_id: TraitImplId) { + // 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 trait_ = self.interner.get_trait(trait_impl.trait_id); + + self.push_str("impl"); + + 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); + + if !type_var_names.is_empty() { + self.push('<'); + for (index, name) in type_var_names.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.push_str(name); + } + self.push('>'); + } + + self.push(' '); + self.push_str(&trait_.name.to_string()); + + 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(); + for (index, method) in trait_impl.methods.iter().enumerate() { + if index != 0 { + self.push_str("\n\n"); + } + self.write_indent(); + self.show_function(*method); + } + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + + 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 { + FunctionAttribute::Foreign(_) + | FunctionAttribute::Builtin(_) + | FunctionAttribute::Oracle(_) => { + self.push_str(" {}"); + } + FunctionAttribute::Test(..) + | FunctionAttribute::Fold + | FunctionAttribute::NoPredicates + | FunctionAttribute::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(", "); + } + + match generic.kind() { + noirc_frontend::Kind::Any | noirc_frontend::Kind::Normal => { + self.push_str(&generic.name); + } + noirc_frontend::Kind::IntegerOrField | noirc_frontend::Kind::Integer => { + self.push_str("let "); + self.push_str(&generic.name); + self.push_str(": u32"); + } + noirc_frontend::Kind::Numeric(typ) => { + self.push_str("let "); + self.push_str(&generic.name); + self.push_str(": "); + self.show_type(&typ); + } + } + } + self.push('>'); + } + + fn show_trait_generics(&mut self, generics: &TraitGenerics) { + if generics.is_empty() { + return; + } + + let mut printed_type = false; + + self.push('<'); + + for typ in &generics.ordered { + if printed_type { + self.push_str(", "); + } + + self.show_type(typ); + printed_type = true; + } + + for named_type in &generics.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_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(..) | HirPattern::Struct(..) => { + self.push('_'); + } + } + } + + 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.push_str("quote {"); + self.push_str(&tokens_to_string_with_indent( + tokens, + self.indent + 1, + self.interner, + )); + self.push_str("}"); + } + 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(_) + | 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) { + 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<(Ident, ModuleDefId, ItemVisibility, bool /* is prelude */)>, + ) { + let mut first = true; + + for (alias, module_def_id, visibility, is_prelude) in imports { + if is_prelude { + continue; + } + + if first { + self.push('\n'); + first = false; + } + self.write_indent(); + self.show_item_visibility(visibility); + self.push_str("use "); + let use_import = false; + let name = self.show_reference_to_module_def_id(module_def_id, use_import); + + if name != alias.0.contents { + self.push_str(" as "); + self.push_str(&alias.to_string()); + } + 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 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() { + 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_types_separated_by_comma(&mut self, types: &[Type]) { + for (index, typ) in types.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_type(typ); + } + } + + fn show_type(&mut self, typ: &Type) { + 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('('); + for (index, typ) in types.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_type(typ); + } + 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("<"); + for (index, generic) in generics.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_type(generic); + } + 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("<"); + for (index, generic) in generics.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_type(generic); + } + self.push('>'); + } + } + Type::TypeVariable(type_variable) => match &*type_variable.borrow() { + TypeBinding::Bound(typ) => { + self.show_type(typ); + } + TypeBinding::Unbound(..) => { + self.push('_'); + } + }, + Type::TraitAsType(..) => { + panic!("Trait as type should not happen") + } + Type::NamedGeneric(_type_variable, 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('('); + for (index, arg) in args.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_type(arg); + } + self.push_str(")"); + 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.push('('); + self.show_type(lhs); + self.push(' '); + self.push_str(&op.to_string()); + self.push(' '); + self.show_type(rhs); + self.push(')'); + } + Type::Unit + | Type::Bool + | Type::Integer(..) + | Type::FieldElement + | Type::String(_) + | Type::Quoted(..) + | Type::Error => self.push_str(&typ.to_string()), + } + } + + 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 show_hir_expression_id_dereferencing(&mut self, expr_id: ExprId) { + let hir_expr = self.interner.expression(&expr_id); + let HirExpression::Prefix(prefix) = &hir_expr else { + self.show_hir_expression(hir_expr); + return; + }; + + match prefix.operator { + UnaryOp::Reference { .. } | UnaryOp::Dereference { implicitly_added: true } => { + self.show_hir_expression_id_dereferencing(prefix.rhs); + } + UnaryOp::Minus | UnaryOp::Not | UnaryOp::Dereference { implicitly_added: false } => { + self.show_hir_expression(hir_expr); + } + } + } + + 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_str("-("); + self.show_hir_expression_id(hir_prefix_expression.rhs); + self.push(')'); + } + UnaryOp::Not => { + self.push_str("!("); + self.show_hir_expression_id(hir_prefix_expression.rhs); + self.push(')'); + } + 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.push('*'); + } + self.show_hir_expression_id(hir_prefix_expression.rhs); + } + }, + HirExpression::Infix(hir_infix_expression) => { + self.push('('); + self.show_hir_expression_id(hir_infix_expression.lhs); + self.push(' '); + self.push_str(&hir_infix_expression.operator.kind.to_string()); + self.push(' '); + self.show_hir_expression_id(hir_infix_expression.rhs); + self.push(')'); + } + HirExpression::Index(hir_index_expression) => { + self.show_hir_expression_id(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 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(" { "); + for (index, (name, value)) in hir_constructor_expression.fields.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.push_str(&name.to_string()); + self.push_str(": "); + self.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(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; + } + + self.show_hir_expression_id(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(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) => 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 { .. } => todo!("hir match guard"), + HirMatch::Switch(..) => todo!("hir match switch"), + }, + 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.push('|'); + for (index, (parameter, typ)) in hir_lambda.parameters.into_iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_hir_pattern(parameter); + self.push_str(": "); + self.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(hir_lambda.body); + } + HirExpression::Quote(tokens) => { + self.push_str("quote {"); + self.push_str(&tokens_to_string_with_indent(&tokens.0, self.indent, self.interner)); + self.push_str("}"); + } + HirExpression::Comptime(hir_block_expression) => { + self.push_str("comptime "); + self.show_hir_block_expression(hir_block_expression); + } + HirExpression::Unsafe(hir_block_expression) => { + // TODO: show the original comment + self.push_str("/* Safety: TODO */\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::MethodCall(_) => { + todo!("method calls should not happen") + } + HirExpression::Unquote(_) => todo!("unquote should not happen"), + } + } + + 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; + }; + + // The function must have a self type + let func_meta = self.interner.function_meta(&func_id); + 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; + } + + self.show_hir_expression_id_dereferencing(arguments[0]); + self.push('.'); + self.push_str(self.interner.function_name(&func_id)); + self.push('('); + for (index, argument) in arguments[1..].iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_hir_expression_id(*argument); + } + self.push(')'); + + true + } + + fn show_hir_block_expression(&mut self, block: HirBlockExpression) { + self.push_str("{\n"); + self.increase_indent(); + for statement in block.statements { + self.write_indent(); + self.show_hir_statement_id(statement); + 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]) { + for (index, expr_id) in expr_ids.iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.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) + { + // TODO: show the original comment + self.push_str("/* Safety: TODO */\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(_) => todo!("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) => { + // TODO: escape the string + self.push_str(&string); + } + FmtStrFragment::Interpolation(string, _) => { + // TODO: interpolate expr_id instead? + 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, element_type: _, location: _ } => { + self.push('*'); + self.show_hir_lvalue(*lvalue); + } + } + } + + 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('('); + for (index, pattern) in hir_patterns.into_iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.show_hir_pattern(pattern); + } + 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(); + for (index, (name, pattern)) in items.into_iter().enumerate() { + if index != 0 { + self.push_str(", "); + } + self.push_str(&name.to_string()); + self.push_str(": "); + self.show_hir_pattern(pattern); + } + self.push('\n'); + self.decrease_indent(); + self.write_indent(); + self.push('}'); + } + } + } + + 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 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). + let name = name.replace('$', "___"); + + self.push_str(&name); + } + } + } + + 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_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 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().unwrap_or_else(String::new) + } + 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 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) => { + // TODO: check _type_alias + 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 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); + } +} + +fn gather_named_type_vars(typ: &Type, type_vars: &mut HashSet) { + 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(_, name) => { + type_vars.insert(name.to_string()); + } + 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::Unit + | Type::FieldElement + | Type::Integer(..) + | Type::Bool + | Type::String(_) + | Type::Quoted(_) + | Type::Constant(..) + | Type::TypeVariable(_) + | Type::Error => (), + } +} + +fn type_mentions_data_type(typ: &Type, data_type: &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) => { + // TODO: check _type_alias + 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(from, 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 => true, + } +} + +fn module_def_id_to_reference_id(module_def_id: ModuleDefId) -> ReferenceId { + match module_def_id { + ModuleDefId::ModuleId(module_id) => ReferenceId::Module(module_id), + ModuleDefId::FunctionId(func_id) => ReferenceId::Function(func_id), + ModuleDefId::TypeId(type_id) => ReferenceId::Type(type_id), + ModuleDefId::TypeAliasId(type_alias_id) => ReferenceId::Alias(type_alias_id), + ModuleDefId::TraitId(trait_id) => ReferenceId::Trait(trait_id), + ModuleDefId::GlobalId(global_id) => ReferenceId::Global(global_id), + } +} diff --git a/tooling/nargo_cli/src/cli/mod.rs b/tooling/nargo_cli/src/cli/mod.rs index 0c644ae554c..7f001d0afc3 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 generate_completion_script_cmd; @@ -106,6 +107,7 @@ enum NargoCommand { Lsp(lsp_cmd::LspCommand), #[command(hide = true)] Dap(dap_cmd::DapCommand), + Expand(expand_cmd::ExpandCommand), GenerateCompletionScript(generate_completion_script_cmd::GenerateCompletionScriptCommand), } @@ -147,6 +149,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_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);