forge_fmt/
formatter.rs

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