diff --git a/compiler/noirc_errors/src/position.rs b/compiler/noirc_errors/src/position.rs index e068ca319ea..e308eb9a2c7 100644 --- a/compiler/noirc_errors/src/position.rs +++ b/compiler/noirc_errors/src/position.rs @@ -89,6 +89,12 @@ impl From for Range { } } +impl From> for Span { + fn from(Range { start, end }: Range) -> Self { + Self(ByteSpan::new(start, end)) + } +} + impl chumsky::Span for Span { type Context = (); diff --git a/tooling/nargo_fmt/src/config.rs b/tooling/nargo_fmt/src/config.rs index 2d76c567e8c..7cb80a9f04d 100644 --- a/tooling/nargo_fmt/src/config.rs +++ b/tooling/nargo_fmt/src/config.rs @@ -33,13 +33,17 @@ macro_rules! config { #[derive(serde::Deserialize, serde::Serialize, Clone)] pub struct TomlConfig { - $(pub $field_name: Option<$field_ty>),+ + $( + #[doc = $description] + pub $field_name: Option<$field_ty> + ),+ } ) } config! { tab_spaces: usize, 4, "Number of spaces per tab"; + remove_nested_parens: bool, true, "Remove nested parens"; } impl Config { diff --git a/tooling/nargo_fmt/src/visitor.rs b/tooling/nargo_fmt/src/visitor.rs index cf2b4d9adfd..b0b897c4cd7 100644 --- a/tooling/nargo_fmt/src/visitor.rs +++ b/tooling/nargo_fmt/src/visitor.rs @@ -33,6 +33,16 @@ impl<'me> FmtVisitor<'me> { } } + pub(crate) fn fork(&self) -> Self { + Self { + config: self.config, + buffer: String::new(), + source: self.source, + block_indent: self.block_indent, + last_position: self.last_position, + } + } + pub(crate) fn finish(self) -> String { self.buffer } @@ -128,6 +138,17 @@ impl<'me> FmtVisitor<'me> { let blank_lines = "\n".repeat(newline_count); self.push_str(&blank_lines); } + + pub(crate) fn format_comment(&self, span: Span) -> String { + let slice = slice!(self, span.start(), span.end()).trim(); + let pos = slice.find('/'); + + if !slice.is_empty() && pos.is_some() { + slice.to_string() + } else { + String::new() + } + } } #[derive(Clone, Copy)] @@ -144,6 +165,10 @@ impl Indent { self.block_indent -= config.tab_spaces; } + fn to_string_with_newline(self) -> String { + "\n".to_string() + &self.to_string() + } + #[allow(clippy::inherent_to_string)] fn to_string(self) -> String { " ".repeat(self.block_indent) diff --git a/tooling/nargo_fmt/src/visitor/expr.rs b/tooling/nargo_fmt/src/visitor/expr.rs index 0c28bf46a51..c2d3b4f6632 100644 --- a/tooling/nargo_fmt/src/visitor/expr.rs +++ b/tooling/nargo_fmt/src/visitor/expr.rs @@ -16,14 +16,11 @@ impl FmtVisitor<'_> { self.last_position = span.end(); } - fn format_expr(&self, Expression { kind, span }: Expression) -> String { + fn format_expr(&self, Expression { kind, mut span }: Expression) -> String { match kind { ExpressionKind::Block(block) => { - let mut visitor = FmtVisitor::new(self.source, self.config); - - visitor.block_indent = self.block_indent; + let mut visitor = self.fork(); visitor.visit_block(block, span, true); - visitor.buffer } ExpressionKind::Prefix(prefix) => { @@ -108,7 +105,64 @@ impl FmtVisitor<'_> { literal.to_string() } }, - ExpressionKind::Parenthesized(subexpr) => format!("({})", self.format_expr(*subexpr)), + ExpressionKind::Parenthesized(mut sub_expr) => { + let remove_nested_parens = self.config.remove_nested_parens; + + let mut leading; + let mut trailing; + + loop { + let leading_span = span.start() + 1..sub_expr.span.start(); + let trailing_span = sub_expr.span.end()..span.end() - 1; + + leading = self.format_comment(leading_span.into()); + trailing = self.format_comment(trailing_span.into()); + + if let ExpressionKind::Parenthesized(ref sub_sub_expr) = sub_expr.kind { + if remove_nested_parens && leading.is_empty() && trailing.is_empty() { + span = sub_expr.span; + sub_expr = sub_sub_expr.clone(); + continue; + } + } + + break; + } + + if !leading.contains("//") && !trailing.contains("//") { + let sub_expr = self.format_expr(*sub_expr); + format!("({leading}{sub_expr}{trailing})") + } else { + let mut visitor = self.fork(); + + let indent = visitor.block_indent.to_string_with_newline(); + visitor.block_indent.block_indent(self.config); + let nested_indent = visitor.block_indent.to_string_with_newline(); + + let sub_expr = visitor.format_expr(*sub_expr); + + let mut result = String::new(); + result.push('('); + + if !leading.is_empty() { + result.push_str(&nested_indent); + result.push_str(&leading); + } + + result.push_str(&nested_indent); + result.push_str(&sub_expr); + + if !trailing.is_empty() { + result.push_str(&nested_indent); + result.push_str(&trailing); + } + + result.push_str(&indent); + result.push(')'); + + result + } + } // TODO: _expr => slice!(self, span.start(), span.end()).to_string(), }