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