From 26f3fed7dda0b94ec3de9abf85a631c697db693b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 2 Dec 2023 14:43:14 +0100 Subject: [PATCH] chore: misc fmt --- crates/config/src/inline/natspec.rs | 1 + crates/fmt/src/formatter.rs | 1778 +++++++++++++-------------- 2 files changed, 890 insertions(+), 889 deletions(-) diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index c73cc4492dc7..11557fd7a829 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -7,6 +7,7 @@ use serde_json::Value; use std::{collections::BTreeMap, path::Path}; /// Convenient struct to hold in-line per-test configurations +#[derive(Debug)] pub struct NatSpec { /// The parent contract of the natspec pub contract: String, diff --git a/crates/fmt/src/formatter.rs b/crates/fmt/src/formatter.rs index 02cf851697fe..2ed579dce59c 100644 --- a/crates/fmt/src/formatter.rs +++ b/crates/fmt/src/formatter.rs @@ -92,46 +92,6 @@ pub struct Formatter<'a, W> { inline_config: InlineConfig, } -/// An action which may be committed to a Formatter -struct Transaction<'f, 'a, W> { - fmt: &'f mut Formatter<'a, W>, - buffer: String, - comments: Comments, -} - -impl<'f, 'a, W> std::ops::Deref for Transaction<'f, 'a, W> { - type Target = Formatter<'a, W>; - fn deref(&self) -> &Self::Target { - self.fmt - } -} - -impl<'f, 'a, W> std::ops::DerefMut for Transaction<'f, 'a, W> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.fmt - } -} - -impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { - /// Create a new transaction from a callback - fn new( - fmt: &'f mut Formatter<'a, W>, - fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, - ) -> Result { - let mut comments = fmt.comments.clone(); - let buffer = fmt.with_temp_buf(fun)?.w; - comments = std::mem::replace(&mut fmt.comments, comments); - Ok(Self { fmt, buffer, comments }) - } - - /// Commit the transaction to the Formatter - fn commit(self) -> Result { - self.fmt.comments = self.comments; - write_chunk!(self.fmt, "{}", self.buffer)?; - Ok(self.buffer) - } -} - impl<'a, W: Write> Formatter<'a, W> { pub fn new( w: W, @@ -1826,6 +1786,18 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } + // Support extension for Solana/Substrate + #[instrument(name = "annotation", skip_all)] + fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { + return_source_if_disabled!(self, annotation.loc); + let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; + write!(self.buf(), "@{id}")?; + write!(self.buf(), "(")?; + annotation.value.visit(self)?; + write!(self.buf(), ")")?; + Ok(()) + } + #[instrument(name = "pragma", skip_all)] fn visit_pragma( &mut self, @@ -1991,6 +1963,114 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } + #[instrument(name = "assembly", skip_all)] + fn visit_assembly( + &mut self, + loc: Loc, + dialect: &mut Option, + block: &mut YulBlock, + flags: &mut Option>, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + write_chunk!(self, loc.start(), "assembly")?; + if let Some(StringLiteral { loc, string, .. }) = dialect { + write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; + } + if let Some(flags) = flags { + if !flags.is_empty() { + let loc_start = flags.first().unwrap().loc.start(); + self.surrounded( + SurroundingChunk::new("(", Some(loc_start), None), + SurroundingChunk::new(")", None, Some(block.loc.start())), + |fmt, _| { + let mut flags = flags.iter_mut().peekable(); + let mut chunks = vec![]; + while let Some(flag) = flags.next() { + let next_byte_offset = + flags.peek().map(|next_flag| next_flag.loc.start()); + chunks.push(fmt.chunked( + flag.loc.start(), + next_byte_offset, + |fmt| { + write!(fmt.buf(), "\"{}\"", flag.string)?; + Ok(()) + }, + )?); + } + fmt.write_chunks_separated(&chunks, ",", false)?; + Ok(()) + }, + )?; + } + } + + block.visit(self) + } + + #[instrument(name = "block", skip_all)] + fn visit_block( + &mut self, + loc: Loc, + unchecked: bool, + statements: &mut Vec, + ) -> Result<()> { + return_source_if_disabled!(self, loc); + if unchecked { + write_chunk!(self, loc.start(), "unchecked ")?; + } + + self.visit_block(loc, statements, false, false)?; + Ok(()) + } + + #[instrument(name = "args", skip_all)] + fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + write!(self.buf(), "{{")?; + + let mut args_iter = args.iter_mut().peekable(); + let mut chunks = Vec::new(); + while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() { + let next_byte_offset = args_iter + .peek() + .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) + .unwrap_or_else(|| loc.end()); + chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { + fmt.grouped(|fmt| { + write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; + expr.visit(fmt) + })?; + Ok(()) + })?); + } + + if let Some(first) = chunks.first_mut() { + if first.prefixes.is_empty() && + first.postfixes_before.is_empty() && + !self.config.bracket_spacing + { + first.needs_space = Some(false); + } + } + let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; + self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?; + + let prefix = if multiline && !self.is_beginning_of_line() { + "\n" + } else if self.config.bracket_spacing { + " " + } else { + "" + }; + let closing_bracket = format!("{prefix}{}", "}"); + let closing_bracket_loc = args.last().unwrap().loc.end(); + write_chunk!(self, closing_bracket_loc, "{closing_bracket}")?; + + Ok(()) + } + #[instrument(name = "expr", skip_all)] fn visit_expr(&mut self, loc: Loc, expr: &mut Expression) -> Result<()> { return_source_if_disabled!(self, loc); @@ -2361,6 +2441,62 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } + #[instrument(name = "var_definition", skip_all)] + fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { + return_source_if_disabled!(self, var.loc, ';'); + + var.ty.visit(self)?; + + let multiline = self.grouped(|fmt| { + let var_name = var.name.safe_unwrap_mut(); + let name_start = var_name.loc.start(); + + let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; + if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { + fmt.write_chunks_separated(&attrs, "", true)?; + } + + let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; + if var.initializer.is_some() { + name.content.push_str(" ="); + } + fmt.write_chunk(&name)?; + + Ok(()) + })?; + + var.initializer + .as_mut() + .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) + .transpose()?; + + self.write_semicolon()?; + + Ok(()) + } + + #[instrument(name = "var_definition_stmt", skip_all)] + fn visit_var_definition_stmt( + &mut self, + loc: Loc, + declaration: &mut VariableDeclaration, + expr: &mut Option, + ) -> Result<()> { + return_source_if_disabled!(self, loc, ';'); + + let declaration = self + .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?; + let multiline = declaration.content.contains('\n'); + self.write_chunk(&declaration)?; + + if let Some(expr) = expr { + write!(self.buf(), " =")?; + self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; + } + + self.write_semicolon() + } + #[instrument(name = "var_declaration", skip_all)] fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> { return_source_if_disabled!(self, var.loc); @@ -2375,526 +2511,321 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - #[instrument(name = "break", skip_all)] - fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); - } - write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" }) - } + #[instrument(name = "return", skip_all)] + fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); - #[instrument(name = "continue", skip_all)] - fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); - } - write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" }) - } + self.write_postfix_comments_before(loc.start())?; + self.write_prefix_comments_before(loc.start())?; - #[instrument(name = "function", skip_all)] - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { - if func.body.is_some() { - return_source_if_disabled!(self, func.loc()); - } else { - return_source_if_disabled!(self, func.loc(), ';'); + if expr.is_none() { + write_chunk!(self, loc.end(), "return;")?; + return Ok(()) } - self.with_function_context(func.clone(), |fmt| { - fmt.write_postfix_comments_before(func.loc.start())?; - fmt.write_prefix_comments_before(func.loc.start())?; - - let body_loc = func.body.as_ref().map(CodeLocation::loc); - let mut attrs_multiline = false; - let fits_on_single = fmt.try_on_single_line(|fmt| { - fmt.write_function_header(func, body_loc, false)?; - Ok(()) - })?; - if !fits_on_single { - attrs_multiline = fmt.write_function_header(func, body_loc, true)?; - } - - // write function body - match &mut func.body { - Some(body) => { - let body_loc = body.loc(); - let byte_offset = body_loc.start(); - let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; - fmt.write_whitespace_separator( - attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), - )?; - fmt.write_chunk(&body)?; - } - None => fmt.write_semicolon()?, - } - + let expr = expr.as_mut().unwrap(); + let expr_loc_start = expr.loc().start(); + let write_return = |fmt: &mut Self| -> Result<()> { + write_chunk!(fmt, loc.start(), "return")?; + fmt.write_postfix_comments_before(expr_loc_start)?; Ok(()) - })?; - - Ok(()) - } - - #[instrument(name = "function_attribute", skip_all)] - fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); + }; - match attribute { - FunctionAttribute::Mutability(mutability) => { - write_chunk!(self, mutability.loc().end(), "{mutability}")? - } - FunctionAttribute::Visibility(visibility) => { - // Visibility will always have a location in a Function attribute - write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? - } - FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, - FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, - FunctionAttribute::Override(loc, args) => { - write_chunk!(self, loc.start(), "override")?; - if !args.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", args, None, Some(loc.end()), false)? + let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { + let fits_on_single = fmt.try_on_single_line(|fmt| { + write_return(fmt)?; + expr.visit(fmt) + })?; + if fits_on_single { + return Ok(()) } - FunctionAttribute::BaseOrModifier(loc, base) => { - let is_contract_base = self.context.contract.as_ref().map_or(false, |contract| { - contract.base.iter().any(|contract_base| { - contract_base - .name - .identifiers - .iter() - .zip(&base.name.identifiers) - .all(|(l, r)| l.name == r.name) - }) - }); - if is_contract_base { - base.visit(self)?; - } else { - let mut base_or_modifier = - self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; - if base_or_modifier.content.ends_with("()") { - base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); + let mut fit_on_next_line = false; + let tx = fmt.transact(|fmt| { + fmt.grouped(|fmt| { + write_return(fmt)?; + if !fmt.is_beginning_of_line() { + fmt.write_whitespace_separator(true)?; } - self.write_chunk(&base_or_modifier)?; - } + fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; + Ok(()) + })?; + Ok(()) + })?; + if fit_on_next_line { + tx.commit()?; + return Ok(()) } - FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, + + write_return(fmt)?; + expr.visit(fmt)?; + Ok(()) }; + write_return_with_expr(self)?; + write_chunk!(self, loc.end(), ";")?; Ok(()) } - #[instrument(name = "base", skip_all)] - fn visit_base(&mut self, base: &mut Base) -> Result<()> { - return_source_if_disabled!(self, base.loc); - - let name_loc = &base.name.loc; - let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { - fmt.visit_ident_path(&mut base.name)?; - Ok(()) - })?; - - if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { - if self.context.function.is_some() { - name.content.push_str("()"); - } - self.write_chunk(&name)?; - return Ok(()) + #[instrument(name = "revert", skip_all)] + fn visit_revert( + &mut self, + loc: Loc, + error: &mut Option, + args: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + write_chunk!(self, loc.start(), "revert")?; + if let Some(error) = error { + error.visit(self)?; } - - let args = base.args.as_mut().unwrap(); - let args_start = CodeLocation::loc(args.first().unwrap()).start(); - - name.content.push('('); - let formatted_name = self.chunk_to_string(&name)?; - - let multiline = !self.will_it_fit(&formatted_name); - - self.surrounded( - SurroundingChunk::new(&formatted_name, Some(args_start), None), - SurroundingChunk::new(")", None, Some(base.loc.end())), - |fmt, multiline_hint| { - let args = fmt.items_to_chunks( - Some(base.loc.end()), - args.iter_mut().map(|arg| (arg.loc(), arg)), - )?; - let multiline = multiline || - multiline_hint || - fmt.are_chunks_separated_multiline("{}", &args, ",")?; - fmt.write_chunks_separated(&args, ",", multiline)?; - Ok(()) - }, - )?; + self.visit_list("", args, None, Some(loc.end()), true)?; + self.write_semicolon()?; Ok(()) } - #[instrument(name = "parameter", skip_all)] - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { - return_source_if_disabled!(self, parameter.loc); - self.grouped(|fmt| { - parameter.ty.visit(fmt)?; - if let Some(storage) = ¶meter.storage { - write_chunk!(fmt, storage.loc().end(), "{storage}")?; - } - if let Some(name) = ¶meter.name { - write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } + #[instrument(name = "revert_named_args", skip_all)] + fn visit_revert_named_args( + &mut self, + loc: Loc, + error: &mut Option, + args: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); - #[instrument(name = "struct", skip_all)] - fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { - return_source_if_disabled!(self, structure.loc); - self.grouped(|fmt| { - let struct_name = structure.name.safe_unwrap_mut(); - write_chunk!(fmt, struct_name.loc.start(), "struct")?; - struct_name.visit(fmt)?; - if structure.fields.is_empty() { - return fmt.write_empty_brackets() + write_chunk!(self, loc.start(), "revert")?; + let mut error_indented = false; + if let Some(error) = error { + if !self.try_on_single_line(|fmt| error.visit(fmt))? { + error.visit(self)?; + error_indented = true; } + } - write!(fmt.buf(), " {{")?; - fmt.surrounded( - SurroundingChunk::new("", Some(struct_name.loc.end()), None), - SurroundingChunk::new("}", None, Some(structure.loc.end())), - |fmt, _multiline| { - let chunks = fmt.items_to_chunks( - Some(structure.loc.end()), - structure.fields.iter_mut().map(|ident| (ident.loc, ident)), - )?; - for mut chunk in chunks { - chunk.content.push(';'); - fmt.write_chunk(&chunk)?; - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }, - ) - })?; + if args.is_empty() { + write!(self.buf(), "({{}});")?; + return Ok(()) + } + + write!(self.buf(), "(")?; + self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; + write!(self.buf(), ")")?; + self.write_semicolon()?; Ok(()) } - #[instrument(name = "type_definition", skip_all)] - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { - return_source_if_disabled!(self, def.loc, ';'); - self.grouped(|fmt| { - write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; - def.name.visit(fmt)?; - write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?; - def.ty.visit(fmt)?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) + #[instrument(name = "break", skip_all)] + fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { + if semicolon { + return_source_if_disabled!(self, loc, ';'); + } else { + return_source_if_disabled!(self, loc); + } + write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" }) } - #[instrument(name = "stray_semicolon", skip_all)] - fn visit_stray_semicolon(&mut self) -> Result<()> { - self.write_semicolon() + #[instrument(name = "continue", skip_all)] + fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { + if semicolon { + return_source_if_disabled!(self, loc, ';'); + } else { + return_source_if_disabled!(self, loc); + } + write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" }) } - #[instrument(name = "block", skip_all)] - fn visit_block( + #[instrument(name = "try", skip_all)] + fn visit_try( &mut self, loc: Loc, - unchecked: bool, - statements: &mut Vec, - ) -> Result<()> { - return_source_if_disabled!(self, loc); - if unchecked { - write_chunk!(self, loc.start(), "unchecked ")?; - } - - self.visit_block(loc, statements, false, false)?; - Ok(()) - } - - #[instrument(name = "opening_paren", skip_all)] - fn visit_opening_paren(&mut self) -> Result<()> { - write_chunk!(self, "(")?; - Ok(()) - } - - #[instrument(name = "closing_paren", skip_all)] - fn visit_closing_paren(&mut self) -> Result<()> { - write_chunk!(self, ")")?; - Ok(()) - } - - #[instrument(name = "newline", skip_all)] - fn visit_newline(&mut self) -> Result<()> { - writeln_chunk!(self)?; - Ok(()) - } - - #[instrument(name = "event", skip_all)] - fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { - return_source_if_disabled!(self, event.loc, ';'); - - let event_name = event.name.safe_unwrap_mut(); - let mut name = - self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; - name.content = format!("event {}(", name.content); - - let last_chunk = if event.anonymous { ") anonymous;" } else { ");" }; - if event.fields.is_empty() { - name.content.push_str(last_chunk); - self.write_chunk(&name)?; - } else { - let byte_offset = event.fields.first().unwrap().loc.start(); - let first_chunk = self.chunk_to_string(&name)?; - self.surrounded( - SurroundingChunk::new(first_chunk, Some(byte_offset), None), - SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), - |fmt, multiline| { - let params = fmt - .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; - - let multiline = - multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; - fmt.write_chunks_separated(¶ms, ",", multiline) - }, - )?; - } - - Ok(()) - } - - #[instrument(name = "event_parameter", skip_all)] - fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if param.indexed { - write_chunk!(fmt, param.loc.start(), "indexed")?; - } - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "error", skip_all)] - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { - return_source_if_disabled!(self, error.loc, ';'); - - let error_name = error.name.safe_unwrap_mut(); - let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; - name.content = format!("error {}", name.content); - - let formatted_name = self.chunk_to_string(&name)?; - write!(self.buf(), "{formatted_name}")?; - let start_offset = error.fields.first().map(|f| f.loc.start()); - self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?; - self.write_semicolon()?; - - Ok(()) - } + expr: &mut Expression, + returns: &mut Option<(Vec<(Loc, Option)>, Box)>, + clauses: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); - #[instrument(name = "error_parameter", skip_all)] - fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; + let try_next_byte = clauses.first().map(|c| match c { + CatchClause::Simple(loc, ..) => loc.start(), + CatchClause::Named(loc, ..) => loc.start(), + }); + let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { + write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; + expr.visit(fmt)?; + if let Some((params, stmt)) = returns { + let mut params = + params.iter_mut().filter(|(_, param)| param.is_some()).collect::>(); + let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); + fmt.surrounded( + SurroundingChunk::new("returns (", Some(byte_offset), None), + SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), + |fmt, _| { + let chunks = fmt.items_to_chunks( + Some(stmt.loc().start()), + params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), + )?; + let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + Ok(()) + }, + )?; + stmt.visit(fmt)?; } Ok(()) })?; - Ok(()) - } - - #[instrument(name = "using", skip_all)] - fn visit_using(&mut self, using: &mut Using) -> Result<()> { - return_source_if_disabled!(self, using.loc, ';'); - - write_chunk!(self, using.loc.start(), "using")?; - - let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); - let global_start = using.global.as_mut().map(|global| global.loc.start()); - let loc_end = using.loc.end(); - let (is_library, mut list_chunks) = match &mut using.list { - UsingList::Library(library) => { - (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?]) - } - UsingList::Functions(funcs) => { - let mut funcs = funcs.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(func) = funcs.next() { - let next_byte_end = funcs.peek().map(|func| func.loc.start()); - chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { - fmt.visit_ident_path(&mut func.path)?; - if let Some(op) = func.oper { - write!(fmt.buf(), " as {op}")?; - } - Ok(()) - })?); + let mut chunks = vec![try_chunk]; + for clause in clauses { + let (loc, ident, mut param, stmt) = match clause { + CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), + CatchClause::Named(loc, ident, param, stmt) => { + (loc, Some(ident), Some(param), stmt) } - (false, chunks) - } - UsingList::Error => return self.visit_parser_error(using.loc), - }; - - let for_chunk = self.chunk_at( - using.loc.start(), - Some(ty_start.or(global_start).unwrap_or(loc_end)), - None, - "for", - ); - let ty_chunk = if let Some(ty) = &mut using.ty { - self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? - } else { - self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*") - }; - let global_chunk = using - .global - .as_mut() - .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) - .transpose()?; + }; - let write_for_def = |fmt: &mut Self| { - fmt.grouped(|fmt| { - fmt.write_chunk(&for_chunk)?; - fmt.write_chunk(&ty_chunk)?; - if let Some(global_chunk) = global_chunk.as_ref() { - fmt.write_chunk(global_chunk)?; + let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { + write_chunk!(fmt, "catch")?; + if let Some(ident) = ident.as_ref() { + fmt.write_postfix_comments_before( + param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()), + )?; + write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; } + if let Some(param) = param.as_mut() { + write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; + fmt.surrounded( + SurroundingChunk::new("", Some(param.loc.start()), None), + SurroundingChunk::new(")", None, Some(stmt.loc().start())), + |fmt, _| param.visit(fmt), + )?; + } + + stmt.visit(fmt)?; Ok(()) })?; - Ok(()) - }; - let simulated_for_def = self.simulate_to_string(write_for_def)?; - - if is_library { - let chunk = list_chunks.pop().unwrap(); - if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { - self.write_chunk(&chunk)?; - write_for_def(self)?; - } else { - self.write_whitespace_separator(true)?; - self.grouped(|fmt| { - fmt.write_chunk(&chunk)?; - Ok(()) - })?; - self.write_whitespace_separator(true)?; - write_for_def(self)?; - } - } else { - self.surrounded( - SurroundingChunk::new("{", Some(using.loc.start()), None), - SurroundingChunk::new( - "}", - None, - Some(ty_start.or(global_start).unwrap_or(loc_end)), - ), - |fmt, _multiline| { - let multiline = fmt.are_chunks_separated_multiline( - &format!("{{ {{}} }} {simulated_for_def};"), - &list_chunks, - ",", - )?; - fmt.write_chunks_separated(&list_chunks, ",", multiline)?; - Ok(()) - }, - )?; - write_for_def(self)?; + chunks.push(chunk); } - self.write_semicolon()?; + let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; + if !multiline { + self.write_chunks_separated(&chunks, "", false)?; + return Ok(()) + } - Ok(()) - } + let mut chunks = chunks.iter_mut().peekable(); + let mut prev_multiline = false; - #[instrument(name = "var_attribute", skip_all)] - fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); + // write try chunk first + if let Some(chunk) = chunks.next() { + let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; + write!(self.buf(), "{chunk_str}")?; + prev_multiline = chunk_str.contains('\n'); + } - let token = match attribute { - VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), - VariableAttribute::Constant(_) => Some("constant".to_string()), - VariableAttribute::Immutable(_) => Some("immutable".to_string()), - VariableAttribute::Override(loc, idents) => { - write_chunk!(self, loc.start(), "override")?; - if !idents.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; - None - } - }; - if let Some(token) = token { - let loc = attribute.loc(); - write_chunk!(self, loc.start(), loc.end(), "{}", token)?; + while let Some(chunk) = chunks.next() { + let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; + let multiline = chunk_str.contains('\n'); + self.indented_if(!multiline, 1, |fmt| { + chunk.needs_space = Some(false); + let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); + let prefix = if fmt.is_beginning_of_line() { + "" + } else if on_same_line { + " " + } else { + "\n" + }; + let chunk_str = format!("{prefix}{chunk_str}"); + write!(fmt.buf(), "{chunk_str}")?; + Ok(()) + })?; + prev_multiline = multiline; } Ok(()) } - #[instrument(name = "var_definition", skip_all)] - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { - return_source_if_disabled!(self, var.loc, ';'); - - var.ty.visit(self)?; - - let multiline = self.grouped(|fmt| { - let var_name = var.name.safe_unwrap_mut(); - let name_start = var_name.loc.start(); - - let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; - if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { - fmt.write_chunks_separated(&attrs, "", true)?; - } + #[instrument(name = "if", skip_all)] + fn visit_if( + &mut self, + loc: Loc, + cond: &mut Expression, + if_branch: &mut Box, + else_branch: &mut Option>, + is_first_stmt: bool, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); - let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; - if var.initializer.is_some() { - name.content.push_str(" ="); - } - fmt.write_chunk(&name)?; + if !is_first_stmt { + self.write_if_stmt(loc, cond, if_branch, else_branch)?; + return Ok(()) + } + self.context.if_stmt_single_line = Some(true); + let mut stmt_fits_on_single = false; + let tx = self.transact(|fmt| { + stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { + Ok(()) => true, + Err(FormatterError::Fmt(_)) => false, + Err(err) => bail!(err), + }; Ok(()) })?; - var.initializer - .as_mut() - .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) - .transpose()?; - - self.write_semicolon()?; + if stmt_fits_on_single { + tx.commit()?; + } else { + self.context.if_stmt_single_line = Some(false); + self.write_if_stmt(loc, cond, if_branch, else_branch)?; + } + self.context.if_stmt_single_line = None; Ok(()) } - #[instrument(name = "var_definition_stmt", skip_all)] - fn visit_var_definition_stmt( + #[instrument(name = "do_while", skip_all)] + fn visit_do_while( &mut self, loc: Loc, - declaration: &mut VariableDeclaration, - expr: &mut Option, - ) -> Result<()> { + body: &mut Statement, + cond: &mut Expression, + ) -> Result<(), Self::Error> { return_source_if_disabled!(self, loc, ';'); + write_chunk!(self, loc.start(), "do ")?; + self.visit_stmt_as_block(body, false)?; + visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { + self.surrounded( + SurroundingChunk::new("while (", Some(cond.loc().start()), None), + SurroundingChunk::new(");", None, Some(loc.end())), + |fmt, _| cond.visit(fmt), + )?; + }); + Ok(()) + } - let declaration = self - .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?; - let multiline = declaration.content.contains('\n'); - self.write_chunk(&declaration)?; - - if let Some(expr) = expr { - write!(self.buf(), " =")?; - self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; - } + #[instrument(name = "while", skip_all)] + fn visit_while( + &mut self, + loc: Loc, + cond: &mut Expression, + body: &mut Statement, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + self.surrounded( + SurroundingChunk::new("while (", Some(loc.start()), None), + SurroundingChunk::new(")", None, Some(cond.loc().end())), + |fmt, _| { + cond.visit(fmt)?; + fmt.write_postfix_comments_before(body.loc().start()) + }, + )?; - self.write_semicolon() + let cond_close_paren_loc = + self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end()); + let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); + self.visit_stmt_as_block(body, attempt_single_line)?; + Ok(()) } #[instrument(name = "for", skip_all)] @@ -2928,416 +2859,457 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { fmt.write_whitespace_separator(true)?; } - match update { - Some(expr) => expr.visit(fmt), - None => Ok(()), + match update { + Some(expr) => expr.visit(fmt), + None => Ok(()), + } + }; + let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?; + if multiline { + write_for_loop_header(fmt, true)?; + } + Ok(()) + }, + )?; + match body { + Some(body) => { + self.visit_stmt_as_block(body, false)?; + } + None => { + self.write_empty_brackets()?; + } + }; + Ok(()) + } + + #[instrument(name = "function", skip_all)] + fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { + if func.body.is_some() { + return_source_if_disabled!(self, func.loc()); + } else { + return_source_if_disabled!(self, func.loc(), ';'); + } + + self.with_function_context(func.clone(), |fmt| { + fmt.write_postfix_comments_before(func.loc.start())?; + fmt.write_prefix_comments_before(func.loc.start())?; + + let body_loc = func.body.as_ref().map(CodeLocation::loc); + let mut attrs_multiline = false; + let fits_on_single = fmt.try_on_single_line(|fmt| { + fmt.write_function_header(func, body_loc, false)?; + Ok(()) + })?; + if !fits_on_single { + attrs_multiline = fmt.write_function_header(func, body_loc, true)?; + } + + // write function body + match &mut func.body { + Some(body) => { + let body_loc = body.loc(); + let byte_offset = body_loc.start(); + let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; + fmt.write_whitespace_separator( + attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), + )?; + fmt.write_chunk(&body)?; + } + None => fmt.write_semicolon()?, + } + + Ok(()) + })?; + + Ok(()) + } + + #[instrument(name = "function_attribute", skip_all)] + fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { + return_source_if_disabled!(self, attribute.loc()); + + match attribute { + FunctionAttribute::Mutability(mutability) => { + write_chunk!(self, mutability.loc().end(), "{mutability}")? + } + FunctionAttribute::Visibility(visibility) => { + // Visibility will always have a location in a Function attribute + write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? + } + FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, + FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, + FunctionAttribute::Override(loc, args) => { + write_chunk!(self, loc.start(), "override")?; + if !args.is_empty() && self.config.override_spacing { + self.write_whitespace_separator(false)?; + } + self.visit_list("", args, None, Some(loc.end()), false)? + } + FunctionAttribute::BaseOrModifier(loc, base) => { + let is_contract_base = self.context.contract.as_ref().map_or(false, |contract| { + contract.base.iter().any(|contract_base| { + contract_base + .name + .identifiers + .iter() + .zip(&base.name.identifiers) + .all(|(l, r)| l.name == r.name) + }) + }); + + if is_contract_base { + base.visit(self)?; + } else { + let mut base_or_modifier = + self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; + if base_or_modifier.content.ends_with("()") { + base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); } - }; - let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?; - if multiline { - write_for_loop_header(fmt, true)?; + self.write_chunk(&base_or_modifier)?; } - Ok(()) - }, - )?; - match body { - Some(body) => { - self.visit_stmt_as_block(body, false)?; - } - None => { - self.write_empty_brackets()?; } + FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, }; - Ok(()) - } - #[instrument(name = "while", skip_all)] - fn visit_while( - &mut self, - loc: Loc, - cond: &mut Expression, - body: &mut Statement, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.surrounded( - SurroundingChunk::new("while (", Some(loc.start()), None), - SurroundingChunk::new(")", None, Some(cond.loc().end())), - |fmt, _| { - cond.visit(fmt)?; - fmt.write_postfix_comments_before(body.loc().start()) - }, - )?; - - let cond_close_paren_loc = - self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end()); - let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); - self.visit_stmt_as_block(body, attempt_single_line)?; Ok(()) } - #[instrument(name = "do_while", skip_all)] - fn visit_do_while( - &mut self, - loc: Loc, - body: &mut Statement, - cond: &mut Expression, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "do ")?; - self.visit_stmt_as_block(body, false)?; - visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { - self.surrounded( - SurroundingChunk::new("while (", Some(cond.loc().start()), None), - SurroundingChunk::new(");", None, Some(loc.end())), - |fmt, _| cond.visit(fmt), - )?; - }); + #[instrument(name = "var_attribute", skip_all)] + fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { + return_source_if_disabled!(self, attribute.loc()); + + let token = match attribute { + VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), + VariableAttribute::Constant(_) => Some("constant".to_string()), + VariableAttribute::Immutable(_) => Some("immutable".to_string()), + VariableAttribute::Override(loc, idents) => { + write_chunk!(self, loc.start(), "override")?; + if !idents.is_empty() && self.config.override_spacing { + self.write_whitespace_separator(false)?; + } + self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; + None + } + }; + if let Some(token) = token { + let loc = attribute.loc(); + write_chunk!(self, loc.start(), loc.end(), "{}", token)?; + } Ok(()) } - #[instrument(name = "if", skip_all)] - fn visit_if( - &mut self, - loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - is_first_stmt: bool, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - if !is_first_stmt { - self.write_if_stmt(loc, cond, if_branch, else_branch)?; - return Ok(()) - } + #[instrument(name = "base", skip_all)] + fn visit_base(&mut self, base: &mut Base) -> Result<()> { + return_source_if_disabled!(self, base.loc); - self.context.if_stmt_single_line = Some(true); - let mut stmt_fits_on_single = false; - let tx = self.transact(|fmt| { - stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; + let name_loc = &base.name.loc; + let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { + fmt.visit_ident_path(&mut base.name)?; Ok(()) })?; - if stmt_fits_on_single { - tx.commit()?; - } else { - self.context.if_stmt_single_line = Some(false); - self.write_if_stmt(loc, cond, if_branch, else_branch)?; + if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { + if self.context.function.is_some() { + name.content.push_str("()"); + } + self.write_chunk(&name)?; + return Ok(()) } - self.context.if_stmt_single_line = None; - Ok(()) - } + let args = base.args.as_mut().unwrap(); + let args_start = CodeLocation::loc(args.first().unwrap()).start(); - #[instrument(name = "args", skip_all)] - fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); + name.content.push('('); + let formatted_name = self.chunk_to_string(&name)?; - write!(self.buf(), "{{")?; + let multiline = !self.will_it_fit(&formatted_name); - let mut args_iter = args.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() { - let next_byte_offset = args_iter - .peek() - .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) - .unwrap_or_else(|| loc.end()); - chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { - fmt.grouped(|fmt| { - write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; - expr.visit(fmt) - })?; + self.surrounded( + SurroundingChunk::new(&formatted_name, Some(args_start), None), + SurroundingChunk::new(")", None, Some(base.loc.end())), + |fmt, multiline_hint| { + let args = fmt.items_to_chunks( + Some(base.loc.end()), + args.iter_mut().map(|arg| (arg.loc(), arg)), + )?; + let multiline = multiline || + multiline_hint || + fmt.are_chunks_separated_multiline("{}", &args, ",")?; + fmt.write_chunks_separated(&args, ",", multiline)?; Ok(()) - })?); - } + }, + )?; - if let Some(first) = chunks.first_mut() { - if first.prefixes.is_empty() && - first.postfixes_before.is_empty() && - !self.config.bracket_spacing - { - first.needs_space = Some(false); + Ok(()) + } + + #[instrument(name = "parameter", skip_all)] + fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { + return_source_if_disabled!(self, parameter.loc); + self.grouped(|fmt| { + parameter.ty.visit(fmt)?; + if let Some(storage) = ¶meter.storage { + write_chunk!(fmt, storage.loc().end(), "{storage}")?; } - } - let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; - self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?; + if let Some(name) = ¶meter.name { + write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; + } + Ok(()) + })?; + Ok(()) + } - let prefix = if multiline && !self.is_beginning_of_line() { - "\n" - } else if self.config.bracket_spacing { - " " - } else { - "" - }; - let closing_bracket = format!("{prefix}{}", "}"); - let closing_bracket_loc = args.last().unwrap().loc.end(); - write_chunk!(self, closing_bracket_loc, "{closing_bracket}")?; + #[instrument(name = "struct", skip_all)] + fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { + return_source_if_disabled!(self, structure.loc); + self.grouped(|fmt| { + let struct_name = structure.name.safe_unwrap_mut(); + write_chunk!(fmt, struct_name.loc.start(), "struct")?; + struct_name.visit(fmt)?; + if structure.fields.is_empty() { + return fmt.write_empty_brackets() + } + + write!(fmt.buf(), " {{")?; + fmt.surrounded( + SurroundingChunk::new("", Some(struct_name.loc.end()), None), + SurroundingChunk::new("}", None, Some(structure.loc.end())), + |fmt, _multiline| { + let chunks = fmt.items_to_chunks( + Some(structure.loc.end()), + structure.fields.iter_mut().map(|ident| (ident.loc, ident)), + )?; + for mut chunk in chunks { + chunk.content.push(';'); + fmt.write_chunk(&chunk)?; + fmt.write_whitespace_separator(true)?; + } + Ok(()) + }, + ) + })?; Ok(()) } - #[instrument(name = "revert", skip_all)] - fn visit_revert( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "revert")?; - if let Some(error) = error { - error.visit(self)?; + #[instrument(name = "event", skip_all)] + fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { + return_source_if_disabled!(self, event.loc, ';'); + + let event_name = event.name.safe_unwrap_mut(); + let mut name = + self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; + name.content = format!("event {}(", name.content); + + let last_chunk = if event.anonymous { ") anonymous;" } else { ");" }; + if event.fields.is_empty() { + name.content.push_str(last_chunk); + self.write_chunk(&name)?; + } else { + let byte_offset = event.fields.first().unwrap().loc.start(); + let first_chunk = self.chunk_to_string(&name)?; + self.surrounded( + SurroundingChunk::new(first_chunk, Some(byte_offset), None), + SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), + |fmt, multiline| { + let params = fmt + .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; + + let multiline = + multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; + fmt.write_chunks_separated(¶ms, ",", multiline) + }, + )?; } - self.visit_list("", args, None, Some(loc.end()), true)?; - self.write_semicolon()?; Ok(()) } - #[instrument(name = "revert_named_args", skip_all)] - fn visit_revert_named_args( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); + #[instrument(name = "event_parameter", skip_all)] + fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { + return_source_if_disabled!(self, param.loc); - write_chunk!(self, loc.start(), "revert")?; - let mut error_indented = false; - if let Some(error) = error { - if !self.try_on_single_line(|fmt| error.visit(fmt))? { - error.visit(self)?; - error_indented = true; + self.grouped(|fmt| { + param.ty.visit(fmt)?; + if param.indexed { + write_chunk!(fmt, param.loc.start(), "indexed")?; } - } - - if args.is_empty() { - write!(self.buf(), "({{}});")?; - return Ok(()) - } - - write!(self.buf(), "(")?; - self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; - write!(self.buf(), ")")?; - self.write_semicolon()?; - + if let Some(name) = ¶m.name { + write_chunk!(fmt, name.loc.end(), "{}", name.name)?; + } + Ok(()) + })?; Ok(()) } - #[instrument(name = "return", skip_all)] - fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - - self.write_postfix_comments_before(loc.start())?; - self.write_prefix_comments_before(loc.start())?; + #[instrument(name = "error", skip_all)] + fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { + return_source_if_disabled!(self, error.loc, ';'); - if expr.is_none() { - write_chunk!(self, loc.end(), "return;")?; - return Ok(()) - } + let error_name = error.name.safe_unwrap_mut(); + let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; + name.content = format!("error {}", name.content); - let expr = expr.as_mut().unwrap(); - let expr_loc_start = expr.loc().start(); - let write_return = |fmt: &mut Self| -> Result<()> { - write_chunk!(fmt, loc.start(), "return")?; - fmt.write_postfix_comments_before(expr_loc_start)?; - Ok(()) - }; + let formatted_name = self.chunk_to_string(&name)?; + write!(self.buf(), "{formatted_name}")?; + let start_offset = error.fields.first().map(|f| f.loc.start()); + self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?; + self.write_semicolon()?; - let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { - let fits_on_single = fmt.try_on_single_line(|fmt| { - write_return(fmt)?; - expr.visit(fmt) - })?; - if fits_on_single { - return Ok(()) - } + Ok(()) + } - let mut fit_on_next_line = false; - let tx = fmt.transact(|fmt| { - fmt.grouped(|fmt| { - write_return(fmt)?; - if !fmt.is_beginning_of_line() { - fmt.write_whitespace_separator(true)?; - } - fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; - Ok(()) - })?; - Ok(()) - })?; - if fit_on_next_line { - tx.commit()?; - return Ok(()) + #[instrument(name = "error_parameter", skip_all)] + fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { + return_source_if_disabled!(self, param.loc); + self.grouped(|fmt| { + param.ty.visit(fmt)?; + if let Some(name) = ¶m.name { + write_chunk!(fmt, name.loc.end(), "{}", name.name)?; } - - write_return(fmt)?; - expr.visit(fmt)?; Ok(()) - }; - - write_return_with_expr(self)?; - write_chunk!(self, loc.end(), ";")?; + })?; Ok(()) } - #[instrument(name = "try", skip_all)] - fn visit_try( - &mut self, - loc: Loc, - expr: &mut Expression, - returns: &mut Option<(Vec<(Loc, Option)>, Box)>, - clauses: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - let try_next_byte = clauses.first().map(|c| match c { - CatchClause::Simple(loc, ..) => loc.start(), - CatchClause::Named(loc, ..) => loc.start(), - }); - let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { - write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; - expr.visit(fmt)?; - if let Some((params, stmt)) = returns { - let mut params = - params.iter_mut().filter(|(_, param)| param.is_some()).collect::>(); - let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); - fmt.surrounded( - SurroundingChunk::new("returns (", Some(byte_offset), None), - SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), - |fmt, _| { - let chunks = fmt.items_to_chunks( - Some(stmt.loc().start()), - params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), - )?; - let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - Ok(()) - }, - )?; - stmt.visit(fmt)?; - } + #[instrument(name = "type_definition", skip_all)] + fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { + return_source_if_disabled!(self, def.loc, ';'); + self.grouped(|fmt| { + write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; + def.name.visit(fmt)?; + write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?; + def.ty.visit(fmt)?; + fmt.write_semicolon()?; Ok(()) })?; + Ok(()) + } - let mut chunks = vec![try_chunk]; - for clause in clauses { - let (loc, ident, mut param, stmt) = match clause { - CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), - CatchClause::Named(loc, ident, param, stmt) => { - (loc, Some(ident), Some(param), stmt) - } - }; - - let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { - write_chunk!(fmt, "catch")?; - if let Some(ident) = ident.as_ref() { - fmt.write_postfix_comments_before( - param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()), - )?; - write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; - } - if let Some(param) = param.as_mut() { - write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; - fmt.surrounded( - SurroundingChunk::new("", Some(param.loc.start()), None), - SurroundingChunk::new(")", None, Some(stmt.loc().start())), - |fmt, _| param.visit(fmt), - )?; - } - - stmt.visit(fmt)?; - Ok(()) - })?; - - chunks.push(chunk); - } - - let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; - if !multiline { - self.write_chunks_separated(&chunks, "", false)?; - return Ok(()) - } + #[instrument(name = "stray_semicolon", skip_all)] + fn visit_stray_semicolon(&mut self) -> Result<()> { + self.write_semicolon() + } - let mut chunks = chunks.iter_mut().peekable(); - let mut prev_multiline = false; + #[instrument(name = "opening_paren", skip_all)] + fn visit_opening_paren(&mut self) -> Result<()> { + write_chunk!(self, "(")?; + Ok(()) + } - // write try chunk first - if let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - write!(self.buf(), "{chunk_str}")?; - prev_multiline = chunk_str.contains('\n'); - } + #[instrument(name = "closing_paren", skip_all)] + fn visit_closing_paren(&mut self) -> Result<()> { + write_chunk!(self, ")")?; + Ok(()) + } - while let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - let multiline = chunk_str.contains('\n'); - self.indented_if(!multiline, 1, |fmt| { - chunk.needs_space = Some(false); - let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); - let prefix = if fmt.is_beginning_of_line() { - "" - } else if on_same_line { - " " - } else { - "\n" - }; - let chunk_str = format!("{prefix}{chunk_str}"); - write!(fmt.buf(), "{chunk_str}")?; - Ok(()) - })?; - prev_multiline = multiline; - } + #[instrument(name = "newline", skip_all)] + fn visit_newline(&mut self) -> Result<()> { + writeln_chunk!(self)?; Ok(()) } - #[instrument(name = "assembly", skip_all)] - fn visit_assembly( - &mut self, - loc: Loc, - dialect: &mut Option, - block: &mut YulBlock, - flags: &mut Option>, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); + #[instrument(name = "using", skip_all)] + fn visit_using(&mut self, using: &mut Using) -> Result<()> { + return_source_if_disabled!(self, using.loc, ';'); - write_chunk!(self, loc.start(), "assembly")?; - if let Some(StringLiteral { loc, string, .. }) = dialect { - write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; - } - if let Some(flags) = flags { - if !flags.is_empty() { - let loc_start = flags.first().unwrap().loc.start(); - self.surrounded( - SurroundingChunk::new("(", Some(loc_start), None), - SurroundingChunk::new(")", None, Some(block.loc.start())), - |fmt, _| { - let mut flags = flags.iter_mut().peekable(); - let mut chunks = vec![]; - while let Some(flag) = flags.next() { - let next_byte_offset = - flags.peek().map(|next_flag| next_flag.loc.start()); - chunks.push(fmt.chunked( - flag.loc.start(), - next_byte_offset, - |fmt| { - write!(fmt.buf(), "\"{}\"", flag.string)?; - Ok(()) - }, - )?); + write_chunk!(self, using.loc.start(), "using")?; + + let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); + let global_start = using.global.as_mut().map(|global| global.loc.start()); + let loc_end = using.loc.end(); + + let (is_library, mut list_chunks) = match &mut using.list { + UsingList::Library(library) => { + (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?]) + } + UsingList::Functions(funcs) => { + let mut funcs = funcs.iter_mut().peekable(); + let mut chunks = Vec::new(); + while let Some(func) = funcs.next() { + let next_byte_end = funcs.peek().map(|func| func.loc.start()); + chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { + fmt.visit_ident_path(&mut func.path)?; + if let Some(op) = func.oper { + write!(fmt.buf(), " as {op}")?; } - fmt.write_chunks_separated(&chunks, ",", false)?; Ok(()) - }, - )?; + })?); + } + (false, chunks) + } + UsingList::Error => return self.visit_parser_error(using.loc), + }; + + let for_chunk = self.chunk_at( + using.loc.start(), + Some(ty_start.or(global_start).unwrap_or(loc_end)), + None, + "for", + ); + let ty_chunk = if let Some(ty) = &mut using.ty { + self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? + } else { + self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*") + }; + let global_chunk = using + .global + .as_mut() + .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) + .transpose()?; + + let write_for_def = |fmt: &mut Self| { + fmt.grouped(|fmt| { + fmt.write_chunk(&for_chunk)?; + fmt.write_chunk(&ty_chunk)?; + if let Some(global_chunk) = global_chunk.as_ref() { + fmt.write_chunk(global_chunk)?; + } + Ok(()) + })?; + Ok(()) + }; + + let simulated_for_def = self.simulate_to_string(write_for_def)?; + + if is_library { + let chunk = list_chunks.pop().unwrap(); + if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { + self.write_chunk(&chunk)?; + write_for_def(self)?; + } else { + self.write_whitespace_separator(true)?; + self.grouped(|fmt| { + fmt.write_chunk(&chunk)?; + Ok(()) + })?; + self.write_whitespace_separator(true)?; + write_for_def(self)?; } + } else { + self.surrounded( + SurroundingChunk::new("{", Some(using.loc.start()), None), + SurroundingChunk::new( + "}", + None, + Some(ty_start.or(global_start).unwrap_or(loc_end)), + ), + |fmt, _multiline| { + let multiline = fmt.are_chunks_separated_multiline( + &format!("{{ {{}} }} {simulated_for_def};"), + &list_chunks, + ",", + )?; + fmt.write_chunks_separated(&list_chunks, ",", multiline)?; + Ok(()) + }, + )?; + write_for_def(self)?; } - block.visit(self) + self.write_semicolon()?; + + Ok(()) } #[instrument(name = "yul_block", skip_all)] @@ -3352,38 +3324,6 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - #[instrument(name = "yul_assignment", skip_all)] - fn visit_yul_assignment( - &mut self, - loc: Loc, - exprs: &mut Vec, - expr: &mut Option<&mut YulExpression>, - ) -> Result<(), Self::Error> - where - T: Visitable + CodeLocation, - { - return_source_if_disabled!(self, loc); - - self.grouped(|fmt| { - let chunks = - fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; - - let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - - if let Some(expr) = expr { - write_chunk!(fmt, expr.loc().start(), ":=")?; - let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; - if !fmt.will_chunk_fit("{}", &chunk)? { - fmt.write_whitespace_separator(true)?; - } - fmt.write_chunk(&chunk)?; - } - Ok(()) - })?; - Ok(()) - } - #[instrument(name = "yul_expr", skip_all)] fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { return_source_if_disabled!(self, expr.loc()); @@ -3428,6 +3368,38 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { } } + #[instrument(name = "yul_assignment", skip_all)] + fn visit_yul_assignment( + &mut self, + loc: Loc, + exprs: &mut Vec, + expr: &mut Option<&mut YulExpression>, + ) -> Result<(), Self::Error> + where + T: Visitable + CodeLocation, + { + return_source_if_disabled!(self, loc); + + self.grouped(|fmt| { + let chunks = + fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; + + let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + + if let Some(expr) = expr { + write_chunk!(fmt, expr.loc().start(), ":=")?; + let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; + if !fmt.will_chunk_fit("{}", &chunk)? { + fmt.write_whitespace_separator(true)?; + } + fmt.write_chunk(&chunk)?; + } + Ok(()) + })?; + Ok(()) + } + #[instrument(name = "yul_for", skip_all)] fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { return_source_if_disabled!(self, stmt.loc); @@ -3446,12 +3418,6 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { self.visit_list("", &mut stmt.arguments, None, Some(stmt.loc.end()), true) } - #[instrument(name = "yul_typed_ident", skip_all)] - fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { - return_source_if_disabled!(self, ident.loc); - self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) - } - #[instrument(name = "yul_fun_def", skip_all)] fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { return_source_if_disabled!(self, stmt.loc); @@ -3540,16 +3506,10 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - // Support extension for Solana/Substrate - #[instrument(name = "annotation", skip_all)] - fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { - return_source_if_disabled!(self, annotation.loc); - let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; - write!(self.buf(), "@{id}")?; - write!(self.buf(), "(")?; - annotation.value.visit(self)?; - write!(self.buf(), ")")?; - Ok(()) + #[instrument(name = "yul_typed_ident", skip_all)] + fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { + return_source_if_disabled!(self, ident.loc); + self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) } #[instrument(name = "parser_error", skip_all)] @@ -3557,3 +3517,43 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Err(FormatterError::InvalidParsedItem(loc)) } } + +/// An action which may be committed to a Formatter +struct Transaction<'f, 'a, W> { + fmt: &'f mut Formatter<'a, W>, + buffer: String, + comments: Comments, +} + +impl<'f, 'a, W> std::ops::Deref for Transaction<'f, 'a, W> { + type Target = Formatter<'a, W>; + fn deref(&self) -> &Self::Target { + self.fmt + } +} + +impl<'f, 'a, W> std::ops::DerefMut for Transaction<'f, 'a, W> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.fmt + } +} + +impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { + /// Create a new transaction from a callback + fn new( + fmt: &'f mut Formatter<'a, W>, + fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, + ) -> Result { + let mut comments = fmt.comments.clone(); + let buffer = fmt.with_temp_buf(fun)?.w; + comments = std::mem::replace(&mut fmt.comments, comments); + Ok(Self { fmt, buffer, comments }) + } + + /// Commit the transaction to the Formatter + fn commit(self) -> Result { + self.fmt.comments = self.comments; + write_chunk!(self.fmt, "{}", self.buffer)?; + Ok(self.buffer) + } +}