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        *(&raw const self.buf.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            if let Some(layout) = &mut contract.layout {
2009                write_chunk!(fmt, "layout at ")?;
2010                fmt.visit_expr(layout.loc(), layout)?;
2011                write_chunk!(fmt, " ")?;
2012            }
2013
2014            write_chunk!(fmt, "{{")?;
2015
2016            fmt.indented(1, |fmt| {
2017                if let Some(first) = contract.parts.first() {
2018                    fmt.write_postfix_comments_before(first.loc().start())?;
2019                    fmt.write_whitespace_separator(true)?;
2020                } else {
2021                    return Ok(())
2022                }
2023
2024                if fmt.config.contract_new_lines {
2025                    write_chunk!(fmt, "\n")?;
2026                }
2027
2028                fmt.write_lined_visitable(
2029                    contract.loc,
2030                    contract.parts.iter_mut(),
2031                    |last_part, part| match last_part {
2032                        ContractPart::ErrorDefinition(_) => {
2033                            !matches!(part, ContractPart::ErrorDefinition(_))
2034                        }
2035                        ContractPart::EventDefinition(_) => {
2036                            !matches!(part, ContractPart::EventDefinition(_))
2037                        }
2038                        ContractPart::VariableDefinition(_) => {
2039                            !matches!(part, ContractPart::VariableDefinition(_))
2040                        }
2041                        ContractPart::TypeDefinition(_) => {
2042                            !matches!(part, ContractPart::TypeDefinition(_))
2043                        }
2044                        ContractPart::EnumDefinition(_) => {
2045                            !matches!(part, ContractPart::EnumDefinition(_))
2046                        }
2047                        ContractPart::Using(_) => !matches!(part, ContractPart::Using(_)),
2048                        ContractPart::FunctionDefinition(last_def) => {
2049                            if last_def.is_empty() {
2050                                match part {
2051                                    ContractPart::FunctionDefinition(def) => !def.is_empty(),
2052                                    _ => true,
2053                                }
2054                            } else {
2055                                true
2056                            }
2057                        }
2058                        ContractPart::Annotation(_) => false,
2059                        _ => true,
2060                    },
2061                )
2062            })?;
2063
2064            if !contract.parts.is_empty() {
2065                fmt.write_whitespace_separator(true)?;
2066
2067                if fmt.config.contract_new_lines {
2068                    write_chunk!(fmt, "\n")?;
2069                }
2070            }
2071
2072            write_chunk!(fmt, contract.loc.end(), "}}")?;
2073
2074            Ok(())
2075        })?;
2076
2077        Ok(())
2078    }
2079
2080    // Support extension for Solana/Substrate
2081    #[instrument(name = "annotation", skip_all)]
2082    fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> {
2083        return_source_if_disabled!(self, annotation.loc);
2084        let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?;
2085        write!(self.buf(), "@{id}")?;
2086        write!(self.buf(), "(")?;
2087        annotation.value.visit(self)?;
2088        write!(self.buf(), ")")?;
2089        Ok(())
2090    }
2091
2092    fn visit_pragma(
2093        &mut self,
2094        pragma: &mut PragmaDirective,
2095    ) -> std::result::Result<(), Self::Error> {
2096        let loc = pragma.loc();
2097        return_source_if_disabled!(self, loc, ';');
2098
2099        match pragma {
2100            PragmaDirective::Identifier(loc, id1, id2) => {
2101                write_chunk!(
2102                    self,
2103                    loc.start(),
2104                    loc.end(),
2105                    "pragma {}{}{};",
2106                    id1.as_ref().map(|id| id.name.to_string()).unwrap_or_default(),
2107                    if id1.is_some() && id2.is_some() { " " } else { "" },
2108                    id2.as_ref().map(|id| id.name.to_string()).unwrap_or_default(),
2109                )?;
2110            }
2111            PragmaDirective::StringLiteral(_loc, id, lit) => {
2112                write_chunk!(self, "pragma {} ", id.name)?;
2113                let StringLiteral { loc, string, .. } = lit;
2114                write_chunk!(self, loc.start(), loc.end(), "\"{string}\";")?;
2115            }
2116            PragmaDirective::Version(loc, id, version) => {
2117                write_chunk!(self, loc.start(), id.loc().end(), "pragma {}", id.name)?;
2118                let version_loc = loc.with_start(version[0].loc().start());
2119                self.visit_source(version_loc)?;
2120                self.write_semicolon()?;
2121            }
2122        }
2123        Ok(())
2124    }
2125
2126    #[instrument(name = "import_plain", skip_all)]
2127    fn visit_import_plain(&mut self, loc: Loc, import: &mut ImportPath) -> Result<()> {
2128        return_source_if_disabled!(self, loc, ';');
2129
2130        self.grouped(|fmt| {
2131            write_chunk!(fmt, loc.start(), import.loc().start(), "import")?;
2132            fmt.write_quoted_str(import.loc(), None, &import_path_string(import))?;
2133            fmt.write_semicolon()?;
2134            Ok(())
2135        })?;
2136        Ok(())
2137    }
2138
2139    #[instrument(name = "import_global", skip_all)]
2140    fn visit_import_global(
2141        &mut self,
2142        loc: Loc,
2143        global: &mut ImportPath,
2144        alias: &mut Identifier,
2145    ) -> Result<()> {
2146        return_source_if_disabled!(self, loc, ';');
2147
2148        self.grouped(|fmt| {
2149            write_chunk!(fmt, loc.start(), global.loc().start(), "import")?;
2150            fmt.write_quoted_str(global.loc(), None, &import_path_string(global))?;
2151            write_chunk!(fmt, loc.start(), alias.loc.start(), "as")?;
2152            alias.visit(fmt)?;
2153            fmt.write_semicolon()?;
2154            Ok(())
2155        })?;
2156        Ok(())
2157    }
2158
2159    #[instrument(name = "import_renames", skip_all)]
2160    fn visit_import_renames(
2161        &mut self,
2162        loc: Loc,
2163        imports: &mut [(Identifier, Option<Identifier>)],
2164        from: &mut ImportPath,
2165    ) -> Result<()> {
2166        return_source_if_disabled!(self, loc, ';');
2167
2168        if imports.is_empty() {
2169            self.grouped(|fmt| {
2170                write_chunk!(fmt, loc.start(), "import")?;
2171                fmt.write_empty_brackets()?;
2172                write_chunk!(fmt, loc.start(), from.loc().start(), "from")?;
2173                fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?;
2174                fmt.write_semicolon()?;
2175                Ok(())
2176            })?;
2177            return Ok(())
2178        }
2179
2180        let imports_start = imports.first().unwrap().0.loc.start();
2181
2182        write_chunk!(self, loc.start(), imports_start, "import")?;
2183
2184        self.surrounded(
2185            SurroundingChunk::new("{", Some(imports_start), None),
2186            SurroundingChunk::new("}", None, Some(from.loc().start())),
2187            |fmt, _multiline| {
2188                let mut imports = imports.iter_mut().peekable();
2189                let mut import_chunks = Vec::new();
2190                while let Some((ident, alias)) = imports.next() {
2191                    import_chunks.push(fmt.chunked(
2192                        ident.loc.start(),
2193                        imports.peek().map(|(ident, _)| ident.loc.start()),
2194                        |fmt| {
2195                            fmt.grouped(|fmt| {
2196                                ident.visit(fmt)?;
2197                                if let Some(alias) = alias {
2198                                    write_chunk!(fmt, ident.loc.end(), alias.loc.start(), "as")?;
2199                                    alias.visit(fmt)?;
2200                                }
2201                                Ok(())
2202                            })?;
2203                            Ok(())
2204                        },
2205                    )?);
2206                }
2207
2208                let multiline = fmt.are_chunks_separated_multiline(
2209                    &format!("{{}} }} from \"{}\";", import_path_string(from)),
2210                    &import_chunks,
2211                    ",",
2212                )?;
2213                fmt.write_chunks_separated(&import_chunks, ",", multiline)?;
2214                Ok(())
2215            },
2216        )?;
2217
2218        self.grouped(|fmt| {
2219            write_chunk!(fmt, imports_start, from.loc().start(), "from")?;
2220            fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?;
2221            fmt.write_semicolon()?;
2222            Ok(())
2223        })?;
2224
2225        Ok(())
2226    }
2227
2228    #[instrument(name = "enum", skip_all)]
2229    fn visit_enum(&mut self, enumeration: &mut EnumDefinition) -> Result<()> {
2230        return_source_if_disabled!(self, enumeration.loc);
2231
2232        let enum_name = enumeration.name.safe_unwrap_mut();
2233        let mut name =
2234            self.visit_to_chunk(enum_name.loc.start(), Some(enum_name.loc.end()), enum_name)?;
2235        name.content = format!("enum {} ", name.content);
2236        if enumeration.values.is_empty() {
2237            self.write_chunk(&name)?;
2238            self.write_empty_brackets()?;
2239        } else {
2240            name.content.push('{');
2241            self.write_chunk(&name)?;
2242
2243            self.indented(1, |fmt| {
2244                let values = fmt.items_to_chunks(
2245                    Some(enumeration.loc.end()),
2246                    enumeration.values.iter_mut().map(|ident| {
2247                        let ident = ident.safe_unwrap_mut();
2248                        (ident.loc, ident)
2249                    }),
2250                )?;
2251                fmt.write_chunks_separated(&values, ",", true)?;
2252                writeln!(fmt.buf())?;
2253                Ok(())
2254            })?;
2255            write_chunk!(self, "}}")?;
2256        }
2257
2258        Ok(())
2259    }
2260
2261    #[instrument(name = "assembly", skip_all)]
2262    fn visit_assembly(
2263        &mut self,
2264        loc: Loc,
2265        dialect: &mut Option<StringLiteral>,
2266        block: &mut YulBlock,
2267        flags: &mut Option<Vec<StringLiteral>>,
2268    ) -> Result<(), Self::Error> {
2269        return_source_if_disabled!(self, loc);
2270
2271        write_chunk!(self, loc.start(), "assembly")?;
2272        if let Some(StringLiteral { loc, string, .. }) = dialect {
2273            write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?;
2274        }
2275        if let Some(flags) = flags {
2276            if !flags.is_empty() {
2277                let loc_start = flags.first().unwrap().loc.start();
2278                self.surrounded(
2279                    SurroundingChunk::new("(", Some(loc_start), None),
2280                    SurroundingChunk::new(")", None, Some(block.loc.start())),
2281                    |fmt, _| {
2282                        let mut flags = flags.iter_mut().peekable();
2283                        let mut chunks = vec![];
2284                        while let Some(flag) = flags.next() {
2285                            let next_byte_offset =
2286                                flags.peek().map(|next_flag| next_flag.loc.start());
2287                            chunks.push(fmt.chunked(
2288                                flag.loc.start(),
2289                                next_byte_offset,
2290                                |fmt| {
2291                                    write!(fmt.buf(), "\"{}\"", flag.string)?;
2292                                    Ok(())
2293                                },
2294                            )?);
2295                        }
2296                        fmt.write_chunks_separated(&chunks, ",", false)?;
2297                        Ok(())
2298                    },
2299                )?;
2300            }
2301        }
2302
2303        block.visit(self)
2304    }
2305
2306    #[instrument(name = "block", skip_all)]
2307    fn visit_block(
2308        &mut self,
2309        loc: Loc,
2310        unchecked: bool,
2311        statements: &mut Vec<Statement>,
2312    ) -> Result<()> {
2313        return_source_if_disabled!(self, loc);
2314        if unchecked {
2315            write_chunk!(self, loc.start(), "unchecked ")?;
2316        }
2317
2318        self.visit_block(loc, statements, false, false)?;
2319        Ok(())
2320    }
2321
2322    #[instrument(name = "args", skip_all)]
2323    fn visit_args(&mut self, loc: Loc, args: &mut Vec<NamedArgument>) -> Result<(), Self::Error> {
2324        return_source_if_disabled!(self, loc);
2325
2326        write!(self.buf(), "{{")?;
2327
2328        let mut args_iter = args.iter_mut().peekable();
2329        let mut chunks = Vec::new();
2330        while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() {
2331            let next_byte_offset = args_iter
2332                .peek()
2333                .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start())
2334                .unwrap_or_else(|| loc.end());
2335            chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| {
2336                fmt.grouped(|fmt| {
2337                    write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?;
2338                    expr.visit(fmt)
2339                })?;
2340                Ok(())
2341            })?);
2342        }
2343
2344        if let Some(first) = chunks.first_mut() {
2345            if first.prefixes.is_empty() &&
2346                first.postfixes_before.is_empty() &&
2347                !self.config.bracket_spacing
2348            {
2349                first.needs_space = Some(false);
2350            }
2351        }
2352        let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?;
2353        self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?;
2354
2355        let prefix = if multiline && !self.is_beginning_of_line() {
2356            "\n"
2357        } else if self.config.bracket_spacing {
2358            " "
2359        } else {
2360            ""
2361        };
2362        let closing_bracket = format!("{prefix}{}", "}");
2363        if let Some(arg) = args.last() {
2364            write_chunk!(self, arg.loc.end(), "{closing_bracket}")?;
2365        } else {
2366            write_chunk!(self, "{closing_bracket}")?;
2367        }
2368
2369        Ok(())
2370    }
2371
2372    #[instrument(name = "expr", skip_all)]
2373    fn visit_expr(&mut self, loc: Loc, expr: &mut Expression) -> Result<()> {
2374        return_source_if_disabled!(self, loc);
2375
2376        match expr {
2377            Expression::Type(loc, typ) => match typ {
2378                Type::Address => write_chunk!(self, loc.start(), "address")?,
2379                Type::AddressPayable => write_chunk!(self, loc.start(), "address payable")?,
2380                Type::Payable => write_chunk!(self, loc.start(), "payable")?,
2381                Type::Bool => write_chunk!(self, loc.start(), "bool")?,
2382                Type::String => write_chunk!(self, loc.start(), "string")?,
2383                Type::Bytes(n) => write_chunk!(self, loc.start(), "bytes{}", n)?,
2384                Type::Rational => write_chunk!(self, loc.start(), "rational")?,
2385                Type::DynamicBytes => write_chunk!(self, loc.start(), "bytes")?,
2386                Type::Int(ref n) | Type::Uint(ref n) => {
2387                    let int = if matches!(typ, Type::Int(_)) { "int" } else { "uint" };
2388                    match n {
2389                        256 => match self.config.int_types {
2390                            IntTypes::Long => write_chunk!(self, loc.start(), "{int}{n}")?,
2391                            IntTypes::Short => write_chunk!(self, loc.start(), "{int}")?,
2392                            IntTypes::Preserve => self.visit_source(*loc)?,
2393                        },
2394                        _ => write_chunk!(self, loc.start(), "{int}{n}")?,
2395                    }
2396                }
2397                Type::Mapping { loc, key, key_name, value, value_name } => {
2398                    let arrow_loc = self.find_next_str_in_src(loc.start(), "=>");
2399                    let close_paren_loc =
2400                        self.find_next_in_src(value.loc().end(), ')').unwrap_or(loc.end());
2401                    let first = SurroundingChunk::new(
2402                        "mapping(",
2403                        Some(loc.start()),
2404                        Some(key.loc().start()),
2405                    );
2406                    let last = SurroundingChunk::new(")", Some(close_paren_loc), Some(loc.end()))
2407                        .non_spaced();
2408                    self.surrounded(first, last, |fmt, multiline| {
2409                        fmt.grouped(|fmt| {
2410                            key.visit(fmt)?;
2411
2412                            if let Some(name) = key_name {
2413                                let end_loc = arrow_loc.unwrap_or(value.loc().start());
2414                                write_chunk!(fmt, name.loc.start(), end_loc, " {}", name)?;
2415                            } else if let Some(arrow_loc) = arrow_loc {
2416                                fmt.write_postfix_comments_before(arrow_loc)?;
2417                            }
2418
2419                            let mut write_arrow_and_value = |fmt: &mut Self| {
2420                                write!(fmt.buf(), "=> ")?;
2421                                value.visit(fmt)?;
2422                                if let Some(name) = value_name {
2423                                    write_chunk!(fmt, name.loc.start(), " {}", name)?;
2424                                }
2425                                Ok(())
2426                            };
2427
2428                            let rest_str = fmt.simulate_to_string(&mut write_arrow_and_value)?;
2429                            let multiline = multiline && !fmt.will_it_fit(rest_str);
2430                            fmt.write_whitespace_separator(multiline)?;
2431
2432                            write_arrow_and_value(fmt)?;
2433
2434                            fmt.write_postfix_comments_before(close_paren_loc)?;
2435                            fmt.write_prefix_comments_before(close_paren_loc)
2436                        })?;
2437                        Ok(())
2438                    })?;
2439                }
2440                Type::Function { .. } => self.visit_source(*loc)?,
2441            },
2442            Expression::BoolLiteral(loc, val) => {
2443                write_chunk!(self, loc.start(), loc.end(), "{val}")?;
2444            }
2445            Expression::NumberLiteral(loc, val, exp, unit) => {
2446                self.write_num_literal(*loc, val, None, exp, unit)?;
2447            }
2448            Expression::HexNumberLiteral(loc, val, unit) => {
2449                // ref: https://docs.soliditylang.org/en/latest/types.html?highlight=address%20literal#address-literals
2450                let val = if val.len() == 42 {
2451                    Address::from_str(val).expect("").to_string()
2452                } else {
2453                    val.to_owned()
2454                };
2455                write_chunk!(self, loc.start(), loc.end(), "{val}")?;
2456                self.write_unit(unit)?;
2457            }
2458            Expression::RationalNumberLiteral(loc, val, fraction, exp, unit) => {
2459                self.write_num_literal(*loc, val, Some(fraction), exp, unit)?;
2460            }
2461            Expression::StringLiteral(vals) => {
2462                for StringLiteral { loc, string, unicode } in vals {
2463                    let prefix = if *unicode { Some("unicode") } else { None };
2464                    self.write_quoted_str(*loc, prefix, string)?;
2465                }
2466            }
2467            Expression::HexLiteral(vals) => {
2468                for val in vals {
2469                    self.write_hex_literal(val)?;
2470                }
2471            }
2472            Expression::AddressLiteral(loc, val) => {
2473                // support of solana/substrate address literals
2474                self.write_quoted_str(*loc, Some("address"), val)?;
2475            }
2476            Expression::Parenthesis(loc, expr) => {
2477                self.surrounded(
2478                    SurroundingChunk::new("(", Some(loc.start()), None),
2479                    SurroundingChunk::new(")", None, Some(loc.end())),
2480                    |fmt, _| expr.visit(fmt),
2481                )?;
2482            }
2483            Expression::ArraySubscript(_, ty_exp, index_expr) => {
2484                ty_exp.visit(self)?;
2485                write!(self.buf(), "[")?;
2486                index_expr.as_mut().map(|index| index.visit(self)).transpose()?;
2487                write!(self.buf(), "]")?;
2488            }
2489            Expression::ArraySlice(loc, expr, start, end) => {
2490                expr.visit(self)?;
2491                write!(self.buf(), "[")?;
2492                let mut write_slice = |fmt: &mut Self, multiline| -> Result<()> {
2493                    if multiline {
2494                        fmt.write_whitespace_separator(true)?;
2495                    }
2496                    fmt.grouped(|fmt| {
2497                        start.as_mut().map(|start| start.visit(fmt)).transpose()?;
2498                        write!(fmt.buf(), ":")?;
2499                        if let Some(end) = end {
2500                            let mut chunk =
2501                                fmt.chunked(end.loc().start(), Some(loc.end()), |fmt| {
2502                                    end.visit(fmt)
2503                                })?;
2504                            if chunk.prefixes.is_empty() &&
2505                                chunk.postfixes_before.is_empty() &&
2506                                (start.is_none() || fmt.will_it_fit(&chunk.content))
2507                            {
2508                                chunk.needs_space = Some(false);
2509                            }
2510                            fmt.write_chunk(&chunk)?;
2511                        }
2512                        Ok(())
2513                    })?;
2514                    if multiline {
2515                        fmt.write_whitespace_separator(true)?;
2516                    }
2517                    Ok(())
2518                };
2519
2520                if !self.try_on_single_line(|fmt| write_slice(fmt, false))? {
2521                    self.indented(1, |fmt| write_slice(fmt, true))?;
2522                }
2523
2524                write!(self.buf(), "]")?;
2525            }
2526            Expression::ArrayLiteral(loc, exprs) => {
2527                write_chunk!(self, loc.start(), "[")?;
2528                let chunks = self.items_to_chunks(
2529                    Some(loc.end()),
2530                    exprs.iter_mut().map(|expr| (expr.loc(), expr)),
2531                )?;
2532                let multiline = self.are_chunks_separated_multiline("{}]", &chunks, ",")?;
2533                self.indented_if(multiline, 1, |fmt| {
2534                    fmt.write_chunks_separated(&chunks, ",", multiline)?;
2535                    if multiline {
2536                        fmt.write_postfix_comments_before(loc.end())?;
2537                        fmt.write_prefix_comments_before(loc.end())?;
2538                        fmt.write_whitespace_separator(true)?;
2539                    }
2540                    Ok(())
2541                })?;
2542                write_chunk!(self, loc.end(), "]")?;
2543            }
2544            Expression::PreIncrement(..) |
2545            Expression::PostIncrement(..) |
2546            Expression::PreDecrement(..) |
2547            Expression::PostDecrement(..) |
2548            Expression::Not(..) |
2549            Expression::UnaryPlus(..) |
2550            Expression::Add(..) |
2551            Expression::Negate(..) |
2552            Expression::Subtract(..) |
2553            Expression::Power(..) |
2554            Expression::Multiply(..) |
2555            Expression::Divide(..) |
2556            Expression::Modulo(..) |
2557            Expression::ShiftLeft(..) |
2558            Expression::ShiftRight(..) |
2559            Expression::BitwiseNot(..) |
2560            Expression::BitwiseAnd(..) |
2561            Expression::BitwiseXor(..) |
2562            Expression::BitwiseOr(..) |
2563            Expression::Less(..) |
2564            Expression::More(..) |
2565            Expression::LessEqual(..) |
2566            Expression::MoreEqual(..) |
2567            Expression::And(..) |
2568            Expression::Or(..) |
2569            Expression::Equal(..) |
2570            Expression::NotEqual(..) => {
2571                let spaced = expr.has_space_around();
2572                let op = expr.operator().unwrap();
2573
2574                match expr.components_mut() {
2575                    (Some(left), Some(right)) => {
2576                        left.visit(self)?;
2577
2578                        let right_chunk =
2579                            self.chunked(right.loc().start(), Some(loc.end()), |fmt| {
2580                                write_chunk!(fmt, right.loc().start(), "{op}")?;
2581                                right.visit(fmt)?;
2582                                Ok(())
2583                            })?;
2584
2585                        self.grouped(|fmt| fmt.write_chunk(&right_chunk))?;
2586                    }
2587                    (Some(left), None) => {
2588                        left.visit(self)?;
2589                        write_chunk_spaced!(self, loc.end(), Some(spaced), "{op}")?;
2590                    }
2591                    (None, Some(right)) => {
2592                        write_chunk!(self, right.loc().start(), "{op}")?;
2593                        let mut right_chunk =
2594                            self.visit_to_chunk(right.loc().end(), Some(loc.end()), right)?;
2595                        right_chunk.needs_space = Some(spaced);
2596                        self.write_chunk(&right_chunk)?;
2597                    }
2598                    (None, None) => {}
2599                }
2600            }
2601            Expression::Assign(..) |
2602            Expression::AssignOr(..) |
2603            Expression::AssignAnd(..) |
2604            Expression::AssignXor(..) |
2605            Expression::AssignShiftLeft(..) |
2606            Expression::AssignShiftRight(..) |
2607            Expression::AssignAdd(..) |
2608            Expression::AssignSubtract(..) |
2609            Expression::AssignMultiply(..) |
2610            Expression::AssignDivide(..) |
2611            Expression::AssignModulo(..) => {
2612                let op = expr.operator().unwrap();
2613                let (left, right) = expr.components_mut();
2614                let (left, right) = (left.unwrap(), right.unwrap());
2615
2616                left.visit(self)?;
2617                write_chunk!(self, "{op}")?;
2618                self.visit_assignment(right)?;
2619            }
2620            Expression::ConditionalOperator(loc, cond, first_expr, second_expr) => {
2621                cond.visit(self)?;
2622
2623                let first_expr = self.chunked(
2624                    first_expr.loc().start(),
2625                    Some(second_expr.loc().start()),
2626                    |fmt| {
2627                        write_chunk!(fmt, "?")?;
2628                        first_expr.visit(fmt)
2629                    },
2630                )?;
2631                let second_expr =
2632                    self.chunked(second_expr.loc().start(), Some(loc.end()), |fmt| {
2633                        write_chunk!(fmt, ":")?;
2634                        second_expr.visit(fmt)
2635                    })?;
2636
2637                let chunks = vec![first_expr, second_expr];
2638                if !self.try_on_single_line(|fmt| fmt.write_chunks_separated(&chunks, "", false))? {
2639                    self.grouped(|fmt| fmt.write_chunks_separated(&chunks, "", true))?;
2640                }
2641            }
2642            Expression::Variable(ident) => {
2643                write_chunk!(self, loc.end(), "{}", ident.name)?;
2644            }
2645            Expression::MemberAccess(_, expr, ident) => {
2646                self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() {
2647                    Expression::MemberAccess(_, inner_expr, inner_ident) => {
2648                        Ok(Some((inner_expr, inner_ident)))
2649                    }
2650                    expr => {
2651                        expr.visit(fmt)?;
2652                        Ok(None)
2653                    }
2654                })?;
2655            }
2656            Expression::List(loc, items) => {
2657                self.surrounded(
2658                    SurroundingChunk::new(
2659                        "(",
2660                        Some(loc.start()),
2661                        items.first().map(|item| item.0.start()),
2662                    ),
2663                    SurroundingChunk::new(")", None, Some(loc.end())),
2664                    |fmt, _| {
2665                        let items = fmt.items_to_chunks(
2666                            Some(loc.end()),
2667                            items.iter_mut().map(|(loc, item)| (*loc, item)),
2668                        )?;
2669                        let write_items = |fmt: &mut Self, multiline| {
2670                            fmt.write_chunks_separated(&items, ",", multiline)
2671                        };
2672                        if !fmt.try_on_single_line(|fmt| write_items(fmt, false))? {
2673                            write_items(fmt, true)?;
2674                        }
2675                        Ok(())
2676                    },
2677                )?;
2678            }
2679            Expression::FunctionCall(loc, expr, exprs) => {
2680                self.visit_expr(expr.loc(), expr)?;
2681                self.visit_list("", exprs, Some(expr.loc().end()), Some(loc.end()), true)?;
2682            }
2683            Expression::NamedFunctionCall(loc, expr, args) => {
2684                self.visit_expr(expr.loc(), expr)?;
2685                write!(self.buf(), "(")?;
2686                self.visit_args(*loc, args)?;
2687                write!(self.buf(), ")")?;
2688            }
2689            Expression::FunctionCallBlock(_, expr, stmt) => {
2690                expr.visit(self)?;
2691                stmt.visit(self)?;
2692            }
2693            Expression::New(_, expr) => {
2694                write_chunk!(self, "new ")?;
2695                self.visit_expr(expr.loc(), expr)?;
2696            }
2697            _ => self.visit_source(loc)?,
2698        };
2699
2700        Ok(())
2701    }
2702
2703    #[instrument(name = "ident", skip_all)]
2704    fn visit_ident(&mut self, loc: Loc, ident: &mut Identifier) -> Result<()> {
2705        return_source_if_disabled!(self, loc);
2706        write_chunk!(self, loc.end(), "{}", ident.name)?;
2707        Ok(())
2708    }
2709
2710    #[instrument(name = "ident_path", skip_all)]
2711    fn visit_ident_path(&mut self, idents: &mut IdentifierPath) -> Result<(), Self::Error> {
2712        if idents.identifiers.is_empty() {
2713            return Ok(())
2714        }
2715        return_source_if_disabled!(self, idents.loc);
2716
2717        idents.identifiers.iter_mut().skip(1).for_each(|chunk| {
2718            if !chunk.name.starts_with('.') {
2719                chunk.name.insert(0, '.')
2720            }
2721        });
2722        let chunks = self.items_to_chunks(
2723            Some(idents.loc.end()),
2724            idents.identifiers.iter_mut().map(|ident| (ident.loc, ident)),
2725        )?;
2726        self.grouped(|fmt| {
2727            let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, "")?;
2728            fmt.write_chunks_separated(&chunks, "", multiline)
2729        })?;
2730        Ok(())
2731    }
2732
2733    #[instrument(name = "emit", skip_all)]
2734    fn visit_emit(&mut self, loc: Loc, event: &mut Expression) -> Result<()> {
2735        return_source_if_disabled!(self, loc);
2736        write_chunk!(self, loc.start(), "emit")?;
2737        event.visit(self)?;
2738        self.write_semicolon()?;
2739        Ok(())
2740    }
2741
2742    #[instrument(name = "var_definition", skip_all)]
2743    fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> {
2744        return_source_if_disabled!(self, var.loc, ';');
2745
2746        var.ty.visit(self)?;
2747
2748        let multiline = self.grouped(|fmt| {
2749            let var_name = var.name.safe_unwrap_mut();
2750            let name_start = var_name.loc.start();
2751
2752            let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?;
2753            if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? {
2754                fmt.write_chunks_separated(&attrs, "", true)?;
2755            }
2756
2757            let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?;
2758            if var.initializer.is_some() {
2759                name.content.push_str(" =");
2760            }
2761            fmt.write_chunk(&name)?;
2762
2763            Ok(())
2764        })?;
2765
2766        var.initializer
2767            .as_mut()
2768            .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init)))
2769            .transpose()?;
2770
2771        self.write_semicolon()?;
2772
2773        Ok(())
2774    }
2775
2776    #[instrument(name = "var_definition_stmt", skip_all)]
2777    fn visit_var_definition_stmt(
2778        &mut self,
2779        loc: Loc,
2780        declaration: &mut VariableDeclaration,
2781        expr: &mut Option<Expression>,
2782    ) -> Result<()> {
2783        return_source_if_disabled!(self, loc, ';');
2784
2785        let declaration = self
2786            .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?;
2787        let multiline = declaration.content.contains('\n');
2788        self.write_chunk(&declaration)?;
2789
2790        if let Some(expr) = expr {
2791            write!(self.buf(), " =")?;
2792            self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?;
2793        }
2794
2795        self.write_semicolon()
2796    }
2797
2798    #[instrument(name = "var_declaration", skip_all)]
2799    fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> {
2800        return_source_if_disabled!(self, var.loc);
2801        self.grouped(|fmt| {
2802            var.ty.visit(fmt)?;
2803            if let Some(storage) = &var.storage {
2804                write_chunk!(fmt, storage.loc().end(), "{storage}")?;
2805            }
2806            let var_name = var.name.safe_unwrap();
2807            write_chunk!(fmt, var_name.loc.end(), "{var_name}")
2808        })?;
2809        Ok(())
2810    }
2811
2812    #[instrument(name = "return", skip_all)]
2813    fn visit_return(&mut self, loc: Loc, expr: &mut Option<Expression>) -> Result<(), Self::Error> {
2814        return_source_if_disabled!(self, loc, ';');
2815
2816        self.write_postfix_comments_before(loc.start())?;
2817        self.write_prefix_comments_before(loc.start())?;
2818
2819        if expr.is_none() {
2820            write_chunk!(self, loc.end(), "return;")?;
2821            return Ok(())
2822        }
2823
2824        let expr = expr.as_mut().unwrap();
2825        let expr_loc_start = expr.loc().start();
2826        let write_return = |fmt: &mut Self| -> Result<()> {
2827            write_chunk!(fmt, loc.start(), "return")?;
2828            fmt.write_postfix_comments_before(expr_loc_start)?;
2829            Ok(())
2830        };
2831
2832        let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> {
2833            let fits_on_single = fmt.try_on_single_line(|fmt| {
2834                write_return(fmt)?;
2835                expr.visit(fmt)
2836            })?;
2837            if fits_on_single {
2838                return Ok(())
2839            }
2840
2841            let mut fit_on_next_line = false;
2842            let tx = fmt.transact(|fmt| {
2843                fmt.grouped(|fmt| {
2844                    write_return(fmt)?;
2845                    if !fmt.is_beginning_of_line() {
2846                        fmt.write_whitespace_separator(true)?;
2847                    }
2848                    fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?;
2849                    Ok(())
2850                })?;
2851                Ok(())
2852            })?;
2853            if fit_on_next_line {
2854                tx.commit()?;
2855                return Ok(())
2856            }
2857
2858            write_return(fmt)?;
2859            expr.visit(fmt)?;
2860            Ok(())
2861        };
2862
2863        write_return_with_expr(self)?;
2864        write_chunk!(self, loc.end(), ";")?;
2865        Ok(())
2866    }
2867
2868    #[instrument(name = "revert", skip_all)]
2869    fn visit_revert(
2870        &mut self,
2871        loc: Loc,
2872        error: &mut Option<IdentifierPath>,
2873        args: &mut Vec<Expression>,
2874    ) -> Result<(), Self::Error> {
2875        return_source_if_disabled!(self, loc, ';');
2876        write_chunk!(self, loc.start(), "revert")?;
2877        if let Some(error) = error {
2878            error.visit(self)?;
2879        }
2880        self.visit_list("", args, None, Some(loc.end()), true)?;
2881        self.write_semicolon()?;
2882
2883        Ok(())
2884    }
2885
2886    #[instrument(name = "revert_named_args", skip_all)]
2887    fn visit_revert_named_args(
2888        &mut self,
2889        loc: Loc,
2890        error: &mut Option<IdentifierPath>,
2891        args: &mut Vec<NamedArgument>,
2892    ) -> Result<(), Self::Error> {
2893        return_source_if_disabled!(self, loc, ';');
2894
2895        write_chunk!(self, loc.start(), "revert")?;
2896        let mut error_indented = false;
2897        if let Some(error) = error {
2898            if !self.try_on_single_line(|fmt| error.visit(fmt))? {
2899                error.visit(self)?;
2900                error_indented = true;
2901            }
2902        }
2903
2904        if args.is_empty() {
2905            write!(self.buf(), "({{}});")?;
2906            return Ok(())
2907        }
2908
2909        write!(self.buf(), "(")?;
2910        self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?;
2911        write!(self.buf(), ")")?;
2912        self.write_semicolon()?;
2913
2914        Ok(())
2915    }
2916
2917    #[instrument(name = "break", skip_all)]
2918    fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> {
2919        if semicolon {
2920            return_source_if_disabled!(self, loc, ';');
2921        } else {
2922            return_source_if_disabled!(self, loc);
2923        }
2924        write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" })
2925    }
2926
2927    #[instrument(name = "continue", skip_all)]
2928    fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> {
2929        if semicolon {
2930            return_source_if_disabled!(self, loc, ';');
2931        } else {
2932            return_source_if_disabled!(self, loc);
2933        }
2934        write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" })
2935    }
2936
2937    #[instrument(name = "try", skip_all)]
2938    fn visit_try(
2939        &mut self,
2940        loc: Loc,
2941        expr: &mut Expression,
2942        returns: &mut Option<(Vec<(Loc, Option<Parameter>)>, Box<Statement>)>,
2943        clauses: &mut Vec<CatchClause>,
2944    ) -> Result<(), Self::Error> {
2945        return_source_if_disabled!(self, loc);
2946
2947        let try_next_byte = clauses.first().map(|c| match c {
2948            CatchClause::Simple(loc, ..) => loc.start(),
2949            CatchClause::Named(loc, ..) => loc.start(),
2950        });
2951        let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| {
2952            write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?;
2953            expr.visit(fmt)?;
2954            if let Some((params, stmt)) = returns {
2955                let mut params =
2956                    params.iter_mut().filter(|(_, param)| param.is_some()).collect::<Vec<_>>();
2957                let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start());
2958                fmt.surrounded(
2959                    SurroundingChunk::new("returns (", Some(byte_offset), None),
2960                    SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())),
2961                    |fmt, _| {
2962                        let chunks = fmt.items_to_chunks(
2963                            Some(stmt.loc().start()),
2964                            params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)),
2965                        )?;
2966                        let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?;
2967                        fmt.write_chunks_separated(&chunks, ",", multiline)?;
2968                        Ok(())
2969                    },
2970                )?;
2971                stmt.visit(fmt)?;
2972            }
2973            Ok(())
2974        })?;
2975
2976        let mut chunks = vec![try_chunk];
2977        for clause in clauses {
2978            let (loc, ident, mut param, stmt) = match clause {
2979                CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt),
2980                CatchClause::Named(loc, ident, param, stmt) => {
2981                    (loc, Some(ident), Some(param), stmt)
2982                }
2983            };
2984
2985            let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| {
2986                write_chunk!(fmt, "catch")?;
2987                if let Some(ident) = ident.as_ref() {
2988                    fmt.write_postfix_comments_before(
2989                        param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()),
2990                    )?;
2991                    write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?;
2992                }
2993                if let Some(param) = param.as_mut() {
2994                    write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?;
2995                    fmt.surrounded(
2996                        SurroundingChunk::new("", Some(param.loc.start()), None),
2997                        SurroundingChunk::new(")", None, Some(stmt.loc().start())),
2998                        |fmt, _| param.visit(fmt),
2999                    )?;
3000                }
3001
3002                stmt.visit(fmt)?;
3003                Ok(())
3004            })?;
3005
3006            chunks.push(chunk);
3007        }
3008
3009        let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?;
3010        if !multiline {
3011            self.write_chunks_separated(&chunks, "", false)?;
3012            return Ok(())
3013        }
3014
3015        let mut chunks = chunks.iter_mut().peekable();
3016        let mut prev_multiline = false;
3017
3018        // write try chunk first
3019        if let Some(chunk) = chunks.next() {
3020            let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?;
3021            write!(self.buf(), "{chunk_str}")?;
3022            prev_multiline = chunk_str.contains('\n');
3023        }
3024
3025        while let Some(chunk) = chunks.next() {
3026            let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?;
3027            let multiline = chunk_str.contains('\n');
3028            self.indented_if(!multiline, 1, |fmt| {
3029                chunk.needs_space = Some(false);
3030                let on_same_line = prev_multiline && (multiline || chunks.peek().is_none());
3031                let prefix = if fmt.is_beginning_of_line() {
3032                    ""
3033                } else if on_same_line {
3034                    " "
3035                } else {
3036                    "\n"
3037                };
3038                let chunk_str = format!("{prefix}{chunk_str}");
3039                write!(fmt.buf(), "{chunk_str}")?;
3040                Ok(())
3041            })?;
3042            prev_multiline = multiline;
3043        }
3044        Ok(())
3045    }
3046
3047    #[instrument(name = "if", skip_all)]
3048    fn visit_if(
3049        &mut self,
3050        loc: Loc,
3051        cond: &mut Expression,
3052        if_branch: &mut Box<Statement>,
3053        else_branch: &mut Option<Box<Statement>>,
3054        is_first_stmt: bool,
3055    ) -> Result<(), Self::Error> {
3056        return_source_if_disabled!(self, loc);
3057
3058        if !is_first_stmt {
3059            self.write_if_stmt(loc, cond, if_branch, else_branch)?;
3060            return Ok(())
3061        }
3062
3063        self.context.if_stmt_single_line = Some(true);
3064        let mut stmt_fits_on_single = false;
3065        let tx = self.transact(|fmt| {
3066            stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) {
3067                Ok(()) => true,
3068                Err(FormatterError::Fmt(_)) => false,
3069                Err(err) => bail!(err),
3070            };
3071            Ok(())
3072        })?;
3073
3074        if stmt_fits_on_single {
3075            tx.commit()?;
3076        } else {
3077            self.context.if_stmt_single_line = Some(false);
3078            self.write_if_stmt(loc, cond, if_branch, else_branch)?;
3079        }
3080        self.context.if_stmt_single_line = None;
3081
3082        Ok(())
3083    }
3084
3085    #[instrument(name = "do_while", skip_all)]
3086    fn visit_do_while(
3087        &mut self,
3088        loc: Loc,
3089        body: &mut Statement,
3090        cond: &mut Expression,
3091    ) -> Result<(), Self::Error> {
3092        return_source_if_disabled!(self, loc, ';');
3093        write_chunk!(self, loc.start(), "do ")?;
3094        self.visit_stmt_as_block(body, false)?;
3095        visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), {
3096            self.surrounded(
3097                SurroundingChunk::new("while (", Some(cond.loc().start()), None),
3098                SurroundingChunk::new(");", None, Some(loc.end())),
3099                |fmt, _| cond.visit(fmt),
3100            )?;
3101        });
3102        Ok(())
3103    }
3104
3105    #[instrument(name = "while", skip_all)]
3106    fn visit_while(
3107        &mut self,
3108        loc: Loc,
3109        cond: &mut Expression,
3110        body: &mut Statement,
3111    ) -> Result<(), Self::Error> {
3112        return_source_if_disabled!(self, loc);
3113        self.surrounded(
3114            SurroundingChunk::new("while (", Some(loc.start()), None),
3115            SurroundingChunk::new(")", None, Some(cond.loc().end())),
3116            |fmt, _| {
3117                cond.visit(fmt)?;
3118                fmt.write_postfix_comments_before(body.loc().start())
3119            },
3120        )?;
3121
3122        let cond_close_paren_loc =
3123            self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end());
3124        let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc);
3125        self.visit_stmt_as_block(body, attempt_single_line)?;
3126        Ok(())
3127    }
3128
3129    #[instrument(name = "for", skip_all)]
3130    fn visit_for(
3131        &mut self,
3132        loc: Loc,
3133        init: &mut Option<Box<Statement>>,
3134        cond: &mut Option<Box<Expression>>,
3135        update: &mut Option<Box<Expression>>,
3136        body: &mut Option<Box<Statement>>,
3137    ) -> Result<(), Self::Error> {
3138        return_source_if_disabled!(self, loc);
3139
3140        let next_byte_end = update.as_ref().map(|u| u.loc().end());
3141        self.surrounded(
3142            SurroundingChunk::new("for (", Some(loc.start()), None),
3143            SurroundingChunk::new(")", None, next_byte_end),
3144            |fmt, _| {
3145                let mut write_for_loop_header = |fmt: &mut Self, multiline: bool| -> Result<()> {
3146                    match init {
3147                        Some(stmt) => stmt.visit(fmt),
3148                        None => fmt.write_semicolon(),
3149                    }?;
3150                    if multiline {
3151                        fmt.write_whitespace_separator(true)?;
3152                    }
3153
3154                    cond.visit(fmt)?;
3155                    fmt.write_semicolon()?;
3156                    if multiline {
3157                        fmt.write_whitespace_separator(true)?;
3158                    }
3159
3160                    match update {
3161                        Some(expr) => expr.visit(fmt),
3162                        None => Ok(()),
3163                    }
3164                };
3165                let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?;
3166                if multiline {
3167                    write_for_loop_header(fmt, true)?;
3168                }
3169                Ok(())
3170            },
3171        )?;
3172        match body {
3173            Some(body) => {
3174                self.visit_stmt_as_block(body, false)?;
3175            }
3176            None => {
3177                self.write_empty_brackets()?;
3178            }
3179        };
3180        Ok(())
3181    }
3182
3183    #[instrument(name = "function", skip_all)]
3184    fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> {
3185        if func.body.is_some() {
3186            return_source_if_disabled!(self, func.loc());
3187        } else {
3188            return_source_if_disabled!(self, func.loc(), ';');
3189        }
3190
3191        self.with_function_context(func.clone(), |fmt| {
3192            fmt.write_postfix_comments_before(func.loc.start())?;
3193            fmt.write_prefix_comments_before(func.loc.start())?;
3194
3195            let body_loc = func.body.as_ref().map(CodeLocation::loc);
3196            let mut attrs_multiline = false;
3197            let fits_on_single = fmt.try_on_single_line(|fmt| {
3198                fmt.write_function_header(func, body_loc, false)?;
3199                Ok(())
3200            })?;
3201            if !fits_on_single {
3202                attrs_multiline = fmt.write_function_header(func, body_loc, true)?;
3203            }
3204
3205            // write function body
3206            match &mut func.body {
3207                Some(body) => {
3208                    let body_loc = body.loc();
3209                    // Handle case where block / statements starts on disabled line.
3210                    if fmt.inline_config.is_disabled(body_loc.with_end(body_loc.start())) {
3211                        match body {
3212                            Statement::Block { statements, .. } if !statements.is_empty() => {
3213                                fmt.write_whitespace_separator(false)?;
3214                                fmt.visit_block(body_loc, statements, false, false)?;
3215                                return Ok(())
3216                            }
3217                            _ => {
3218                                // Attrs should be written on same line if first line is disabled
3219                                // and there's no statement.
3220                                attrs_multiline = false
3221                            }
3222                        }
3223                    }
3224
3225                    let byte_offset = body_loc.start();
3226                    let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?;
3227                    fmt.write_whitespace_separator(
3228                        attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()),
3229                    )?;
3230                    fmt.write_chunk(&body)?;
3231                }
3232                None => fmt.write_semicolon()?,
3233            }
3234            Ok(())
3235        })?;
3236
3237        Ok(())
3238    }
3239
3240    #[instrument(name = "function_attribute", skip_all)]
3241    fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> {
3242        return_source_if_disabled!(self, attribute.loc());
3243
3244        match attribute {
3245            FunctionAttribute::Mutability(mutability) => {
3246                write_chunk!(self, mutability.loc().end(), "{mutability}")?
3247            }
3248            FunctionAttribute::Visibility(visibility) => {
3249                // Visibility will always have a location in a Function attribute
3250                write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")?
3251            }
3252            FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?,
3253            FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?,
3254            FunctionAttribute::Override(loc, args) => {
3255                write_chunk!(self, loc.start(), "override")?;
3256                if !args.is_empty() && self.config.override_spacing {
3257                    self.write_whitespace_separator(false)?;
3258                }
3259                self.visit_list("", args, None, Some(loc.end()), false)?
3260            }
3261            FunctionAttribute::BaseOrModifier(loc, base) => {
3262                // here we need to find out if this attribute belongs to the constructor because the
3263                // modifier need to include the trailing parenthesis
3264                // This is very ambiguous because the modifier can either by an inherited contract
3265                // or a modifier here: e.g.: This is valid constructor:
3266                // `constructor() public  Ownable() OnlyOwner {}`
3267                let is_constructor = self.context.is_constructor_function();
3268                // we can't make any decisions here regarding trailing `()` because we'd need to
3269                // find out if the `base` is a solidity modifier or an
3270                // interface/contract therefore we use its raw content.
3271
3272                // we can however check if the contract `is` the `base`, this however also does
3273                // not cover all cases
3274                let is_contract_base = self.context.contract.as_ref().is_some_and(|contract| {
3275                    contract.base.iter().any(|contract_base| {
3276                        contract_base
3277                            .name
3278                            .identifiers
3279                            .iter()
3280                            .zip(&base.name.identifiers)
3281                            .all(|(l, r)| l.name == r.name)
3282                    })
3283                });
3284
3285                if is_contract_base {
3286                    base.visit(self)?;
3287                } else if is_constructor {
3288                    // This is ambiguous because the modifier can either by an inherited
3289                    // contract modifiers with empty parenthesis are
3290                    // valid, but not required so we make the assumption
3291                    // here that modifiers are lowercase
3292                    let mut base_or_modifier =
3293                        self.visit_to_chunk(loc.start(), Some(loc.end()), base)?;
3294                    let is_lowercase =
3295                        base_or_modifier.content.chars().next().is_some_and(|c| c.is_lowercase());
3296                    if is_lowercase && base_or_modifier.content.ends_with("()") {
3297                        base_or_modifier.content.truncate(base_or_modifier.content.len() - 2);
3298                    }
3299
3300                    self.write_chunk(&base_or_modifier)?;
3301                } else {
3302                    let mut base_or_modifier =
3303                        self.visit_to_chunk(loc.start(), Some(loc.end()), base)?;
3304                    if base_or_modifier.content.ends_with("()") {
3305                        base_or_modifier.content.truncate(base_or_modifier.content.len() - 2);
3306                    }
3307                    self.write_chunk(&base_or_modifier)?;
3308                }
3309            }
3310            FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?,
3311        };
3312
3313        Ok(())
3314    }
3315
3316    #[instrument(name = "var_attribute", skip_all)]
3317    fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> {
3318        return_source_if_disabled!(self, attribute.loc());
3319
3320        let token = match attribute {
3321            VariableAttribute::Visibility(visibility) => Some(visibility.to_string()),
3322            VariableAttribute::Constant(_) => Some("constant".to_string()),
3323            VariableAttribute::Immutable(_) => Some("immutable".to_string()),
3324            VariableAttribute::StorageType(_) => None, // Unsupported
3325            VariableAttribute::Override(loc, idents) => {
3326                write_chunk!(self, loc.start(), "override")?;
3327                if !idents.is_empty() && self.config.override_spacing {
3328                    self.write_whitespace_separator(false)?;
3329                }
3330                self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?;
3331                None
3332            }
3333            VariableAttribute::StorageLocation(storage) => Some(storage.to_string()),
3334        };
3335        if let Some(token) = token {
3336            let loc = attribute.loc();
3337            write_chunk!(self, loc.start(), loc.end(), "{}", token)?;
3338        }
3339        Ok(())
3340    }
3341
3342    #[instrument(name = "base", skip_all)]
3343    fn visit_base(&mut self, base: &mut Base) -> Result<()> {
3344        return_source_if_disabled!(self, base.loc);
3345
3346        let name_loc = &base.name.loc;
3347        let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| {
3348            fmt.visit_ident_path(&mut base.name)?;
3349            Ok(())
3350        })?;
3351
3352        if base.args.is_none() || base.args.as_ref().unwrap().is_empty() {
3353            // This is ambiguous because the modifier can either by an inherited contract or a
3354            // modifier
3355            if self.context.function.is_some() {
3356                name.content.push_str("()");
3357            }
3358            self.write_chunk(&name)?;
3359            return Ok(())
3360        }
3361
3362        let args = base.args.as_mut().unwrap();
3363        let args_start = CodeLocation::loc(args.first().unwrap()).start();
3364
3365        name.content.push('(');
3366        let formatted_name = self.chunk_to_string(&name)?;
3367
3368        let multiline = !self.will_it_fit(&formatted_name);
3369
3370        self.surrounded(
3371            SurroundingChunk::new(&formatted_name, Some(args_start), None),
3372            SurroundingChunk::new(")", None, Some(base.loc.end())),
3373            |fmt, multiline_hint| {
3374                let args = fmt.items_to_chunks(
3375                    Some(base.loc.end()),
3376                    args.iter_mut().map(|arg| (arg.loc(), arg)),
3377                )?;
3378                let multiline = multiline ||
3379                    multiline_hint ||
3380                    fmt.are_chunks_separated_multiline("{}", &args, ",")?;
3381                fmt.write_chunks_separated(&args, ",", multiline)?;
3382                Ok(())
3383            },
3384        )?;
3385
3386        Ok(())
3387    }
3388
3389    #[instrument(name = "parameter", skip_all)]
3390    fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> {
3391        return_source_if_disabled!(self, parameter.loc);
3392        self.grouped(|fmt| {
3393            parameter.ty.visit(fmt)?;
3394            if let Some(storage) = &parameter.storage {
3395                write_chunk!(fmt, storage.loc().end(), "{storage}")?;
3396            }
3397            if let Some(name) = &parameter.name {
3398                write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?;
3399            }
3400            Ok(())
3401        })?;
3402        Ok(())
3403    }
3404
3405    #[instrument(name = "struct", skip_all)]
3406    fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> {
3407        return_source_if_disabled!(self, structure.loc);
3408        self.grouped(|fmt| {
3409            let struct_name = structure.name.safe_unwrap_mut();
3410            write_chunk!(fmt, struct_name.loc.start(), "struct")?;
3411            struct_name.visit(fmt)?;
3412            if structure.fields.is_empty() {
3413                return fmt.write_empty_brackets()
3414            }
3415
3416            write!(fmt.buf(), " {{")?;
3417            fmt.surrounded(
3418                SurroundingChunk::new("", Some(struct_name.loc.end()), None),
3419                SurroundingChunk::new("}", None, Some(structure.loc.end())),
3420                |fmt, _multiline| {
3421                    let chunks = fmt.items_to_chunks(
3422                        Some(structure.loc.end()),
3423                        structure.fields.iter_mut().map(|ident| (ident.loc, ident)),
3424                    )?;
3425                    for mut chunk in chunks {
3426                        chunk.content.push(';');
3427                        fmt.write_chunk(&chunk)?;
3428                        fmt.write_whitespace_separator(true)?;
3429                    }
3430                    Ok(())
3431                },
3432            )
3433        })?;
3434
3435        Ok(())
3436    }
3437
3438    #[instrument(name = "event", skip_all)]
3439    fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> {
3440        return_source_if_disabled!(self, event.loc, ';');
3441
3442        let event_name = event.name.safe_unwrap_mut();
3443        let mut name =
3444            self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?;
3445        name.content = format!("event {}(", name.content);
3446
3447        let last_chunk = if event.anonymous { ") anonymous;" } else { ");" };
3448        if event.fields.is_empty() {
3449            name.content.push_str(last_chunk);
3450            self.write_chunk(&name)?;
3451        } else {
3452            let byte_offset = event.fields.first().unwrap().loc.start();
3453            let first_chunk = self.chunk_to_string(&name)?;
3454            self.surrounded(
3455                SurroundingChunk::new(first_chunk, Some(byte_offset), None),
3456                SurroundingChunk::new(last_chunk, None, Some(event.loc.end())),
3457                |fmt, multiline| {
3458                    let params = fmt
3459                        .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?;
3460
3461                    let multiline =
3462                        multiline && fmt.are_chunks_separated_multiline("{}", &params, ",")?;
3463                    fmt.write_chunks_separated(&params, ",", multiline)
3464                },
3465            )?;
3466        }
3467
3468        Ok(())
3469    }
3470
3471    #[instrument(name = "event_parameter", skip_all)]
3472    fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> {
3473        return_source_if_disabled!(self, param.loc);
3474
3475        self.grouped(|fmt| {
3476            param.ty.visit(fmt)?;
3477            if param.indexed {
3478                write_chunk!(fmt, param.loc.start(), "indexed")?;
3479            }
3480            if let Some(name) = &param.name {
3481                write_chunk!(fmt, name.loc.end(), "{}", name.name)?;
3482            }
3483            Ok(())
3484        })?;
3485        Ok(())
3486    }
3487
3488    #[instrument(name = "error", skip_all)]
3489    fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> {
3490        return_source_if_disabled!(self, error.loc, ';');
3491
3492        let error_name = error.name.safe_unwrap_mut();
3493        let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?;
3494        name.content = format!("error {}", name.content);
3495
3496        let formatted_name = self.chunk_to_string(&name)?;
3497        write!(self.buf(), "{formatted_name}")?;
3498        let start_offset = error.fields.first().map(|f| f.loc.start());
3499        self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?;
3500        self.write_semicolon()?;
3501
3502        Ok(())
3503    }
3504
3505    #[instrument(name = "error_parameter", skip_all)]
3506    fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> {
3507        return_source_if_disabled!(self, param.loc);
3508        self.grouped(|fmt| {
3509            param.ty.visit(fmt)?;
3510            if let Some(name) = &param.name {
3511                write_chunk!(fmt, name.loc.end(), "{}", name.name)?;
3512            }
3513            Ok(())
3514        })?;
3515        Ok(())
3516    }
3517
3518    #[instrument(name = "type_definition", skip_all)]
3519    fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> {
3520        return_source_if_disabled!(self, def.loc, ';');
3521        self.grouped(|fmt| {
3522            write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?;
3523            def.name.visit(fmt)?;
3524            write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?;
3525            def.ty.visit(fmt)?;
3526            fmt.write_semicolon()?;
3527            Ok(())
3528        })?;
3529        Ok(())
3530    }
3531
3532    #[instrument(name = "stray_semicolon", skip_all)]
3533    fn visit_stray_semicolon(&mut self) -> Result<()> {
3534        self.write_semicolon()
3535    }
3536
3537    #[instrument(name = "opening_paren", skip_all)]
3538    fn visit_opening_paren(&mut self) -> Result<()> {
3539        write_chunk!(self, "(")?;
3540        Ok(())
3541    }
3542
3543    #[instrument(name = "closing_paren", skip_all)]
3544    fn visit_closing_paren(&mut self) -> Result<()> {
3545        write_chunk!(self, ")")?;
3546        Ok(())
3547    }
3548
3549    #[instrument(name = "newline", skip_all)]
3550    fn visit_newline(&mut self) -> Result<()> {
3551        writeln_chunk!(self)?;
3552        Ok(())
3553    }
3554
3555    #[instrument(name = "using", skip_all)]
3556    fn visit_using(&mut self, using: &mut Using) -> Result<()> {
3557        return_source_if_disabled!(self, using.loc, ';');
3558
3559        write_chunk!(self, using.loc.start(), "using")?;
3560
3561        let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start());
3562        let global_start = using.global.as_mut().map(|global| global.loc.start());
3563        let loc_end = using.loc.end();
3564
3565        let (is_library, mut list_chunks) = match &mut using.list {
3566            UsingList::Library(library) => {
3567                (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?])
3568            }
3569            UsingList::Functions(funcs) => {
3570                let mut funcs = funcs.iter_mut().peekable();
3571                let mut chunks = Vec::new();
3572                while let Some(func) = funcs.next() {
3573                    let next_byte_end = funcs.peek().map(|func| func.loc.start());
3574                    chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| {
3575                        fmt.visit_ident_path(&mut func.path)?;
3576                        if let Some(op) = func.oper {
3577                            write!(fmt.buf(), " as {op}")?;
3578                        }
3579                        Ok(())
3580                    })?);
3581                }
3582                (false, chunks)
3583            }
3584            UsingList::Error => return self.visit_parser_error(using.loc),
3585        };
3586
3587        let for_chunk = self.chunk_at(
3588            using.loc.start(),
3589            Some(ty_start.or(global_start).unwrap_or(loc_end)),
3590            None,
3591            "for",
3592        );
3593        let ty_chunk = if let Some(ty) = &mut using.ty {
3594            self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)?
3595        } else {
3596            self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*")
3597        };
3598        let global_chunk = using
3599            .global
3600            .as_mut()
3601            .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global))
3602            .transpose()?;
3603
3604        let write_for_def = |fmt: &mut Self| {
3605            fmt.grouped(|fmt| {
3606                fmt.write_chunk(&for_chunk)?;
3607                fmt.write_chunk(&ty_chunk)?;
3608                if let Some(global_chunk) = global_chunk.as_ref() {
3609                    fmt.write_chunk(global_chunk)?;
3610                }
3611                Ok(())
3612            })?;
3613            Ok(())
3614        };
3615
3616        let simulated_for_def = self.simulate_to_string(write_for_def)?;
3617
3618        if is_library {
3619            let chunk = list_chunks.pop().unwrap();
3620            if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? {
3621                self.write_chunk(&chunk)?;
3622                write_for_def(self)?;
3623            } else {
3624                self.write_whitespace_separator(true)?;
3625                self.grouped(|fmt| {
3626                    fmt.write_chunk(&chunk)?;
3627                    Ok(())
3628                })?;
3629                self.write_whitespace_separator(true)?;
3630                write_for_def(self)?;
3631            }
3632        } else {
3633            self.surrounded(
3634                SurroundingChunk::new("{", Some(using.loc.start()), None),
3635                SurroundingChunk::new(
3636                    "}",
3637                    None,
3638                    Some(ty_start.or(global_start).unwrap_or(loc_end)),
3639                ),
3640                |fmt, _multiline| {
3641                    let multiline = fmt.are_chunks_separated_multiline(
3642                        &format!("{{ {{}} }} {simulated_for_def};"),
3643                        &list_chunks,
3644                        ",",
3645                    )?;
3646                    fmt.write_chunks_separated(&list_chunks, ",", multiline)?;
3647                    Ok(())
3648                },
3649            )?;
3650            write_for_def(self)?;
3651        }
3652
3653        self.write_semicolon()?;
3654
3655        Ok(())
3656    }
3657
3658    #[instrument(name = "yul_block", skip_all)]
3659    fn visit_yul_block(
3660        &mut self,
3661        loc: Loc,
3662        statements: &mut Vec<YulStatement>,
3663        attempt_single_line: bool,
3664    ) -> Result<(), Self::Error> {
3665        return_source_if_disabled!(self, loc);
3666        self.visit_block(loc, statements, attempt_single_line, false)?;
3667        Ok(())
3668    }
3669
3670    #[instrument(name = "yul_expr", skip_all)]
3671    fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> {
3672        return_source_if_disabled!(self, expr.loc());
3673
3674        match expr {
3675            YulExpression::BoolLiteral(loc, val, ident) => {
3676                let val = if *val { "true" } else { "false" };
3677                self.visit_yul_string_with_ident(*loc, val, ident)
3678            }
3679            YulExpression::FunctionCall(expr) => self.visit_yul_function_call(expr),
3680            YulExpression::HexNumberLiteral(loc, val, ident) => {
3681                self.visit_yul_string_with_ident(*loc, val, ident)
3682            }
3683            YulExpression::HexStringLiteral(val, ident) => self.visit_yul_string_with_ident(
3684                val.loc,
3685                &self.quote_str(val.loc, Some("hex"), &val.hex),
3686                ident,
3687            ),
3688            YulExpression::NumberLiteral(loc, val, expr, ident) => {
3689                let val = if expr.is_empty() { val.to_owned() } else { format!("{val}e{expr}") };
3690                self.visit_yul_string_with_ident(*loc, &val, ident)
3691            }
3692            YulExpression::StringLiteral(val, ident) => self.visit_yul_string_with_ident(
3693                val.loc,
3694                &self.quote_str(val.loc, None, &val.string),
3695                ident,
3696            ),
3697            YulExpression::SuffixAccess(_, expr, ident) => {
3698                self.visit_member_access(expr, ident, |fmt, expr| match expr.as_mut() {
3699                    YulExpression::SuffixAccess(_, inner_expr, inner_ident) => {
3700                        Ok(Some((inner_expr, inner_ident)))
3701                    }
3702                    expr => {
3703                        expr.visit(fmt)?;
3704                        Ok(None)
3705                    }
3706                })
3707            }
3708            YulExpression::Variable(ident) => {
3709                write_chunk!(self, ident.loc.start(), ident.loc.end(), "{}", ident.name)
3710            }
3711        }
3712    }
3713
3714    #[instrument(name = "yul_assignment", skip_all)]
3715    fn visit_yul_assignment<T>(
3716        &mut self,
3717        loc: Loc,
3718        exprs: &mut Vec<T>,
3719        expr: &mut Option<&mut YulExpression>,
3720    ) -> Result<(), Self::Error>
3721    where
3722        T: Visitable + CodeLocation,
3723    {
3724        return_source_if_disabled!(self, loc);
3725
3726        self.grouped(|fmt| {
3727            let chunks =
3728                fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?;
3729
3730            let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?;
3731            fmt.write_chunks_separated(&chunks, ",", multiline)?;
3732
3733            if let Some(expr) = expr {
3734                write_chunk!(fmt, expr.loc().start(), ":=")?;
3735                let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?;
3736                if !fmt.will_chunk_fit("{}", &chunk)? {
3737                    fmt.write_whitespace_separator(true)?;
3738                }
3739                fmt.write_chunk(&chunk)?;
3740            }
3741            Ok(())
3742        })?;
3743        Ok(())
3744    }
3745
3746    #[instrument(name = "yul_for", skip_all)]
3747    fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> {
3748        return_source_if_disabled!(self, stmt.loc);
3749        write_chunk!(self, stmt.loc.start(), "for")?;
3750        self.visit_yul_block(stmt.init_block.loc, &mut stmt.init_block.statements, true)?;
3751        stmt.condition.visit(self)?;
3752        self.visit_yul_block(stmt.post_block.loc, &mut stmt.post_block.statements, true)?;
3753        self.visit_yul_block(stmt.execution_block.loc, &mut stmt.execution_block.statements, true)?;
3754        Ok(())
3755    }
3756
3757    #[instrument(name = "yul_function_call", skip_all)]
3758    fn visit_yul_function_call(&mut self, stmt: &mut YulFunctionCall) -> Result<(), Self::Error> {
3759        return_source_if_disabled!(self, stmt.loc);
3760        write_chunk!(self, stmt.loc.start(), "{}", stmt.id.name)?;
3761        self.visit_list("", &mut stmt.arguments, None, Some(stmt.loc.end()), true)
3762    }
3763
3764    #[instrument(name = "yul_fun_def", skip_all)]
3765    fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> {
3766        return_source_if_disabled!(self, stmt.loc);
3767
3768        write_chunk!(self, stmt.loc.start(), "function {}", stmt.id.name)?;
3769
3770        self.visit_list("", &mut stmt.params, None, None, true)?;
3771
3772        if !stmt.returns.is_empty() {
3773            self.grouped(|fmt| {
3774                write_chunk!(fmt, "->")?;
3775
3776                let chunks = fmt.items_to_chunks(
3777                    Some(stmt.body.loc.start()),
3778                    stmt.returns.iter_mut().map(|param| (param.loc, param)),
3779                )?;
3780                let multiline = fmt.are_chunks_separated_multiline("{}", &chunks, ",")?;
3781                fmt.write_chunks_separated(&chunks, ",", multiline)?;
3782                if multiline {
3783                    fmt.write_whitespace_separator(true)?;
3784                }
3785                Ok(())
3786            })?;
3787        }
3788
3789        stmt.body.visit(self)?;
3790
3791        Ok(())
3792    }
3793
3794    #[instrument(name = "yul_if", skip_all)]
3795    fn visit_yul_if(
3796        &mut self,
3797        loc: Loc,
3798        expr: &mut YulExpression,
3799        block: &mut YulBlock,
3800    ) -> Result<(), Self::Error> {
3801        return_source_if_disabled!(self, loc);
3802        write_chunk!(self, loc.start(), "if")?;
3803        expr.visit(self)?;
3804        self.visit_yul_block(block.loc, &mut block.statements, true)
3805    }
3806
3807    #[instrument(name = "yul_leave", skip_all)]
3808    fn visit_yul_leave(&mut self, loc: Loc) -> Result<(), Self::Error> {
3809        return_source_if_disabled!(self, loc);
3810        write_chunk!(self, loc.start(), loc.end(), "leave")
3811    }
3812
3813    #[instrument(name = "yul_switch", skip_all)]
3814    fn visit_yul_switch(&mut self, stmt: &mut YulSwitch) -> Result<(), Self::Error> {
3815        return_source_if_disabled!(self, stmt.loc);
3816
3817        write_chunk!(self, stmt.loc.start(), "switch")?;
3818        stmt.condition.visit(self)?;
3819        writeln_chunk!(self)?;
3820        let mut cases = stmt.cases.iter_mut().peekable();
3821        while let Some(YulSwitchOptions::Case(loc, expr, block)) = cases.next() {
3822            write_chunk!(self, loc.start(), "case")?;
3823            expr.visit(self)?;
3824            self.visit_yul_block(block.loc, &mut block.statements, true)?;
3825            let is_last = cases.peek().is_none();
3826            if !is_last || stmt.default.is_some() {
3827                writeln_chunk!(self)?;
3828            }
3829        }
3830        if let Some(YulSwitchOptions::Default(loc, ref mut block)) = stmt.default {
3831            write_chunk!(self, loc.start(), "default")?;
3832            self.visit_yul_block(block.loc, &mut block.statements, true)?;
3833        }
3834        Ok(())
3835    }
3836
3837    #[instrument(name = "yul_var_declaration", skip_all)]
3838    fn visit_yul_var_declaration(
3839        &mut self,
3840        loc: Loc,
3841        idents: &mut Vec<YulTypedIdentifier>,
3842        expr: &mut Option<YulExpression>,
3843    ) -> Result<(), Self::Error> {
3844        return_source_if_disabled!(self, loc);
3845        self.grouped(|fmt| {
3846            write_chunk!(fmt, loc.start(), "let")?;
3847            fmt.visit_yul_assignment(loc, idents, &mut expr.as_mut())
3848        })?;
3849        Ok(())
3850    }
3851
3852    #[instrument(name = "yul_typed_ident", skip_all)]
3853    fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> {
3854        return_source_if_disabled!(self, ident.loc);
3855        self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty)
3856    }
3857
3858    #[instrument(name = "parser_error", skip_all)]
3859    fn visit_parser_error(&mut self, loc: Loc) -> Result<()> {
3860        Err(FormatterError::InvalidParsedItem(loc))
3861    }
3862}
3863
3864/// An action which may be committed to a Formatter
3865struct Transaction<'f, 'a, W> {
3866    fmt: &'f mut Formatter<'a, W>,
3867    buffer: String,
3868    comments: Comments,
3869}
3870
3871impl<'a, W> std::ops::Deref for Transaction<'_, 'a, W> {
3872    type Target = Formatter<'a, W>;
3873    fn deref(&self) -> &Self::Target {
3874        self.fmt
3875    }
3876}
3877
3878impl<W> std::ops::DerefMut for Transaction<'_, '_, W> {
3879    fn deref_mut(&mut self) -> &mut Self::Target {
3880        self.fmt
3881    }
3882}
3883
3884impl<'f, 'a, W: Write> Transaction<'f, 'a, W> {
3885    /// Create a new transaction from a callback
3886    fn new(
3887        fmt: &'f mut Formatter<'a, W>,
3888        fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>,
3889    ) -> Result<Self> {
3890        let mut comments = fmt.comments.clone();
3891        let buffer = fmt.with_temp_buf(fun)?.w;
3892        comments = std::mem::replace(&mut fmt.comments, comments);
3893        Ok(Self { fmt, buffer, comments })
3894    }
3895
3896    /// Commit the transaction to the Formatter
3897    fn commit(self) -> Result<String> {
3898        self.fmt.comments = self.comments;
3899        write_chunk!(self.fmt, "{}", self.buffer)?;
3900        Ok(self.buffer)
3901    }
3902}