Skip to main content

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