forge_fmt/
formatter.rs

1//! A Solidity formatter
2
3use crate::{
4    buffer::*,
5    chunk::*,
6    comments::{
7        CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments,
8    },
9    format_diagnostics_report,
10    helpers::import_path_string,
11    macros::*,
12    solang_ext::{pt::*, *},
13    string::{QuoteState, QuotedStringExt},
14    visit::{Visitable, Visitor},
15    FormatterConfig, InlineConfig, IntTypes,
16};
17use alloy_primitives::Address;
18use foundry_config::fmt::{HexUnderscore, MultilineFuncHeaderStyle, SingleLineBlockStyle};
19use itertools::{Either, Itertools};
20use solang_parser::diagnostics::Diagnostic;
21use std::{fmt::Write, path::PathBuf, str::FromStr};
22use thiserror::Error;
23
24type Result<T, E = FormatterError> = std::result::Result<T, E>;
25
26/// A custom Error thrown by the Formatter
27#[derive(Debug, Error)]
28pub enum FormatterError {
29    /// Error thrown by `std::fmt::Write` interfaces
30    #[error(transparent)]
31    Fmt(#[from] std::fmt::Error),
32    /// Encountered invalid parse tree item.
33    #[error("encountered invalid parse tree item at {0:?}")]
34    InvalidParsedItem(Loc),
35    /// Failed to parse the source code
36    #[error("failed to parse file:\n{}", format_diagnostics_report(_0, _1.as_deref(), _2))]
37    Parse(String, Option<PathBuf>, Vec<Diagnostic>),
38    /// All other errors
39    #[error(transparent)]
40    Custom(Box<dyn std::error::Error + Send + Sync>),
41}
42
43impl FormatterError {
44    fn fmt() -> Self {
45        Self::Fmt(std::fmt::Error)
46    }
47
48    fn custom(err: impl std::error::Error + Send + Sync + 'static) -> Self {
49        Self::Custom(Box::new(err))
50    }
51}
52
53#[expect(unused_macros)]
54macro_rules! format_err {
55    ($msg:literal $(,)?) => {
56        $crate::formatter::FormatterError::custom($msg.to_string())
57    };
58    ($err:expr $(,)?) => {
59        $crate::formatter::FormatterError::custom($err)
60    };
61    ($fmt:expr, $($arg:tt)*) => {
62        $crate::formatter::FormatterError::custom(format!($fmt, $($arg)*))
63    };
64}
65
66macro_rules! bail {
67    ($msg:literal $(,)?) => {
68        return Err($crate::formatter::format_err!($msg))
69    };
70    ($err:expr $(,)?) => {
71        return Err($err)
72    };
73    ($fmt:expr, $($arg:tt)*) => {
74        return Err($crate::formatter::format_err!($fmt, $(arg)*))
75    };
76}
77
78// TODO: store context entities as references without copying
79/// Current context of the Formatter (e.g. inside Contract or Function definition)
80#[derive(Debug, Default)]
81struct Context {
82    contract: Option<ContractDefinition>,
83    function: Option<FunctionDefinition>,
84    if_stmt_single_line: Option<bool>,
85}
86
87impl Context {
88    /// Returns true if the current function context is the constructor
89    pub(crate) fn is_constructor_function(&self) -> bool {
90        self.function.as_ref().is_some_and(|f| matches!(f.ty, FunctionTy::Constructor))
91    }
92}
93
94/// A Solidity formatter
95#[derive(Debug)]
96pub struct Formatter<'a, W> {
97    buf: FormatBuffer<W>,
98    source: &'a str,
99    config: FormatterConfig,
100    temp_bufs: Vec<FormatBuffer<String>>,
101    context: Context,
102    comments: Comments,
103    inline_config: InlineConfig,
104}
105
106impl<'a, W: Write> Formatter<'a, W> {
107    pub fn new(
108        w: W,
109        source: &'a str,
110        comments: Comments,
111        inline_config: InlineConfig,
112        config: FormatterConfig,
113    ) -> Self {
114        Self {
115            buf: FormatBuffer::new(w, config.tab_width),
116            source,
117            config,
118            temp_bufs: Vec::new(),
119            context: Context::default(),
120            comments,
121            inline_config,
122        }
123    }
124
125    /// Get the Write interface of the current temp buffer or the underlying Write
126    fn buf(&mut self) -> &mut dyn Write {
127        match &mut self.temp_bufs[..] {
128            [] => &mut self.buf as &mut dyn Write,
129            [.., buf] => buf as &mut dyn Write,
130        }
131    }
132
133    /// Casts the current writer `w` as a `String` reference. Should only be used for debugging.
134    unsafe fn buf_contents(&self) -> &String {
135        *(&self.buf.w as *const W as *const &mut String)
136    }
137
138    /// Casts the current `W` writer or the current temp buffer as a `String` reference.
139    /// Should only be used for debugging.
140    #[expect(dead_code)]
141    unsafe fn temp_buf_contents(&self) -> &String {
142        match &self.temp_bufs[..] {
143            [] => self.buf_contents(),
144            [.., buf] => &buf.w,
145        }
146    }
147
148    buf_fn! { fn indent(&mut self, delta: usize) }
149    buf_fn! { fn dedent(&mut self, delta: usize) }
150    buf_fn! { fn start_group(&mut self) }
151    buf_fn! { fn end_group(&mut self) }
152    buf_fn! { fn create_temp_buf(&self) -> FormatBuffer<String> }
153    buf_fn! { fn restrict_to_single_line(&mut self, restricted: bool) }
154    buf_fn! { fn current_line_len(&self) -> usize }
155    buf_fn! { fn total_indent_len(&self) -> usize }
156    buf_fn! { fn is_beginning_of_line(&self) -> bool }
157    buf_fn! { fn last_char(&self) -> Option<char> }
158    buf_fn! { fn last_indent_group_skipped(&self) -> bool }
159    buf_fn! { fn set_last_indent_group_skipped(&mut self, skip: bool) }
160    buf_fn! { fn write_raw(&mut self, s: impl AsRef<str>) -> std::fmt::Result }
161
162    /// Do the callback within the context of a temp buffer
163    fn with_temp_buf(
164        &mut self,
165        mut fun: impl FnMut(&mut Self) -> Result<()>,
166    ) -> Result<FormatBuffer<String>> {
167        self.temp_bufs.push(self.create_temp_buf());
168        let res = fun(self);
169        let out = self.temp_bufs.pop().unwrap();
170        res?;
171        Ok(out)
172    }
173
174    /// Does the next written character require whitespace before
175    fn next_char_needs_space(&self, next_char: char) -> bool {
176        if self.is_beginning_of_line() {
177            return false
178        }
179        let last_char =
180            if let Some(last_char) = self.last_char() { last_char } else { return false };
181        if last_char.is_whitespace() || next_char.is_whitespace() {
182            return false
183        }
184        match last_char {
185            '{' => match next_char {
186                '{' | '[' | '(' => false,
187                '/' => true,
188                _ => self.config.bracket_spacing,
189            },
190            '(' | '.' | '[' => matches!(next_char, '/'),
191            '/' => true,
192            _ => match next_char {
193                '}' => self.config.bracket_spacing,
194                ')' | ',' | '.' | ';' | ']' => false,
195                _ => true,
196            },
197        }
198    }
199
200    /// Is length of the `text` with respect to already written line <= `config.line_length`
201    fn will_it_fit(&self, text: impl AsRef<str>) -> bool {
202        let text = text.as_ref();
203        if text.is_empty() {
204            return true
205        }
206        if text.contains('\n') {
207            return false
208        }
209        let space: usize = self.next_char_needs_space(text.chars().next().unwrap()).into();
210        self.config.line_length >=
211            self.total_indent_len()
212                .saturating_add(self.current_line_len())
213                .saturating_add(text.chars().count() + space)
214    }
215
216    /// Write empty brackets with respect to `config.bracket_spacing` setting:
217    /// `"{ }"` if `true`, `"{}"` if `false`
218    fn write_empty_brackets(&mut self) -> Result<()> {
219        let brackets = if self.config.bracket_spacing { "{ }" } else { "{}" };
220        write_chunk!(self, "{brackets}")?;
221        Ok(())
222    }
223
224    /// Write semicolon to the buffer
225    fn write_semicolon(&mut self) -> Result<()> {
226        write!(self.buf(), ";")?;
227        Ok(())
228    }
229
230    /// Write whitespace separator to the buffer
231    /// `"\n"` if `multiline` is `true`, `" "` if `false`
232    fn write_whitespace_separator(&mut self, multiline: bool) -> Result<()> {
233        if !self.is_beginning_of_line() {
234            write!(self.buf(), "{}", if multiline { "\n" } else { " " })?;
235        }
236        Ok(())
237    }
238
239    /// Write new line with preserved `last_indent_group_skipped` flag
240    fn write_preserved_line(&mut self) -> Result<()> {
241        let last_indent_group_skipped = self.last_indent_group_skipped();
242        writeln!(self.buf())?;
243        self.set_last_indent_group_skipped(last_indent_group_skipped);
244        Ok(())
245    }
246
247    /// Write unformatted src and comments for given location.
248    fn write_raw_src(&mut self, loc: Loc) -> Result<()> {
249        let disabled_stmts_src = String::from_utf8(self.source.as_bytes()[loc.range()].to_vec())
250            .map_err(FormatterError::custom)?;
251        self.write_raw(disabled_stmts_src.trim_end())?;
252        self.write_whitespace_separator(true)?;
253        // Remove comments as they're already included in disabled src.
254        let _ = self.comments.remove_all_comments_before(loc.end());
255        Ok(())
256    }
257
258    /// Returns number of blank lines in source between two byte indexes
259    fn blank_lines(&self, start: usize, end: usize) -> usize {
260        // because of sorting import statements, start can be greater than end
261        if start > end {
262            return 0
263        }
264        self.source[start..end].trim_comments().matches('\n').count()
265    }
266
267    /// Get the byte offset of the next line
268    fn find_next_line(&self, byte_offset: usize) -> Option<usize> {
269        let mut iter = self.source[byte_offset..].char_indices();
270        while let Some((_, ch)) = iter.next() {
271            match ch {
272                '\n' => return iter.next().map(|(idx, _)| byte_offset + idx),
273                '\r' => {
274                    return iter.next().and_then(|(idx, ch)| match ch {
275                        '\n' => iter.next().map(|(idx, _)| byte_offset + idx),
276                        _ => Some(byte_offset + idx),
277                    })
278                }
279                _ => {}
280            }
281        }
282        None
283    }
284
285    /// Find the next instance of the character in source excluding comments
286    fn find_next_in_src(&self, byte_offset: usize, needle: char) -> Option<usize> {
287        self.source[byte_offset..]
288            .comment_state_char_indices()
289            .position(|(state, _, ch)| needle == ch && state == CommentState::None)
290            .map(|p| byte_offset + p)
291    }
292
293    /// Find the start of the next instance of a slice in source
294    fn find_next_str_in_src(&self, byte_offset: usize, needle: &str) -> Option<usize> {
295        let subset = &self.source[byte_offset..];
296        needle.chars().next().and_then(|first_char| {
297            subset
298                .comment_state_char_indices()
299                .position(|(state, idx, ch)| {
300                    first_char == ch &&
301                        state == CommentState::None &&
302                        idx + needle.len() <= subset.len() &&
303                        subset[idx..idx + needle.len()] == *needle
304                })
305                .map(|p| byte_offset + p)
306        })
307    }
308
309    /// Extends the location to the next instance of a character. Returns true if the loc was
310    /// extended
311    fn extend_loc_until(&self, loc: &mut Loc, needle: char) -> bool {
312        if let Some(end) = self.find_next_in_src(loc.end(), needle).map(|offset| offset + 1) {
313            *loc = loc.with_end(end);
314            true
315        } else {
316            false
317        }
318    }
319
320    /// Return the flag whether the attempt should be made
321    /// to write the block on a single line.
322    /// If the block style is configured to [SingleLineBlockStyle::Preserve],
323    /// lookup whether there was a newline introduced in `[start_from, end_at]` range
324    /// where `end_at` is the start of the block.
325    fn should_attempt_block_single_line(
326        &mut self,
327        stmt: &mut Statement,
328        start_from: usize,
329    ) -> bool {
330        match self.config.single_line_statement_blocks {
331            SingleLineBlockStyle::Single => true,
332            SingleLineBlockStyle::Multi => false,
333            SingleLineBlockStyle::Preserve => {
334                let end_at = match stmt {
335                    Statement::Block { statements, .. } if !statements.is_empty() => {
336                        statements.first().as_ref().unwrap().loc().start()
337                    }
338                    Statement::Expression(loc, _) => loc.start(),
339                    _ => stmt.loc().start(),
340                };
341
342                self.find_next_line(start_from).is_some_and(|loc| loc >= end_at)
343            }
344        }
345    }
346
347    /// Create a chunk given a string and the location information
348    fn chunk_at(
349        &mut self,
350        byte_offset: usize,
351        next_byte_offset: Option<usize>,
352        needs_space: Option<bool>,
353        content: impl std::fmt::Display,
354    ) -> Chunk {
355        Chunk {
356            postfixes_before: self.comments.remove_postfixes_before(byte_offset),
357            prefixes: self.comments.remove_prefixes_before(byte_offset),
358            content: content.to_string(),
359            postfixes: next_byte_offset
360                .map(|byte_offset| self.comments.remove_postfixes_before(byte_offset))
361                .unwrap_or_default(),
362            needs_space,
363        }
364    }
365
366    /// Create a chunk given a callback
367    fn chunked(
368        &mut self,
369        byte_offset: usize,
370        next_byte_offset: Option<usize>,
371        mut fun: impl FnMut(&mut Self) -> Result<()>,
372    ) -> Result<Chunk> {
373        self.chunked_mono(byte_offset, next_byte_offset, &mut fun)
374    }
375
376    fn chunked_mono(
377        &mut self,
378        byte_offset: usize,
379        next_byte_offset: Option<usize>,
380        fun: &mut dyn FnMut(&mut Self) -> Result<()>,
381    ) -> Result<Chunk> {
382        let postfixes_before = self.comments.remove_postfixes_before(byte_offset);
383        let prefixes = self.comments.remove_prefixes_before(byte_offset);
384        let content = self.with_temp_buf(fun)?.w;
385        let postfixes = next_byte_offset
386            .map(|byte_offset| self.comments.remove_postfixes_before(byte_offset))
387            .unwrap_or_default();
388        Ok(Chunk { postfixes_before, prefixes, content, postfixes, needs_space: None })
389    }
390
391    /// Create a chunk given a [Visitable] item
392    fn visit_to_chunk(
393        &mut self,
394        byte_offset: usize,
395        next_byte_offset: Option<usize>,
396        visitable: &mut impl Visitable,
397    ) -> Result<Chunk> {
398        self.chunked(byte_offset, next_byte_offset, |fmt| {
399            visitable.visit(fmt)?;
400            Ok(())
401        })
402    }
403
404    /// Transform [Visitable] items to the list of chunks
405    fn items_to_chunks<'b>(
406        &mut self,
407        next_byte_offset: Option<usize>,
408        items: impl Iterator<Item = (Loc, &'b mut (impl Visitable + 'b))> + 'b,
409    ) -> Result<Vec<Chunk>> {
410        let mut items = items.peekable();
411        let mut out = Vec::with_capacity(items.size_hint().1.unwrap_or(0));
412        while let Some((loc, item)) = items.next() {
413            let chunk_next_byte_offset =
414                items.peek().map(|(loc, _)| loc.start()).or(next_byte_offset);
415
416            let chunk = if self.inline_config.is_disabled(loc) {
417                // If item format is disabled, we determine last disabled line from item and create
418                // chunk with raw src.
419                let mut disabled_loc = loc;
420                self.chunked(disabled_loc.start(), chunk_next_byte_offset, |fmt| {
421                    while fmt.inline_config.is_disabled(disabled_loc) {
422                        if let Some(next_line) = fmt.find_next_line(disabled_loc.end()) {
423                            disabled_loc = disabled_loc.with_end(next_line);
424                        } else {
425                            break;
426                        }
427                    }
428                    fmt.write_raw_src(disabled_loc)?;
429                    Ok(())
430                })?
431            } else {
432                self.visit_to_chunk(loc.start(), chunk_next_byte_offset, item)?
433            };
434            out.push(chunk);
435        }
436        Ok(out)
437    }
438
439    /// Transform [Visitable] items to a list of chunks and then sort those chunks.
440    fn items_to_chunks_sorted<'b>(
441        &mut self,
442        next_byte_offset: Option<usize>,
443        items: impl Iterator<Item = &'b mut (impl Visitable + CodeLocation + Ord + 'b)> + 'b,
444    ) -> Result<Vec<Chunk>> {
445        let mut items = items.peekable();
446        let mut out = Vec::with_capacity(items.size_hint().1.unwrap_or(0));
447        while let Some(item) = items.next() {
448            let chunk_next_byte_offset =
449                items.peek().map(|next| next.loc().start()).or(next_byte_offset);
450            let chunk = self.visit_to_chunk(item.loc().start(), chunk_next_byte_offset, item)?;
451            out.push((item, chunk));
452        }
453        out.sort_by(|(a, _), (b, _)| a.cmp(b));
454        Ok(out.into_iter().map(|(_, c)| c).collect())
455    }
456
457    /// Write a comment to the buffer formatted.
458    /// WARNING: This may introduce a newline if the comment is a Line comment
459    /// or if the comment are wrapped
460    fn write_comment(&mut self, comment: &CommentWithMetadata, is_first: bool) -> Result<()> {
461        if self.inline_config.is_disabled(comment.loc) {
462            return self.write_raw_comment(comment)
463        }
464
465        match comment.position {
466            CommentPosition::Prefix => self.write_prefix_comment(comment, is_first),
467            CommentPosition::Postfix => self.write_postfix_comment(comment),
468        }
469    }
470
471    /// Write a comment with position [CommentPosition::Prefix]
472    fn write_prefix_comment(
473        &mut self,
474        comment: &CommentWithMetadata,
475        is_first: bool,
476    ) -> Result<()> {
477        if !self.is_beginning_of_line() {
478            self.write_preserved_line()?;
479        }
480        if !is_first && comment.has_newline_before {
481            self.write_preserved_line()?;
482        }
483
484        if matches!(comment.ty, CommentType::DocBlock) {
485            let mut lines = comment.contents().trim().lines();
486            writeln!(self.buf(), "{}", comment.start_token())?;
487            lines.try_for_each(|l| self.write_doc_block_line(comment, l))?;
488            write!(self.buf(), " {}", comment.end_token().unwrap())?;
489            self.write_preserved_line()?;
490            return Ok(())
491        }
492
493        write!(self.buf(), "{}", comment.start_token())?;
494
495        let mut wrapped = false;
496        let contents = comment.contents();
497        let mut lines = contents.lines().peekable();
498        while let Some(line) = lines.next() {
499            wrapped |= self.write_comment_line(comment, line)?;
500            if lines.peek().is_some() {
501                self.write_preserved_line()?;
502            }
503        }
504
505        if let Some(end) = comment.end_token() {
506            // Check if the end token in the original comment was on the separate line
507            if !wrapped && comment.comment.lines().count() > contents.lines().count() {
508                self.write_preserved_line()?;
509            }
510            write!(self.buf(), "{end}")?;
511        }
512        if self.find_next_line(comment.loc.end()).is_some() {
513            self.write_preserved_line()?;
514        }
515
516        Ok(())
517    }
518
519    /// Write a comment with position [CommentPosition::Postfix]
520    fn write_postfix_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> {
521        let indented = self.is_beginning_of_line();
522        self.indented_if(indented, 1, |fmt| {
523            if !indented && fmt.next_char_needs_space('/') {
524                fmt.write_whitespace_separator(false)?;
525            }
526
527            write!(fmt.buf(), "{}", comment.start_token())?;
528            let start_token_pos = fmt.current_line_len();
529
530            let mut lines = comment.contents().lines().peekable();
531            fmt.grouped(|fmt| {
532                while let Some(line) = lines.next() {
533                    fmt.write_comment_line(comment, line)?;
534                    if lines.peek().is_some() {
535                        fmt.write_whitespace_separator(true)?;
536                    }
537                }
538                Ok(())
539            })?;
540
541            if let Some(end) = comment.end_token() {
542                // If comment is not multiline, end token has to be aligned with the start
543                if fmt.is_beginning_of_line() {
544                    write!(fmt.buf(), "{}{end}", " ".repeat(start_token_pos))?;
545                } else {
546                    write!(fmt.buf(), "{end}")?;
547                }
548            }
549
550            if comment.is_line() {
551                fmt.write_whitespace_separator(true)?;
552            }
553            Ok(())
554        })
555    }
556
557    /// Write the line of a doc block comment line
558    fn write_doc_block_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result<()> {
559        if line.trim().starts_with('*') {
560            let line = line.trim().trim_start_matches('*');
561            let needs_space = line.chars().next().is_some_and(|ch| !ch.is_whitespace());
562            write!(self.buf(), " *{}", if needs_space { " " } else { "" })?;
563            self.write_comment_line(comment, line)?;
564            self.write_whitespace_separator(true)?;
565            return Ok(())
566        }
567
568        let indent_whitespace_count = line
569            .char_indices()
570            .take_while(|(idx, ch)| ch.is_whitespace() && *idx <= self.buf.current_indent_len())
571            .count();
572        let to_skip = indent_whitespace_count - indent_whitespace_count % self.config.tab_width;
573        write!(self.buf(), " *")?;
574        let content = &line[to_skip..];
575        if !content.trim().is_empty() {
576            write!(self.buf(), " ")?;
577            self.write_comment_line(comment, &line[to_skip..])?;
578        }
579        self.write_whitespace_separator(true)?;
580        Ok(())
581    }
582
583    /// Write a comment line that might potentially overflow the maximum line length
584    /// and, if configured, will be wrapped to the next line.
585    fn write_comment_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result<bool> {
586        if self.will_it_fit(line) || !self.config.wrap_comments {
587            let start_with_ws =
588                line.chars().next().map(|ch| ch.is_whitespace()).unwrap_or_default();
589            if !self.is_beginning_of_line() || !start_with_ws {
590                write!(self.buf(), "{line}")?;
591                return Ok(false)
592            }
593
594            // if this is the beginning of the line,
595            // the comment should start with at least an indent
596            let indent = self.buf.current_indent_len();
597            let mut chars = line
598                .char_indices()
599                .skip_while(|(idx, ch)| ch.is_whitespace() && *idx < indent)
600                .map(|(_, ch)| ch);
601            let padded = format!("{}{}", " ".repeat(indent), chars.join(""));
602            self.write_raw(padded)?;
603            return Ok(false)
604        }
605
606        let mut words = line.split(' ').peekable();
607        while let Some(word) = words.next() {
608            if self.is_beginning_of_line() {
609                write!(self.buf(), "{}", word.trim_start())?;
610            } else {
611                self.write_raw(word)?;
612            }
613
614            if let Some(next) = words.peek() {
615                if !word.is_empty() && !self.will_it_fit(next) {
616                    // the next word doesn't fit on this line,
617                    // write remaining words on the next
618                    self.write_whitespace_separator(true)?;
619                    // write newline wrap token
620                    write!(self.buf(), "{}", comment.wrap_token())?;
621                    self.write_comment_line(comment, &words.join(" "))?;
622                    return Ok(true)
623                }
624
625                self.write_whitespace_separator(false)?;
626            }
627        }
628        Ok(false)
629    }
630
631    /// Write a raw comment. This is like [`write_comment`](Self::write_comment) but won't do any
632    /// formatting or worry about whitespace behind the comment.
633    fn write_raw_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> {
634        self.write_raw(&comment.comment)?;
635        if comment.is_line() {
636            self.write_preserved_line()?;
637        }
638        Ok(())
639    }
640
641    // TODO handle whitespace between comments for disabled sections
642    /// Write multiple comments
643    fn write_comments<'b>(
644        &mut self,
645        comments: impl IntoIterator<Item = &'b CommentWithMetadata>,
646    ) -> Result<()> {
647        let mut comments = comments.into_iter().peekable();
648        let mut last_byte_written = match comments.peek() {
649            Some(comment) => comment.loc.start(),
650            None => return Ok(()),
651        };
652        let mut is_first = true;
653        for comment in comments {
654            let unwritten_whitespace_loc =
655                Loc::File(comment.loc.file_no(), last_byte_written, comment.loc.start());
656            if self.inline_config.is_disabled(unwritten_whitespace_loc) {
657                self.write_raw(&self.source[unwritten_whitespace_loc.range()])?;
658                self.write_raw_comment(comment)?;
659                last_byte_written = if comment.is_line() {
660                    self.find_next_line(comment.loc.end()).unwrap_or_else(|| comment.loc.end())
661                } else {
662                    comment.loc.end()
663                };
664            } else {
665                self.write_comment(comment, is_first)?;
666            }
667            is_first = false;
668        }
669        Ok(())
670    }
671
672    /// Write a postfix comments before a given location
673    fn write_postfix_comments_before(&mut self, byte_end: usize) -> Result<()> {
674        let comments = self.comments.remove_postfixes_before(byte_end);
675        self.write_comments(&comments)
676    }
677
678    /// Write all prefix comments before a given location
679    fn write_prefix_comments_before(&mut self, byte_end: usize) -> Result<()> {
680        let comments = self.comments.remove_prefixes_before(byte_end);
681        self.write_comments(&comments)
682    }
683
684    /// Check if a chunk will fit on the current line
685    fn will_chunk_fit(&mut self, format_string: &str, chunk: &Chunk) -> Result<bool> {
686        if let Some(chunk_str) = self.simulate_to_single_line(|fmt| fmt.write_chunk(chunk))? {
687            Ok(self.will_it_fit(format_string.replacen("{}", &chunk_str, 1)))
688        } else {
689            Ok(false)
690        }
691    }
692
693    /// Check if a separated list of chunks will fit on the current line
694    fn are_chunks_separated_multiline<'b>(
695        &mut self,
696        format_string: &str,
697        items: impl IntoIterator<Item = &'b Chunk>,
698        separator: &str,
699    ) -> Result<bool> {
700        let items = items.into_iter().collect_vec();
701        if let Some(chunks) = self.simulate_to_single_line(|fmt| {
702            fmt.write_chunks_separated(items.iter().copied(), separator, false)
703        })? {
704            Ok(!self.will_it_fit(format_string.replacen("{}", &chunks, 1)))
705        } else {
706            Ok(true)
707        }
708    }
709
710    /// Write the chunk and any surrounding comments into the buffer
711    /// This will automatically add whitespace before the chunk given the rule set in
712    /// `next_char_needs_space`. If the chunk does not fit on the current line it will be put on
713    /// to the next line
714    fn write_chunk(&mut self, chunk: &Chunk) -> Result<()> {
715        // handle comments before chunk
716        self.write_comments(&chunk.postfixes_before)?;
717        self.write_comments(&chunk.prefixes)?;
718
719        // trim chunk start
720        let content = if chunk.content.starts_with('\n') {
721            let mut chunk = chunk.content.trim_start().to_string();
722            chunk.insert(0, '\n');
723            chunk
724        } else if chunk.content.starts_with(' ') {
725            let mut chunk = chunk.content.trim_start().to_string();
726            chunk.insert(0, ' ');
727            chunk
728        } else {
729            chunk.content.clone()
730        };
731
732        if !content.is_empty() {
733            // add whitespace if necessary
734            let needs_space = chunk
735                .needs_space
736                .unwrap_or_else(|| self.next_char_needs_space(content.chars().next().unwrap()));
737            if needs_space {
738                if self.will_it_fit(&content) {
739                    write!(self.buf(), " ")?;
740                } else {
741                    writeln!(self.buf())?;
742                }
743            }
744
745            // write chunk
746            write!(self.buf(), "{content}")?;
747        }
748
749        // write any postfix comments
750        self.write_comments(&chunk.postfixes)?;
751
752        Ok(())
753    }
754
755    /// Write chunks separated by a separator. If `multiline`, each chunk will be written to a
756    /// separate line
757    fn write_chunks_separated<'b>(
758        &mut self,
759        chunks: impl IntoIterator<Item = &'b Chunk>,
760        separator: &str,
761        multiline: bool,
762    ) -> Result<()> {
763        let mut chunks = chunks.into_iter().peekable();
764        while let Some(chunk) = chunks.next() {
765            let mut chunk = chunk.clone();
766
767            // handle postfixes before and add newline if necessary
768            self.write_comments(&std::mem::take(&mut chunk.postfixes_before))?;
769            if multiline && !self.is_beginning_of_line() {
770                writeln!(self.buf())?;
771            }
772
773            // remove postfixes so we can add separator between
774            let postfixes = std::mem::take(&mut chunk.postfixes);
775
776            self.write_chunk(&chunk)?;
777
778            // add separator
779            if chunks.peek().is_some() {
780                write!(self.buf(), "{separator}")?;
781                self.write_comments(&postfixes)?;
782                if multiline && !self.is_beginning_of_line() {
783                    writeln!(self.buf())?;
784                }
785            } else {
786                self.write_comments(&postfixes)?;
787            }
788        }
789        Ok(())
790    }
791
792    /// Apply the callback indented by the indent size
793    fn indented(&mut self, delta: usize, fun: impl FnMut(&mut Self) -> Result<()>) -> Result<()> {
794        self.indented_if(true, delta, fun)
795    }
796
797    /// Apply the callback indented by the indent size if the condition is true
798    fn indented_if(
799        &mut self,
800        condition: bool,
801        delta: usize,
802        mut fun: impl FnMut(&mut Self) -> Result<()>,
803    ) -> Result<()> {
804        if condition {
805            self.indent(delta);
806        }
807        let res = fun(self);
808        if condition {
809            self.dedent(delta);
810        }
811        res?;
812        Ok(())
813    }
814
815    /// Apply the callback into an indent group. The first line of the indent group is not
816    /// indented but lines thereafter are
817    fn grouped(&mut self, mut fun: impl FnMut(&mut Self) -> Result<()>) -> Result<bool> {
818        self.start_group();
819        let res = fun(self);
820        let indented = !self.last_indent_group_skipped();
821        self.end_group();
822        res?;
823        Ok(indented)
824    }
825
826    /// Add a function context around a procedure and revert the context at the end of the procedure
827    /// regardless of the response
828    fn with_function_context(
829        &mut self,
830        context: FunctionDefinition,
831        mut fun: impl FnMut(&mut Self) -> Result<()>,
832    ) -> Result<()> {
833        self.context.function = Some(context);
834        let res = fun(self);
835        self.context.function = None;
836        res
837    }
838
839    /// Add a contract context around a procedure and revert the context at the end of the procedure
840    /// regardless of the response
841    fn with_contract_context(
842        &mut self,
843        context: ContractDefinition,
844        mut fun: impl FnMut(&mut Self) -> Result<()>,
845    ) -> Result<()> {
846        self.context.contract = Some(context);
847        let res = fun(self);
848        self.context.contract = None;
849        res
850    }
851
852    /// Create a transaction. The result of the transaction is not applied to the buffer unless
853    /// `Transacton::commit` is called
854    fn transact<'b>(
855        &'b mut self,
856        fun: impl FnMut(&mut Self) -> Result<()>,
857    ) -> Result<Transaction<'b, 'a, W>> {
858        Transaction::new(self, fun)
859    }
860
861    /// Do the callback and return the result on the buffer as a string
862    fn simulate_to_string(&mut self, fun: impl FnMut(&mut Self) -> Result<()>) -> Result<String> {
863        Ok(self.transact(fun)?.buffer)
864    }
865
866    /// Turn a chunk and its surrounding comments into a string
867    fn chunk_to_string(&mut self, chunk: &Chunk) -> Result<String> {
868        self.simulate_to_string(|fmt| fmt.write_chunk(chunk))
869    }
870
871    /// Try to create a string based on a callback. If the string does not fit on a single line
872    /// this will return `None`
873    fn simulate_to_single_line(
874        &mut self,
875        mut fun: impl FnMut(&mut Self) -> Result<()>,
876    ) -> Result<Option<String>> {
877        let mut single_line = false;
878        let tx = self.transact(|fmt| {
879            fmt.restrict_to_single_line(true);
880            single_line = match fun(fmt) {
881                Ok(()) => true,
882                Err(FormatterError::Fmt(_)) => false,
883                Err(err) => bail!(err),
884            };
885            Ok(())
886        })?;
887        Ok(if single_line && tx.will_it_fit(&tx.buffer) { Some(tx.buffer) } else { None })
888    }
889
890    /// Try to apply a callback to a single line. If the callback cannot be applied to a single
891    /// line the callback will not be applied to the buffer and `false` will be returned. Otherwise
892    /// `true` will be returned
893    fn try_on_single_line(&mut self, mut fun: impl FnMut(&mut Self) -> Result<()>) -> Result<bool> {
894        let mut single_line = false;
895        let tx = self.transact(|fmt| {
896            fmt.restrict_to_single_line(true);
897            single_line = match fun(fmt) {
898                Ok(()) => true,
899                Err(FormatterError::Fmt(_)) => false,
900                Err(err) => bail!(err),
901            };
902            Ok(())
903        })?;
904        Ok(if single_line && tx.will_it_fit(&tx.buffer) {
905            tx.commit()?;
906            true
907        } else {
908            false
909        })
910    }
911
912    /// Surrounds a callback with parentheses. The callback will try to be applied to a single
913    /// line. If the callback cannot be applied to a single line the callback will applied to the
914    /// nextline indented. The callback receives a `multiline` hint as the second argument which
915    /// receives `true` in the latter case
916    fn surrounded(
917        &mut self,
918        first: SurroundingChunk,
919        last: SurroundingChunk,
920        mut fun: impl FnMut(&mut Self, bool) -> Result<()>,
921    ) -> Result<()> {
922        let first_chunk =
923            self.chunk_at(first.loc_before(), first.loc_next(), first.spaced, first.content);
924        self.write_chunk(&first_chunk)?;
925
926        let multiline = !self.try_on_single_line(|fmt| {
927            fun(fmt, false)?;
928            let last_chunk =
929                fmt.chunk_at(last.loc_before(), last.loc_next(), last.spaced, &last.content);
930            fmt.write_chunk(&last_chunk)?;
931            Ok(())
932        })?;
933
934        if multiline {
935            self.indented(1, |fmt| {
936                fmt.write_whitespace_separator(true)?;
937                let stringified = fmt.with_temp_buf(|fmt| fun(fmt, true))?.w;
938                write_chunk!(fmt, "{}", stringified.trim_start())
939            })?;
940            if !last.content.trim_start().is_empty() {
941                self.indented(1, |fmt| fmt.write_whitespace_separator(true))?;
942            }
943            let last_chunk =
944                self.chunk_at(last.loc_before(), last.loc_next(), last.spaced, &last.content);
945            self.write_chunk(&last_chunk)?;
946        }
947
948        Ok(())
949    }
950
951    /// Write each [Visitable] item on a separate line. The function will check if there are any
952    /// blank lines between each visitable statement and will apply a single blank line if there
953    /// exists any. The `needs_space` callback can force a newline and is given the last_item if
954    /// any and the next item as arguments
955    fn write_lined_visitable<'b, I, V, F>(
956        &mut self,
957        loc: Loc,
958        items: I,
959        needs_space_fn: F,
960    ) -> Result<()>
961    where
962        I: Iterator<Item = &'b mut V> + 'b,
963        V: Visitable + CodeLocation + 'b,
964        F: Fn(&V, &V) -> bool,
965    {
966        let mut items = items.collect::<Vec<_>>();
967        items.reverse();
968        // get next item
969        let pop_next = |fmt: &mut Self, items: &mut Vec<&'b mut V>| {
970            let comment =
971                fmt.comments.iter().next().filter(|comment| comment.loc.end() < loc.end());
972            let item = items.last();
973            if let (Some(comment), Some(item)) = (comment, item) {
974                if comment.loc < item.loc() {
975                    Some(Either::Left(fmt.comments.pop().unwrap()))
976                } else {
977                    Some(Either::Right(items.pop().unwrap()))
978                }
979            } else if comment.is_some() {
980                Some(Either::Left(fmt.comments.pop().unwrap()))
981            } else if item.is_some() {
982                Some(Either::Right(items.pop().unwrap()))
983            } else {
984                None
985            }
986        };
987        // get whitespace between to offsets. this needs to account for possible left over
988        // semicolons which are not included in the `Loc`
989        let unwritten_whitespace = |from: usize, to: usize| {
990            let to = to.max(from);
991            let mut loc = Loc::File(loc.file_no(), from, to);
992            let src = &self.source[from..to];
993            if let Some(semi) = src.find(';') {
994                loc = loc.with_start(from + semi + 1);
995            }
996            (loc, &self.source[loc.range()])
997        };
998
999        let mut last_byte_written = match (
1000            self.comments.iter().next().filter(|comment| comment.loc.end() < loc.end()),
1001            items.last(),
1002        ) {
1003            (Some(comment), Some(item)) => comment.loc.min(item.loc()),
1004            (None, Some(item)) => item.loc(),
1005            (Some(comment), None) => comment.loc,
1006            (None, None) => return Ok(()),
1007        }
1008        .start();
1009
1010        let mut last_loc: Option<Loc> = None;
1011        let mut visited_locs: Vec<Loc> = Vec::new();
1012
1013        // marker for whether the next item needs additional space
1014        let mut needs_space = false;
1015        let mut last_comment = None;
1016
1017        while let Some(mut line_item) = pop_next(self, &mut items) {
1018            let loc = line_item.as_ref().either(|c| c.loc, |i| i.loc());
1019            let (unwritten_whitespace_loc, unwritten_whitespace) =
1020                unwritten_whitespace(last_byte_written, loc.start());
1021            let ignore_whitespace = if self.inline_config.is_disabled(unwritten_whitespace_loc) {
1022                trace!("Unwritten whitespace: {unwritten_whitespace:?}");
1023                self.write_raw(unwritten_whitespace)?;
1024                true
1025            } else {
1026                false
1027            };
1028            match line_item.as_mut() {
1029                Either::Left(comment) => {
1030                    if ignore_whitespace {
1031                        self.write_raw_comment(comment)?;
1032                        if unwritten_whitespace.contains('\n') {
1033                            needs_space = false;
1034                        }
1035                    } else {
1036                        self.write_comment(comment, last_loc.is_none())?;
1037                        if last_loc.is_some() && comment.has_newline_before {
1038                            needs_space = false;
1039                        }
1040                    }
1041                }
1042                Either::Right(item) => {
1043                    if !ignore_whitespace {
1044                        self.write_whitespace_separator(true)?;
1045                        if let Some(mut last_loc) = last_loc {
1046                            // here's an edge case when we reordered items so the last_loc isn't
1047                            // necessarily the item that directly precedes the current item because
1048                            // the order might have changed, so we need to find the last item that
1049                            // is before the current item by checking the recorded locations
1050                            if let Some(last_item) = visited_locs
1051                                .iter()
1052                                .rev()
1053                                .find(|prev_item| prev_item.start() > last_loc.end())
1054                            {
1055                                last_loc = *last_item;
1056                            }
1057
1058                            // The blank lines check is susceptible additional trailing new lines
1059                            // because the block docs can contain
1060                            // multiple lines, but the function def should follow directly after the
1061                            // block comment
1062                            let is_last_doc_comment = matches!(
1063                                last_comment,
1064                                Some(CommentWithMetadata { ty: CommentType::DocBlock, .. })
1065                            );
1066
1067                            if needs_space ||
1068                                (!is_last_doc_comment &&
1069                                    self.blank_lines(last_loc.end(), loc.start()) > 1)
1070                            {
1071                                writeln!(self.buf())?;
1072                            }
1073                        }
1074                    }
1075                    if let Some(next_item) = items.last() {
1076                        needs_space = needs_space_fn(item, next_item);
1077                    }
1078                    trace!("Visiting {}", {
1079                        let n = std::any::type_name::<V>();
1080                        n.strip_prefix("solang_parser::pt::").unwrap_or(n)
1081                    });
1082                    item.visit(self)?;
1083                }
1084            }
1085
1086            last_loc = Some(loc);
1087            visited_locs.push(loc);
1088
1089            last_comment = None;
1090
1091            last_byte_written = loc.end();
1092            if let Some(comment) = line_item.left() {
1093                if comment.is_line() {
1094                    last_byte_written =
1095                        self.find_next_line(last_byte_written).unwrap_or(last_byte_written);
1096                }
1097                last_comment = Some(comment);
1098            }
1099        }
1100
1101        // write manually to avoid eof comment being detected as first
1102        let comments = self.comments.remove_prefixes_before(loc.end());
1103        for comment in comments {
1104            self.write_comment(&comment, false)?;
1105        }
1106
1107        let (unwritten_src_loc, mut unwritten_whitespace) =
1108            unwritten_whitespace(last_byte_written, loc.end());
1109        if self.inline_config.is_disabled(unwritten_src_loc) {
1110            if unwritten_src_loc.end() == self.source.len() {
1111                // remove EOF line ending
1112                unwritten_whitespace = unwritten_whitespace
1113                    .strip_suffix('\n')
1114                    .map(|w| w.strip_suffix('\r').unwrap_or(w))
1115                    .unwrap_or(unwritten_whitespace);
1116            }
1117            trace!("Unwritten whitespace: {unwritten_whitespace:?}");
1118            self.write_raw(unwritten_whitespace)?;
1119        }
1120
1121        Ok(())
1122    }
1123
1124    /// Visit the right side of an assignment. The function will try to write the assignment on a
1125    /// single line or indented on the next line. If it can't do this it resorts to letting the
1126    /// expression decide how to split itself on multiple lines
1127    fn visit_assignment(&mut self, expr: &mut Expression) -> Result<()> {
1128        if self.try_on_single_line(|fmt| expr.visit(fmt))? {
1129            return Ok(())
1130        }
1131
1132        self.write_postfix_comments_before(expr.loc().start())?;
1133        self.write_prefix_comments_before(expr.loc().start())?;
1134
1135        if self.try_on_single_line(|fmt| fmt.indented(1, |fmt| expr.visit(fmt)))? {
1136            return Ok(())
1137        }
1138
1139        let mut fit_on_next_line = false;
1140        self.indented(1, |fmt| {
1141            let tx = fmt.transact(|fmt| {
1142                writeln!(fmt.buf())?;
1143                fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?;
1144                Ok(())
1145            })?;
1146            if fit_on_next_line {
1147                tx.commit()?;
1148            }
1149            Ok(())
1150        })?;
1151
1152        if !fit_on_next_line {
1153            self.indented_if(expr.is_unsplittable(), 1, |fmt| expr.visit(fmt))?;
1154        }
1155
1156        Ok(())
1157    }
1158
1159    /// Visit the list of comma separated items.
1160    /// If the prefix is not empty, then the function will write
1161    /// the whitespace before the parentheses (if they are required).
1162    fn visit_list<T>(
1163        &mut self,
1164        prefix: &str,
1165        items: &mut [T],
1166        start_offset: Option<usize>,
1167        end_offset: Option<usize>,
1168        paren_required: bool,
1169    ) -> Result<()>
1170    where
1171        T: Visitable + CodeLocation,
1172    {
1173        write_chunk!(self, "{}", prefix)?;
1174        let whitespace = if !prefix.is_empty() { " " } else { "" };
1175        let next_after_start_offset = items.first().map(|item| item.loc().start());
1176        let first_surrounding = SurroundingChunk::new("", start_offset, next_after_start_offset);
1177        let last_surronding = SurroundingChunk::new(")", None, end_offset);
1178        if items.is_empty() {
1179            if paren_required {
1180                write!(self.buf(), "{whitespace}(")?;
1181                self.surrounded(first_surrounding, last_surronding, |fmt, _| {
1182                    // write comments before the list end
1183                    write_chunk!(fmt, end_offset.unwrap_or_default(), "")?;
1184                    Ok(())
1185                })?;
1186            }
1187        } else {
1188            write!(self.buf(), "{whitespace}(")?;
1189            self.surrounded(first_surrounding, last_surronding, |fmt, multiline| {
1190                let args =
1191                    fmt.items_to_chunks(end_offset, items.iter_mut().map(|arg| (arg.loc(), arg)))?;
1192                let multiline =
1193                    multiline && fmt.are_chunks_separated_multiline("{}", &args, ",")?;
1194                fmt.write_chunks_separated(&args, ",", multiline)?;
1195                Ok(())
1196            })?;
1197        }
1198        Ok(())
1199    }
1200
1201    /// Visit the block item. Attempt to write it on the single
1202    /// line if requested. Surround by curly braces and indent
1203    /// each line otherwise. Returns `true` if the block fit
1204    /// on a single line
1205    fn visit_block<T>(
1206        &mut self,
1207        loc: Loc,
1208        statements: &mut [T],
1209        attempt_single_line: bool,
1210        attempt_omit_braces: bool,
1211    ) -> Result<bool>
1212    where
1213        T: Visitable + CodeLocation,
1214    {
1215        if attempt_single_line && statements.len() == 1 {
1216            let fits_on_single = self.try_on_single_line(|fmt| {
1217                if !attempt_omit_braces {
1218                    write!(fmt.buf(), "{{ ")?;
1219                }
1220                statements.first_mut().unwrap().visit(fmt)?;
1221                if !attempt_omit_braces {
1222                    write!(fmt.buf(), " }}")?;
1223                }
1224                Ok(())
1225            })?;
1226
1227            if fits_on_single {
1228                return Ok(true)
1229            }
1230        }
1231
1232        // Determine if any of start / end of the block is disabled and block lines boundaries.
1233        let is_start_disabled = self.inline_config.is_disabled(loc.with_end(loc.start()));
1234        let is_end_disabled = self.inline_config.is_disabled(loc.with_start(loc.end()));
1235        let end_of_first_line = self.find_next_line(loc.start()).unwrap_or_default();
1236        let end_of_last_line = self.find_next_line(loc.end()).unwrap_or_default();
1237
1238        // Write first line of the block:
1239        // - as it is until the end of line, if format disabled
1240        // - start block if line formatted
1241        if is_start_disabled {
1242            self.write_raw_src(loc.with_end(end_of_first_line))?;
1243        } else {
1244            write_chunk!(self, "{{")?;
1245        }
1246
1247        // Write comments and close block if no statement.
1248        if statements.is_empty() {
1249            self.indented(1, |fmt| {
1250                fmt.write_prefix_comments_before(loc.end())?;
1251                fmt.write_postfix_comments_before(loc.end())?;
1252                Ok(())
1253            })?;
1254
1255            write_chunk!(self, "}}")?;
1256            return Ok(false)
1257        }
1258
1259        // Determine writable statements by excluding statements from disabled start / end lines.
1260        // We check the position of last statement from first line (if disabled) and position of
1261        // first statement from last line (if disabled) and slice accordingly.
1262        let writable_statments = match (
1263            statements.iter().rposition(|stmt| {
1264                is_start_disabled &&
1265                    self.find_next_line(stmt.loc().end()).unwrap_or_default() ==
1266                        end_of_first_line
1267            }),
1268            statements.iter().position(|stmt| {
1269                is_end_disabled &&
1270                    self.find_next_line(stmt.loc().end()).unwrap_or_default() == end_of_last_line
1271            }),
1272        ) {
1273            // We have statements on both disabled start / end lines.
1274            (Some(start), Some(end)) => {
1275                if start == end || start + 1 == end {
1276                    None
1277                } else {
1278                    Some(&mut statements[start + 1..end])
1279                }
1280            }
1281            // We have statements only on disabled start line.
1282            (Some(start), None) => {
1283                if start + 1 == statements.len() {
1284                    None
1285                } else {
1286                    Some(&mut statements[start + 1..])
1287                }
1288            }
1289            // We have statements only on disabled end line.
1290            (None, Some(end)) => {
1291                if end == 0 {
1292                    None
1293                } else {
1294                    Some(&mut statements[..end])
1295                }
1296            }
1297            // No statements on disabled start / end line.
1298            (None, None) => Some(statements),
1299        };
1300
1301        // Write statements that are not on any disabled first / last block line.
1302        let mut statements_loc = loc;
1303        if let Some(writable_statements) = writable_statments {
1304            if let Some(first_statement) = writable_statements.first() {
1305                statements_loc = statements_loc.with_start(first_statement.loc().start());
1306                self.write_whitespace_separator(true)?;
1307                self.write_postfix_comments_before(statements_loc.start())?;
1308            }
1309            // If last line is disabled then statements location ends where last block line starts.
1310            if is_end_disabled {
1311                if let Some(last_statement) = writable_statements.last() {
1312                    statements_loc = statements_loc.with_end(
1313                        self.find_next_line(last_statement.loc().end()).unwrap_or_default(),
1314                    );
1315                }
1316            }
1317            self.indented(1, |fmt| {
1318                fmt.write_lined_visitable(
1319                    statements_loc,
1320                    writable_statements.iter_mut(),
1321                    |_, _| false,
1322                )?;
1323                Ok(())
1324            })?;
1325            self.write_whitespace_separator(true)?;
1326        }
1327
1328        // Write last line of the block:
1329        // - as it is from where statements location ends until the end of last line, if format
1330        // disabled
1331        // - close block if line formatted
1332        if is_end_disabled {
1333            self.write_raw_src(loc.with_start(statements_loc.end()).with_end(end_of_last_line))?;
1334        } else {
1335            if end_of_first_line != end_of_last_line {
1336                self.write_whitespace_separator(true)?;
1337            }
1338            write_chunk!(self, loc.end(), "}}")?;
1339        }
1340
1341        Ok(false)
1342    }
1343
1344    /// Visit statement as `Statement::Block`.
1345    fn visit_stmt_as_block(
1346        &mut self,
1347        stmt: &mut Statement,
1348        attempt_single_line: bool,
1349    ) -> Result<bool> {
1350        match stmt {
1351            Statement::Block { loc, statements, .. } => {
1352                self.visit_block(*loc, statements, attempt_single_line, true)
1353            }
1354            _ => self.visit_block(stmt.loc(), &mut [stmt], attempt_single_line, true),
1355        }
1356    }
1357
1358    /// Visit the generic member access expression and
1359    /// attempt flatten it by checking if the inner expression
1360    /// matches a given member access variant.
1361    fn visit_member_access<'b, T, M>(
1362        &mut self,
1363        expr: &'b mut Box<T>,
1364        ident: &mut Identifier,
1365        mut matcher: M,
1366    ) -> Result<()>
1367    where
1368        T: CodeLocation + Visitable,
1369        M: FnMut(&mut Self, &'b mut Box<T>) -> Result<Option<(&'b mut Box<T>, &'b mut Identifier)>>,
1370    {
1371        let chunk_member_access = |fmt: &mut Self, ident: &mut Identifier, expr: &mut Box<T>| {
1372            fmt.chunked(ident.loc.start(), Some(expr.loc().start()), |fmt| ident.visit(fmt))
1373        };
1374
1375        let mut chunks: Vec<Chunk> = vec![chunk_member_access(self, ident, expr)?];
1376        let mut remaining = expr;
1377        while let Some((inner_expr, inner_ident)) = matcher(self, remaining)? {
1378            chunks.push(chunk_member_access(self, inner_ident, inner_expr)?);
1379            remaining = inner_expr;
1380        }
1381
1382        chunks.reverse();
1383        chunks.iter_mut().for_each(|chunk| chunk.content.insert(0, '.'));
1384
1385        if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? {
1386            self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?;
1387        }
1388        Ok(())
1389    }
1390
1391    /// Visit the yul string with an optional identifier.
1392    /// If the identifier is present, write the value in the format `<val>:<ident>`.
1393    ///
1394    /// Ref: <https://docs.soliditylang.org/en/v0.8.15/yul.html#variable-declarations>
1395    fn visit_yul_string_with_ident(
1396        &mut self,
1397        loc: Loc,
1398        val: &str,
1399        ident: &mut Option<Identifier>,
1400    ) -> Result<()> {
1401        let ident =
1402            if let Some(ident) = ident { format!(":{}", ident.name) } else { String::new() };
1403        write_chunk!(self, loc.start(), loc.end(), "{val}{ident}")?;
1404        Ok(())
1405    }
1406
1407    /// Format a quoted string as `prefix"string"` where the quote character is handled
1408    /// by the configuration `quote_style`
1409    fn quote_str(&self, loc: Loc, prefix: Option<&str>, string: &str) -> String {
1410        let get_og_quote = || {
1411            self.source[loc.range()]
1412                .quote_state_char_indices()
1413                .find_map(
1414                    |(state, _, ch)| {
1415                        if matches!(state, QuoteState::Opening(_)) {
1416                            Some(ch)
1417                        } else {
1418                            None
1419                        }
1420                    },
1421                )
1422                .expect("Could not find quote character for quoted string")
1423        };
1424        let mut quote = self.config.quote_style.quote().unwrap_or_else(get_og_quote);
1425        let mut quoted = format!("{quote}{string}{quote}");
1426        if !quoted.is_quoted() {
1427            quote = get_og_quote();
1428            quoted = format!("{quote}{string}{quote}");
1429        }
1430        let prefix = prefix.unwrap_or("");
1431        format!("{prefix}{quoted}")
1432    }
1433
1434    /// Write a quoted string. See `Formatter::quote_str` for more information
1435    fn write_quoted_str(&mut self, loc: Loc, prefix: Option<&str>, string: &str) -> Result<()> {
1436        write_chunk!(self, loc.start(), loc.end(), "{}", self.quote_str(loc, prefix, string))
1437    }
1438
1439    /// Write and format numbers. This will fix underscores as well as remove unnecessary 0's and
1440    /// exponents
1441    fn write_num_literal(
1442        &mut self,
1443        loc: Loc,
1444        value: &str,
1445        fractional: Option<&str>,
1446        exponent: &str,
1447        unit: &mut Option<Identifier>,
1448    ) -> Result<()> {
1449        let config = self.config.number_underscore;
1450
1451        // get source if we preserve underscores
1452        let (value, fractional, exponent) = if config.is_preserve() {
1453            let source = &self.source[loc.start()..loc.end()];
1454            // Strip unit
1455            let (source, _) = source.split_once(' ').unwrap_or((source, ""));
1456            let (val, exp) = source.split_once(['e', 'E']).unwrap_or((source, ""));
1457            let (val, fract) =
1458                val.split_once('.').map(|(val, fract)| (val, Some(fract))).unwrap_or((val, None));
1459            (
1460                val.trim().to_string(),
1461                fract.map(|fract| fract.trim().to_string()),
1462                exp.trim().to_string(),
1463            )
1464        } else {
1465            // otherwise strip underscores
1466            (
1467                value.trim().replace('_', ""),
1468                fractional.map(|fract| fract.trim().replace('_', "")),
1469                exponent.trim().replace('_', ""),
1470            )
1471        };
1472
1473        // strip any padded 0's
1474        let val = value.trim_start_matches('0');
1475        let fract = fractional.as_ref().map(|fract| fract.trim_end_matches('0'));
1476        let (exp_sign, mut exp) = if let Some(exp) = exponent.strip_prefix('-') {
1477            ("-", exp)
1478        } else {
1479            ("", exponent.as_str())
1480        };
1481        exp = exp.trim().trim_start_matches('0');
1482
1483        let add_underscores = |string: &str, reversed: bool| -> String {
1484            if !config.is_thousands() || string.len() < 5 {
1485                return string.to_string()
1486            }
1487            if reversed {
1488                Box::new(string.as_bytes().chunks(3)) as Box<dyn Iterator<Item = &[u8]>>
1489            } else {
1490                Box::new(string.as_bytes().rchunks(3).rev()) as Box<dyn Iterator<Item = &[u8]>>
1491            }
1492            .map(|chunk| std::str::from_utf8(chunk).expect("valid utf8 content."))
1493            .collect::<Vec<_>>()
1494            .join("_")
1495        };
1496
1497        let mut out = String::new();
1498        if val.is_empty() {
1499            out.push('0');
1500        } else {
1501            out.push_str(&add_underscores(val, false));
1502        }
1503        if let Some(fract) = fract {
1504            out.push('.');
1505            if fract.is_empty() {
1506                out.push('0');
1507            } else {
1508                // TODO re-enable me on the next solang-parser v0.1.18
1509                // currently disabled because of the following bug
1510                // https://github.com/hyperledger-labs/solang/pull/954
1511                // out.push_str(&add_underscores(fract, true));
1512                out.push_str(fract)
1513            }
1514        }
1515        if !exp.is_empty() {
1516            out.push('e');
1517            out.push_str(exp_sign);
1518            out.push_str(&add_underscores(exp, false));
1519        }
1520
1521        write_chunk!(self, loc.start(), loc.end(), "{out}")?;
1522        self.write_unit(unit)
1523    }
1524
1525    /// Write and hex literals according to the configuration.
1526    fn write_hex_literal(&mut self, lit: &HexLiteral) -> Result<()> {
1527        let HexLiteral { loc, hex } = lit;
1528        match self.config.hex_underscore {
1529            HexUnderscore::Remove => self.write_quoted_str(*loc, Some("hex"), hex),
1530            HexUnderscore::Preserve => {
1531                let quote = &self.source[loc.start()..loc.end()].trim_start_matches("hex");
1532                // source is always quoted so we remove the quotes first so we can adhere to the
1533                // configured quoting style
1534                let hex = &quote[1..quote.len() - 1];
1535                self.write_quoted_str(*loc, Some("hex"), hex)
1536            }
1537            HexUnderscore::Bytes => {
1538                // split all bytes
1539                let hex = hex
1540                    .chars()
1541                    .chunks(2)
1542                    .into_iter()
1543                    .map(|chunk| chunk.collect::<String>())
1544                    .collect::<Vec<_>>()
1545                    .join("_");
1546                self.write_quoted_str(*loc, Some("hex"), &hex)
1547            }
1548        }
1549    }
1550
1551    /// Write built-in unit.
1552    fn write_unit(&mut self, unit: &mut Option<Identifier>) -> Result<()> {
1553        if let Some(unit) = unit {
1554            write_chunk!(self, unit.loc.start(), unit.loc.end(), "{}", unit.name)?;
1555        }
1556        Ok(())
1557    }
1558
1559    /// Write the function header
1560    fn write_function_header(
1561        &mut self,
1562        func: &mut FunctionDefinition,
1563        body_loc: Option<Loc>,
1564        header_multiline: bool,
1565    ) -> Result<bool> {
1566        let func_name = if let Some(ident) = &func.name {
1567            format!("{} {}", func.ty, ident.name)
1568        } else {
1569            func.ty.to_string()
1570        };
1571
1572        // calculate locations of chunk groups
1573        let attrs_loc = func.attributes.first().map(|attr| attr.loc());
1574        let returns_loc = func.returns.first().map(|param| param.0);
1575
1576        let params_next_offset = attrs_loc
1577            .as_ref()
1578            .or(returns_loc.as_ref())
1579            .or(body_loc.as_ref())
1580            .map(|loc| loc.start());
1581        let attrs_end = returns_loc.as_ref().or(body_loc.as_ref()).map(|loc| loc.start());
1582        let returns_end = body_loc.as_ref().map(|loc| loc.start());
1583
1584        let mut params_multiline = false;
1585
1586        let params_loc = {
1587            let mut loc = func.loc.with_end(func.loc.start());
1588            self.extend_loc_until(&mut loc, ')');
1589            loc
1590        };
1591        let params_disabled = self.inline_config.is_disabled(params_loc);
1592        if params_disabled {
1593            let chunk = self.chunked(func.loc.start(), None, |fmt| fmt.visit_source(params_loc))?;
1594            params_multiline = chunk.content.contains('\n');
1595            self.write_chunk(&chunk)?;
1596        } else {
1597            let first_surrounding = SurroundingChunk::new(
1598                format!("{func_name}("),
1599                Some(func.loc.start()),
1600                Some(
1601                    func.params
1602                        .first()
1603                        .map(|param| param.0.start())
1604                        .unwrap_or_else(|| params_loc.end()),
1605                ),
1606            );
1607            self.surrounded(
1608                first_surrounding,
1609                SurroundingChunk::new(")", None, params_next_offset),
1610                |fmt, multiline| {
1611                    let params = fmt.items_to_chunks(
1612                        params_next_offset,
1613                        func.params
1614                            .iter_mut()
1615                            .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))),
1616                    )?;
1617                    let after_params = if !func.attributes.is_empty() || !func.returns.is_empty() {
1618                        ""
1619                    } else if func.body.is_some() {
1620                        " {"
1621                    } else {
1622                        ";"
1623                    };
1624                    let should_multiline = header_multiline &&
1625                        matches!(
1626                            fmt.config.multiline_func_header,
1627                            MultilineFuncHeaderStyle::ParamsFirst |
1628                                MultilineFuncHeaderStyle::ParamsFirstMulti |
1629                                MultilineFuncHeaderStyle::All |
1630                                MultilineFuncHeaderStyle::AllParams
1631                        );
1632                    params_multiline = should_multiline ||
1633                        multiline ||
1634                        fmt.are_chunks_separated_multiline(
1635                            &format!("{{}}){after_params}"),
1636                            &params,
1637                            ",",
1638                        )?;
1639                    // Write new line if we have only one parameter and params first set,
1640                    // or if the function definition is multiline and all params set.
1641                    let single_param_multiline = matches!(
1642                        fmt.config.multiline_func_header,
1643                        MultilineFuncHeaderStyle::ParamsFirst
1644                    ) || params_multiline &&
1645                        matches!(
1646                            fmt.config.multiline_func_header,
1647                            MultilineFuncHeaderStyle::AllParams
1648                        );
1649                    if params.len() == 1 && single_param_multiline {
1650                        writeln!(fmt.buf())?;
1651                    }
1652                    fmt.write_chunks_separated(&params, ",", params_multiline)?;
1653                    Ok(())
1654                },
1655            )?;
1656        }
1657
1658        let mut write_attributes = |fmt: &mut Self, multiline: bool| -> Result<()> {
1659            // write attributes
1660            if !func.attributes.is_empty() {
1661                let attrs_loc = func
1662                    .attributes
1663                    .first()
1664                    .unwrap()
1665                    .loc()
1666                    .with_end_from(&func.attributes.last().unwrap().loc());
1667                if fmt.inline_config.is_disabled(attrs_loc) {
1668                    // If params are also disabled then write functions attributes on the same line.
1669                    if params_disabled {
1670                        fmt.write_whitespace_separator(false)?;
1671                        let attrs_src =
1672                            String::from_utf8(self.source.as_bytes()[attrs_loc.range()].to_vec())
1673                                .map_err(FormatterError::custom)?;
1674                        fmt.write_raw(attrs_src)?;
1675                    } else {
1676                        fmt.indented(1, |fmt| fmt.visit_source(attrs_loc))?;
1677                    }
1678                } else {
1679                    fmt.write_postfix_comments_before(attrs_loc.start())?;
1680                    fmt.write_whitespace_separator(multiline)?;
1681                    let attributes =
1682                        fmt.items_to_chunks_sorted(attrs_end, func.attributes.iter_mut())?;
1683                    fmt.indented(1, |fmt| {
1684                        fmt.write_chunks_separated(&attributes, "", multiline)?;
1685                        Ok(())
1686                    })?;
1687                }
1688            }
1689
1690            // write returns
1691            if !func.returns.is_empty() {
1692                let returns_start_loc = func.returns.first().unwrap().0;
1693                let returns_loc = returns_start_loc.with_end_from(&func.returns.last().unwrap().0);
1694                if fmt.inline_config.is_disabled(returns_loc) {
1695                    fmt.write_whitespace_separator(false)?;
1696                    let returns_src =
1697                        String::from_utf8(self.source.as_bytes()[returns_loc.range()].to_vec())
1698                            .map_err(FormatterError::custom)?;
1699                    fmt.write_raw(format!("returns ({returns_src})"))?;
1700                } else {
1701                    let mut returns = fmt.items_to_chunks(
1702                        returns_end,
1703                        func.returns
1704                            .iter_mut()
1705                            .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))),
1706                    )?;
1707
1708                    // there's an issue with function return value that would lead to indent issues because those can be formatted with line breaks <https://github.com/foundry-rs/foundry/issues/4080>
1709                    for function_chunk in
1710                        returns.iter_mut().filter(|chunk| chunk.content.starts_with("function("))
1711                    {
1712                        // this will bypass the recursive indent that was applied when the function
1713                        // content was formatted in the chunk
1714                        function_chunk.content = function_chunk
1715                            .content
1716                            .split('\n')
1717                            .map(|s| s.trim_start())
1718                            .collect::<Vec<_>>()
1719                            .join("\n");
1720                    }
1721
1722                    fmt.write_postfix_comments_before(returns_loc.start())?;
1723                    fmt.write_whitespace_separator(multiline)?;
1724                    fmt.indented(1, |fmt| {
1725                        fmt.surrounded(
1726                            SurroundingChunk::new("returns (", Some(returns_loc.start()), None),
1727                            SurroundingChunk::new(")", None, returns_end),
1728                            |fmt, multiline_hint| {
1729                                fmt.write_chunks_separated(&returns, ",", multiline_hint)?;
1730                                Ok(())
1731                            },
1732                        )?;
1733                        Ok(())
1734                    })?;
1735                }
1736            }
1737            Ok(())
1738        };
1739
1740        let should_multiline = header_multiline &&
1741            if params_multiline {
1742                matches!(
1743                    self.config.multiline_func_header,
1744                    MultilineFuncHeaderStyle::All | MultilineFuncHeaderStyle::AllParams
1745                )
1746            } else {
1747                matches!(
1748                    self.config.multiline_func_header,
1749                    MultilineFuncHeaderStyle::AttributesFirst
1750                )
1751            };
1752        let attrs_multiline = should_multiline ||
1753            !self.try_on_single_line(|fmt| {
1754                write_attributes(fmt, false)?;
1755                if !fmt.will_it_fit(if func.body.is_some() { " {" } else { ";" }) {
1756                    bail!(FormatterError::fmt())
1757                }
1758                Ok(())
1759            })?;
1760        if attrs_multiline {
1761            write_attributes(self, true)?;
1762        }
1763        Ok(attrs_multiline)
1764    }
1765
1766    /// Write potentially nested `if statements`
1767    fn write_if_stmt(
1768        &mut self,
1769        loc: Loc,
1770        cond: &mut Expression,
1771        if_branch: &mut Box<Statement>,
1772        else_branch: &mut Option<Box<Statement>>,
1773    ) -> Result<(), FormatterError> {
1774        let single_line_stmt_wide = self.context.if_stmt_single_line.unwrap_or_default();
1775
1776        visit_source_if_disabled_else!(self, loc.with_end(if_branch.loc().start()), {
1777            self.surrounded(
1778                SurroundingChunk::new("if (", Some(loc.start()), Some(cond.loc().start())),
1779                SurroundingChunk::new(")", None, Some(if_branch.loc().start())),
1780                |fmt, _| {
1781                    fmt.write_prefix_comments_before(cond.loc().end())?;
1782                    cond.visit(fmt)?;
1783                    fmt.write_postfix_comments_before(if_branch.loc().start())
1784                },
1785            )?;
1786        });
1787
1788        let cond_close_paren_loc =
1789            self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end());
1790        let attempt_single_line = single_line_stmt_wide &&
1791            self.should_attempt_block_single_line(if_branch.as_mut(), cond_close_paren_loc);
1792        let if_branch_is_single_line = self.visit_stmt_as_block(if_branch, attempt_single_line)?;
1793        if single_line_stmt_wide && !if_branch_is_single_line {
1794            bail!(FormatterError::fmt())
1795        }
1796
1797        if let Some(else_branch) = else_branch {
1798            self.write_postfix_comments_before(else_branch.loc().start())?;
1799            if if_branch_is_single_line {
1800                writeln!(self.buf())?;
1801            }
1802            write_chunk!(self, else_branch.loc().start(), "else")?;
1803            if let Statement::If(loc, cond, if_branch, else_branch) = else_branch.as_mut() {
1804                self.visit_if(*loc, cond, if_branch, else_branch, false)?;
1805            } else {
1806                let else_branch_is_single_line =
1807                    self.visit_stmt_as_block(else_branch, attempt_single_line)?;
1808                if single_line_stmt_wide && !else_branch_is_single_line {
1809                    bail!(FormatterError::fmt())
1810                }
1811            }
1812        }
1813        Ok(())
1814    }
1815
1816    /// Sorts grouped import statement alphabetically.
1817    fn sort_imports(&self, source_unit: &mut SourceUnit) {
1818        // first we need to find the grouped import statements
1819        // A group is defined as a set of import statements that are separated by a blank line
1820        let mut import_groups = Vec::new();
1821        let mut current_group = Vec::new();
1822        let mut source_unit_parts = source_unit.0.iter().enumerate().peekable();
1823        while let Some((i, part)) = source_unit_parts.next() {
1824            if let SourceUnitPart::ImportDirective(_) = part {
1825                current_group.push(i);
1826                let current_loc = part.loc();
1827                if let Some((_, next_part)) = source_unit_parts.peek() {
1828                    let next_loc = next_part.loc();
1829                    // import statements are followed by a new line, so if there are more than one
1830                    // we have a group
1831                    if self.blank_lines(current_loc.end(), next_loc.start()) > 1 {
1832                        import_groups.push(std::mem::take(&mut current_group));
1833                    }
1834                }
1835            } else if !current_group.is_empty() {
1836                import_groups.push(std::mem::take(&mut current_group));
1837            }
1838        }
1839
1840        if !current_group.is_empty() {
1841            import_groups.push(current_group);
1842        }
1843
1844        if import_groups.is_empty() {
1845            // nothing to sort
1846            return
1847        }
1848
1849        // order all groups alphabetically
1850        for group in &import_groups {
1851            // SAFETY: group is not empty
1852            let first = group[0];
1853            let last = group.last().copied().expect("group is not empty");
1854            let import_directives = &mut source_unit.0[first..=last];
1855
1856            // sort rename style imports alphabetically based on the actual import and not the
1857            // rename
1858            for source_unit_part in import_directives.iter_mut() {
1859                if let SourceUnitPart::ImportDirective(Import::Rename(_, renames, _)) =
1860                    source_unit_part
1861                {
1862                    renames.sort_by_cached_key(|(og_ident, _)| og_ident.name.clone());
1863                }
1864            }
1865
1866            import_directives.sort_by_cached_key(|item| match item {
1867                SourceUnitPart::ImportDirective(import) => match import {
1868                    Import::Plain(path, _) => path.to_string(),
1869                    Import::GlobalSymbol(path, _, _) => path.to_string(),
1870                    Import::Rename(path, _, _) => path.to_string(),
1871                },
1872                _ => {
1873                    unreachable!("import group contains non-import statement")
1874                }
1875            });
1876        }
1877    }
1878}
1879
1880// Traverse the Solidity Parse Tree and write to the code formatter
1881impl<W: Write> Visitor for Formatter<'_, W> {
1882    type Error = FormatterError;
1883
1884    #[instrument(name = "source", skip(self))]
1885    fn visit_source(&mut self, loc: Loc) -> Result<()> {
1886        let source = String::from_utf8(self.source.as_bytes()[loc.range()].to_vec())
1887            .map_err(FormatterError::custom)?;
1888        let mut lines = source.splitn(2, '\n');
1889
1890        write_chunk!(self, loc.start(), "{}", lines.next().unwrap())?;
1891        if let Some(remainder) = lines.next() {
1892            // Call with `self.write_str` and not `write!`, so we can have `\n` at the beginning
1893            // without triggering an indentation
1894            self.write_raw(format!("\n{remainder}"))?;
1895        }
1896
1897        let _ = self.comments.remove_all_comments_before(loc.end());
1898
1899        Ok(())
1900    }
1901
1902    #[instrument(name = "SU", skip_all)]
1903    fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> Result<()> {
1904        if self.config.sort_imports {
1905            self.sort_imports(source_unit);
1906        }
1907        // TODO: do we need to put pragma and import directives at the top of the file?
1908        // source_unit.0.sort_by_key(|item| match item {
1909        //     SourceUnitPart::PragmaDirective(_, _, _) => 0,
1910        //     SourceUnitPart::ImportDirective(_, _) => 1,
1911        //     _ => usize::MAX,
1912        // });
1913        let loc = Loc::File(
1914            source_unit
1915                .loc_opt()
1916                .or_else(|| self.comments.iter().next().map(|comment| comment.loc))
1917                .map(|loc| loc.file_no())
1918                .unwrap_or_default(),
1919            0,
1920            self.source.len(),
1921        );
1922
1923        self.write_lined_visitable(
1924            loc,
1925            source_unit.0.iter_mut(),
1926            |last_unit, unit| match last_unit {
1927                SourceUnitPart::PragmaDirective(..) => {
1928                    !matches!(unit, SourceUnitPart::PragmaDirective(..))
1929                }
1930                SourceUnitPart::ImportDirective(_) => {
1931                    !matches!(unit, SourceUnitPart::ImportDirective(_))
1932                }
1933                SourceUnitPart::ErrorDefinition(_) => {
1934                    !matches!(unit, SourceUnitPart::ErrorDefinition(_))
1935                }
1936                SourceUnitPart::Using(_) => !matches!(unit, SourceUnitPart::Using(_)),
1937                SourceUnitPart::VariableDefinition(_) => {
1938                    !matches!(unit, SourceUnitPart::VariableDefinition(_))
1939                }
1940                SourceUnitPart::Annotation(_) => false,
1941                _ => true,
1942            },
1943        )?;
1944
1945        // EOF newline
1946        if self.last_char() != Some('\n') {
1947            writeln!(self.buf())?;
1948        }
1949
1950        Ok(())
1951    }
1952
1953    #[instrument(name = "contract", skip_all)]
1954    fn visit_contract(&mut self, contract: &mut ContractDefinition) -> Result<()> {
1955        return_source_if_disabled!(self, contract.loc);
1956
1957        self.with_contract_context(contract.clone(), |fmt| {
1958            let contract_name = contract.name.safe_unwrap();
1959
1960            visit_source_if_disabled_else!(
1961                fmt,
1962                contract.loc.with_end_from(
1963                    &contract.base.first().map(|b| b.loc).unwrap_or(contract_name.loc)
1964                ),
1965                {
1966                    fmt.grouped(|fmt| {
1967                        write_chunk!(fmt, contract.loc.start(), "{}", contract.ty)?;
1968                        write_chunk!(fmt, contract_name.loc.end(), "{}", contract_name.name)?;
1969                        if !contract.base.is_empty() {
1970                            write_chunk!(
1971                                fmt,
1972                                contract_name.loc.end(),
1973                                contract.base.first().unwrap().loc.start(),
1974                                "is"
1975                            )?;
1976                        }
1977                        Ok(())
1978                    })?;
1979                }
1980            );
1981
1982            if !contract.base.is_empty() {
1983                visit_source_if_disabled_else!(
1984                    fmt,
1985                    contract
1986                        .base
1987                        .first()
1988                        .unwrap()
1989                        .loc
1990                        .with_end_from(&contract.base.last().unwrap().loc),
1991                    {
1992                        fmt.indented(1, |fmt| {
1993                            let base_end = contract.parts.first().map(|part| part.loc().start());
1994                            let bases = fmt.items_to_chunks(
1995                                base_end,
1996                                contract.base.iter_mut().map(|base| (base.loc, base)),
1997                            )?;
1998                            let multiline =
1999                                fmt.are_chunks_separated_multiline("{}", &bases, ",")?;
2000                            fmt.write_chunks_separated(&bases, ",", multiline)?;
2001                            fmt.write_whitespace_separator(multiline)?;
2002                            Ok(())
2003                        })?;
2004                    }
2005                );
2006            }
2007
2008            write_chunk!(fmt, "{{")?;
2009
2010            fmt.indented(1, |fmt| {
2011                if let Some(first) = contract.parts.first() {
2012                    fmt.write_postfix_comments_before(first.loc().start())?;
2013                    fmt.write_whitespace_separator(true)?;
2014                } else {
2015                    return Ok(())
2016                }
2017
2018                if fmt.config.contract_new_lines {
2019                    write_chunk!(fmt, "\n")?;
2020                }
2021
2022                fmt.write_lined_visitable(
2023                    contract.loc,
2024                    contract.parts.iter_mut(),
2025                    |last_part, part| match last_part {
2026                        ContractPart::ErrorDefinition(_) => {
2027                            !matches!(part, ContractPart::ErrorDefinition(_))
2028                        }
2029                        ContractPart::EventDefinition(_) => {
2030                            !matches!(part, ContractPart::EventDefinition(_))
2031                        }
2032                        ContractPart::VariableDefinition(_) => {
2033                            !matches!(part, ContractPart::VariableDefinition(_))
2034                        }
2035                        ContractPart::TypeDefinition(_) => {
2036                            !matches!(part, ContractPart::TypeDefinition(_))
2037                        }
2038                        ContractPart::EnumDefinition(_) => {
2039                            !matches!(part, ContractPart::EnumDefinition(_))
2040                        }
2041                        ContractPart::Using(_) => !matches!(part, ContractPart::Using(_)),
2042                        ContractPart::FunctionDefinition(last_def) => {
2043                            if last_def.is_empty() {
2044                                match part {
2045                                    ContractPart::FunctionDefinition(def) => !def.is_empty(),
2046                                    _ => true,
2047                                }
2048                            } else {
2049                                true
2050                            }
2051                        }
2052                        ContractPart::Annotation(_) => false,
2053                        _ => true,
2054                    },
2055                )
2056            })?;
2057
2058            if !contract.parts.is_empty() {
2059                fmt.write_whitespace_separator(true)?;
2060
2061                if fmt.config.contract_new_lines {
2062                    write_chunk!(fmt, "\n")?;
2063                }
2064            }
2065
2066            write_chunk!(fmt, contract.loc.end(), "}}")?;
2067
2068            Ok(())
2069        })?;
2070
2071        Ok(())
2072    }
2073
2074    // Support extension for Solana/Substrate
2075    #[instrument(name = "annotation", skip_all)]
2076    fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> {
2077        return_source_if_disabled!(self, annotation.loc);
2078        let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?;
2079        write!(self.buf(), "@{id}")?;
2080        write!(self.buf(), "(")?;
2081        annotation.value.visit(self)?;
2082        write!(self.buf(), ")")?;
2083        Ok(())
2084    }
2085
2086    #[instrument(name = "pragma", skip_all)]
2087    fn visit_pragma(
2088        &mut self,
2089        loc: Loc,
2090        ident: &mut Option<Identifier>,
2091        string: &mut Option<StringLiteral>,
2092    ) -> Result<()> {
2093        let (ident, string) = (ident.safe_unwrap(), string.safe_unwrap());
2094        return_source_if_disabled!(self, loc, ';');
2095
2096        let pragma_descriptor = if ident.name == "solidity" {
2097            // There are some issues with parsing Solidity's versions with crates like `semver`:
2098            // 1. Ranges like `>=0.4.21<0.6.0` or `>=0.4.21 <0.6.0` are not parseable at all.
2099            // 2. Versions like `0.8.10` got transformed into `^0.8.10` which is not the same.
2100            // TODO: semver-solidity crate :D
2101            &string.string
2102        } else {
2103            &string.string
2104        };
2105
2106        write_chunk!(self, string.loc.end(), "pragma {} {};", &ident.name, pragma_descriptor)?;
2107
2108        Ok(())
2109    }
2110
2111    #[instrument(name = "import_plain", skip_all)]
2112    fn visit_import_plain(&mut self, loc: Loc, import: &mut ImportPath) -> Result<()> {
2113        return_source_if_disabled!(self, loc, ';');
2114
2115        self.grouped(|fmt| {
2116            write_chunk!(fmt, loc.start(), import.loc().start(), "import")?;
2117            fmt.write_quoted_str(import.loc(), None, &import_path_string(import))?;
2118            fmt.write_semicolon()?;
2119            Ok(())
2120        })?;
2121        Ok(())
2122    }
2123
2124    #[instrument(name = "import_global", skip_all)]
2125    fn visit_import_global(
2126        &mut self,
2127        loc: Loc,
2128        global: &mut ImportPath,
2129        alias: &mut Identifier,
2130    ) -> Result<()> {
2131        return_source_if_disabled!(self, loc, ';');
2132
2133        self.grouped(|fmt| {
2134            write_chunk!(fmt, loc.start(), global.loc().start(), "import")?;
2135            fmt.write_quoted_str(global.loc(), None, &import_path_string(global))?;
2136            write_chunk!(fmt, loc.start(), alias.loc.start(), "as")?;
2137            alias.visit(fmt)?;
2138            fmt.write_semicolon()?;
2139            Ok(())
2140        })?;
2141        Ok(())
2142    }
2143
2144    #[instrument(name = "import_renames", skip_all)]
2145    fn visit_import_renames(
2146        &mut self,
2147        loc: Loc,
2148        imports: &mut [(Identifier, Option<Identifier>)],
2149        from: &mut ImportPath,
2150    ) -> Result<()> {
2151        return_source_if_disabled!(self, loc, ';');
2152
2153        if imports.is_empty() {
2154            self.grouped(|fmt| {
2155                write_chunk!(fmt, loc.start(), "import")?;
2156                fmt.write_empty_brackets()?;
2157                write_chunk!(fmt, loc.start(), from.loc().start(), "from")?;
2158                fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?;
2159                fmt.write_semicolon()?;
2160                Ok(())
2161            })?;
2162            return Ok(())
2163        }
2164
2165        let imports_start = imports.first().unwrap().0.loc.start();
2166
2167        write_chunk!(self, loc.start(), imports_start, "import")?;
2168
2169        self.surrounded(
2170            SurroundingChunk::new("{", Some(imports_start), None),
2171            SurroundingChunk::new("}", None, Some(from.loc().start())),
2172            |fmt, _multiline| {
2173                let mut imports = imports.iter_mut().peekable();
2174                let mut import_chunks = Vec::new();
2175                while let Some((ident, alias)) = imports.next() {
2176                    import_chunks.push(fmt.chunked(
2177                        ident.loc.start(),
2178                        imports.peek().map(|(ident, _)| ident.loc.start()),
2179                        |fmt| {
2180                            fmt.grouped(|fmt| {
2181                                ident.visit(fmt)?;
2182                                if let Some(alias) = alias {
2183                                    write_chunk!(fmt, ident.loc.end(), alias.loc.start(), "as")?;
2184                                    alias.visit(fmt)?;
2185                                }
2186                                Ok(())
2187                            })?;
2188                            Ok(())
2189                        },
2190                    )?);
2191                }
2192
2193                let multiline = fmt.are_chunks_separated_multiline(
2194                    &format!("{{}} }} from \"{}\";", import_path_string(from)),
2195                    &import_chunks,
2196                    ",",
2197                )?;
2198                fmt.write_chunks_separated(&import_chunks, ",", multiline)?;
2199                Ok(())
2200            },
2201        )?;
2202
2203        self.grouped(|fmt| {
2204            write_chunk!(fmt, imports_start, from.loc().start(), "from")?;
2205            fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?;
2206            fmt.write_semicolon()?;
2207            Ok(())
2208        })?;
2209
2210        Ok(())
2211    }
2212
2213    #[instrument(name = "enum", skip_all)]
2214    fn visit_enum(&mut self, enumeration: &mut EnumDefinition) -> Result<()> {
2215        return_source_if_disabled!(self, enumeration.loc);
2216
2217        let enum_name = enumeration.name.safe_unwrap_mut();
2218        let mut name =
2219            self.visit_to_chunk(enum_name.loc.start(), Some(enum_name.loc.end()), enum_name)?;
2220        name.content = format!("enum {} ", name.content);
2221        if enumeration.values.is_empty() {
2222            self.write_chunk(&name)?;
2223            self.write_empty_brackets()?;
2224        } else {
2225            name.content.push('{');
2226            self.write_chunk(&name)?;
2227
2228            self.indented(1, |fmt| {
2229                let values = fmt.items_to_chunks(
2230                    Some(enumeration.loc.end()),
2231                    enumeration.values.iter_mut().map(|ident| {
2232                        let ident = ident.safe_unwrap_mut();
2233                        (ident.loc, ident)
2234                    }),
2235                )?;
2236                fmt.write_chunks_separated(&values, ",", true)?;
2237                writeln!(fmt.buf())?;
2238                Ok(())
2239            })?;
2240            write_chunk!(self, "}}")?;
2241        }
2242
2243        Ok(())
2244    }
2245
2246    #[instrument(name = "assembly", skip_all)]
2247    fn visit_assembly(
2248        &mut self,
2249        loc: Loc,
2250        dialect: &mut Option<StringLiteral>,
2251        block: &mut YulBlock,
2252        flags: &mut Option<Vec<StringLiteral>>,
2253    ) -> Result<(), Self::Error> {
2254        return_source_if_disabled!(self, loc);
2255
2256        write_chunk!(self, loc.start(), "assembly")?;
2257        if let Some(StringLiteral { loc, string, .. }) = dialect {
2258            write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?;
2259        }
2260        if let Some(flags) = flags {
2261            if !flags.is_empty() {
2262                let loc_start = flags.first().unwrap().loc.start();
2263                self.surrounded(
2264                    SurroundingChunk::new("(", Some(loc_start), None),
2265                    SurroundingChunk::new(")", None, Some(block.loc.start())),
2266                    |fmt, _| {
2267                        let mut flags = flags.iter_mut().peekable();
2268                        let mut chunks = vec![];
2269                        while let Some(flag) = flags.next() {
2270                            let next_byte_offset =
2271                                flags.peek().map(|next_flag| next_flag.loc.start());
2272                            chunks.push(fmt.chunked(
2273                                flag.loc.start(),
2274                                next_byte_offset,
2275                                |fmt| {
2276                                    write!(fmt.buf(), "\"{}\"", flag.string)?;
2277                                    Ok(())
2278                                },
2279                            )?);
2280                        }
2281                        fmt.write_chunks_separated(&chunks, ",", false)?;
2282                        Ok(())
2283                    },
2284                )?;
2285            }
2286        }
2287
2288        block.visit(self)
2289    }
2290
2291    #[instrument(name = "block", skip_all)]
2292    fn visit_block(
2293        &mut self,
2294        loc: Loc,
2295        unchecked: bool,
2296        statements: &mut Vec<Statement>,
2297    ) -> Result<()> {
2298        return_source_if_disabled!(self, loc);
2299        if unchecked {
2300            write_chunk!(self, loc.start(), "unchecked ")?;
2301        }
2302
2303        self.visit_block(loc, statements, false, false)?;
2304        Ok(())
2305    }
2306
2307    #[instrument(name = "args", skip_all)]
2308    fn visit_args(&mut self, loc: Loc, args: &mut Vec<NamedArgument>) -> Result<(), Self::Error> {
2309        return_source_if_disabled!(self, loc);
2310
2311        write!(self.buf(), "{{")?;
2312
2313        let mut args_iter = args.iter_mut().peekable();
2314        let mut chunks = Vec::new();
2315        while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() {
2316            let next_byte_offset = args_iter
2317                .peek()
2318                .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start())
2319                .unwrap_or_else(|| loc.end());
2320            chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| {
2321                fmt.grouped(|fmt| {
2322                    write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?;
2323                    expr.visit(fmt)
2324                })?;
2325                Ok(())
2326            })?);
2327        }
2328
2329        if let Some(first) = chunks.first_mut() {
2330            if first.prefixes.is_empty() &&
2331                first.postfixes_before.is_empty() &&
2332                !self.config.bracket_spacing
2333            {
2334                first.needs_space = Some(false);
2335            }
2336        }
2337        let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?;
2338        self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?;
2339
2340        let prefix = if multiline && !self.is_beginning_of_line() {
2341            "\n"
2342        } else if self.config.bracket_spacing {
2343            " "
2344        } else {
2345            ""
2346        };
2347        let closing_bracket = format!("{prefix}{}", "}");
2348        if let Some(arg) = args.last() {
2349            write_chunk!(self, arg.loc.end(), "{closing_bracket}")?;
2350        } else {
2351            write_chunk!(self, "{closing_bracket}")?;
2352        }
2353
2354        Ok(())
2355    }
2356
2357    #[instrument(name = "expr", skip_all)]
2358    fn visit_expr(&mut self, loc: Loc, expr: &mut Expression) -> Result<()> {
2359        return_source_if_disabled!(self, loc);
2360
2361        match expr {
2362            Expression::Type(loc, typ) => match typ {
2363                Type::Address => write_chunk!(self, loc.start(), "address")?,
2364                Type::AddressPayable => write_chunk!(self, loc.start(), "address payable")?,
2365                Type::Payable => write_chunk!(self, loc.start(), "payable")?,
2366                Type::Bool => write_chunk!(self, loc.start(), "bool")?,
2367                Type::String => write_chunk!(self, loc.start(), "string")?,
2368                Type::Bytes(n) => write_chunk!(self, loc.start(), "bytes{}", n)?,
2369                Type::Rational => write_chunk!(self, loc.start(), "rational")?,
2370                Type::DynamicBytes => write_chunk!(self, loc.start(), "bytes")?,
2371                Type::Int(ref n) | Type::Uint(ref n) => {
2372                    let int = if matches!(typ, Type::Int(_)) { "int" } else { "uint" };
2373                    match n {
2374                        256 => match self.config.int_types {
2375                            IntTypes::Long => write_chunk!(self, loc.start(), "{int}{n}")?,
2376                            IntTypes::Short => write_chunk!(self, loc.start(), "{int}")?,
2377                            IntTypes::Preserve => self.visit_source(*loc)?,
2378                        },
2379                        _ => write_chunk!(self, loc.start(), "{int}{n}")?,
2380                    }
2381                }
2382                Type::Mapping { loc, key, key_name, value, value_name } => {
2383                    let arrow_loc = self.find_next_str_in_src(loc.start(), "=>");
2384                    let close_paren_loc =
2385                        self.find_next_in_src(value.loc().end(), ')').unwrap_or(loc.end());
2386                    let first = SurroundingChunk::new(
2387                        "mapping(",
2388                        Some(loc.start()),
2389                        Some(key.loc().start()),
2390                    );
2391                    let last = SurroundingChunk::new(")", Some(close_paren_loc), Some(loc.end()))
2392                        .non_spaced();
2393                    self.surrounded(first, last, |fmt, multiline| {
2394                        fmt.grouped(|fmt| {
2395                            key.visit(fmt)?;
2396
2397                            if let Some(name) = key_name {
2398                                let end_loc = arrow_loc.unwrap_or(value.loc().start());
2399                                write_chunk!(fmt, name.loc.start(), end_loc, " {}", name)?;
2400                            } else if let Some(arrow_loc) = arrow_loc {
2401                                fmt.write_postfix_comments_before(arrow_loc)?;
2402                            }
2403
2404                            let mut write_arrow_and_value = |fmt: &mut Self| {
2405                                write!(fmt.buf(), "=> ")?;
2406                                value.visit(fmt)?;
2407                                if let Some(name) = value_name {
2408                                    write_chunk!(fmt, name.loc.start(), " {}", name)?;
2409                                }
2410                                Ok(())
2411                            };
2412
2413                            let rest_str = fmt.simulate_to_string(&mut write_arrow_and_value)?;
2414                            let multiline = multiline && !fmt.will_it_fit(rest_str);
2415                            fmt.write_whitespace_separator(multiline)?;
2416
2417                            write_arrow_and_value(fmt)?;
2418
2419                            fmt.write_postfix_comments_before(close_paren_loc)?;
2420                            fmt.write_prefix_comments_before(close_paren_loc)
2421                        })?;
2422                        Ok(())
2423                    })?;
2424                }
2425                Type::Function { .. } => self.visit_source(*loc)?,
2426            },
2427            Expression::BoolLiteral(loc, val) => {
2428                write_chunk!(self, loc.start(), loc.end(), "{val}")?;
2429            }
2430            Expression::NumberLiteral(loc, val, exp, unit) => {
2431                self.write_num_literal(*loc, val, None, exp, unit)?;
2432            }
2433            Expression::HexNumberLiteral(loc, val, unit) => {
2434                // ref: https://docs.soliditylang.org/en/latest/types.html?highlight=address%20literal#address-literals
2435                let val = if val.len() == 42 {
2436                    Address::from_str(val).expect("").to_string()
2437                } else {
2438                    val.to_owned()
2439                };
2440                write_chunk!(self, loc.start(), loc.end(), "{val}")?;
2441                self.write_unit(unit)?;
2442            }
2443            Expression::RationalNumberLiteral(loc, val, fraction, exp, unit) => {
2444                self.write_num_literal(*loc, val, Some(fraction), exp, unit)?;
2445            }
2446            Expression::StringLiteral(vals) => {
2447                for StringLiteral { loc, string, unicode } in vals {
2448                    let prefix = if *unicode { Some("unicode") } else { None };
2449                    self.write_quoted_str(*loc, prefix, string)?;
2450                }
2451            }
2452            Expression::HexLiteral(vals) => {
2453                for val in vals {
2454                    self.write_hex_literal(val)?;
2455                }
2456            }
2457            Expression::AddressLiteral(loc, val) => {
2458                // support of solana/substrate address literals
2459                self.write_quoted_str(*loc, Some("address"), val)?;
2460            }
2461            Expression::Parenthesis(loc, expr) => {
2462                self.surrounded(
2463                    SurroundingChunk::new("(", Some(loc.start()), None),
2464                    SurroundingChunk::new(")", None, Some(loc.end())),
2465                    |fmt, _| expr.visit(fmt),
2466                )?;
2467            }
2468            Expression::ArraySubscript(_, ty_exp, index_expr) => {
2469                ty_exp.visit(self)?;
2470                write!(self.buf(), "[")?;
2471                index_expr.as_mut().map(|index| index.visit(self)).transpose()?;
2472                write!(self.buf(), "]")?;
2473            }
2474            Expression::ArraySlice(loc, expr, start, end) => {
2475                expr.visit(self)?;
2476                write!(self.buf(), "[")?;
2477                let mut write_slice = |fmt: &mut Self, multiline| -> Result<()> {
2478                    if multiline {
2479                        fmt.write_whitespace_separator(true)?;
2480                    }
2481                    fmt.grouped(|fmt| {
2482                        start.as_mut().map(|start| start.visit(fmt)).transpose()?;
2483                        write!(fmt.buf(), ":")?;
2484                        if let Some(end) = end {
2485                            let mut chunk =
2486                                fmt.chunked(end.loc().start(), Some(loc.end()), |fmt| {
2487                                    end.visit(fmt)
2488                                })?;
2489                            if chunk.prefixes.is_empty() &&
2490                                chunk.postfixes_before.is_empty() &&
2491                                (start.is_none() || fmt.will_it_fit(&chunk.content))
2492                            {
2493                                chunk.needs_space = Some(false);
2494                            }
2495                            fmt.write_chunk(&chunk)?;
2496                        }
2497                        Ok(())
2498                    })?;
2499                    if multiline {
2500                        fmt.write_whitespace_separator(true)?;
2501                    }
2502                    Ok(())
2503                };
2504
2505                if !self.try_on_single_line(|fmt| write_slice(fmt, false))? {
2506                    self.indented(1, |fmt| write_slice(fmt, true))?;
2507                }
2508
2509                write!(self.buf(), "]")?;
2510            }
2511            Expression::ArrayLiteral(loc, exprs) => {
2512                write_chunk!(self, loc.start(), "[")?;
2513                let chunks = self.items_to_chunks(
2514                    Some(loc.end()),
2515                    exprs.iter_mut().map(|expr| (expr.loc(), expr)),
2516                )?;
2517                let multiline = self.are_chunks_separated_multiline("{}]", &chunks, ",")?;
2518                self.indented_if(multiline, 1, |fmt| {
2519                    fmt.write_chunks_separated(&chunks, ",", multiline)?;
2520                    if multiline {
2521                        fmt.write_postfix_comments_before(loc.end())?;
2522                        fmt.write_prefix_comments_before(loc.end())?;
2523                        fmt.write_whitespace_separator(true)?;
2524                    }
2525                    Ok(())
2526                })?;
2527                write_chunk!(self, loc.end(), "]")?;
2528            }
2529            Expression::PreIncrement(..) |
2530            Expression::PostIncrement(..) |
2531            Expression::PreDecrement(..) |
2532            Expression::PostDecrement(..) |
2533            Expression::Not(..) |
2534            Expression::UnaryPlus(..) |
2535            Expression::Add(..) |
2536            Expression::Negate(..) |
2537            Expression::Subtract(..) |
2538            Expression::Power(..) |
2539            Expression::Multiply(..) |
2540            Expression::Divide(..) |
2541            Expression::Modulo(..) |
2542            Expression::ShiftLeft(..) |
2543            Expression::ShiftRight(..) |
2544            Expression::BitwiseNot(..) |
2545            Expression::BitwiseAnd(..) |
2546            Expression::BitwiseXor(..) |
2547            Expression::BitwiseOr(..) |
2548            Expression::Less(..) |
2549            Expression::More(..) |
2550            Expression::LessEqual(..) |
2551            Expression::MoreEqual(..) |
2552            Expression::And(..) |
2553            Expression::Or(..) |
2554            Expression::Equal(..) |
2555            Expression::NotEqual(..) => {
2556                let spaced = expr.has_space_around();
2557                let op = expr.operator().unwrap();
2558
2559                match expr.components_mut() {
2560                    (Some(left), Some(right)) => {
2561                        left.visit(self)?;
2562
2563                        let right_chunk =
2564                            self.chunked(right.loc().start(), Some(loc.end()), |fmt| {
2565                                write_chunk!(fmt, right.loc().start(), "{op}")?;
2566                                right.visit(fmt)?;
2567                                Ok(())
2568                            })?;
2569
2570                        self.grouped(|fmt| fmt.write_chunk(&right_chunk))?;
2571                    }
2572                    (Some(left), None) => {
2573                        left.visit(self)?;
2574                        write_chunk_spaced!(self, loc.end(), Some(spaced), "{op}")?;
2575                    }
2576                    (None, Some(right)) => {
2577                        write_chunk!(self, right.loc().start(), "{op}")?;
2578                        let mut right_chunk =
2579                            self.visit_to_chunk(right.loc().end(), Some(loc.end()), right)?;
2580                        right_chunk.needs_space = Some(spaced);
2581                        self.write_chunk(&right_chunk)?;
2582                    }
2583                    (None, None) => {}
2584                }
2585            }
2586            Expression::Assign(..) |
2587            Expression::AssignOr(..) |
2588            Expression::AssignAnd(..) |
2589            Expression::AssignXor(..) |
2590            Expression::AssignShiftLeft(..) |
2591            Expression::AssignShiftRight(..) |
2592            Expression::AssignAdd(..) |
2593            Expression::AssignSubtract(..) |
2594            Expression::AssignMultiply(..) |
2595            Expression::AssignDivide(..) |
2596            Expression::AssignModulo(..) => {
2597                let op = expr.operator().unwrap();
2598                let (left, right) = expr.components_mut();
2599                let (left, right) = (left.unwrap(), right.unwrap());
2600
2601                left.visit(self)?;
2602                write_chunk!(self, "{op}")?;
2603                self.visit_assignment(right)?;
2604            }
2605            Expression::ConditionalOperator(loc, cond, first_expr, second_expr) => {
2606                cond.visit(self)?;
2607
2608                let first_expr = self.chunked(
2609                    first_expr.loc().start(),
2610                    Some(second_expr.loc().start()),
2611                    |fmt| {
2612                        write_chunk!(fmt, "?")?;
2613                        first_expr.visit(fmt)
2614                    },
2615                )?;
2616                let second_expr =
2617                    self.chunked(second_expr.loc().start(), Some(loc.end()), |fmt| {
2618                        write_chunk!(fmt, ":")?;
2619                        second_expr.visit(fmt)
2620                    })?;
2621
2622                let chunks = vec![first_expr, second_expr];
2623                if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? {
2624                    self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?;
2625                }
2626            }
2627            Expression::Variable(ident) => {
2628                write_chunk!(self, loc.end(), "{}", ident.name)?;
2629            }
2630            Expression::MemberAccess(_, expr, ident) => {
2631                self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() {
2632                    Expression::MemberAccess(_, inner_expr, inner_ident) => {
2633                        Ok(Some((inner_expr, inner_ident)))
2634                    }
2635                    expr => {
2636                        expr.visit(fmt)?;
2637                        Ok(None)
2638                    }
2639                })?;
2640            }
2641            Expression::List(loc, items) => {
2642                self.surrounded(
2643                    SurroundingChunk::new(
2644                        "(",
2645                        Some(loc.start()),
2646                        items.first().map(|item| item.0.start()),
2647                    ),
2648                    SurroundingChunk::new(")", None, Some(loc.end())),
2649                    |fmt, _| {
2650                        let items = fmt.items_to_chunks(
2651                            Some(loc.end()),
2652                            items.iter_mut().map(|(loc, item)| (*loc, item)),
2653                        )?;
2654                        let write_items = |fmt: &mut Self, multiline| {
2655                            fmt.write_chunks_separated(&items, ",", multiline)
2656                        };
2657                        if !fmt.try_on_single_line(|fmt| write_items(fmt, false))? {
2658                            write_items(fmt, true)?;
2659                        }
2660                        Ok(())
2661                    },
2662                )?;
2663            }
2664            Expression::FunctionCall(loc, expr, exprs) => {
2665                self.visit_expr(expr.loc(), expr)?;
2666                self.visit_list("", exprs, Some(expr.loc().end()), Some(loc.end()), true)?;
2667            }
2668            Expression::NamedFunctionCall(loc, expr, args) => {
2669                self.visit_expr(expr.loc(), expr)?;
2670                write!(self.buf(), "(")?;
2671                self.visit_args(*loc, args)?;
2672                write!(self.buf(), ")")?;
2673            }
2674            Expression::FunctionCallBlock(_, expr, stmt) => {
2675                expr.visit(self)?;
2676                stmt.visit(self)?;
2677            }
2678            Expression::New(_, expr) => {
2679                write_chunk!(self, "new ")?;
2680                self.visit_expr(expr.loc(), expr)?;
2681            }
2682            _ => self.visit_source(loc)?,
2683        };
2684
2685        Ok(())
2686    }
2687
2688    #[instrument(name = "ident", skip_all)]
2689    fn visit_ident(&mut self, loc: Loc, ident: &mut Identifier) -> Result<()> {
2690        return_source_if_disabled!(self, loc);
2691        write_chunk!(self, loc.end(), "{}", ident.name)?;
2692        Ok(())
2693    }
2694
2695    #[instrument(name = "ident_path", skip_all)]
2696    fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> {
2697        if idents.identifiers.is_empty() {
2698            return Ok(())
2699        }
2700        return_source_if_disabled!(self, idents.loc);
2701
2702        idents.identifiers.iter_mut().skip(1).for_each(|chunk| {
2703            if !chunk.name.starts_with('.') {
2704                chunk.name.insert(0, '.')
2705            }
2706        });
2707        let chunks = self.items_to_chunks(
2708            Some(idents.loc.end()),
2709            idents.identifiers.iter_mut().map(|ident| (ident.loc, ident)),
2710        )?;
2711        self.grouped(|fmt| {
2712            let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, "")?;
2713            fmt.write_chunks_separated(&chunks, "", multiline)
2714        })?;
2715        Ok(())
2716    }
2717
2718    #[instrument(name = "emit", skip_all)]
2719    fn visit_emit(&mut self, loc: Loc, event: &mut Expression) -> Result<()> {
2720        return_source_if_disabled!(self, loc);
2721        write_chunk!(self, loc.start(), "emit")?;
2722        event.visit(self)?;
2723        self.write_semicolon()?;
2724        Ok(())
2725    }
2726
2727    #[instrument(name = "var_definition", skip_all)]
2728    fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> {
2729        return_source_if_disabled!(self, var.loc, ';');
2730
2731        var.ty.visit(self)?;
2732
2733        let multiline = self.grouped(|fmt| {
2734            let var_name = var.name.safe_unwrap_mut();
2735            let name_start = var_name.loc.start();
2736
2737            let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?;
2738            if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? {
2739                fmt.write_chunks_separated(&attrs, "", true)?;
2740            }
2741
2742            let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?;
2743            if var.initializer.is_some() {
2744                name.content.push_str(" =");
2745            }
2746            fmt.write_chunk(&name)?;
2747
2748            Ok(())
2749        })?;
2750
2751        var.initializer
2752            .as_mut()
2753            .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init)))
2754            .transpose()?;
2755
2756        self.write_semicolon()?;
2757
2758        Ok(())
2759    }
2760
2761    #[instrument(name = "var_definition_stmt", skip_all)]
2762    fn visit_var_definition_stmt(
2763        &mut self,
2764        loc: Loc,
2765        declaration: &mut VariableDeclaration,
2766        expr: &mut Option<Expression>,
2767    ) -> Result<()> {
2768        return_source_if_disabled!(self, loc, ';');
2769
2770        let declaration = self
2771            .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?;
2772        let multiline = declaration.content.contains('\n');
2773        self.write_chunk(&declaration)?;
2774
2775        if let Some(expr) = expr {
2776            write!(self.buf(), " =")?;
2777            self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?;
2778        }
2779
2780        self.write_semicolon()
2781    }
2782
2783    #[instrument(name = "var_declaration", skip_all)]
2784    fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> {
2785        return_source_if_disabled!(self, var.loc);
2786        self.grouped(|fmt| {
2787            var.ty.visit(fmt)?;
2788            if let Some(storage) = &var.storage {
2789                write_chunk!(fmt, storage.loc().end(), "{storage}")?;
2790            }
2791            let var_name = var.name.safe_unwrap();
2792            write_chunk!(fmt, var_name.loc.end(), "{var_name}")
2793        })?;
2794        Ok(())
2795    }
2796
2797    #[instrument(name = "return", skip_all)]
2798    fn visit_return(&mut self, loc: Loc, expr: &mut Option<Expression>) -> Result<(), Self::Error> {
2799        return_source_if_disabled!(self, loc, ';');
2800
2801        self.write_postfix_comments_before(loc.start())?;
2802        self.write_prefix_comments_before(loc.start())?;
2803
2804        if expr.is_none() {
2805            write_chunk!(self, loc.end(), "return;")?;
2806            return Ok(())
2807        }
2808
2809        let expr = expr.as_mut().unwrap();
2810        let expr_loc_start = expr.loc().start();
2811        let write_return = |fmt: &mut Self| -> Result<()> {
2812            write_chunk!(fmt, loc.start(), "return")?;
2813            fmt.write_postfix_comments_before(expr_loc_start)?;
2814            Ok(())
2815        };
2816
2817        let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> {
2818            let fits_on_single = fmt.try_on_single_line(|fmt| {
2819                write_return(fmt)?;
2820                expr.visit(fmt)
2821            })?;
2822            if fits_on_single {
2823                return Ok(())
2824            }
2825
2826            let mut fit_on_next_line = false;
2827            let tx = fmt.transact(|fmt| {
2828                fmt.grouped(|fmt| {
2829                    write_return(fmt)?;
2830                    if !fmt.is_beginning_of_line() {
2831                        fmt.write_whitespace_separator(true)?;
2832                    }
2833                    fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?;
2834                    Ok(())
2835                })?;
2836                Ok(())
2837            })?;
2838            if fit_on_next_line {
2839                tx.commit()?;
2840                return Ok(())
2841            }
2842
2843            write_return(fmt)?;
2844            expr.visit(fmt)?;
2845            Ok(())
2846        };
2847
2848        write_return_with_expr(self)?;
2849        write_chunk!(self, loc.end(), ";")?;
2850        Ok(())
2851    }
2852
2853    #[instrument(name = "revert", skip_all)]
2854    fn visit_revert(
2855        &mut self,
2856        loc: Loc,
2857        error: &mut Option<IdentifierPath>,
2858        args: &mut Vec<Expression>,
2859    ) -> Result<(), Self::Error> {
2860        return_source_if_disabled!(self, loc, ';');
2861        write_chunk!(self, loc.start(), "revert")?;
2862        if let Some(error) = error {
2863            error.visit(self)?;
2864        }
2865        self.visit_list("", args, None, Some(loc.end()), true)?;
2866        self.write_semicolon()?;
2867
2868        Ok(())
2869    }
2870
2871    #[instrument(name = "revert_named_args", skip_all)]
2872    fn visit_revert_named_args(
2873        &mut self,
2874        loc: Loc,
2875        error: &mut Option<IdentifierPath>,
2876        args: &mut Vec<NamedArgument>,
2877    ) -> Result<(), Self::Error> {
2878        return_source_if_disabled!(self, loc, ';');
2879
2880        write_chunk!(self, loc.start(), "revert")?;
2881        let mut error_indented = false;
2882        if let Some(error) = error {
2883            if !self.try_on_single_line(|fmt| error.visit(fmt))? {
2884                error.visit(self)?;
2885                error_indented = true;
2886            }
2887        }
2888
2889        if args.is_empty() {
2890            write!(self.buf(), "({{}});")?;
2891            return Ok(())
2892        }
2893
2894        write!(self.buf(), "(")?;
2895        self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?;
2896        write!(self.buf(), ")")?;
2897        self.write_semicolon()?;
2898
2899        Ok(())
2900    }
2901
2902    #[instrument(name = "break", skip_all)]
2903    fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> {
2904        if semicolon {
2905            return_source_if_disabled!(self, loc, ';');
2906        } else {
2907            return_source_if_disabled!(self, loc);
2908        }
2909        write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" })
2910    }
2911
2912    #[instrument(name = "continue", skip_all)]
2913    fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> {
2914        if semicolon {
2915            return_source_if_disabled!(self, loc, ';');
2916        } else {
2917            return_source_if_disabled!(self, loc);
2918        }
2919        write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" })
2920    }
2921
2922    #[instrument(name = "try", skip_all)]
2923    fn visit_try(
2924        &mut self,
2925        loc: Loc,
2926        expr: &mut Expression,
2927        returns: &mut Option<(Vec<(Loc, Option<Parameter>)>, Box<Statement>)>,
2928        clauses: &mut Vec<CatchClause>,
2929    ) -> Result<(), Self::Error> {
2930        return_source_if_disabled!(self, loc);
2931
2932        let try_next_byte = clauses.first().map(|c| match c {
2933            CatchClause::Simple(loc, ..) => loc.start(),
2934            CatchClause::Named(loc, ..) => loc.start(),
2935        });
2936        let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| {
2937            write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?;
2938            expr.visit(fmt)?;
2939            if let Some((params, stmt)) = returns {
2940                let mut params =
2941                    params.iter_mut().filter(|(_, param)| param.is_some()).collect::<Vec<_>>();
2942                let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start());
2943                fmt.surrounded(
2944                    SurroundingChunk::new("returns (", Some(byte_offset), None),
2945                    SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())),
2946                    |fmt, _| {
2947                        let chunks = fmt.items_to_chunks(
2948                            Some(stmt.loc().start()),
2949                            params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)),
2950                        )?;
2951                        let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?;
2952                        fmt.write_chunks_separated(&chunks, ",", multiline)?;
2953                        Ok(())
2954                    },
2955                )?;
2956                stmt.visit(fmt)?;
2957            }
2958            Ok(())
2959        })?;
2960
2961        let mut chunks = vec![try_chunk];
2962        for clause in clauses {
2963            let (loc, ident, mut param, stmt) = match clause {
2964                CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt),
2965                CatchClause::Named(loc, ident, param, stmt) => {
2966                    (loc, Some(ident), Some(param), stmt)
2967                }
2968            };
2969
2970            let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| {
2971                write_chunk!(fmt, "catch")?;
2972                if let Some(ident) = ident.as_ref() {
2973                    fmt.write_postfix_comments_before(
2974                        param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()),
2975                    )?;
2976                    write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?;
2977                }
2978                if let Some(param) = param.as_mut() {
2979                    write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?;
2980                    fmt.surrounded(
2981                        SurroundingChunk::new("", Some(param.loc.start()), None),
2982                        SurroundingChunk::new(")", None, Some(stmt.loc().start())),
2983                        |fmt, _| param.visit(fmt),
2984                    )?;
2985                }
2986
2987                stmt.visit(fmt)?;
2988                Ok(())
2989            })?;
2990
2991            chunks.push(chunk);
2992        }
2993
2994        let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?;
2995        if !multiline {
2996            self.write_chunks_separated(&chunks, "", false)?;
2997            return Ok(())
2998        }
2999
3000        let mut chunks = chunks.iter_mut().peekable();
3001        let mut prev_multiline = false;
3002
3003        // write try chunk first
3004        if let Some(chunk) = chunks.next() {
3005            let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?;
3006            write!(self.buf(), "{chunk_str}")?;
3007            prev_multiline = chunk_str.contains('\n');
3008        }
3009
3010        while let Some(chunk) = chunks.next() {
3011            let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?;
3012            let multiline = chunk_str.contains('\n');
3013            self.indented_if(!multiline, 1, |fmt| {
3014                chunk.needs_space = Some(false);
3015                let on_same_line = prev_multiline && (multiline || chunks.peek().is_none());
3016                let prefix = if fmt.is_beginning_of_line() {
3017                    ""
3018                } else if on_same_line {
3019                    " "
3020                } else {
3021                    "\n"
3022                };
3023                let chunk_str = format!("{prefix}{chunk_str}");
3024                write!(fmt.buf(), "{chunk_str}")?;
3025                Ok(())
3026            })?;
3027            prev_multiline = multiline;
3028        }
3029        Ok(())
3030    }
3031
3032    #[instrument(name = "if", skip_all)]
3033    fn visit_if(
3034        &mut self,
3035        loc: Loc,
3036        cond: &mut Expression,
3037        if_branch: &mut Box<Statement>,
3038        else_branch: &mut Option<Box<Statement>>,
3039        is_first_stmt: bool,
3040    ) -> Result<(), Self::Error> {
3041        return_source_if_disabled!(self, loc);
3042
3043        if !is_first_stmt {
3044            self.write_if_stmt(loc, cond, if_branch, else_branch)?;
3045            return Ok(())
3046        }
3047
3048        self.context.if_stmt_single_line = Some(true);
3049        let mut stmt_fits_on_single = false;
3050        let tx = self.transact(|fmt| {
3051            stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) {
3052                Ok(()) => true,
3053                Err(FormatterError::Fmt(_)) => false,
3054                Err(err) => bail!(err),
3055            };
3056            Ok(())
3057        })?;
3058
3059        if stmt_fits_on_single {
3060            tx.commit()?;
3061        } else {
3062            self.context.if_stmt_single_line = Some(false);
3063            self.write_if_stmt(loc, cond, if_branch, else_branch)?;
3064        }
3065        self.context.if_stmt_single_line = None;
3066
3067        Ok(())
3068    }
3069
3070    #[instrument(name = "do_while", skip_all)]
3071    fn visit_do_while(
3072        &mut self,
3073        loc: Loc,
3074        body: &mut Statement,
3075        cond: &mut Expression,
3076    ) -> Result<(), Self::Error> {
3077        return_source_if_disabled!(self, loc, ';');
3078        write_chunk!(self, loc.start(), "do ")?;
3079        self.visit_stmt_as_block(body, false)?;
3080        visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), {
3081            self.surrounded(
3082                SurroundingChunk::new("while (", Some(cond.loc().start()), None),
3083                SurroundingChunk::new(");", None, Some(loc.end())),
3084                |fmt, _| cond.visit(fmt),
3085            )?;
3086        });
3087        Ok(())
3088    }
3089
3090    #[instrument(name = "while", skip_all)]
3091    fn visit_while(
3092        &mut self,
3093        loc: Loc,
3094        cond: &mut Expression,
3095        body: &mut Statement,
3096    ) -> Result<(), Self::Error> {
3097        return_source_if_disabled!(self, loc);
3098        self.surrounded(
3099            SurroundingChunk::new("while (", Some(loc.start()), None),
3100            SurroundingChunk::new(")", None, Some(cond.loc().end())),
3101            |fmt, _| {
3102                cond.visit(fmt)?;
3103                fmt.write_postfix_comments_before(body.loc().start())
3104            },
3105        )?;
3106
3107        let cond_close_paren_loc =
3108            self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end());
3109        let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc);
3110        self.visit_stmt_as_block(body, attempt_single_line)?;
3111        Ok(())
3112    }
3113
3114    #[instrument(name = "for", skip_all)]
3115    fn visit_for(
3116        &mut self,
3117        loc: Loc,
3118        init: &mut Option<Box<Statement>>,
3119        cond: &mut Option<Box<Expression>>,
3120        update: &mut Option<Box<Expression>>,
3121        body: &mut Option<Box<Statement>>,
3122    ) -> Result<(), Self::Error> {
3123        return_source_if_disabled!(self, loc);
3124
3125        let next_byte_end = update.as_ref().map(|u| u.loc().end());
3126        self.surrounded(
3127            SurroundingChunk::new("for (", Some(loc.start()), None),
3128            SurroundingChunk::new(")", None, next_byte_end),
3129            |fmt, _| {
3130                let mut write_for_loop_header = |fmt: &mut Self, multiline: bool| -> Result<()> {
3131                    match init {
3132                        Some(stmt) => stmt.visit(fmt),
3133                        None => fmt.write_semicolon(),
3134                    }?;
3135                    if multiline {
3136                        fmt.write_whitespace_separator(true)?;
3137                    }
3138
3139                    cond.visit(fmt)?;
3140                    fmt.write_semicolon()?;
3141                    if multiline {
3142                        fmt.write_whitespace_separator(true)?;
3143                    }
3144
3145                    match update {
3146                        Some(expr) => expr.visit(fmt),
3147                        None => Ok(()),
3148                    }
3149                };
3150                let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?;
3151                if multiline {
3152                    write_for_loop_header(fmt, true)?;
3153                }
3154                Ok(())
3155            },
3156        )?;
3157        match body {
3158            Some(body) => {
3159                self.visit_stmt_as_block(body, false)?;
3160            }
3161            None => {
3162                self.write_empty_brackets()?;
3163            }
3164        };
3165        Ok(())
3166    }
3167
3168    #[instrument(name = "function", skip_all)]
3169    fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> {
3170        if func.body.is_some() {
3171            return_source_if_disabled!(self, func.loc());
3172        } else {
3173            return_source_if_disabled!(self, func.loc(), ';');
3174        }
3175
3176        self.with_function_context(func.clone(), |fmt| {
3177            fmt.write_postfix_comments_before(func.loc.start())?;
3178            fmt.write_prefix_comments_before(func.loc.start())?;
3179
3180            let body_loc = func.body.as_ref().map(CodeLocation::loc);
3181            let mut attrs_multiline = false;
3182            let fits_on_single = fmt.try_on_single_line(|fmt| {
3183                fmt.write_function_header(func, body_loc, false)?;
3184                Ok(())
3185            })?;
3186            if !fits_on_single {
3187                attrs_multiline = fmt.write_function_header(func, body_loc, true)?;
3188            }
3189
3190            // write function body
3191            match &mut func.body {
3192                Some(body) => {
3193                    let body_loc = body.loc();
3194                    // Handle case where block / statements starts on disabled line.
3195                    if fmt.inline_config.is_disabled(body_loc.with_end(body_loc.start())) {
3196                        match body {
3197                            Statement::Block { statements, .. } if !statements.is_empty() => {
3198                                fmt.write_whitespace_separator(false)?;
3199                                fmt.visit_block(body_loc, statements, false, false)?;
3200                                return Ok(())
3201                            }
3202                            _ => {
3203                                // Attrs should be written on same line if first line is disabled
3204                                // and there's no statement.
3205                                attrs_multiline = false
3206                            }
3207                        }
3208                    }
3209
3210                    let byte_offset = body_loc.start();
3211                    let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?;
3212                    fmt.write_whitespace_separator(
3213                        attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()),
3214                    )?;
3215                    fmt.write_chunk(&body)?;
3216                }
3217                None => fmt.write_semicolon()?,
3218            }
3219            Ok(())
3220        })?;
3221
3222        Ok(())
3223    }
3224
3225    #[instrument(name = "function_attribute", skip_all)]
3226    fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> {
3227        return_source_if_disabled!(self, attribute.loc());
3228
3229        match attribute {
3230            FunctionAttribute::Mutability(mutability) => {
3231                write_chunk!(self, mutability.loc().end(), "{mutability}")?
3232            }
3233            FunctionAttribute::Visibility(visibility) => {
3234                // Visibility will always have a location in a Function attribute
3235                write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")?
3236            }
3237            FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?,
3238            FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?,
3239            FunctionAttribute::Override(loc, args) => {
3240                write_chunk!(self, loc.start(), "override")?;
3241                if !args.is_empty() && self.config.override_spacing {
3242                    self.write_whitespace_separator(false)?;
3243                }
3244                self.visit_list("", args, None, Some(loc.end()), false)?
3245            }
3246            FunctionAttribute::BaseOrModifier(loc, base) => {
3247                // here we need to find out if this attribute belongs to the constructor because the
3248                // modifier need to include the trailing parenthesis
3249                // This is very ambiguous because the modifier can either by an inherited contract
3250                // or a modifier here: e.g.: This is valid constructor:
3251                // `constructor() public  Ownable() OnlyOwner {}`
3252                let is_constructor = self.context.is_constructor_function();
3253                // we can't make any decisions here regarding trailing `()` because we'd need to
3254                // find out if the `base` is a solidity modifier or an
3255                // interface/contract therefore we we its raw content.
3256
3257                // we can however check if the contract `is` the `base`, this however also does
3258                // not cover all cases
3259                let is_contract_base = self.context.contract.as_ref().is_some_and(|contract| {
3260                    contract.base.iter().any(|contract_base| {
3261                        contract_base
3262                            .name
3263                            .identifiers
3264                            .iter()
3265                            .zip(&base.name.identifiers)
3266                            .all(|(l, r)| l.name == r.name)
3267                    })
3268                });
3269
3270                if is_contract_base {
3271                    base.visit(self)?;
3272                } else if is_constructor {
3273                    // This is ambiguous because the modifier can either by an inherited
3274                    // contract modifiers with empty parenthesis are
3275                    // valid, but not required so we make the assumption
3276                    // here that modifiers are lowercase
3277                    let mut base_or_modifier =
3278                        self.visit_to_chunk(loc.start(), Some(loc.end()), base)?;
3279                    let is_lowercase =
3280                        base_or_modifier.content.chars().next().is_some_and(|c| c.is_lowercase());
3281                    if is_lowercase && base_or_modifier.content.ends_with("()") {
3282                        base_or_modifier.content.truncate(base_or_modifier.content.len() - 2);
3283                    }
3284
3285                    self.write_chunk(&base_or_modifier)?;
3286                } else {
3287                    let mut base_or_modifier =
3288                        self.visit_to_chunk(loc.start(), Some(loc.end()), base)?;
3289                    if base_or_modifier.content.ends_with("()") {
3290                        base_or_modifier.content.truncate(base_or_modifier.content.len() - 2);
3291                    }
3292                    self.write_chunk(&base_or_modifier)?;
3293                }
3294            }
3295            FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?,
3296        };
3297
3298        Ok(())
3299    }
3300
3301    #[instrument(name = "var_attribute", skip_all)]
3302    fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> {
3303        return_source_if_disabled!(self, attribute.loc());
3304
3305        let token = match attribute {
3306            VariableAttribute::Visibility(visibility) => Some(visibility.to_string()),
3307            VariableAttribute::Constant(_) => Some("constant".to_string()),
3308            VariableAttribute::Immutable(_) => Some("immutable".to_string()),
3309            VariableAttribute::Override(loc, idents) => {
3310                write_chunk!(self, loc.start(), "override")?;
3311                if !idents.is_empty() && self.config.override_spacing {
3312                    self.write_whitespace_separator(false)?;
3313                }
3314                self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?;
3315                None
3316            }
3317        };
3318        if let Some(token) = token {
3319            let loc = attribute.loc();
3320            write_chunk!(self, loc.start(), loc.end(), "{}", token)?;
3321        }
3322        Ok(())
3323    }
3324
3325    #[instrument(name = "base", skip_all)]
3326    fn visit_base(&mut self, base: &mut Base) -> Result<()> {
3327        return_source_if_disabled!(self, base.loc);
3328
3329        let name_loc = &base.name.loc;
3330        let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| {
3331            fmt.visit_ident_path(&mut base.name)?;
3332            Ok(())
3333        })?;
3334
3335        if base.args.is_none() || base.args.as_ref().unwrap().is_empty() {
3336            // This is ambiguous because the modifier can either by an inherited contract or a
3337            // modifier
3338            if self.context.function.is_some() {
3339                name.content.push_str("()");
3340            }
3341            self.write_chunk(&name)?;
3342            return Ok(())
3343        }
3344
3345        let args = base.args.as_mut().unwrap();
3346        let args_start = CodeLocation::loc(args.first().unwrap()).start();
3347
3348        name.content.push('(');
3349        let formatted_name = self.chunk_to_string(&name)?;
3350
3351        let multiline = !self.will_it_fit(&formatted_name);
3352
3353        self.surrounded(
3354            SurroundingChunk::new(&formatted_name, Some(args_start), None),
3355            SurroundingChunk::new(")", None, Some(base.loc.end())),
3356            |fmt, multiline_hint| {
3357                let args = fmt.items_to_chunks(
3358                    Some(base.loc.end()),
3359                    args.iter_mut().map(|arg| (arg.loc(), arg)),
3360                )?;
3361                let multiline = multiline ||
3362                    multiline_hint ||
3363                    fmt.are_chunks_separated_multiline("{}", &args, ",")?;
3364                fmt.write_chunks_separated(&args, ",", multiline)?;
3365                Ok(())
3366            },
3367        )?;
3368
3369        Ok(())
3370    }
3371
3372    #[instrument(name = "parameter", skip_all)]
3373    fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> {
3374        return_source_if_disabled!(self, parameter.loc);
3375        self.grouped(|fmt| {
3376            parameter.ty.visit(fmt)?;
3377            if let Some(storage) = &parameter.storage {
3378                write_chunk!(fmt, storage.loc().end(), "{storage}")?;
3379            }
3380            if let Some(name) = &parameter.name {
3381                write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?;
3382            }
3383            Ok(())
3384        })?;
3385        Ok(())
3386    }
3387
3388    #[instrument(name = "struct", skip_all)]
3389    fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> {
3390        return_source_if_disabled!(self, structure.loc);
3391        self.grouped(|fmt| {
3392            let struct_name = structure.name.safe_unwrap_mut();
3393            write_chunk!(fmt, struct_name.loc.start(), "struct")?;
3394            struct_name.visit(fmt)?;
3395            if structure.fields.is_empty() {
3396                return fmt.write_empty_brackets()
3397            }
3398
3399            write!(fmt.buf(), " {{")?;
3400            fmt.surrounded(
3401                SurroundingChunk::new("", Some(struct_name.loc.end()), None),
3402                SurroundingChunk::new("}", None, Some(structure.loc.end())),
3403                |fmt, _multiline| {
3404                    let chunks = fmt.items_to_chunks(
3405                        Some(structure.loc.end()),
3406                        structure.fields.iter_mut().map(|ident| (ident.loc, ident)),
3407                    )?;
3408                    for mut chunk in chunks {
3409                        chunk.content.push(';');
3410                        fmt.write_chunk(&chunk)?;
3411                        fmt.write_whitespace_separator(true)?;
3412                    }
3413                    Ok(())
3414                },
3415            )
3416        })?;
3417
3418        Ok(())
3419    }
3420
3421    #[instrument(name = "event", skip_all)]
3422    fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> {
3423        return_source_if_disabled!(self, event.loc, ';');
3424
3425        let event_name = event.name.safe_unwrap_mut();
3426        let mut name =
3427            self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?;
3428        name.content = format!("event {}(", name.content);
3429
3430        let last_chunk = if event.anonymous { ") anonymous;" } else { ");" };
3431        if event.fields.is_empty() {
3432            name.content.push_str(last_chunk);
3433            self.write_chunk(&name)?;
3434        } else {
3435            let byte_offset = event.fields.first().unwrap().loc.start();
3436            let first_chunk = self.chunk_to_string(&name)?;
3437            self.surrounded(
3438                SurroundingChunk::new(first_chunk, Some(byte_offset), None),
3439                SurroundingChunk::new(last_chunk, None, Some(event.loc.end())),
3440                |fmt, multiline| {
3441                    let params = fmt
3442                        .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?;
3443
3444                    let multiline =
3445                        multiline && fmt.are_chunks_separated_multiline("{}", &params, ",")?;
3446                    fmt.write_chunks_separated(&params, ",", multiline)
3447                },
3448            )?;
3449        }
3450
3451        Ok(())
3452    }
3453
3454    #[instrument(name = "event_parameter", skip_all)]
3455    fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> {
3456        return_source_if_disabled!(self, param.loc);
3457
3458        self.grouped(|fmt| {
3459            param.ty.visit(fmt)?;
3460            if param.indexed {
3461                write_chunk!(fmt, param.loc.start(), "indexed")?;
3462            }
3463            if let Some(name) = &param.name {
3464                write_chunk!(fmt, name.loc.end(), "{}", name.name)?;
3465            }
3466            Ok(())
3467        })?;
3468        Ok(())
3469    }
3470
3471    #[instrument(name = "error", skip_all)]
3472    fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> {
3473        return_source_if_disabled!(self, error.loc, ';');
3474
3475        let error_name = error.name.safe_unwrap_mut();
3476        let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?;
3477        name.content = format!("error {}", name.content);
3478
3479        let formatted_name = self.chunk_to_string(&name)?;
3480        write!(self.buf(), "{formatted_name}")?;
3481        let start_offset = error.fields.first().map(|f| f.loc.start());
3482        self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?;
3483        self.write_semicolon()?;
3484
3485        Ok(())
3486    }
3487
3488    #[instrument(name = "error_parameter", skip_all)]
3489    fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> {
3490        return_source_if_disabled!(self, param.loc);
3491        self.grouped(|fmt| {
3492            param.ty.visit(fmt)?;
3493            if let Some(name) = &param.name {
3494                write_chunk!(fmt, name.loc.end(), "{}", name.name)?;
3495            }
3496            Ok(())
3497        })?;
3498        Ok(())
3499    }
3500
3501    #[instrument(name = "type_definition", skip_all)]
3502    fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> {
3503        return_source_if_disabled!(self, def.loc, ';');
3504        self.grouped(|fmt| {
3505            write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?;
3506            def.name.visit(fmt)?;
3507            write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?;
3508            def.ty.visit(fmt)?;
3509            fmt.write_semicolon()?;
3510            Ok(())
3511        })?;
3512        Ok(())
3513    }
3514
3515    #[instrument(name = "stray_semicolon", skip_all)]
3516    fn visit_stray_semicolon(&mut self) -> Result<()> {
3517        self.write_semicolon()
3518    }
3519
3520    #[instrument(name = "opening_paren", skip_all)]
3521    fn visit_opening_paren(&mut self) -> Result<()> {
3522        write_chunk!(self, "(")?;
3523        Ok(())
3524    }
3525
3526    #[instrument(name = "closing_paren", skip_all)]
3527    fn visit_closing_paren(&mut self) -> Result<()> {
3528        write_chunk!(self, ")")?;
3529        Ok(())
3530    }
3531
3532    #[instrument(name = "newline", skip_all)]
3533    fn visit_newline(&mut self) -> Result<()> {
3534        writeln_chunk!(self)?;
3535        Ok(())
3536    }
3537
3538    #[instrument(name = "using", skip_all)]
3539    fn visit_using(&mut self, using: &mut Using) -> Result<()> {
3540        return_source_if_disabled!(self, using.loc, ';');
3541
3542        write_chunk!(self, using.loc.start(), "using")?;
3543
3544        let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start());
3545        let global_start = using.global.as_mut().map(|global| global.loc.start());
3546        let loc_end = using.loc.end();
3547
3548        let (is_library, mut list_chunks) = match &mut using.list {
3549            UsingList::Library(library) => {
3550                (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?])
3551            }
3552            UsingList::Functions(funcs) => {
3553                let mut funcs = funcs.iter_mut().peekable();
3554                let mut chunks = Vec::new();
3555                while let Some(func) = funcs.next() {
3556                    let next_byte_end = funcs.peek().map(|func| func.loc.start());
3557                    chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| {
3558                        fmt.visit_ident_path(&mut func.path)?;
3559                        if let Some(op) = func.oper {
3560                            write!(fmt.buf(), " as {op}")?;
3561                        }
3562                        Ok(())
3563                    })?);
3564                }
3565                (false, chunks)
3566            }
3567            UsingList::Error => return self.visit_parser_error(using.loc),
3568        };
3569
3570        let for_chunk = self.chunk_at(
3571            using.loc.start(),
3572            Some(ty_start.or(global_start).unwrap_or(loc_end)),
3573            None,
3574            "for",
3575        );
3576        let ty_chunk = if let Some(ty) = &mut using.ty {
3577            self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)?
3578        } else {
3579            self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*")
3580        };
3581        let global_chunk = using
3582            .global
3583            .as_mut()
3584            .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global))
3585            .transpose()?;
3586
3587        let write_for_def = |fmt: &mut Self| {
3588            fmt.grouped(|fmt| {
3589                fmt.write_chunk(&for_chunk)?;
3590                fmt.write_chunk(&ty_chunk)?;
3591                if let Some(global_chunk) = global_chunk.as_ref() {
3592                    fmt.write_chunk(global_chunk)?;
3593                }
3594                Ok(())
3595            })?;
3596            Ok(())
3597        };
3598
3599        let simulated_for_def = self.simulate_to_string(write_for_def)?;
3600
3601        if is_library {
3602            let chunk = list_chunks.pop().unwrap();
3603            if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? {
3604                self.write_chunk(&chunk)?;
3605                write_for_def(self)?;
3606            } else {
3607                self.write_whitespace_separator(true)?;
3608                self.grouped(|fmt| {
3609                    fmt.write_chunk(&chunk)?;
3610                    Ok(())
3611                })?;
3612                self.write_whitespace_separator(true)?;
3613                write_for_def(self)?;
3614            }
3615        } else {
3616            self.surrounded(
3617                SurroundingChunk::new("{", Some(using.loc.start()), None),
3618                SurroundingChunk::new(
3619                    "}",
3620                    None,
3621                    Some(ty_start.or(global_start).unwrap_or(loc_end)),
3622                ),
3623                |fmt, _multiline| {
3624                    let multiline = fmt.are_chunks_separated_multiline(
3625                        &format!("{{ {{}} }} {simulated_for_def};"),
3626                        &list_chunks,
3627                        ",",
3628                    )?;
3629                    fmt.write_chunks_separated(&list_chunks, ",", multiline)?;
3630                    Ok(())
3631                },
3632            )?;
3633            write_for_def(self)?;
3634        }
3635
3636        self.write_semicolon()?;
3637
3638        Ok(())
3639    }
3640
3641    #[instrument(name = "yul_block", skip_all)]
3642    fn visit_yul_block(
3643        &mut self,
3644        loc: Loc,
3645        statements: &mut Vec<YulStatement>,
3646        attempt_single_line: bool,
3647    ) -> Result<(), Self::Error> {
3648        return_source_if_disabled!(self, loc);
3649        self.visit_block(loc, statements, attempt_single_line, false)?;
3650        Ok(())
3651    }
3652
3653    #[instrument(name = "yul_expr", skip_all)]
3654    fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> {
3655        return_source_if_disabled!(self, expr.loc());
3656
3657        match expr {
3658            YulExpression::BoolLiteral(loc, val, ident) => {
3659                let val = if *val { "true" } else { "false" };
3660                self.visit_yul_string_with_ident(*loc, val, ident)
3661            }
3662            YulExpression::FunctionCall(expr) => self.visit_yul_function_call(expr),
3663            YulExpression::HexNumberLiteral(loc, val, ident) => {
3664                self.visit_yul_string_with_ident(*loc, val, ident)
3665            }
3666            YulExpression::HexStringLiteral(val, ident) => self.visit_yul_string_with_ident(
3667                val.loc,
3668                &self.quote_str(val.loc, Some("hex"), &val.hex),
3669                ident,
3670            ),
3671            YulExpression::NumberLiteral(loc, val, expr, ident) => {
3672                let val = if expr.is_empty() { val.to_owned() } else { format!("{val}e{expr}") };
3673                self.visit_yul_string_with_ident(*loc, &val, ident)
3674            }
3675            YulExpression::StringLiteral(val, ident) => self.visit_yul_string_with_ident(
3676                val.loc,
3677                &self.quote_str(val.loc, None, &val.string),
3678                ident,
3679            ),
3680            YulExpression::SuffixAccess(_, expr, ident) => {
3681                self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() {
3682                    YulExpression::SuffixAccess(_, inner_expr, inner_ident) => {
3683                        Ok(Some((inner_expr, inner_ident)))
3684                    }
3685                    expr => {
3686                        expr.visit(fmt)?;
3687                        Ok(None)
3688                    }
3689                })
3690            }
3691            YulExpression::Variable(ident) => {
3692                write_chunk!(self, ident.loc.start(), ident.loc.end(), "{}", ident.name)
3693            }
3694        }
3695    }
3696
3697    #[instrument(name = "yul_assignment", skip_all)]
3698    fn visit_yul_assignment<T>(
3699        &mut self,
3700        loc: Loc,
3701        exprs: &mut Vec<T>,
3702        expr: &mut Option<&mut YulExpression>,
3703    ) -> Result<(), Self::Error>
3704    where
3705        T: Visitable + CodeLocation,
3706    {
3707        return_source_if_disabled!(self, loc);
3708
3709        self.grouped(|fmt| {
3710            let chunks =
3711                fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?;
3712
3713            let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?;
3714            fmt.write_chunks_separated(&chunks, ",", multiline)?;
3715
3716            if let Some(expr) = expr {
3717                write_chunk!(fmt, expr.loc().start(), ":=")?;
3718                let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?;
3719                if !fmt.will_chunk_fit("{}", &chunk)? {
3720                    fmt.write_whitespace_separator(true)?;
3721                }
3722                fmt.write_chunk(&chunk)?;
3723            }
3724            Ok(())
3725        })?;
3726        Ok(())
3727    }
3728
3729    #[instrument(name = "yul_for", skip_all)]
3730    fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> {
3731        return_source_if_disabled!(self, stmt.loc);
3732        write_chunk!(self, stmt.loc.start(), "for")?;
3733        self.visit_yul_block(stmt.init_block.loc, &mut stmt.init_block.statements, true)?;
3734        stmt.condition.visit(self)?;
3735        self.visit_yul_block(stmt.post_block.loc, &mut stmt.post_block.statements, true)?;
3736        self.visit_yul_block(stmt.execution_block.loc, &mut stmt.execution_block.statements, true)?;
3737        Ok(())
3738    }
3739
3740    #[instrument(name = "yul_function_call", skip_all)]
3741    fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> {
3742        return_source_if_disabled!(self, stmt.loc);
3743        write_chunk!(self, stmt.loc.start(), "{}", stmt.id.name)?;
3744        self.visit_list("", &mut stmt.arguments, None, Some(stmt.loc.end()), true)
3745    }
3746
3747    #[instrument(name = "yul_fun_def", skip_all)]
3748    fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> {
3749        return_source_if_disabled!(self, stmt.loc);
3750
3751        write_chunk!(self, stmt.loc.start(), "function {}", stmt.id.name)?;
3752
3753        self.visit_list("", &mut stmt.params, None, None, true)?;
3754
3755        if !stmt.returns.is_empty() {
3756            self.grouped(|fmt| {
3757                write_chunk!(fmt, "->")?;
3758
3759                let chunks = fmt.items_to_chunks(
3760                    Some(stmt.body.loc.start()),
3761                    stmt.returns.iter_mut().map(|param| (param.loc, param)),
3762                )?;
3763                let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, ",")?;
3764                fmt.write_chunks_separated(&chunks, ",", multiline)?;
3765                if multiline {
3766                    fmt.write_whitespace_separator(true)?;
3767                }
3768                Ok(())
3769            })?;
3770        }
3771
3772        stmt.body.visit(self)?;
3773
3774        Ok(())
3775    }
3776
3777    #[instrument(name = "yul_if", skip_all)]
3778    fn visit_yul_if(
3779        &mut self,
3780        loc: Loc,
3781        expr: &mut YulExpression,
3782        block: &mut YulBlock,
3783    ) -> Result<(), Self::Error> {
3784        return_source_if_disabled!(self, loc);
3785        write_chunk!(self, loc.start(), "if")?;
3786        expr.visit(self)?;
3787        self.visit_yul_block(block.loc, &mut block.statements, true)
3788    }
3789
3790    #[instrument(name = "yul_leave", skip_all)]
3791    fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> {
3792        return_source_if_disabled!(self, loc);
3793        write_chunk!(self, loc.start(), loc.end(), "leave")
3794    }
3795
3796    #[instrument(name = "yul_switch", skip_all)]
3797    fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> {
3798        return_source_if_disabled!(self, stmt.loc);
3799
3800        write_chunk!(self, stmt.loc.start(), "switch")?;
3801        stmt.condition.visit(self)?;
3802        writeln_chunk!(self)?;
3803        let mut cases = stmt.cases.iter_mut().peekable();
3804        while let Some(YulSwitchOptions::Case(loc, expr, block)) = cases.next() {
3805            write_chunk!(self, loc.start(), "case")?;
3806            expr.visit(self)?;
3807            self.visit_yul_block(block.loc, &mut block.statements, true)?;
3808            let is_last = cases.peek().is_none();
3809            if !is_last || stmt.default.is_some() {
3810                writeln_chunk!(self)?;
3811            }
3812        }
3813        if let Some(YulSwitchOptions::Default(loc, ref mut block)) = stmt.default {
3814            write_chunk!(self, loc.start(), "default")?;
3815            self.visit_yul_block(block.loc, &mut block.statements, true)?;
3816        }
3817        Ok(())
3818    }
3819
3820    #[instrument(name = "yul_var_declaration", skip_all)]
3821    fn visit_yul_var_declaration(
3822        &mut self,
3823        loc: Loc,
3824        idents: &mut Vec<YulTypedIdentifier>,
3825        expr: &mut Option<YulExpression>,
3826    ) -> Result<(), Self::Error> {
3827        return_source_if_disabled!(self, loc);
3828        self.grouped(|fmt| {
3829            write_chunk!(fmt, loc.start(), "let")?;
3830            fmt.visit_yul_assignment(loc, idents, &mut expr.as_mut())
3831        })?;
3832        Ok(())
3833    }
3834
3835    #[instrument(name = "yul_typed_ident", skip_all)]
3836    fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> {
3837        return_source_if_disabled!(self, ident.loc);
3838        self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty)
3839    }
3840
3841    #[instrument(name = "parser_error", skip_all)]
3842    fn visit_parser_error(&mut self, loc: Loc) -> Result<()> {
3843        Err(FormatterError::InvalidParsedItem(loc))
3844    }
3845}
3846
3847/// An action which may be committed to a Formatter
3848struct Transaction<'f, 'a, W> {
3849    fmt: &'f mut Formatter<'a, W>,
3850    buffer: String,
3851    comments: Comments,
3852}
3853
3854impl<'a, W> std::ops::Deref for Transaction<'_, 'a, W> {
3855    type Target = Formatter<'a, W>;
3856    fn deref(&self) -> &Self::Target {
3857        self.fmt
3858    }
3859}
3860
3861impl<W> std::ops::DerefMut for Transaction<'_, '_, W> {
3862    fn deref_mut(&mut self) -> &mut Self::Target {
3863        self.fmt
3864    }
3865}
3866
3867impl<'f, 'a, W: Write> Transaction<'f, 'a, W> {
3868    /// Create a new transaction from a callback
3869    fn new(
3870        fmt: &'f mut Formatter<'a, W>,
3871        fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>,
3872    ) -> Result<Self> {
3873        let mut comments = fmt.comments.clone();
3874        let buffer = fmt.with_temp_buf(fun)?.w;
3875        comments = std::mem::replace(&mut fmt.comments, comments);
3876        Ok(Self { fmt, buffer, comments })
3877    }
3878
3879    /// Commit the transaction to the Formatter
3880    fn commit(self) -> Result<String> {
3881        self.fmt.comments = self.comments;
3882        write_chunk!(self.fmt, "{}", self.buffer)?;
3883        Ok(self.buffer)
3884    }
3885}