solang_forge_fmt/
formatter.rs

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