forge_fmt/state/
yul.rs

1#![allow(clippy::too_many_arguments)]
2
3use super::{
4    CommentConfig, State,
5    common::{BlockFormat, ListFormat},
6};
7use solar::parse::ast::{self, Span, yul};
8
9#[rustfmt::skip]
10macro_rules! get_span {
11    () => { |value| value.span };
12    (()) => { |value| value.span() };
13}
14
15/// Language-specific pretty printing: Yul.
16impl<'ast> State<'_, 'ast> {
17    pub(crate) fn print_yul_stmt(&mut self, stmt: &'ast yul::Stmt<'ast>) {
18        let yul::Stmt { ref docs, span, ref kind } = *stmt;
19        self.print_docs(docs);
20        if self.handle_span(span, false) {
21            return;
22        }
23
24        match kind {
25            yul::StmtKind::Block(stmts) => self.print_yul_block(stmts, span, false),
26            yul::StmtKind::AssignSingle(path, expr) => {
27                self.print_path(path, false);
28                self.word(" := ");
29                self.neverbreak();
30                self.print_yul_expr(expr);
31            }
32            yul::StmtKind::AssignMulti(paths, expr_call) => {
33                self.ibox(0);
34                self.commasep(
35                    paths,
36                    stmt.span.lo(),
37                    stmt.span.hi(),
38                    |this, path| this.print_path(path, false),
39                    get_span!(()),
40                    ListFormat::consistent(),
41                );
42                self.word(" :=");
43                self.space();
44                self.s.offset(self.ind);
45                self.ibox(0);
46                self.print_yul_expr(expr_call);
47                self.end();
48                self.end();
49            }
50            yul::StmtKind::Expr(expr_call) => self.print_yul_expr(expr_call),
51            yul::StmtKind::If(expr, stmts) => {
52                self.word("if ");
53                self.print_yul_expr(expr);
54                self.nbsp();
55                self.print_yul_block(stmts, span, false);
56            }
57            yul::StmtKind::For(yul::StmtFor { init, cond, step, body }) => {
58                self.ibox(0);
59
60                self.word("for ");
61                self.print_yul_block(init, init.span, false);
62
63                self.space();
64                self.print_yul_expr(cond);
65
66                self.space();
67                self.print_yul_block(step, step.span, false);
68
69                self.space();
70                self.print_yul_block(body, body.span, false);
71
72                self.end();
73            }
74            yul::StmtKind::Switch(yul::StmtSwitch { selector, cases }) => {
75                self.word("switch ");
76                self.print_yul_expr(selector);
77
78                self.print_trailing_comment(selector.span.hi(), None);
79
80                for yul::StmtSwitchCase { constant, body, span } in cases.iter() {
81                    self.hardbreak_if_not_bol();
82                    if let Some(constant) = constant {
83                        self.print_comments(
84                            constant.span.lo(),
85                            CommentConfig::default().mixed_prev_space(),
86                        );
87                        self.word("case ");
88                        self.print_lit(constant);
89                        self.nbsp();
90                    } else {
91                        self.print_comments(
92                            body.span.lo(),
93                            CommentConfig::default().mixed_prev_space(),
94                        );
95                        self.word("default ");
96                    }
97                    self.print_yul_block(body, *span, false);
98
99                    self.print_trailing_comment(selector.span.hi(), None);
100                }
101            }
102            yul::StmtKind::Leave => self.word("leave"),
103            yul::StmtKind::Break => self.word("break"),
104            yul::StmtKind::Continue => self.word("continue"),
105            yul::StmtKind::FunctionDef(func) => {
106                let yul::Function { name, parameters, returns, body } = func;
107                let params_hi = parameters
108                    .last()
109                    .map_or(returns.first().map_or(body.span.lo(), |r| r.span.lo()), |p| {
110                        p.span.hi()
111                    });
112
113                self.cbox(0);
114                self.s.ibox(0);
115                self.word("function ");
116                self.print_ident(name);
117                self.print_tuple(
118                    parameters,
119                    span.lo(),
120                    params_hi,
121                    Self::print_ident,
122                    get_span!(),
123                    ListFormat::consistent(),
124                );
125                self.nbsp();
126                let has_returns = !returns.is_empty();
127                let skip_opening_brace = has_returns;
128                if self.can_yul_header_params_be_inlined(func) {
129                    self.neverbreak();
130                }
131                if has_returns {
132                    self.commasep(
133                        returns,
134                        returns.first().map_or(params_hi, |ret| ret.span.lo()),
135                        returns.last().map_or(body.span.lo(), |ret| ret.span.hi()),
136                        Self::print_ident,
137                        get_span!(),
138                        ListFormat::yul(Some("->"), Some("{")),
139                    );
140                }
141                self.end();
142                self.print_yul_block(body, span, skip_opening_brace);
143                self.end();
144            }
145            yul::StmtKind::VarDecl(idents, expr) => {
146                self.s.ibox(self.ind);
147                self.word("let ");
148                self.commasep(
149                    idents,
150                    stmt.span.lo(),
151                    idents.last().map_or(stmt.span.lo(), |i| i.span.hi()),
152                    Self::print_ident,
153                    get_span!(),
154                    ListFormat::consistent(),
155                );
156                if let Some(expr) = expr {
157                    self.word(" :=");
158                    self.space();
159                    self.print_yul_expr(expr);
160                }
161                self.end();
162            }
163        }
164    }
165
166    fn print_yul_expr(&mut self, expr: &'ast yul::Expr<'ast>) {
167        let yul::Expr { span, ref kind } = *expr;
168        if self.handle_span(span, false) {
169            return;
170        }
171
172        match kind {
173            yul::ExprKind::Path(path) => self.print_path(path, false),
174            yul::ExprKind::Call(yul::ExprCall { name, arguments }) => {
175                self.print_ident(name);
176                self.print_tuple(
177                    arguments,
178                    span.lo(),
179                    span.hi(),
180                    |s, arg| s.print_yul_expr(arg),
181                    get_span!(),
182                    ListFormat::consistent().break_single(true),
183                );
184            }
185            yul::ExprKind::Lit(lit) => {
186                if matches!(&lit.kind, ast::LitKind::Address(_)) {
187                    self.print_span_cold(lit.span);
188                } else {
189                    self.print_lit(lit);
190                }
191            }
192        }
193    }
194
195    pub(super) fn print_yul_block(
196        &mut self,
197        block: &'ast yul::Block<'ast>,
198        span: Span,
199        skip_opening_brace: bool,
200    ) {
201        if self.handle_span(span, false) {
202            return;
203        }
204
205        if !skip_opening_brace {
206            self.print_word("{");
207        }
208
209        let can_inline_block = block.len() <= 1
210            && !self.is_multiline_yul_block(block)
211            && self.estimate_size(block.span) <= self.space_left();
212        if can_inline_block {
213            self.neverbreak();
214            self.print_block_inner(
215                block,
216                BlockFormat::NoBraces(None),
217                |s, stmt| {
218                    s.nbsp();
219                    s.print_yul_stmt(stmt);
220                    if s.peek_comment_before(stmt.span.hi()).is_none()
221                        && s.peek_trailing_comment(stmt.span.hi(), None).is_none()
222                    {
223                        s.nbsp();
224                    }
225                    s.print_comments(
226                        stmt.span.hi(),
227                        CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
228                    );
229                    if !s.last_token_is_space() {
230                        s.nbsp();
231                    }
232                },
233                |b| b.span,
234                span.hi(),
235            );
236        } else {
237            let (mut i, n_args) = (0, block.len().saturating_sub(1));
238            self.print_block_inner(
239                block,
240                BlockFormat::NoBraces(Some(self.ind)),
241                |s, stmt| {
242                    s.print_yul_stmt(stmt);
243                    s.print_comments(stmt.span.hi(), CommentConfig::default());
244                    if i != n_args {
245                        let next_span = block[i + 1].span;
246                        s.print_trailing_comment(stmt.span.hi(), Some(next_span.lo()));
247                        if !s.is_bol_or_only_ind() && !s.inline_config.is_disabled(stmt.span) {
248                            // when disabling a single line, manually add a nonbreaking line jump so
249                            // that the indentation of the disabled line is maintained.
250                            if s.inline_config.is_disabled(next_span)
251                                && s.peek_comment_before(next_span.lo())
252                                    .is_none_or(|cmnt| !cmnt.style.is_isolated())
253                            {
254                                s.word("\n");
255                            // otherwise, use a regular hardbreak
256                            } else {
257                                s.hardbreak_if_not_bol();
258                            }
259                        }
260                        i += 1;
261                    } else {
262                        s.print_trailing_comment(stmt.span.hi(), Some(span.hi()));
263                    }
264                },
265                |b| b.span,
266                span.hi(),
267            );
268        }
269        self.print_word("}");
270        self.print_trailing_comment(span.hi(), None);
271    }
272
273    /// Checks if a block statement `{ ... }` contains more than one line of actual code.
274    fn is_multiline_yul_block(&self, block: &'ast yul::Block<'ast>) -> bool {
275        if block.stmts.is_empty() {
276            return false;
277        }
278        if self.sm.is_multiline(block.span)
279            && let Ok(snip) = self.sm.span_to_snippet(block.span)
280        {
281            let code_lines = snip.lines().filter(|line| {
282                let trimmed = line.trim();
283                // Ignore empty lines and lines with only '{' or '}'
284                !trimmed.is_empty()
285            });
286            return code_lines.count() > 1;
287        }
288        false
289    }
290
291    fn estimate_yul_header_params_size(&mut self, func: &yul::Function<'_>) -> usize {
292        // '(' + param + (', ' + param) + ')'
293        let params = func
294            .parameters
295            .iter()
296            .fold(0, |len, p| if len != 0 { len + 2 } else { 2 } + self.estimate_size(p.span));
297
298        // 'function ' + name + ' ' + params + ' ->'
299        9 + self.estimate_size(func.name.span) + 1 + params + 3
300    }
301
302    fn can_yul_header_params_be_inlined(&mut self, func: &yul::Function<'_>) -> bool {
303        self.estimate_yul_header_params_size(func) <= self.space_left()
304    }
305}