1#![allow(clippy::too_many_arguments)]
2
3use super::{
4 ChainedNamedCall, CommentConfig, Separator, State,
5 common::{BlockFormat, ListFormat},
6};
7use crate::{
8 pp::SIZE_INFINITY,
9 state::{CallContext, common::LitExt},
10};
11use foundry_common::{comments::Comment, iter::IterDelimited};
12use foundry_config::fmt::{self as config, MultilineFuncHeaderStyle};
13use solar::{
14 ast::BoxSlice,
15 interface::SpannedOption,
16 parse::{
17 ast::{self, Span},
18 interface::BytePos,
19 },
20};
21use std::{collections::HashMap, fmt::Debug};
22
23#[rustfmt::skip]
24macro_rules! get_span {
25 () => { |value| value.span };
26 (()) => { |value| value.span() };
27}
28
29impl<'ast> State<'_, 'ast> {
31 pub(crate) fn print_source_unit(&mut self, source_unit: &'ast ast::SourceUnit<'ast>) {
32 if let Some(item) = source_unit.items.first() {
34 self.check_crlf(item.span.to(source_unit.items.last().unwrap().span));
35 }
36
37 let mut items = source_unit.items.iter().peekable();
38 let mut is_first = true;
39 while let Some(item) = items.next() {
40 if !self.config.sort_imports || !matches!(item.kind, ast::ItemKind::Import(_)) {
42 self.print_item(item, is_first);
43 is_first = false;
44 if let Some(next_item) = items.peek() {
45 self.separate_items(next_item, false);
46 }
47 continue;
48 }
49
50 let mut import_group = vec![item];
52 while let Some(next_item) = items.peek() {
53 if !matches!(next_item.kind, ast::ItemKind::Import(_))
55 || self.has_comment_between(item.span.hi(), next_item.span.lo())
56 {
57 break;
58 }
59 import_group.push(items.next().unwrap());
60 }
61
62 import_group.sort_by_key(|item| {
63 if let ast::ItemKind::Import(import) = &item.kind {
64 import.path.value.as_str()
65 } else {
66 unreachable!("Expected an import item")
67 }
68 });
69
70 for (pos, group_item) in import_group.iter().delimited() {
71 self.print_item(group_item, is_first);
72 is_first = false;
73
74 if !pos.is_last {
75 self.hardbreak_if_not_bol();
76 }
77 }
78 if let Some(next_item) = items.peek() {
79 self.separate_items(next_item, false);
80 }
81 }
82
83 self.print_remaining_comments(is_first);
84 }
85
86 fn separate_items(&mut self, next_item: &'ast ast::Item<'ast>, advance: bool) {
88 if !item_needs_iso(&next_item.kind) {
89 return;
90 }
91 let span = next_item.span;
92
93 let cmnts = self
94 .comments
95 .iter()
96 .filter_map(|c| (c.pos() < span.lo()).then_some(c.style))
97 .collect::<Vec<_>>();
98
99 if let Some(first) = cmnts.first()
100 && let Some(last) = cmnts.last()
101 {
102 if !(first.is_blank() || last.is_blank()) {
103 self.hardbreak();
104 return;
105 }
106 if advance {
107 if self.peek_comment_before(span.lo()).is_some() {
108 self.print_comments(span.lo(), CommentConfig::default());
109 } else if self.inline_config.is_disabled(span.shrink_to_lo()) {
110 self.hardbreak();
111 self.cursor.advance_to(span.lo(), true);
112 }
113 }
114 } else {
115 self.hardbreak();
116 }
117 }
118
119 fn print_item(&mut self, item: &'ast ast::Item<'ast>, skip_ws: bool) {
120 let ast::Item { ref docs, span, ref kind } = *item;
121 self.print_docs(docs);
122
123 if self.handle_span(item.span, skip_ws) {
124 if !self.print_trailing_comment(span.hi(), None) {
125 self.print_sep(Separator::Hardbreak);
126 }
127 return;
128 }
129
130 if self
131 .print_comments(
132 span.lo(),
133 if skip_ws {
134 CommentConfig::skip_leading_ws(false)
135 } else {
136 CommentConfig::default()
137 },
138 )
139 .is_some_and(|cmnt| cmnt.is_mixed())
140 {
141 self.zerobreak();
142 }
143
144 match kind {
145 ast::ItemKind::Pragma(pragma) => self.print_pragma(pragma),
146 ast::ItemKind::Import(import) => self.print_import(import),
147 ast::ItemKind::Using(using) => self.print_using(using),
148 ast::ItemKind::Contract(contract) => self.print_contract(contract, span),
149 ast::ItemKind::Function(func) => self.print_function(func),
150 ast::ItemKind::Variable(var) => self.print_var_def(var),
151 ast::ItemKind::Struct(strukt) => self.print_struct(strukt, span),
152 ast::ItemKind::Enum(enm) => self.print_enum(enm, span),
153 ast::ItemKind::Udvt(udvt) => self.print_udvt(udvt),
154 ast::ItemKind::Error(err) => self.print_error(err),
155 ast::ItemKind::Event(event) => self.print_event(event),
156 }
157
158 self.cursor.advance_to(span.hi(), true);
159 self.print_comments(span.hi(), CommentConfig::default());
160 self.print_trailing_comment(span.hi(), None);
161 self.hardbreak_if_not_bol();
162 self.cursor.next_line(self.is_at_crlf());
163 }
164
165 fn print_pragma(&mut self, pragma: &'ast ast::PragmaDirective<'ast>) {
166 self.word("pragma ");
167 match &pragma.tokens {
168 ast::PragmaTokens::Version(ident, semver_req) => {
169 self.print_ident(ident);
170 self.nbsp();
171 self.word(semver_req.to_string());
172 }
173 ast::PragmaTokens::Custom(a, b) => {
174 self.print_ident_or_strlit(a);
175 if let Some(b) = b {
176 self.nbsp();
177 self.print_ident_or_strlit(b);
178 }
179 }
180 ast::PragmaTokens::Verbatim(tokens) => {
181 self.print_tokens(tokens);
182 }
183 }
184 self.word(";");
185 }
186
187 fn print_commasep_aliases<'a, I>(&mut self, aliases: I)
188 where
189 I: Iterator<Item = &'a (ast::Ident, Option<ast::Ident>)>,
190 'ast: 'a,
191 {
192 for (pos, (ident, alias)) in aliases.delimited() {
193 self.print_ident(ident);
194 if let Some(alias) = alias {
195 self.word(" as ");
196 self.print_ident(alias);
197 }
198 if !pos.is_last {
199 self.word(",");
200 self.space();
201 }
202 }
203 }
204
205 fn print_import(&mut self, import: &'ast ast::ImportDirective<'ast>) {
206 let ast::ImportDirective { path, items } = import;
207 self.word("import ");
208
209 use ast::ImportItems;
210 use config::NamespaceImportStyle as NIStyle;
211
212 match (items, self.config.namespace_import_style) {
213 (ImportItems::Plain(None), _) => {
214 self.print_ast_str_lit(path);
215 }
216
217 (ImportItems::Plain(Some(source_alias)), NIStyle::Preserve | NIStyle::PreferPlain)
218 | (ImportItems::Glob(source_alias), NIStyle::PreferPlain) => {
219 self.print_ast_str_lit(path);
220 self.word(" as ");
221 self.print_ident(source_alias);
222 }
223
224 (ImportItems::Glob(source_alias), NIStyle::Preserve | NIStyle::PreferGlob)
225 | (ImportItems::Plain(Some(source_alias)), NIStyle::PreferGlob) => {
226 self.word("*");
227 self.word(" as ");
228 self.print_ident(source_alias);
229 self.word(" from ");
230 self.print_ast_str_lit(path);
231 }
232
233 (ImportItems::Aliases(aliases), _) => {
234 let use_single_line = self.config.single_line_imports && aliases.len() == 1;
236
237 if use_single_line {
238 self.word("{");
239 if self.config.bracket_spacing {
240 self.nbsp();
241 }
242 } else {
243 self.s.cbox(self.ind);
244 self.word("{");
245 self.braces_break();
246 }
247
248 if self.config.sort_imports {
249 let mut sorted: Vec<_> = aliases.iter().collect();
250 sorted.sort_by_key(|(ident, _alias)| ident.name.as_str());
251 self.print_commasep_aliases(sorted.into_iter());
252 } else {
253 self.print_commasep_aliases(aliases.iter());
254 };
255
256 if use_single_line {
257 if self.config.bracket_spacing {
258 self.nbsp();
259 }
260 self.word("}");
261 } else {
262 self.braces_break();
263 self.s.offset(-self.ind);
264 self.word("}");
265 self.end();
266 }
267 self.word(" from ");
268 self.print_ast_str_lit(path);
269 }
270 }
271 self.word(";");
272 }
273
274 fn print_using(&mut self, using: &'ast ast::UsingDirective<'ast>) {
275 let ast::UsingDirective { list, ty, global } = using;
276 self.word("using ");
277 match list {
278 ast::UsingList::Single(path) => self.print_path(path, true),
279 ast::UsingList::Multiple(items) => {
280 self.s.cbox(self.ind);
281 self.word("{");
282 self.braces_break();
283 for (pos, (path, op)) in items.iter().delimited() {
284 self.print_path(path, true);
285 if let Some(op) = op {
286 self.word(" as ");
287 self.word(op.to_str());
288 }
289 if !pos.is_last {
290 self.word(",");
291 self.space();
292 }
293 }
294 self.braces_break();
295 self.s.offset(-self.ind);
296 self.word("}");
297 self.end();
298 }
299 }
300 self.word(" for ");
301 if let Some(ty) = ty {
302 self.print_ty(ty);
303 } else {
304 self.word("*");
305 }
306 if *global {
307 self.word(" global");
308 }
309 self.word(";");
310 }
311
312 fn print_contract(&mut self, c: &'ast ast::ItemContract<'ast>, span: Span) {
313 let ast::ItemContract { kind, name, layout, bases, body } = c;
314 self.contract = Some(c);
315 self.cursor.advance_to(span.lo(), true);
316
317 self.s.cbox(self.ind);
318 self.ibox(0);
319 self.cbox(0);
320 self.word_nbsp(kind.to_str());
321 self.print_ident(name);
322 self.nbsp();
323
324 if let Some(layout) = layout
325 && !self.handle_span(layout.span, false)
326 {
327 self.word("layout at ");
328 self.print_expr(layout.slot);
329 self.print_sep(Separator::Space);
330 }
331
332 if let Some(first) = bases.first().map(|base| base.span())
333 && let Some(last) = bases.last().map(|base| base.span())
334 && self.inline_config.is_disabled(first.to(last))
335 {
336 _ = self.handle_span(first.until(last), false);
337 } else if !bases.is_empty() {
338 self.word("is");
339 self.space();
340 let last = bases.len() - 1;
341 for (i, base) in bases.iter().enumerate() {
342 if !self.handle_span(base.span(), false) {
343 self.print_modifier_call(base, false);
344 if i != last {
345 self.word(",");
346 if self
347 .print_comments(
348 bases[i + 1].span().lo(),
349 CommentConfig::skip_ws().mixed_prev_space().mixed_post_nbsp(),
350 )
351 .is_none()
352 {
353 self.space();
354 }
355 }
356 }
357 }
358 if !self.print_trailing_comment(bases.last().unwrap().span().hi(), None) {
359 self.space();
360 }
361 self.s.offset(-self.ind);
362 }
363 self.end();
364
365 self.print_word("{");
366 self.end();
367 if body.is_empty() {
368 if self.print_comments(span.hi(), CommentConfig::skip_ws()).is_some() {
369 self.s.offset(-self.ind);
372 } else if self.config.bracket_spacing {
373 self.nbsp();
374 };
375 self.end();
376 } else {
377 self.block_depth += 1;
379
380 self.print_sep(Separator::Hardbreak);
381 if self.config.contract_new_lines {
382 self.hardbreak();
383 }
384 let body_lo = body[0].span.lo();
385 if self.peek_comment_before(body_lo).is_some() {
386 self.print_comments(body_lo, CommentConfig::skip_leading_ws(true));
387 }
388
389 let mut is_first = true;
390 let mut items = body.iter().peekable();
391 while let Some(item) = items.next() {
392 self.print_item(item, is_first);
393 is_first = false;
394 if let Some(next_item) = items.peek() {
395 if self.inline_config.is_disabled(next_item.span) {
396 _ = self.handle_span(next_item.span, false);
397 } else {
398 self.separate_items(next_item, true);
399 }
400 }
401 }
402
403 if let Some(cmnt) = self.print_comments(span.hi(), CommentConfig::skip_trailing_ws())
404 && self.config.contract_new_lines
405 && !cmnt.is_blank()
406 {
407 self.print_sep(Separator::Hardbreak);
408 }
409 self.s.offset(-self.ind);
410 self.end();
411 if self.config.contract_new_lines {
412 self.hardbreak_if_nonempty();
413 }
414
415 self.block_depth -= 1;
417 }
418 self.print_word("}");
419
420 self.cursor.advance_to(span.hi(), true);
421 self.contract = None;
422 }
423
424 fn print_struct(&mut self, strukt: &'ast ast::ItemStruct<'ast>, span: Span) {
425 let ast::ItemStruct { name, fields } = strukt;
426 let ind = if self.estimate_size(name.span) + 8 >= self.space_left() { self.ind } else { 0 };
427 self.s.ibox(self.ind);
428 self.word("struct");
429 self.space();
430 self.print_ident(name);
431 self.word(" {");
432 if !fields.is_empty() {
433 self.break_offset(SIZE_INFINITY as usize, ind);
434 }
435 self.s.ibox(0);
436 for var in fields.iter() {
437 self.print_var_def(var);
438 if !self.print_trailing_comment(var.span.hi(), None) {
439 self.hardbreak();
440 }
441 }
442 self.print_comments(span.hi(), CommentConfig::skip_ws());
443 if ind == 0 {
444 self.s.offset(-self.ind);
445 }
446 self.end();
447 self.end();
448 self.word("}");
449 }
450
451 fn print_enum(&mut self, enm: &'ast ast::ItemEnum<'ast>, span: Span) {
452 let ast::ItemEnum { name, variants } = enm;
453 self.s.cbox(self.ind);
454 self.word("enum ");
455 self.print_ident(name);
456 self.word(" {");
457 self.hardbreak_if_nonempty();
458 for (pos, ident) in variants.iter().delimited() {
459 self.print_comments(ident.span.lo(), CommentConfig::default());
460 self.print_ident(ident);
461 if !pos.is_last {
462 self.word(",");
463 }
464 if !self.print_trailing_comment(ident.span.hi(), None) {
465 self.hardbreak();
466 }
467 }
468 self.print_comments(span.hi(), CommentConfig::skip_ws());
469 self.s.offset(-self.ind);
470 self.end();
471 self.word("}");
472 }
473
474 fn print_udvt(&mut self, udvt: &'ast ast::ItemUdvt<'ast>) {
475 let ast::ItemUdvt { name, ty } = udvt;
476 self.word("type ");
477 self.print_ident(name);
478 self.word(" is ");
479 self.print_ty(ty);
480 self.word(";");
481 }
482
483 fn print_function(&mut self, func: &'ast ast::ItemFunction<'ast>) {
485 let ast::ItemFunction { kind, ref header, ref body, body_span } = *func;
486 let ast::FunctionHeader {
487 name,
488 ref parameters,
489 visibility,
490 state_mutability: sm,
491 virtual_,
492 ref override_,
493 ref returns,
494 ..
495 } = *header;
496
497 self.s.cbox(self.ind);
498
499 _ = self.handle_span(self.cursor.span(header.span.lo()), false);
501 self.print_word(kind.to_str());
502 if let Some(name) = name {
503 self.print_sep(Separator::Nbsp);
504 self.print_ident(&name);
505 self.cursor.advance_to(name.span.hi(), true);
506 }
507 self.s.cbox(-self.ind);
508 let header_style = self.config.multiline_func_header;
509 let params_format = match header_style {
510 MultilineFuncHeaderStyle::ParamsAlways => ListFormat::always_break(),
511 MultilineFuncHeaderStyle::All
512 if header.parameters.len() > 1 && !self.can_header_be_inlined(func) =>
513 {
514 ListFormat::always_break()
515 }
516 MultilineFuncHeaderStyle::AllParams
517 if !header.parameters.is_empty() && !self.can_header_be_inlined(func) =>
518 {
519 ListFormat::always_break()
520 }
521 _ => ListFormat::consistent().break_cmnts().break_single(
522 parameters.len() == 1
524 && matches!(
525 ¶meters[0].ty,
526 ast::Type { kind: ast::TypeKind::Custom(ty), .. } if ty.segments().len() > 1
527 ),
528 ),
529 };
530 self.print_parameter_list(parameters, parameters.span, params_format);
531 self.end();
532
533 let (mut map, attributes, first_attrib_pos) =
535 AttributeCommentMapper::new(returns.as_ref(), body_span.lo()).build(self, header);
536
537 let mut handle_pre_cmnts = |this: &mut Self, span: Span| -> bool {
538 if this.inline_config.is_disabled(span)
539 && let Some((pre_cmnts, ..)) = map.remove(&span.lo())
541 {
542 for (pos, cmnt) in pre_cmnts.into_iter().delimited() {
543 if pos.is_first && cmnt.style.is_isolated() && !this.is_bol_or_only_ind() {
544 this.print_sep(Separator::Hardbreak);
545 }
546 if let Some(cmnt) = this.handle_comment(cmnt, false) {
547 this.print_comment(cmnt, CommentConfig::skip_ws().mixed_post_nbsp());
548 }
549 if pos.is_last {
550 return true;
551 }
552 }
553 }
554 false
555 };
556
557 let skip_attribs = returns.as_ref().is_some_and(|ret| {
558 let attrib_span = Span::new(first_attrib_pos, ret.span.lo());
559 handle_pre_cmnts(self, attrib_span);
560 self.handle_span(attrib_span, false)
561 });
562 let skip_returns = {
563 let pos = if skip_attribs { self.cursor.pos } else { first_attrib_pos };
564 let ret_span = Span::new(pos, body_span.lo());
565 handle_pre_cmnts(self, ret_span);
566 self.handle_span(ret_span, false)
567 };
568
569 let attrib_box = self.config.multiline_func_header.params_first()
570 || (self.config.multiline_func_header.attrib_first()
571 && !self.can_header_params_be_inlined(func));
572 if attrib_box {
573 self.s.cbox(0);
574 }
575 if !(skip_attribs || skip_returns) {
576 if let Some(v) = visibility {
578 self.print_fn_attribute(v.span, &mut map, &mut |s| s.word(v.to_str()));
579 }
580 if let Some(sm) = sm
581 && !matches!(*sm, ast::StateMutability::NonPayable)
582 {
583 self.print_fn_attribute(sm.span, &mut map, &mut |s| s.word(sm.to_str()));
584 }
585 if let Some(v) = virtual_ {
586 self.print_fn_attribute(v, &mut map, &mut |s| s.word("virtual"));
587 }
588 if let Some(o) = override_ {
589 self.print_fn_attribute(o.span, &mut map, &mut |s| s.print_override(o));
590 }
591 for m in attributes.iter().filter(|a| matches!(a.kind, AttributeKind::Modifier(_))) {
592 if let AttributeKind::Modifier(modifier) = m.kind {
593 let is_base = self.is_modifier_a_base_contract(kind, modifier);
594 self.print_fn_attribute(m.span, &mut map, &mut |s| {
595 s.print_modifier_call(modifier, is_base)
596 });
597 }
598 }
599 }
600 if !skip_returns
601 && let Some(ret) = returns
602 && !ret.is_empty()
603 {
604 if !self.handle_span(self.cursor.span(ret.span.lo()), false) {
605 if !self.is_bol_or_only_ind() && !self.last_token_is_space() {
606 self.print_sep(Separator::Space);
607 }
608 self.cursor.advance_to(ret.span.lo(), true);
609 self.print_word("returns ");
610 }
611 self.print_parameter_list(
612 ret,
613 ret.span,
614 ListFormat::consistent(), );
616 }
617
618 if let Some(body) = body {
620 if self.handle_span(self.cursor.span(body_span.lo()), false) {
621 } else {
623 if let Some(cmnt) = self.peek_comment_before(body_span.lo()) {
624 if cmnt.style.is_mixed() {
625 self.space();
627 self.s.offset(-self.ind);
628 self.print_comments(body_span.lo(), CommentConfig::skip_ws());
629 } else {
630 self.zerobreak();
631 self.s.offset(-self.ind);
632 self.print_comments(body_span.lo(), CommentConfig::skip_ws());
633 self.s.offset(-self.ind);
634 }
635 } else {
636 if header.modifiers.is_empty()
638 && header.override_.is_none()
639 && returns.as_ref().is_none_or(|r| r.is_empty())
640 && (header.visibility().is_none() || body.is_empty())
641 {
642 self.nbsp();
643 } else {
644 self.space();
645 self.s.offset(-self.ind);
646 }
647 }
648 self.cursor.advance_to(body_span.lo(), true);
649 }
650 self.print_word("{");
651 self.end();
652 if attrib_box {
653 self.end();
654 }
655
656 self.print_block_without_braces(body, body_span.hi(), Some(self.ind));
657 if self.cursor.enabled || self.cursor.pos < body_span.hi() {
658 self.print_word("}");
659 self.cursor.advance_to(body_span.hi(), true);
660 }
661 } else {
662 self.print_comments(body_span.lo(), CommentConfig::skip_ws().mixed_prev_space());
663 self.end();
664 if attrib_box {
665 self.end();
666 }
667 self.neverbreak();
668 self.print_word(";");
669 }
670
671 if let Some(cmnt) = self.peek_trailing_comment(body_span.hi(), None) {
672 if cmnt.is_doc {
673 self.hardbreak();
676 self.hardbreak();
677 }
678 self.print_trailing_comment(body_span.hi(), None);
679 }
680 }
681
682 fn print_fn_attribute(
683 &mut self,
684 span: Span,
685 map: &mut AttributeCommentMap,
686 print_fn: &mut dyn FnMut(&mut Self),
687 ) {
688 match map.remove(&span.lo()) {
689 Some((pre_cmnts, inner_cmnts, post_cmnts)) => {
690 for cmnt in pre_cmnts {
692 let Some(cmnt) = self.handle_comment(cmnt, false) else {
693 continue;
694 };
695 self.print_comment(cmnt, CommentConfig::default());
696 }
697 for cmnt in inner_cmnts.into_iter().rev() {
700 self.comments.push_front(cmnt);
701 }
702 let enabled = if self.handle_span(span, false) {
703 false
704 } else {
705 if !self.is_bol_or_only_ind() {
706 self.space();
707 }
708 self.ibox(0);
709 print_fn(self);
710 self.cursor.advance_to(span.hi(), true);
711 true
712 };
713 for cmnt in post_cmnts {
715 let Some(cmnt) = self.handle_comment(cmnt, false) else {
716 continue;
717 };
718 self.print_comment(cmnt, CommentConfig::default().mixed_prev_space());
719 }
720 if enabled {
721 self.end();
722 }
723 }
724 None => {
726 if !self.is_bol_or_only_ind() {
727 self.space();
728 }
729 print_fn(self);
730 self.cursor.advance_to(span.hi(), true);
731 }
732 }
733 }
734
735 fn is_modifier_a_base_contract(
736 &self,
737 kind: ast::FunctionKind,
738 modifier: &'ast ast::Modifier<'ast>,
739 ) -> bool {
740 let is_contract_base = self.contract.is_some_and(|contract| {
745 contract
746 .bases
747 .iter()
748 .any(|contract_base| contract_base.name.to_string() == modifier.name.to_string())
749 });
750 let is_constructor = matches!(kind, ast::FunctionKind::Constructor);
753 is_contract_base
755 || (is_constructor
756 && modifier.name.first().name.as_str().starts_with(char::is_uppercase))
757 }
758
759 fn print_error(&mut self, err: &'ast ast::ItemError<'ast>) {
760 let ast::ItemError { name, parameters } = err;
761 self.word("error ");
762 self.print_ident(name);
763 self.print_parameter_list(
764 parameters,
765 parameters.span,
766 if self.config.prefer_compact.errors() {
767 ListFormat::compact()
768 } else {
769 ListFormat::consistent()
770 },
771 );
772 self.word(";");
773 }
774
775 fn print_event(&mut self, event: &'ast ast::ItemEvent<'ast>) {
776 let ast::ItemEvent { name, parameters, anonymous } = event;
777 self.word("event ");
778 self.print_ident(name);
779 self.print_parameter_list(
780 parameters,
781 parameters.span,
782 if self.config.prefer_compact.events() {
783 ListFormat::compact().break_cmnts()
784 } else {
785 ListFormat::consistent().break_cmnts()
786 },
787 );
788 if *anonymous {
789 self.word(" anonymous");
790 }
791 self.word(";");
792 }
793
794 fn print_var_def(&mut self, var: &'ast ast::VariableDefinition<'ast>) {
795 self.print_var(var, true);
796 self.word(";");
797 }
798
799 fn print_assign_rhs(
801 &mut self,
802 rhs: &'ast ast::Expr<'ast>,
803 lhs_size: usize,
804 space_left: usize,
805 ty: Option<&ast::TypeKind<'ast>>,
806 cache: bool,
807 ) {
808 let rhs_size = self.estimate_size(rhs.span);
811 let overflows = lhs_size + rhs_size >= space_left;
812 let fits_alone = rhs_size + self.config.tab_width < space_left;
813 let fits_alone_no_cmnts =
814 fits_alone && !self.has_comment_between(rhs.span.lo(), rhs.span.hi());
815 let force_break = overflows && fits_alone_no_cmnts;
816
817 if lhs_size <= space_left {
818 self.neverbreak();
819 }
820
821 if let Some(cmnt) = self.peek_comment_before(rhs.span.lo())
823 && self.inline_config.is_disabled(cmnt.span)
824 {
825 self.print_sep(Separator::Nbsp);
826 }
827 if self
828 .print_comments(
829 rhs.span.lo(),
830 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
831 )
832 .is_some_and(|cmnt| cmnt.is_trailing())
833 {
834 self.break_offset_if_not_bol(SIZE_INFINITY as usize, self.ind, false);
835 }
836
837 match &rhs.kind {
839 ast::ExprKind::Lit(lit, ..) if lit.is_str_concatenation() => {
840 self.print_sep(Separator::Nbsp);
842 self.neverbreak();
843 self.s.ibox(self.ind);
844 self.print_expr(rhs);
845 self.end();
846 }
847 ast::ExprKind::Lit(..) if ty.is_none() && !fits_alone => {
848 self.print_sep(Separator::Space);
850 self.s.offset(self.ind);
851 self.print_expr(rhs);
852 }
853 ast::ExprKind::Binary(lhs, op, _) => {
854 let print_inline = |this: &mut Self| {
855 this.print_sep(Separator::Nbsp);
856 this.neverbreak();
857 this.print_expr(rhs);
858 };
859 let print_with_break = |this: &mut Self, force_break: bool| {
860 if !this.is_bol_or_only_ind() {
861 if force_break {
862 this.print_sep(Separator::Hardbreak);
863 } else {
864 this.print_sep(Separator::Space);
865 }
866 }
867 this.s.offset(this.ind);
868 this.s.ibox(this.ind);
869 this.print_expr(rhs);
870 this.end();
871 };
872
873 if force_break {
875 print_with_break(self, true);
876 } else if self.estimate_lhs_size(rhs, op) + lhs_size > space_left {
877 if has_complex_successor(&rhs.kind, true)
878 && get_callee_head_size(lhs) + lhs_size <= space_left
879 {
880 if matches!(lhs.kind, ast::ExprKind::Call(..)) {
882 self.s.ibox(-self.ind);
883 print_inline(self);
884 self.end();
885 } else {
886 print_inline(self);
887 }
888 } else {
889 print_with_break(self, false);
890 }
891 }
892 else {
894 print_inline(self);
895 }
896 }
897 _ => {
898 let callee_doesnt_fit = if let ast::ExprKind::Call(call_expr, ..) = &rhs.kind {
900 let callee_size = get_callee_head_size(call_expr);
901 callee_size + lhs_size > space_left
902 && callee_size + self.config.tab_width < space_left
903 } else {
904 false
905 };
906
907 if (lhs_size + 1 >= space_left && !is_call_chain(&rhs.kind, false))
908 || callee_doesnt_fit
909 {
910 self.s.ibox(self.ind);
911 } else {
912 self.s.ibox(0);
913 };
914
915 if has_complex_successor(&rhs.kind, true)
916 && !matches!(&rhs.kind, ast::ExprKind::Member(..))
917 {
918 if !self.is_bol_or_only_ind() {
920 let needs_offset = !callee_doesnt_fit
921 && rhs_size + lhs_size + 1 >= space_left
922 && fits_alone_no_cmnts;
923 let separator = if callee_doesnt_fit || needs_offset {
924 Separator::Space
925 } else {
926 Separator::Nbsp
927 };
928 self.print_sep(separator);
929 if needs_offset {
930 self.s.offset(self.ind);
931 }
932 }
933 } else {
934 if !self.is_bol_or_only_ind() {
935 self.print_sep_unhandled(Separator::Space);
936 }
937 if let Some(ty) = ty
939 && matches!(ty, ast::TypeKind::Elementary(..) | ast::TypeKind::Mapping(..))
940 {
941 self.s.offset(self.ind);
942 }
943 }
944 self.print_expr(rhs);
945 self.end();
946 }
947 }
948
949 self.var_init = cache;
950 }
951
952 fn print_var(&mut self, var: &'ast ast::VariableDefinition<'ast>, is_var_def: bool) {
953 let ast::VariableDefinition {
954 span,
955 ty,
956 visibility,
957 mutability,
958 data_location,
959 override_,
960 indexed,
961 name,
962 initializer,
963 } = var;
964
965 if self.handle_span(*span, false) {
966 return;
967 }
968
969 let init_space_left = self.space_left();
974 let mut pre_init_size = self.estimate_size(ty.span);
975
976 self.s.ibox(0);
978 if override_.is_some() {
979 self.s.cbox(self.ind);
980 } else {
981 self.s.ibox(self.ind);
982 }
983 self.print_ty(ty);
984
985 self.print_attribute(visibility.map(|v| v.to_str()), is_var_def, &mut pre_init_size);
986 self.print_attribute(mutability.map(|m| m.to_str()), is_var_def, &mut pre_init_size);
987 self.print_attribute(data_location.map(|d| d.to_str()), is_var_def, &mut pre_init_size);
988
989 if let Some(override_) = override_ {
990 if self
991 .print_comments(override_.span.lo(), CommentConfig::skip_ws().mixed_prev_space())
992 .is_none()
993 {
994 self.print_sep(Separator::SpaceOrNbsp(is_var_def));
995 }
996 self.ibox(0);
997 self.print_override(override_);
998 pre_init_size += self.estimate_size(override_.span) + 1;
999 }
1000
1001 if *indexed {
1002 self.print_attribute(indexed.then_some("indexed"), is_var_def, &mut pre_init_size);
1003 }
1004
1005 if let Some(ident) = name {
1006 self.print_sep(Separator::SpaceOrNbsp(is_var_def && override_.is_none()));
1007 self.print_comments(
1008 ident.span.lo(),
1009 CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
1010 );
1011 self.print_ident(ident);
1012 pre_init_size += self.estimate_size(ident.span) + 1;
1013 }
1014 if let Some(init) = initializer {
1015 let cache = self.var_init;
1016 self.var_init = true;
1017
1018 pre_init_size += 2;
1019 self.print_word(" =");
1020 if override_.is_some() {
1021 self.end();
1022 }
1023 self.end();
1024
1025 self.print_assign_rhs(init, pre_init_size, init_space_left, Some(&ty.kind), cache);
1026 } else {
1027 if override_.is_some() {
1028 self.end();
1029 }
1030 self.end();
1031 }
1032 self.end();
1033 }
1034
1035 fn print_attribute(
1036 &mut self,
1037 attribute: Option<&'static str>,
1038 is_var_def: bool,
1039 size: &mut usize,
1040 ) {
1041 if let Some(s) = attribute {
1042 self.print_sep(Separator::SpaceOrNbsp(is_var_def));
1043 self.print_word(s);
1044 *size += s.len() + 1;
1045 }
1046 }
1047
1048 fn print_parameter_list(
1049 &mut self,
1050 parameters: &'ast [ast::VariableDefinition<'ast>],
1051 span: Span,
1052 format: ListFormat,
1053 ) {
1054 if self.handle_span(span, false) {
1055 return;
1056 }
1057
1058 self.print_tuple(
1059 parameters,
1060 span.lo(),
1061 span.hi(),
1062 |fmt, var| fmt.print_var(var, false),
1063 get_span!(),
1064 format,
1065 );
1066 }
1067
1068 fn print_ident_or_strlit(&mut self, value: &'ast ast::IdentOrStrLit) {
1069 match value {
1070 ast::IdentOrStrLit::Ident(ident) => self.print_ident(ident),
1071 ast::IdentOrStrLit::StrLit(strlit) => self.print_ast_str_lit(strlit),
1072 }
1073 }
1074
1075 fn print_ast_str_lit(&mut self, strlit: &'ast ast::StrLit) {
1077 self.print_str_lit(ast::StrKind::Str, strlit.span.lo(), strlit.value.as_str());
1078 }
1079
1080 fn print_lit(&mut self, lit: &'ast ast::Lit<'ast>) {
1081 self.print_lit_inner(lit, false);
1082 }
1083
1084 fn print_ty(&mut self, ty: &'ast ast::Type<'ast>) {
1085 if self.handle_span(ty.span, false) {
1086 return;
1087 }
1088
1089 match &ty.kind {
1090 &ast::TypeKind::Elementary(ty) => 'b: {
1091 match ty {
1092 ast::ElementaryType::Address(true) => {
1094 self.word("address payable");
1095 break 'b;
1096 }
1097 ast::ElementaryType::Int(size) | ast::ElementaryType::UInt(size) => {
1099 match (self.config.int_types, size.bits_raw()) {
1100 (config::IntTypes::Short, 0 | 256)
1101 | (config::IntTypes::Preserve, 0) => {
1102 let short = match ty {
1103 ast::ElementaryType::Int(_) => "int",
1104 ast::ElementaryType::UInt(_) => "uint",
1105 _ => unreachable!(),
1106 };
1107 self.word(short);
1108 break 'b;
1109 }
1110 _ => {}
1111 }
1112 }
1113 _ => {}
1114 }
1115 self.word(ty.to_abi_str());
1116 }
1117 ast::TypeKind::Array(ast::TypeArray { element, size }) => {
1118 self.print_ty(element);
1119 if let Some(size) = size {
1120 self.word("[");
1121 self.print_expr(size);
1122 self.word("]");
1123 } else {
1124 self.word("[]");
1125 }
1126 }
1127 ast::TypeKind::Function(ast::TypeFunction {
1128 parameters,
1129 visibility,
1130 state_mutability,
1131 returns,
1132 }) => {
1133 self.cbox(0);
1134 self.word("function");
1135 self.print_parameter_list(parameters, parameters.span, ListFormat::inline());
1136
1137 if let Some(v) = visibility {
1138 self.space();
1139 self.word(v.to_str());
1140 }
1141 if let Some(sm) = state_mutability
1142 && !matches!(**sm, ast::StateMutability::NonPayable)
1143 {
1144 self.space();
1145 self.word(sm.to_str());
1146 }
1147 if let Some(ret) = returns
1148 && !ret.is_empty()
1149 {
1150 self.nbsp();
1151 self.word("returns");
1152 self.nbsp();
1153 self.print_parameter_list(
1154 ret,
1155 ret.span,
1156 ListFormat::consistent(), );
1158 }
1159 self.end();
1160 }
1161 ast::TypeKind::Mapping(ast::TypeMapping { key, key_name, value, value_name }) => {
1162 self.word("mapping(");
1163 self.s.cbox(0);
1164 if let Some(cmnt) = self.peek_comment_before(key.span.lo()) {
1165 if cmnt.style.is_mixed() {
1166 self.print_comments(
1167 key.span.lo(),
1168 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1169 );
1170 self.break_offset_if_not_bol(SIZE_INFINITY as usize, 0, false);
1171 } else {
1172 self.print_comments(key.span.lo(), CommentConfig::skip_ws());
1173 }
1174 }
1175 else if 18
1179 + self.estimate_size(key.span)
1180 + key_name.map(|k| self.estimate_size(k.span)).unwrap_or(0)
1181 + self.estimate_size(value.span)
1182 + value_name.map(|v| self.estimate_size(v.span)).unwrap_or(0)
1183 >= self.space_left()
1184 {
1185 self.hardbreak();
1186 } else {
1187 self.zerobreak();
1188 }
1189 self.s.cbox(0);
1190 self.print_ty(key);
1191 if let Some(ident) = key_name {
1192 if self
1193 .print_comments(
1194 ident.span.lo(),
1195 CommentConfig::skip_ws()
1196 .mixed_no_break()
1197 .mixed_prev_space()
1198 .mixed_post_nbsp(),
1199 )
1200 .is_none()
1201 {
1202 self.nbsp();
1203 }
1204 self.print_ident(ident);
1205 }
1206 self.print_comments(
1209 value.span.lo(),
1210 CommentConfig::skip_ws()
1211 .trailing_no_break()
1212 .mixed_no_break()
1213 .mixed_prev_space(),
1214 );
1215 self.space();
1216 self.s.offset(self.ind);
1217 self.word("=> ");
1218 self.s.ibox(self.ind);
1219 self.print_ty(value);
1220 if let Some(ident) = value_name {
1221 self.neverbreak();
1222 if self
1223 .print_comments(
1224 ident.span.lo(),
1225 CommentConfig::skip_ws()
1226 .mixed_no_break()
1227 .mixed_prev_space()
1228 .mixed_post_nbsp(),
1229 )
1230 .is_none()
1231 {
1232 self.nbsp();
1233 }
1234 self.print_ident(ident);
1235 if self
1236 .peek_comment_before(ty.span.hi())
1237 .is_some_and(|cmnt| cmnt.style.is_mixed())
1238 {
1239 self.neverbreak();
1240 self.print_comments(
1241 value.span.lo(),
1242 CommentConfig::skip_ws().mixed_no_break(),
1243 );
1244 }
1245 }
1246 self.end();
1247 self.end();
1248 if self
1249 .print_comments(
1250 ty.span.hi(),
1251 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1252 )
1253 .is_some_and(|cmnt| !cmnt.is_mixed())
1254 {
1255 self.break_offset_if_not_bol(0, -self.ind, false);
1256 } else {
1257 self.zerobreak();
1258 self.s.offset(-self.ind);
1259 }
1260 self.end();
1261 self.word(")");
1262 }
1263 ast::TypeKind::Custom(path) => self.print_path(path, false),
1264 }
1265 }
1266
1267 fn print_override(&mut self, override_: &'ast ast::Override<'ast>) {
1268 let ast::Override { span, paths } = override_;
1269 if self.handle_span(*span, false) {
1270 return;
1271 }
1272 self.word("override");
1273 if !paths.is_empty() {
1274 if self.config.override_spacing {
1275 self.nbsp();
1276 }
1277 self.print_tuple(
1278 paths,
1279 span.lo(),
1280 span.hi(),
1281 |this, path| this.print_path(path, false),
1282 get_span!(()),
1283 ListFormat::consistent(), );
1285 }
1286 }
1287
1288 fn print_expr(&mut self, expr: &'ast ast::Expr<'ast>) {
1292 let ast::Expr { span, ref kind } = *expr;
1293 if self.handle_span(span, false) {
1294 return;
1295 }
1296
1297 match kind {
1298 ast::ExprKind::Array(exprs) => {
1299 self.print_array(exprs, expr.span, |this, e| this.print_expr(e), get_span!())
1300 }
1301 ast::ExprKind::Assign(lhs, None, rhs) => self.print_assign_expr(lhs, rhs),
1302 ast::ExprKind::Assign(lhs, Some(op), rhs) => self.print_bin_expr(lhs, op, rhs, true),
1303 ast::ExprKind::Binary(lhs, op, rhs) => self.print_bin_expr(lhs, op, rhs, false),
1304 ast::ExprKind::Call(call_expr, call_args) => {
1305 let cache = self.call_with_opts_and_args;
1306 let chained_named_call_cache = self.chained_named_call;
1307 let keep_inline = chained_named_call_cache
1310 .is_some_and(|call| call.keep_inline && call.callee.contains(expr.span))
1311 && !self.has_comments_between_elements(call_args.span, call_args.exprs());
1312 self.call_with_opts_and_args = is_call_with_opts_and_args(&expr.kind);
1313 let named_args_size = if call_args.is_empty() {
1314 4 + usize::from(self.config.bracket_spacing)
1315 } else {
1316 2
1317 };
1318 self.chained_named_call = (matches!(call_args.kind, ast::CallArgsKind::Named(_))
1319 && is_call_chain(&call_expr.kind, true))
1320 .then(|| ChainedNamedCall {
1321 callee: call_expr.span,
1322 keep_inline: !call_chain_contains_options(call_expr)
1323 && !self.has_comment_between(call_expr.span.lo(), call_expr.span.hi())
1324 && self
1325 .estimate_call_chain_size(call_expr)
1326 .is_some_and(|size| size + named_args_size <= self.space_left()),
1327 })
1328 .or_else(|| {
1329 chained_named_call_cache.filter(|call| call.callee.contains(expr.span))
1330 });
1331 let list_format = if keep_inline {
1332 ListFormat::inline()
1333 } else {
1334 ListFormat::compact().break_cmnts().break_single(true)
1335 };
1336 let terminal_callee = call_expr.peel_parens();
1337 let callee_has_breakable_comment = self
1338 .has_breakable_comment_between(call_expr.span.lo(), terminal_callee.span.lo())
1339 || self.has_breakable_comment_between(
1340 terminal_callee.span.hi(),
1341 call_expr.span.hi(),
1342 )
1343 || if let ast::ExprKind::Member(member_expr, ident) = &terminal_callee.kind {
1344 self.has_breakable_comment_between(member_expr.span.hi(), ident.span.lo())
1345 } else {
1346 false
1347 };
1348 self.print_member_or_call_chain(
1349 call_expr,
1350 MemberOrCallArgs::CallArgs(
1351 self.estimate_size(call_args.span),
1352 self.has_comments_between_elements(call_args.span, call_args.exprs()),
1353 ),
1354 |s| {
1355 let callee_suffix_can_break = callee_has_breakable_comment
1356 || match &terminal_callee.kind {
1357 ast::ExprKind::Member(member_expr, _) => {
1358 s.member_suffix_emits_break(terminal_callee, member_expr)
1359 }
1360 ast::ExprKind::Index(..) => !s.skip_index_break,
1361 _ => false,
1362 };
1363 s.print_call_args(
1364 call_args,
1365 list_format
1366 .without_ind(s.return_bin_expr)
1367 .with_delimiters(!s.call_with_opts_and_args),
1368 get_callee_head_size(call_expr),
1369 callee_suffix_can_break,
1370 );
1371 },
1372 );
1373 self.call_with_opts_and_args = cache;
1374 self.chained_named_call = chained_named_call_cache;
1375 }
1376 ast::ExprKind::CallOptions(expr, named_args) => {
1377 let cache = self.call_with_opts_and_args;
1379 self.call_with_opts_and_args = false;
1380
1381 self.print_expr(expr);
1382 self.print_named_args(named_args, span.hi(), false);
1383
1384 self.call_with_opts_and_args = cache;
1386 }
1387 ast::ExprKind::Delete(expr) => {
1388 self.word("delete ");
1389 self.print_expr(expr);
1390 }
1391 ast::ExprKind::Ident(ident) => self.print_ident(ident),
1392 ast::ExprKind::Index(expr, kind) => self.print_index_expr(span, expr, kind),
1393 ast::ExprKind::Lit(lit, unit) => {
1394 self.print_lit(lit);
1395 if let Some(unit) = unit {
1396 self.nbsp();
1397 self.word(unit.to_str());
1398 }
1399 }
1400 ast::ExprKind::Member(member_expr, ident) => {
1401 self.print_member_or_call_chain(
1402 member_expr,
1403 MemberOrCallArgs::Member(self.estimate_size(ident.span)),
1404 |s| {
1405 let has_mixed_comment = s
1406 .peek_comment_between(member_expr.span.hi(), ident.span.lo())
1407 .is_some_and(|comment| comment.style.is_mixed());
1408 if has_mixed_comment {
1409 s.print_comments(
1410 ident.span.lo(),
1411 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1412 );
1413 } else {
1414 s.print_trailing_comment(member_expr.span.hi(), Some(ident.span.lo()));
1415 }
1416 if has_mixed_comment || s.member_suffix_emits_break(expr, member_expr) {
1417 s.zerobreak();
1418 }
1419 s.word(".");
1420 s.print_ident(ident);
1421 },
1422 );
1423 }
1424 ast::ExprKind::New(ty) => {
1425 self.word("new ");
1426 self.print_ty(ty);
1427 }
1428 ast::ExprKind::Payable(args) => {
1429 self.word("payable");
1430 self.print_call_args(args, ListFormat::compact().break_cmnts(), 7, false);
1431 }
1432 ast::ExprKind::Ternary(cond, then, els) => self.print_ternary_expr(cond, then, els),
1433 ast::ExprKind::Tuple(exprs) => self.print_tuple(
1434 exprs,
1435 span.lo(),
1436 span.hi(),
1437 |this, expr| match expr.as_ref() {
1438 SpannedOption::Some(expr) => this.print_expr(expr),
1439 SpannedOption::None(span) => {
1440 this.print_comments(span.hi(), CommentConfig::skip_ws().no_breaks());
1441 }
1442 },
1443 |expr| match expr.as_ref() {
1444 SpannedOption::Some(expr) => expr.span,
1445 SpannedOption::None(..) => Span::DUMMY,
1447 },
1448 ListFormat::compact().break_single(is_binary_expr(&expr.kind)),
1449 ),
1450 ast::ExprKind::TypeCall(ty) => {
1451 self.word("type");
1452 self.print_tuple(
1453 std::slice::from_ref(ty),
1454 span.lo(),
1455 span.hi(),
1456 Self::print_ty,
1457 get_span!(),
1458 ListFormat::consistent(),
1459 );
1460 }
1461 ast::ExprKind::Type(ty) => self.print_ty(ty),
1462 ast::ExprKind::Unary(un_op, expr) => {
1463 let prefix = un_op.kind.is_prefix();
1464 let op = un_op.kind.to_str();
1465 if prefix {
1466 self.word(op);
1467 }
1468 self.print_expr(expr);
1469 if !prefix {
1470 debug_assert!(un_op.kind.is_postfix());
1471 self.word(op);
1472 }
1473 }
1474 ast::ExprKind::Err(_) => self.print_span(span),
1475 }
1476 self.cursor.advance_to(span.hi(), true);
1477 }
1478
1479 fn print_assign_expr(&mut self, lhs: &'ast ast::Expr<'ast>, rhs: &'ast ast::Expr<'ast>) {
1481 let cache = self.var_init;
1482 self.var_init = true;
1483
1484 let space_left = self.space_left();
1485 let lhs_size = self.estimate_size(lhs.span);
1486 self.print_expr(lhs);
1487 self.word(" =");
1488 self.print_assign_rhs(rhs, lhs_size + 2, space_left, None, cache);
1489 }
1490
1491 fn print_bin_expr(
1493 &mut self,
1494 lhs: &'ast ast::Expr<'ast>,
1495 bin_op: &ast::BinOp,
1496 rhs: &'ast ast::Expr<'ast>,
1497 is_assign: bool,
1498 ) {
1499 let prev_chain = self.binary_expr;
1500 let is_chain = prev_chain.is_some_and(|prev| prev == bin_op.kind.group());
1501
1502 if !is_chain {
1504 self.binary_expr = Some(bin_op.kind.group());
1505
1506 let indent = if (is_assign && has_complex_successor(&rhs.kind, true))
1507 || self.call_stack.is_nested()
1508 && is_call_chain(&lhs.kind, false)
1509 && self.estimate_size(lhs.span) >= self.space_left()
1510 {
1511 0
1512 } else {
1513 self.ind
1514 };
1515 self.s.ibox(indent);
1516 }
1517
1518 self.print_expr(lhs);
1520
1521 let no_trailing_comment = !self.print_trailing_comment(lhs.span.hi(), Some(rhs.span.lo()));
1523 if is_assign {
1524 if no_trailing_comment {
1525 self.nbsp();
1526 }
1527 self.word(bin_op.kind.to_str());
1528 self.word("= ");
1529 } else {
1530 if no_trailing_comment
1531 && self
1532 .print_comments(
1533 bin_op.span.lo(),
1534 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1535 )
1536 .is_none_or(|cmnt| cmnt.is_mixed())
1537 {
1538 if !self.config.pow_no_space || !matches!(bin_op.kind, ast::BinOpKind::Pow) {
1539 self.space_if_not_bol();
1540 } else if !self.is_bol_or_only_ind() && !self.last_token_is_break() {
1541 self.zerobreak();
1542 }
1543 }
1544
1545 self.word(bin_op.kind.to_str());
1546
1547 if !self.config.pow_no_space || !matches!(bin_op.kind, ast::BinOpKind::Pow) {
1548 self.nbsp();
1549 }
1550 }
1551
1552 let rhs_has_mixed_comment =
1554 self.peek_comment_before(rhs.span.lo()).is_some_and(|cmnt| cmnt.style.is_mixed());
1555 if rhs_has_mixed_comment {
1556 self.ibox(0);
1557 self.print_expr(rhs);
1558 self.end();
1559 } else {
1560 self.print_expr(rhs);
1561 }
1562
1563 if !is_chain {
1565 self.binary_expr = prev_chain;
1566 self.end();
1567 }
1568 }
1569
1570 fn print_index_expr(
1572 &mut self,
1573 span: Span,
1574 expr: &'ast ast::Expr<'ast>,
1575 kind: &'ast ast::IndexKind<'ast>,
1576 ) {
1577 self.print_expr(expr);
1578 self.word("[");
1579 self.s.cbox(self.ind);
1580
1581 let mut skip_break = false;
1582 let mut zerobreak = |this: &mut Self| {
1583 if this.skip_index_break {
1584 skip_break = true;
1585 } else {
1586 this.zerobreak();
1587 }
1588 };
1589 match kind {
1590 ast::IndexKind::Index(Some(inner_expr)) => {
1591 zerobreak(self);
1592 self.print_expr(inner_expr);
1593 }
1594 ast::IndexKind::Index(None) => {}
1595 ast::IndexKind::Range(start, end) => {
1596 if let Some(start_expr) = start {
1597 if self
1598 .print_comments(start_expr.span.lo(), CommentConfig::skip_ws())
1599 .is_none_or(|s| s.is_mixed())
1600 {
1601 zerobreak(self);
1602 }
1603 self.print_expr(start_expr);
1604 } else {
1605 zerobreak(self);
1606 }
1607
1608 self.word(":");
1609
1610 if let Some(end_expr) = end {
1611 self.s.ibox(self.ind);
1612 if start.is_some() {
1613 zerobreak(self);
1614 }
1615 self.print_comments(
1616 end_expr.span.lo(),
1617 CommentConfig::skip_ws()
1618 .mixed_prev_space()
1619 .mixed_no_break()
1620 .mixed_post_nbsp(),
1621 );
1622 self.print_expr(end_expr);
1623 }
1624
1625 let is_trailing = if let Some(style) = self.print_comments(
1627 span.hi(),
1628 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1629 ) {
1630 skip_break = true;
1631 style.is_trailing()
1632 } else {
1633 false
1634 };
1635
1636 match (skip_break, end.is_some()) {
1638 (true, true) => {
1639 self.break_offset_if_not_bol(0, -2 * self.ind, false);
1640 self.end();
1641 if !is_trailing {
1642 self.break_offset_if_not_bol(0, -self.ind, false);
1643 }
1644 }
1645 (true, false) => {
1646 self.break_offset_if_not_bol(0, -self.ind, false);
1647 }
1648 (false, true) => {
1649 self.end();
1650 }
1651 _ => {}
1652 }
1653 }
1654 }
1655
1656 if !skip_break {
1657 self.zerobreak();
1658 self.s.offset(-self.ind);
1659 }
1660
1661 self.end();
1662 self.word("]");
1663 }
1664
1665 fn print_ternary_expr(
1667 &mut self,
1668 cond: &'ast ast::Expr<'ast>,
1669 then: &'ast ast::Expr<'ast>,
1670 els: &'ast ast::Expr<'ast>,
1671 ) {
1672 self.s.cbox(self.ind);
1673 self.s.ibox(0);
1674
1675 let print_sub_expr = |this: &mut Self, span_lo, prefix, expr: &'ast ast::Expr<'ast>| {
1676 match prefix {
1677 Some(prefix) => {
1678 if this.peek_comment_before(span_lo).is_some() {
1679 this.space();
1680 }
1681 this.print_comments(span_lo, CommentConfig::skip_ws());
1682 this.end();
1683 if !this.is_bol_or_only_ind() {
1684 this.space();
1685 }
1686 this.s.ibox(0);
1687 this.word(prefix);
1688 }
1689 None => {
1690 this.print_comments(expr.span.lo(), CommentConfig::skip_ws());
1691 }
1692 };
1693 this.print_expr(expr);
1694 };
1695
1696 self.s.ibox(-self.ind);
1698 print_sub_expr(self, then.span.lo(), None, cond);
1699 self.end();
1700 print_sub_expr(self, then.span.lo(), Some("? "), then);
1702 print_sub_expr(self, els.span.lo(), Some(": "), els);
1704
1705 self.end();
1706 self.neverbreak();
1707 self.s.offset(-self.ind);
1708 self.end();
1709 }
1710
1711 fn print_modifier_call(
1713 &mut self,
1714 modifier: &'ast ast::Modifier<'ast>,
1715 add_parens_if_empty: bool,
1716 ) {
1717 let ast::Modifier { name, arguments } = modifier;
1718 self.print_path(name, false);
1719 if !arguments.is_empty() || add_parens_if_empty {
1720 self.print_call_args(
1721 arguments,
1722 ListFormat::compact().break_cmnts(),
1723 name.to_string().len(),
1724 false,
1725 );
1726 }
1727 }
1728
1729 fn member_suffix_emits_break(&self, expr: &ast::Expr<'_>, member_expr: &ast::Expr<'_>) -> bool {
1730 match member_expr.kind {
1731 ast::ExprKind::Ident(_) | ast::ExprKind::Type(_) => false,
1732 ast::ExprKind::Index(..) if self.skip_index_break => false,
1733 _ if self
1734 .chained_named_call
1735 .is_some_and(|call| call.keep_inline && call.callee.contains(expr.span)) =>
1736 {
1737 false
1738 }
1739 _ if is_call_with_named_args(&member_expr.kind) => false,
1744 _ => true,
1745 }
1746 }
1747
1748 fn print_member_or_call_chain<F>(
1749 &mut self,
1750 child_expr: &'ast ast::Expr<'ast>,
1751 member_or_args: MemberOrCallArgs,
1752 print_suffix: F,
1753 ) where
1754 F: FnOnce(&mut Self),
1755 {
1756 fn member_depth(depth: usize, expr: &ast::Expr<'_>) -> usize {
1757 if let ast::ExprKind::Member(child, ..) = &expr.kind {
1758 member_depth(depth + 1, child)
1759 } else {
1760 depth
1761 }
1762 }
1763
1764 let (mut extra_box, skip_cache) = (false, self.skip_index_break);
1765 let parent_is_chain = self.call_stack.last().copied().is_some_and(|call| call.is_chained());
1766 if !parent_is_chain {
1767 let callee_size = get_callee_head_size(child_expr) + member_or_args.member_size();
1769 let expr_size = self.estimate_size(child_expr.span);
1770
1771 let callee_fits_line = self.space_left() > callee_size + 1;
1772 let total_fits_line = self.space_left() > expr_size + member_or_args.size() + 2;
1773 let no_cmnt_or_mixed =
1774 self.peek_comment_before(child_expr.span.hi()).is_none_or(|c| c.style.is_mixed());
1775
1776 if self.call_with_opts_and_args {
1778 self.cbox(0);
1779 extra_box = true;
1780 }
1781
1782 let keep_chain_inline = self
1784 .chained_named_call
1785 .is_some_and(|call| call.keep_inline && call.callee.contains(child_expr.span));
1786 let chain_has_indent = !keep_chain_inline
1787 && (is_call_chain(&child_expr.kind, true)
1788 || !(no_cmnt_or_mixed
1789 || matches!(&child_expr.kind, ast::ExprKind::CallOptions(..)))
1790 || !callee_fits_line
1791 || (member_depth(0, child_expr) >= 2
1792 && (!total_fits_line || member_or_args.has_comments())));
1793
1794 if is_call_chain(&child_expr.kind, false) {
1796 self.call_stack.push(CallContext::chained(callee_size, chain_has_indent));
1797 }
1798
1799 if chain_has_indent {
1800 self.s.ibox(self.ind);
1801 } else {
1802 self.skip_index_break = true;
1803 self.cbox(0);
1804 }
1805 }
1806
1807 self.print_expr(child_expr);
1809
1810 if extra_box {
1812 self.end();
1813 }
1814
1815 print_suffix(self);
1817
1818 if !parent_is_chain {
1820 if is_call_chain(&child_expr.kind, false) {
1821 self.call_stack.pop();
1822 }
1823 self.end();
1824 }
1825
1826 if self.skip_index_break {
1828 self.skip_index_break = skip_cache;
1829 }
1830 }
1831
1832 fn print_call_args(
1833 &mut self,
1834 args: &'ast ast::CallArgs<'ast>,
1835 format: ListFormat,
1836 callee_size: usize,
1837 callee_suffix_can_break: bool,
1838 ) {
1839 let ast::CallArgs { span, ref kind } = *args;
1840 if self.handle_span(span, true) {
1841 return;
1842 }
1843
1844 self.call_stack.push(CallContext::nested(callee_size));
1845
1846 let cache = self.binary_expr.take();
1848
1849 match kind {
1850 ast::CallArgsKind::Unnamed(exprs) => {
1851 self.print_tuple(
1852 exprs,
1853 span.lo(),
1854 span.hi(),
1855 |this, e| this.print_expr(e),
1856 get_span!(),
1857 format,
1858 );
1859 }
1860 ast::CallArgsKind::Named(named_args) => {
1861 let without_ind =
1862 self.call_stack.has_indented_parent_chain() && !callee_suffix_can_break;
1863 self.print_inside_parens(|state| {
1864 state.print_named_args(named_args, span.hi(), without_ind)
1865 });
1866 }
1867 }
1868
1869 self.binary_expr = cache;
1871 self.call_stack.pop();
1872 }
1873
1874 fn print_named_args(
1875 &mut self,
1876 args: &'ast [ast::NamedArg<'ast>],
1877 pos_hi: BytePos,
1878 without_ind: bool,
1879 ) {
1880 let list_format = match (self.config.bracket_spacing, self.config.prefer_compact.calls()) {
1881 (false, true) => ListFormat::compact(),
1882 (false, false) => ListFormat::consistent(),
1883 (true, true) => ListFormat::compact().with_space(),
1884 (true, false) => ListFormat::consistent().with_space(),
1885 };
1886
1887 self.word("{");
1888 if let Some(first_arg) = args.first() {
1890 let list_lo = first_arg.name.span.lo();
1891 self.commasep(
1892 args,
1893 list_lo,
1894 pos_hi,
1895 |s, arg| {
1897 s.cbox(0);
1898 s.print_ident(&arg.name);
1899 s.word(":");
1900 if s.same_source_line(arg.name.span.hi(), arg.value.span.hi())
1901 || !s.print_trailing_comment(arg.name.span.hi(), None)
1902 {
1903 s.nbsp();
1904 }
1905 s.print_comments(
1906 arg.value.span.lo(),
1907 CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
1908 );
1909 s.print_expr(arg.value);
1910 s.end();
1911 },
1912 |arg| arg.name.span.until(arg.value.span),
1913 list_format
1914 .break_cmnts()
1915 .break_single(true)
1916 .without_ind(without_ind)
1917 .with_delimiters(!self.call_with_opts_and_args),
1918 );
1919 } else if self.config.bracket_spacing {
1920 self.nbsp();
1921 }
1922 self.word("}");
1923 }
1924
1925 fn print_stmt(&mut self, stmt: &'ast ast::Stmt<'ast>) {
1929 let ast::Stmt { ref docs, span, ref kind } = *stmt;
1930 self.print_docs(docs);
1931
1932 if self.handle_span(span, false) {
1934 self.print_trailing_comment_no_break(stmt.span.hi(), None);
1935 return;
1936 }
1937
1938 let force_break = matches!(kind, ast::StmtKind::Return(..))
1940 && self.peek_comment_before(span.lo()).is_some_and(|cmnt| cmnt.style.is_mixed());
1941
1942 match kind {
1943 ast::StmtKind::Assembly(ast::StmtAssembly { dialect, flags, block }) => {
1944 self.print_assembly_stmt(span, dialect, flags, block)
1945 }
1946 ast::StmtKind::DeclSingle(var) => self.print_var(var, true),
1947 ast::StmtKind::DeclMulti(vars, init_expr) => {
1948 self.print_multi_decl_stmt(span, vars, init_expr)
1949 }
1950 ast::StmtKind::Block(stmts) => self.print_block(stmts, span),
1951 ast::StmtKind::Break => self.word("break"),
1952 ast::StmtKind::Continue => self.word("continue"),
1953 ast::StmtKind::DoWhile(stmt, cond) => {
1954 self.word("do ");
1955 self.print_stmt_as_block(stmt, cond.span.lo(), false);
1956 self.nbsp();
1957 self.print_if_cond("while", cond, cond.span.hi());
1958 }
1959 ast::StmtKind::Emit(path, args) => self.print_emit_or_revert("emit", path, args),
1960 ast::StmtKind::Expr(expr) => self.print_expr(expr),
1961 ast::StmtKind::For { init, cond, next, body } => {
1962 self.print_for_stmt(span, init, cond, next, body)
1963 }
1964 ast::StmtKind::If(cond, then, els_opt) => self.print_if_stmt(span, cond, then, els_opt),
1965 ast::StmtKind::Return(expr) => self.print_return_stmt(force_break, expr),
1966 ast::StmtKind::Revert(path, args) => self.print_emit_or_revert("revert", path, args),
1967 ast::StmtKind::Try(ast::StmtTry { expr, clauses }) => {
1968 self.print_try_stmt(expr, clauses)
1969 }
1970 ast::StmtKind::UncheckedBlock(block) => {
1971 self.word("unchecked ");
1972 self.print_block(block, stmt.span);
1973 }
1974 ast::StmtKind::While(cond, stmt) => {
1975 let inline = self.is_single_line_block(span.lo(), cond, stmt, None);
1977 if !inline.is_cached && self.single_line_stmt.is_none() {
1978 self.single_line_stmt = Some(inline.outcome);
1979 }
1980
1981 self.print_if_cond("while", cond, stmt.span.lo());
1983 self.nbsp();
1984 self.print_stmt_as_block(stmt, stmt.span.hi(), inline.outcome);
1985
1986 if !inline.is_cached && self.single_line_stmt.is_some() {
1988 self.single_line_stmt = None;
1989 }
1990 }
1991 ast::StmtKind::Placeholder => self.word("_"),
1992 }
1993 if stmt_needs_semi(kind) {
1994 self.neverbreak(); self.word(";");
1996 self.cursor.advance_to(span.hi(), true);
1997 }
1998 self.print_comments(
2000 stmt.span.hi(),
2001 CommentConfig::default().trailing_no_break().mixed_no_break().mixed_prev_space(),
2002 );
2003 self.print_trailing_comment_no_break(stmt.span.hi(), None);
2004 }
2005
2006 fn print_assembly_stmt(
2009 &mut self,
2010 span: Span,
2011 dialect: &'ast Option<ast::StrLit>,
2012 flags: &'ast [ast::StrLit],
2013 block: &'ast ast::yul::Block<'ast>,
2014 ) {
2015 _ = self.handle_span(self.cursor.span(span.lo()), false);
2016 if !self.handle_span(span.until(block.span), false) {
2017 self.cursor.advance_to(span.lo(), true);
2018 self.print_word("assembly "); if let Some(dialect) = dialect {
2020 self.print_ast_str_lit(dialect);
2021 self.print_sep(Separator::Nbsp);
2022 }
2023 if !flags.is_empty() {
2024 self.print_tuple(
2025 flags,
2026 span.lo(),
2027 block.span.lo(),
2028 Self::print_ast_str_lit,
2029 get_span!(),
2030 ListFormat::consistent(),
2031 );
2032 self.print_sep(Separator::Nbsp);
2033 }
2034 }
2035 self.print_yul_block(block, block.span, false, 9);
2036 }
2037
2038 fn print_multi_decl_stmt(
2041 &mut self,
2042 span: Span,
2043 vars: &'ast BoxSlice<'ast, SpannedOption<ast::VariableDefinition<'ast>>>,
2044 init_expr: &'ast ast::Expr<'ast>,
2045 ) {
2046 let space_left = self.space_left();
2047
2048 self.s.ibox(self.ind);
2049 self.s.ibox(-self.ind);
2050 self.print_tuple(
2051 vars,
2052 span.lo(),
2053 init_expr.span.lo(),
2054 |this, var| match var {
2055 SpannedOption::Some(var) => this.print_var(var, true),
2056 SpannedOption::None(span) => {
2057 this.print_comments(span.hi(), CommentConfig::skip_ws().mixed_no_break_post());
2058 }
2059 },
2060 |var| match var {
2061 SpannedOption::Some(var) => var.span,
2062 SpannedOption::None(..) => Span::DUMMY,
2064 },
2065 ListFormat::consistent(),
2066 );
2067 self.end();
2068 self.word(" =");
2069
2070 if self.estimate_size(init_expr.span) + self.config.tab_width
2071 <= std::cmp::max(space_left, self.space_left())
2072 {
2073 self.print_sep(Separator::Space);
2074 self.ibox(0);
2075 } else {
2076 self.print_sep(Separator::Nbsp);
2077 self.neverbreak();
2078 self.s.ibox(-self.ind);
2079 }
2080 self.print_expr(init_expr);
2081 self.end();
2082 self.end();
2083 }
2084
2085 fn print_for_stmt(
2088 &mut self,
2089 span: Span,
2090 init: &'ast Option<&mut ast::Stmt<'ast>>,
2091 cond: &'ast Option<&mut ast::Expr<'ast>>,
2092 next: &'ast Option<&mut ast::Expr<'ast>>,
2093 body: &'ast ast::Stmt<'ast>,
2094 ) {
2095 self.cbox(0);
2096 self.s.ibox(self.ind);
2097 self.print_word("for (");
2098 self.zerobreak();
2099
2100 self.s.cbox(0);
2102 match init {
2103 Some(init_stmt) => self.print_stmt(init_stmt),
2104 None => self.print_word(";"),
2105 }
2106
2107 match cond {
2109 Some(cond_expr) => {
2110 self.print_sep(Separator::Space);
2111 self.print_expr(cond_expr);
2112 }
2113 None => self.zerobreak(),
2114 }
2115 self.print_word(";");
2116
2117 match next {
2119 Some(next_expr) => {
2120 self.space();
2121 self.print_expr(next_expr);
2122 }
2123 None => self.zerobreak(),
2124 }
2125
2126 self.break_offset_if_not_bol(0, -self.ind, false);
2128 self.end();
2129 self.print_word(") ");
2130 self.neverbreak();
2131 self.end();
2132
2133 self.print_comments(body.span.lo(), CommentConfig::skip_ws());
2135 self.print_stmt_as_block(body, span.hi(), false);
2136 self.end();
2137 }
2138
2139 fn print_if_stmt(
2142 &mut self,
2143 span: Span,
2144 cond: &'ast ast::Expr<'ast>,
2145 then: &'ast ast::Stmt<'ast>,
2146 els_opt: &'ast Option<&mut ast::Stmt<'ast>>,
2147 ) {
2148 let inline = self.is_single_line_block(span.lo(), cond, then, els_opt.as_ref());
2150 let set_inline_cache = !inline.is_cached && self.single_line_stmt.is_none();
2151 if set_inline_cache {
2152 self.single_line_stmt = Some(inline.outcome);
2153 }
2154
2155 self.cbox(0);
2156 self.ibox(0);
2157 self.print_if_no_else(cond, then, inline.outcome);
2159
2160 let mut current_else = els_opt.as_deref();
2162 while let Some(els) = current_else {
2163 if self.ends_with('}') {
2164 if self.has_comment_before_with(els.span.lo(), |cmnt| !cmnt.style.is_mixed()) {
2166 if self
2168 .print_comments(els.span.lo(), CommentConfig::skip_ws().mixed_no_break())
2169 .is_some_and(|cmnt| cmnt.is_mixed())
2170 {
2171 self.hardbreak();
2172 }
2173 }
2174 else if self
2176 .print_comments(
2177 els.span.lo(),
2178 CommentConfig::skip_ws()
2179 .mixed_no_break()
2180 .mixed_prev_space()
2181 .mixed_post_nbsp(),
2182 )
2183 .is_none()
2184 {
2185 self.nbsp();
2186 }
2187 } else {
2188 self.hardbreak_if_not_bol();
2189 if self
2190 .print_comments(els.span.lo(), CommentConfig::skip_ws())
2191 .is_some_and(|cmnt| cmnt.is_mixed())
2192 {
2193 self.hardbreak();
2194 };
2195 }
2196
2197 self.ibox(0);
2198 self.print_word("else ");
2199 match &els.kind {
2200 ast::StmtKind::If(cond, then, next_else) => {
2201 self.print_if_no_else(cond, then, inline.outcome);
2202 current_else = next_else.as_deref();
2203 }
2204 _ => {
2205 self.print_stmt_as_block(els, span.hi(), inline.outcome);
2206 self.end(); break;
2208 }
2209 }
2210 }
2211 self.end();
2212
2213 if set_inline_cache {
2215 self.single_line_stmt = None;
2216 }
2217 }
2218
2219 fn print_return_stmt(&mut self, force_break: bool, expr: &'ast Option<&mut ast::Expr<'ast>>) {
2222 if force_break {
2223 self.hardbreak_if_not_bol();
2224 }
2225
2226 let space_left = self.space_left();
2227 let expr_size = expr.as_ref().map_or(0, |expr| self.estimate_size(expr.span));
2228
2229 let overflows = space_left < 8 + expr_size;
2231 let fits_alone = space_left > expr_size;
2232
2233 if let Some(expr) = expr {
2234 let is_simple = matches!(expr.kind, ast::ExprKind::Lit(..) | ast::ExprKind::Ident(..));
2235 let allow_break = overflows && fits_alone;
2236
2237 self.return_bin_expr = matches!(expr.kind, ast::ExprKind::Binary(..));
2238 self.s.ibox(if is_simple || allow_break { self.ind } else { 0 });
2239
2240 self.print_word("return");
2241
2242 match self.print_comments(
2243 expr.span.lo(),
2244 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space().mixed_post_nbsp(),
2245 ) {
2246 Some(cmnt) if cmnt.is_trailing() && !is_simple => self.s.offset(self.ind),
2247 None => self.print_sep(Separator::SpaceOrNbsp(allow_break)),
2248 _ => {}
2249 }
2250
2251 self.print_expr(expr);
2252 self.end();
2253 self.return_bin_expr = false;
2254 } else {
2255 self.print_word("return");
2256 }
2257 }
2258
2259 fn print_try_stmt(
2262 &mut self,
2263 expr: &'ast ast::Expr<'ast>,
2264 clauses: &'ast [ast::TryCatchClause<'ast>],
2265 ) {
2266 self.cbox(0);
2267 if let Some((first, other)) = clauses.split_first() {
2268 let ast::TryCatchClause { args, block, span: try_span, .. } = first;
2270 self.cbox(0);
2271 self.ibox(0);
2272 self.print_word("try ");
2273 self.print_comments(expr.span.lo(), CommentConfig::skip_ws());
2274 self.print_expr(expr);
2275
2276 self.print_comments(
2278 args.first().map(|p| p.span.lo()).unwrap_or_else(|| expr.span.lo()),
2279 CommentConfig::skip_ws(),
2280 );
2281 if !self.is_beginning_of_line() {
2282 self.nbsp();
2283 }
2284
2285 if args.is_empty() {
2286 self.end();
2287 } else {
2288 self.print_word("returns ");
2289 self.print_word("(");
2290 self.zerobreak();
2291 self.end();
2292 let span = args.span.with_hi(block.span.lo());
2293 self.commasep(
2294 args,
2295 span.lo(),
2296 span.hi(),
2297 |fmt, var| fmt.print_var(var, false),
2298 get_span!(),
2299 ListFormat::compact().with_delimiters(false),
2300 );
2301 self.print_word(")");
2302 self.nbsp();
2303 }
2304 if block.is_empty() {
2305 self.print_block(block, *try_span);
2306 self.end();
2307 } else {
2308 self.print_word("{");
2309 self.end();
2310 self.neverbreak();
2311 self.print_trailing_comment_no_break(try_span.lo(), None);
2312 self.print_block_without_braces(block, try_span.hi(), Some(self.ind));
2313 if self.cursor.enabled || self.cursor.pos < try_span.hi() {
2314 self.print_word("}");
2315 self.cursor.advance_to(try_span.hi(), true);
2316 }
2317 }
2318
2319 let mut skip_ind = false;
2320 if self.print_trailing_comment(try_span.hi(), other.first().map(|c| c.span.lo())) {
2321 self.break_offset_if_not_bol(0, self.ind, false);
2324 skip_ind = true;
2325 };
2326
2327 let mut prev_block_multiline = self.is_multiline_block(block, false, true);
2328
2329 for (pos, ast::TryCatchClause { name, args, block, span: catch_span }) in
2331 other.iter().delimited()
2332 {
2333 let current_block_multiline = self.is_multiline_block(block, false, true);
2334 if !pos.is_first || !skip_ind {
2335 if (pos.is_first && block.is_empty() && is_call_with_named_args(&expr.kind))
2336 || (prev_block_multiline && (current_block_multiline || pos.is_last))
2337 {
2338 self.nbsp();
2339 } else {
2340 self.space();
2341 if !current_block_multiline {
2342 self.s.offset(self.ind);
2343 }
2344 }
2345 }
2346 self.s.ibox(self.ind);
2347 self.print_comments(
2348 catch_span.lo(),
2349 CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
2350 );
2351
2352 self.print_word("catch ");
2353 if !args.is_empty() {
2354 self.print_comments(
2355 args[0].span.lo(),
2356 CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
2357 );
2358 if let Some(name) = name {
2359 self.print_ident(name);
2360 }
2361 self.print_parameter_list(
2362 args,
2363 args.span.with_hi(block.span.lo()),
2364 ListFormat::inline(),
2365 );
2366 self.nbsp();
2367 }
2368 self.print_word("{");
2369 self.end();
2370 if !block.is_empty() {
2371 self.print_trailing_comment_no_break(catch_span.lo(), None);
2372 }
2373 self.print_block_without_braces(block, catch_span.hi(), Some(self.ind));
2374 if self.cursor.enabled || self.cursor.pos < try_span.hi() {
2375 self.print_word("}");
2376 self.cursor.advance_to(catch_span.hi(), true);
2377 }
2378
2379 prev_block_multiline = current_block_multiline;
2380 }
2381 }
2382 self.end();
2383 }
2384
2385 fn print_if_no_else(
2386 &mut self,
2387 cond: &'ast ast::Expr<'ast>,
2388 then: &'ast ast::Stmt<'ast>,
2389 inline: bool,
2390 ) {
2391 if !self.handle_span(cond.span.until(then.span), true) {
2392 self.print_if_cond("if", cond, then.span.lo());
2393 if let ast::StmtKind::Block(block) = &then.kind
2395 && block.is_empty()
2396 && self.peek_comment_before(then.span.hi()).is_none()
2397 {
2398 self.neverbreak();
2399 self.print_sep(Separator::Nbsp);
2400 } else {
2401 self.print_sep(Separator::Space);
2402 }
2403 }
2404 self.end();
2405 self.print_stmt_as_block(then, then.span.hi(), inline);
2406 self.cursor.advance_to(then.span.hi(), true);
2407 }
2408
2409 fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) {
2410 self.print_word(kw);
2411 self.print_sep_unhandled(Separator::Nbsp);
2412 self.print_tuple(
2413 std::slice::from_ref(cond),
2414 cond.span.lo(),
2415 pos_hi,
2416 Self::print_expr,
2417 get_span!(),
2418 ListFormat::compact().break_cmnts().break_single(is_binary_expr(&cond.kind)),
2419 );
2420 }
2421
2422 fn print_emit_or_revert(
2423 &mut self,
2424 kw: &'static str,
2425 path: &'ast ast::PathSlice,
2426 args: &'ast ast::CallArgs<'ast>,
2427 ) {
2428 self.word(kw);
2429 if self
2430 .print_comments(
2431 path.span().lo(),
2432 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space().mixed_post_nbsp(),
2433 )
2434 .is_none()
2435 {
2436 self.nbsp();
2437 };
2438 self.s.cbox(0);
2439 self.emit_or_revert = path.segments().len() > 1;
2440 self.print_path(path, false);
2441 let format = if self.config.prefer_compact.calls() {
2442 ListFormat::compact()
2443 } else {
2444 ListFormat::consistent()
2445 };
2446 self.print_call_args(args, format.break_cmnts(), path.to_string().len(), false);
2447 self.emit_or_revert = false;
2448 self.end();
2449 }
2450
2451 fn print_block(&mut self, block: &'ast [ast::Stmt<'ast>], span: Span) {
2452 self.print_block_inner(
2453 block,
2454 BlockFormat::Regular,
2455 Self::print_stmt,
2456 |b| b.span,
2457 span.hi(),
2458 );
2459 }
2460
2461 fn print_block_without_braces(
2462 &mut self,
2463 block: &'ast [ast::Stmt<'ast>],
2464 pos_hi: BytePos,
2465 offset: Option<isize>,
2466 ) {
2467 self.print_block_inner(
2468 block,
2469 BlockFormat::NoBraces(offset),
2470 Self::print_stmt,
2471 |b| b.span,
2472 pos_hi,
2473 );
2474 }
2475
2476 fn print_stmt_as_block(&mut self, stmt: &'ast ast::Stmt<'ast>, pos_hi: BytePos, inline: bool) {
2478 if self.handle_span(stmt.span, false) {
2479 return;
2480 }
2481
2482 let stmts = if let ast::StmtKind::Block(stmts) = &stmt.kind {
2483 stmts
2484 } else {
2485 std::slice::from_ref(stmt)
2486 };
2487
2488 if inline && stmts.len() == 1 {
2489 self.neverbreak();
2490 self.print_block_without_braces(stmts, pos_hi, None);
2491 } else {
2492 let inline_parent = self.single_line_stmt.take();
2494
2495 self.print_word("{");
2496 self.print_block_without_braces(stmts, pos_hi, Some(self.ind));
2497 self.print_word("}");
2498
2499 self.single_line_stmt = inline_parent;
2501 }
2502 }
2503
2504 fn is_single_line_block(
2513 &mut self,
2514 stmt_span_lo: BytePos,
2515 cond: &'ast ast::Expr<'ast>,
2516 then: &'ast ast::Stmt<'ast>,
2517 els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>,
2518 ) -> Decision {
2519 if Self::then_block_can_capture_trailing_else(then, els_opt.is_some()) {
2522 return Decision { outcome: false, is_cached: false };
2523 }
2524
2525 if let Some(cached_decision) = self.single_line_stmt {
2527 return Decision { outcome: cached_decision, is_cached: true };
2528 }
2529
2530 if std::slice::from_ref(then).is_empty() {
2532 return Decision { outcome: false, is_cached: false };
2533 }
2534
2535 if self.peek_comment_between(stmt_span_lo, then.span.lo()).is_some() {
2537 return Decision { outcome: false, is_cached: false };
2538 }
2539
2540 match self.config.single_line_statement_blocks {
2542 config::SingleLineBlockStyle::Preserve => {
2543 if self.is_stmt_in_new_line(cond, then) || self.is_multiline_block_stmt(then, true)
2544 {
2545 return Decision { outcome: false, is_cached: false };
2546 }
2547 }
2548 config::SingleLineBlockStyle::Single => {
2549 if self.is_multiline_block_stmt(then, true) {
2550 return Decision { outcome: false, is_cached: false };
2551 }
2552 }
2553 config::SingleLineBlockStyle::Multi => {
2554 return Decision { outcome: false, is_cached: false };
2555 }
2556 };
2557
2558 if !self.can_stmts_be_inlined(cond, then, els_opt) {
2561 return Decision { outcome: false, is_cached: false };
2562 }
2563
2564 if let ast::StmtKind::If(child_cond, child_then, child_els_opt) = &then.kind {
2566 let child_decision = self.is_single_line_block(
2567 then.span.lo(),
2568 child_cond,
2569 child_then,
2570 child_els_opt.as_ref(),
2571 );
2572 if !child_decision.outcome {
2573 return child_decision;
2574 }
2575 }
2576 if let Some(stmt) = els_opt {
2577 if let ast::StmtKind::If(child_cond, child_then, child_els_opt) = &stmt.kind {
2578 return self.is_single_line_block(
2579 stmt.span.lo(),
2580 child_cond,
2581 child_then,
2582 child_els_opt.as_ref(),
2583 );
2584 } else if self.is_multiline_block_stmt(stmt, true) {
2585 return Decision { outcome: false, is_cached: false };
2586 }
2587 }
2588
2589 Decision { outcome: true, is_cached: false }
2591 }
2592
2593 fn is_inline_stmt(&self, stmt: &'ast ast::Stmt<'ast>, cond_len: usize) -> bool {
2594 if let ast::StmtKind::If(cond, then, els_opt) = &stmt.kind {
2595 let if_span = cond.span.to(then.span);
2596 if self.sm.is_multiline(if_span)
2597 && matches!(
2598 self.config.single_line_statement_blocks,
2599 config::SingleLineBlockStyle::Preserve
2600 )
2601 {
2602 return false;
2603 }
2604 if cond_len + self.estimate_size(if_span) >= self.space_left() {
2605 return false;
2606 }
2607 if let Some(els) = els_opt
2608 && !self.is_inline_stmt(els, 6)
2609 {
2610 return false;
2611 }
2612 } else {
2613 if matches!(
2614 self.config.single_line_statement_blocks,
2615 config::SingleLineBlockStyle::Preserve
2616 ) && self.sm.is_multiline(stmt.span)
2617 {
2618 return false;
2619 }
2620 if cond_len + self.estimate_size(stmt.span) >= self.space_left() {
2621 return false;
2622 }
2623 }
2624 true
2625 }
2626
2627 fn is_stmt_in_new_line(
2629 &self,
2630 cond: &'ast ast::Expr<'ast>,
2631 then: &'ast ast::Stmt<'ast>,
2632 ) -> bool {
2633 let span_between = cond.span.between(then.span);
2634 if let Ok(snip) = self.sm.span_to_snippet(span_between) {
2635 if let Some((_, after_paren)) = snip.split_once(')') {
2637 return after_paren.lines().count() > 1;
2638 }
2639 }
2640 false
2641 }
2642
2643 fn then_block_can_capture_trailing_else(
2646 then: &'ast ast::Stmt<'ast>,
2647 has_outer_else: bool,
2648 ) -> bool {
2649 let ast::StmtKind::Block(block) = &then.kind else { return false };
2650 if block.stmts.len() != 1 {
2651 return false;
2652 }
2653 match &block.stmts[0].kind {
2654 ast::StmtKind::If(_, _, inner_else) => has_outer_else || inner_else.is_some(),
2655 ast::StmtKind::While(..) | ast::StmtKind::For { .. } => has_outer_else,
2656 _ => false,
2657 }
2658 }
2659
2660 fn is_multiline_block_stmt(
2662 &mut self,
2663 stmt: &'ast ast::Stmt<'ast>,
2664 empty_as_multiline: bool,
2665 ) -> bool {
2666 match &stmt.kind {
2667 ast::StmtKind::Block(block) => {
2668 self.is_multiline_block(block, empty_as_multiline, false)
2669 }
2670 ast::StmtKind::While(cond, body) => {
2671 !self.is_single_line_block(stmt.span.lo(), cond, body, None).outcome
2672 }
2673 ast::StmtKind::For { body, .. } => {
2674 if let ast::StmtKind::Block(block) = &body.kind {
2677 self.is_multiline_block(block, empty_as_multiline, true)
2678 } else {
2679 true
2680 }
2681 }
2682
2683 ast::StmtKind::If(_, _, Some(_)) => true,
2684 ast::StmtKind::If(_, then, None) => {
2685 self.is_multiline_block_stmt(then, empty_as_multiline)
2686 }
2687
2688 ast::StmtKind::Assembly(_)
2690 | ast::StmtKind::DoWhile(_, _)
2691 | ast::StmtKind::Try(_)
2692 | ast::StmtKind::UncheckedBlock(_) => true,
2693
2694 ast::StmtKind::Break
2695 | ast::StmtKind::Continue
2696 | ast::StmtKind::DeclMulti(_, _)
2697 | ast::StmtKind::DeclSingle(_)
2698 | ast::StmtKind::Emit(_, _)
2699 | ast::StmtKind::Expr(_)
2700 | ast::StmtKind::Return(_)
2701 | ast::StmtKind::Revert(_, _)
2702 | ast::StmtKind::Placeholder => false,
2703 }
2704 }
2705
2706 fn is_multiline_block(
2709 &mut self,
2710 block: &'ast ast::Block<'ast>,
2711 empty_as_multiline: bool,
2712 force_single_as_multiline: bool,
2713 ) -> bool {
2714 if block.stmts.is_empty() {
2715 return empty_as_multiline;
2716 }
2717 if block.stmts.len() > 1 {
2720 return true;
2721 }
2722
2723 if force_single_as_multiline {
2724 return true;
2725 }
2726
2727 if self.sm.is_multiline(block.span)
2730 && let Ok(snip) = self.sm.span_to_snippet(block.span)
2731 {
2732 let code_lines = snip.lines().filter(|line| {
2733 let trimmed = line.trim();
2734 if empty_as_multiline {
2736 !trimmed.is_empty() && trimmed != "{" && trimmed != "}"
2737 } else {
2738 !trimmed.is_empty()
2739 }
2740 });
2741 if code_lines.count() > 1 {
2742 return true;
2743 }
2744 }
2745
2746 let stmt = &block.stmts[0];
2747
2748 if self.peek_comment_between(block.span.lo(), stmt.span.lo()).is_some() {
2751 return true;
2752 }
2753
2754 self.is_multiline_block_stmt(stmt, empty_as_multiline)
2755 }
2756
2757 fn can_stmts_be_inlined(
2759 &mut self,
2760 cond: &'ast ast::Expr<'ast>,
2761 then: &'ast ast::Stmt<'ast>,
2762 els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>,
2763 ) -> bool {
2764 let cond_len = self.estimate_size(cond.span);
2765
2766 let then_margin = if 6 + cond_len < self.space_left() { 6 + cond_len } else { 2 };
2769
2770 if !self.is_inline_stmt(then, then_margin) {
2771 return false;
2772 }
2773
2774 els_opt.is_none_or(|els| self.is_inline_stmt(els, 6))
2776 }
2777
2778 fn can_header_be_inlined(&mut self, func: &ast::ItemFunction<'_>) -> bool {
2779 self.estimate_header_size(func) <= self.space_left()
2780 }
2781
2782 fn can_header_params_be_inlined(&mut self, func: &ast::ItemFunction<'_>) -> bool {
2783 self.estimate_header_params_size(func) <= self.space_left()
2784 }
2785
2786 fn estimate_header_size(&mut self, func: &ast::ItemFunction<'_>) -> usize {
2787 let ast::ItemFunction { kind: _, ref header, ref body, body_span: _ } = *func;
2788
2789 let visibility = header.visibility.map_or(0, |v| self.estimate_size(v.span) + 1);
2791 let mutability = header.state_mutability.map_or(0, |sm| self.estimate_size(sm.span) + 1);
2793 let m = header.modifiers.iter().fold(0, |len, m| len + self.estimate_size(m.span()));
2795 let modifiers = if m != 0 { m + 1 } else { 0 };
2796 let override_ = header.override_.as_ref().map_or(0, |o| self.estimate_size(o.span) + 1);
2798 let virtual_ = if header.virtual_.is_none() { 0 } else { 8 };
2800 let returns = header.returns.as_ref().map_or(0, |ret| {
2802 ret.vars
2803 .iter()
2804 .fold(0, |len, p| if len != 0 { len + 2 } else { 10 } + self.estimate_size(p.span))
2805 });
2806 let end = if body.is_some() { 2 } else { 1 };
2808
2809 self.estimate_header_params_size(func)
2810 + visibility
2811 + mutability
2812 + modifiers
2813 + override_
2814 + virtual_
2815 + returns
2816 + end
2817 }
2818
2819 fn estimate_header_params_size(&mut self, func: &ast::ItemFunction<'_>) -> usize {
2820 let ast::ItemFunction { kind, ref header, body: _, body_span: _ } = *func;
2821
2822 let kw = match kind {
2823 ast::FunctionKind::Constructor => 11, ast::FunctionKind::Function => 9, ast::FunctionKind::Modifier => 9, ast::FunctionKind::Fallback => 8, ast::FunctionKind::Receive => 7, };
2829
2830 let params = header
2832 .parameters
2833 .vars
2834 .iter()
2835 .fold(0, |len, p| if len != 0 { len + 2 } else { 2 } + self.estimate_size(p.span));
2836
2837 kw + header.name.map_or(0, |name| self.estimate_size(name.span)) + std::cmp::max(2, params)
2838 }
2839
2840 fn estimate_lhs_size(&self, expr: &ast::Expr<'_>, parent_op: &ast::BinOp) -> usize {
2841 match &expr.kind {
2842 ast::ExprKind::Binary(lhs, op, _) if op.kind.group() == parent_op.kind.group() => {
2843 self.estimate_lhs_size(lhs, op)
2844 }
2845 _ => self.estimate_size(expr.span),
2846 }
2847 }
2848
2849 fn estimate_call_chain_size(&self, expr: &ast::Expr<'_>) -> Option<usize> {
2850 match &expr.kind {
2851 ast::ExprKind::Call(callee, args) => {
2852 let ast::CallArgsKind::Unnamed(args) = &args.kind else { return None };
2853 let mut size = self.estimate_call_chain_size(callee)? + 2;
2854 for arg in args.iter() {
2855 size += self.estimate_call_chain_size(arg)?;
2856 }
2857 Some(size + args.len().saturating_sub(1) * 2)
2858 }
2859 ast::ExprKind::Ident(ident) => Some(ident.to_string().len()),
2860 ast::ExprKind::Index(expr, kind) => {
2861 let index_size = match kind {
2862 ast::IndexKind::Index(Some(index)) => self.estimate_call_chain_size(index)?,
2863 ast::IndexKind::Index(None) => 0,
2864 ast::IndexKind::Range(start, end) => {
2865 let start = match start {
2866 Some(start) => self.estimate_call_chain_size(start)?,
2867 None => 0,
2868 };
2869 let end = match end {
2870 Some(end) => self.estimate_call_chain_size(end)?,
2871 None => 0,
2872 };
2873 start + end + 1
2874 }
2875 };
2876 Some(self.estimate_call_chain_size(expr)? + index_size + 2)
2877 }
2878 ast::ExprKind::Lit(lit, None)
2880 if matches!(lit.kind, ast::LitKind::Number(_)) && lit.symbol.as_str() == "0" =>
2881 {
2882 Some(1)
2883 }
2884 ast::ExprKind::Member(expr, ident) => {
2885 Some(self.estimate_call_chain_size(expr)? + ident.to_string().len() + 1)
2886 }
2887 ast::ExprKind::Tuple(exprs) if let [SpannedOption::Some(expr)] = exprs.as_ref() => {
2888 Some(self.estimate_call_chain_size(expr)? + 2)
2889 }
2890 _ => None,
2891 }
2892 }
2893
2894 fn has_comments_between_elements<I>(&self, limits: Span, elements: I) -> bool
2895 where
2896 I: IntoIterator<Item = &'ast ast::Expr<'ast>>,
2897 {
2898 let mut last_span_end = limits.lo();
2899 for expr in elements {
2900 if self.has_comment_between(last_span_end, expr.span.lo()) {
2901 return true;
2902 }
2903 last_span_end = expr.span.hi();
2904 }
2905
2906 if self.has_comment_between(last_span_end, limits.hi()) {
2907 return true;
2908 }
2909
2910 false
2911 }
2912}
2913
2914#[derive(Debug)]
2917enum MemberOrCallArgs {
2918 Member(usize),
2919 CallArgs(usize, bool),
2920}
2921
2922impl MemberOrCallArgs {
2923 const fn size(&self) -> usize {
2924 match self {
2925 Self::CallArgs(size, ..) | Self::Member(size) => *size,
2926 }
2927 }
2928
2929 const fn member_size(&self) -> usize {
2930 match self {
2931 Self::CallArgs(..) => 0,
2932 Self::Member(size) => *size,
2933 }
2934 }
2935
2936 const fn has_comments(&self) -> bool {
2937 matches!(self, Self::CallArgs(.., true))
2938 }
2939}
2940
2941#[derive(Debug, Clone)]
2942#[expect(dead_code)]
2943enum AttributeKind<'ast> {
2944 Visibility(ast::Visibility),
2945 StateMutability(ast::StateMutability),
2946 Virtual,
2947 Override(&'ast ast::Override<'ast>),
2948 Modifier(&'ast ast::Modifier<'ast>),
2949}
2950
2951type AttributeCommentMap = HashMap<BytePos, (Vec<Comment>, Vec<Comment>, Vec<Comment>)>;
2952
2953#[derive(Debug, Clone)]
2954struct AttributeInfo<'ast> {
2955 kind: AttributeKind<'ast>,
2956 span: Span,
2957}
2958
2959struct AttributeCommentMapper<'ast> {
2961 limit_pos: BytePos,
2962 comments: Vec<Comment>,
2963 attributes: Vec<AttributeInfo<'ast>>,
2964}
2965
2966impl<'ast> AttributeCommentMapper<'ast> {
2967 fn new(returns: Option<&'ast ast::ParameterList<'ast>>, body_pos: BytePos) -> Self {
2968 Self {
2969 comments: Vec::new(),
2970 attributes: Vec::new(),
2971 limit_pos: returns.as_ref().map_or(body_pos, |ret| ret.span.lo()),
2972 }
2973 }
2974
2975 #[allow(clippy::type_complexity)]
2976 fn build(
2977 mut self,
2978 state: &mut State<'_, 'ast>,
2979 header: &'ast ast::FunctionHeader<'ast>,
2980 ) -> (AttributeCommentMap, Vec<AttributeInfo<'ast>>, BytePos) {
2981 let first_attr = self.collect_attributes(header);
2982 self.cache_comments(state);
2983 (self.map(), self.attributes, first_attr)
2984 }
2985
2986 fn map(&mut self) -> AttributeCommentMap {
2987 let mut map = HashMap::new();
2988 for a in 0..self.attributes.len() {
2989 let is_last = a == self.attributes.len() - 1;
2990 let (mut before, mut inner, mut after) = (Vec::new(), Vec::new(), Vec::new());
2991
2992 let before_limit = self.attributes[a].span.lo();
2993 let inner_limit = self.attributes[a].span.hi();
2994 let after_limit =
2995 if is_last { self.limit_pos } else { self.attributes[a + 1].span.lo() };
2996
2997 let mut c = 0;
2998 while c < self.comments.len() {
2999 if self.comments[c].pos() <= before_limit {
3000 before.push(self.comments.remove(c));
3001 } else if self.comments[c].pos() <= inner_limit {
3002 inner.push(self.comments.remove(c));
3003 } else if (after.is_empty() || is_last) && self.comments[c].pos() <= after_limit {
3004 after.push(self.comments.remove(c));
3005 } else {
3006 c += 1;
3007 }
3008 }
3009 map.insert(before_limit, (before, inner, after));
3010 }
3011 map
3012 }
3013
3014 fn collect_attributes(&mut self, header: &'ast ast::FunctionHeader<'ast>) -> BytePos {
3015 let mut first_pos = BytePos(u32::MAX);
3016 if let Some(v) = header.visibility {
3017 if v.span.lo() < first_pos {
3018 first_pos = v.span.lo()
3019 }
3020 self.attributes
3021 .push(AttributeInfo { kind: AttributeKind::Visibility(*v), span: v.span });
3022 }
3023 if let Some(sm) = header.state_mutability {
3024 if sm.span.lo() < first_pos {
3025 first_pos = sm.span.lo()
3026 }
3027 self.attributes
3028 .push(AttributeInfo { kind: AttributeKind::StateMutability(*sm), span: sm.span });
3029 }
3030 if let Some(span) = header.virtual_ {
3031 if span.lo() < first_pos {
3032 first_pos = span.lo()
3033 }
3034 self.attributes.push(AttributeInfo { kind: AttributeKind::Virtual, span });
3035 }
3036 if let Some(ref o) = header.override_ {
3037 if o.span.lo() < first_pos {
3038 first_pos = o.span.lo()
3039 }
3040 self.attributes.push(AttributeInfo { kind: AttributeKind::Override(o), span: o.span });
3041 }
3042 for m in header.modifiers.iter() {
3043 if m.span().lo() < first_pos {
3044 first_pos = m.span().lo()
3045 }
3046 self.attributes
3047 .push(AttributeInfo { kind: AttributeKind::Modifier(m), span: m.span() });
3048 }
3049 self.attributes.sort_by_key(|attr| attr.span.lo());
3050 first_pos
3051 }
3052
3053 fn cache_comments(&mut self, state: &mut State<'_, 'ast>) {
3054 let mut pending = None;
3055 for cmnt in state.comments.iter() {
3056 if cmnt.pos() >= self.limit_pos {
3057 break;
3058 }
3059 match pending {
3060 Some(ref p) => pending = Some(p + 1),
3061 None => pending = Some(0),
3062 }
3063 }
3064 while let Some(p) = pending {
3065 if p == 0 {
3066 pending = None;
3067 } else {
3068 pending = Some(p - 1);
3069 }
3070 let cmnt = state.next_comment().unwrap();
3071 if cmnt.style.is_blank() {
3072 continue;
3073 }
3074 self.comments.push(cmnt);
3075 }
3076 }
3077}
3078
3079const fn stmt_needs_semi(stmt: &ast::StmtKind<'_>) -> bool {
3080 match stmt {
3081 ast::StmtKind::Assembly { .. }
3082 | ast::StmtKind::Block { .. }
3083 | ast::StmtKind::For { .. }
3084 | ast::StmtKind::If { .. }
3085 | ast::StmtKind::Try { .. }
3086 | ast::StmtKind::UncheckedBlock { .. }
3087 | ast::StmtKind::While { .. } => false,
3088
3089 ast::StmtKind::DeclSingle { .. }
3090 | ast::StmtKind::DeclMulti { .. }
3091 | ast::StmtKind::Break { .. }
3092 | ast::StmtKind::Continue { .. }
3093 | ast::StmtKind::DoWhile { .. }
3094 | ast::StmtKind::Emit { .. }
3095 | ast::StmtKind::Expr { .. }
3096 | ast::StmtKind::Return { .. }
3097 | ast::StmtKind::Revert { .. }
3098 | ast::StmtKind::Placeholder { .. } => true,
3099 }
3100}
3101
3102fn item_needs_iso(item: &ast::ItemKind<'_>) -> bool {
3104 match item {
3105 ast::ItemKind::Pragma(..)
3106 | ast::ItemKind::Import(..)
3107 | ast::ItemKind::Using(..)
3108 | ast::ItemKind::Variable(..)
3109 | ast::ItemKind::Udvt(..)
3110 | ast::ItemKind::Enum(..)
3111 | ast::ItemKind::Error(..)
3112 | ast::ItemKind::Event(..) => false,
3113
3114 ast::ItemKind::Contract(..) => true,
3115
3116 ast::ItemKind::Struct(strukt) => !strukt.fields.is_empty(),
3117 ast::ItemKind::Function(func) => {
3118 func.body.as_ref().is_some_and(|b| !b.is_empty())
3119 && !matches!(func.kind, ast::FunctionKind::Modifier)
3120 }
3121 }
3122}
3123
3124const fn is_binary_expr(expr_kind: &ast::ExprKind<'_>) -> bool {
3125 matches!(expr_kind, ast::ExprKind::Binary(..))
3126}
3127
3128fn has_complex_successor(expr_kind: &ast::ExprKind<'_>, left: bool) -> bool {
3129 match expr_kind {
3130 ast::ExprKind::Binary(lhs, _, rhs) => {
3131 if left {
3132 has_complex_successor(&lhs.kind, left)
3133 } else {
3134 has_complex_successor(&rhs.kind, left)
3135 }
3136 }
3137 ast::ExprKind::Unary(_, expr) => has_complex_successor(&expr.kind, left),
3138 ast::ExprKind::Lit(..) | ast::ExprKind::Ident(_) => false,
3139 ast::ExprKind::Tuple(..) => false,
3140 _ => true,
3141 }
3142}
3143
3144const fn is_call(expr_kind: &ast::ExprKind<'_>) -> bool {
3145 matches!(expr_kind, ast::ExprKind::Call(..))
3146}
3147
3148const fn is_call_with_named_args(expr_kind: &ast::ExprKind<'_>) -> bool {
3153 if let ast::ExprKind::Call(_, args) = expr_kind {
3154 matches!(args.kind, ast::CallArgsKind::Named(_))
3155 } else {
3156 false
3157 }
3158}
3159
3160fn is_call_chain(expr_kind: &ast::ExprKind<'_>, must_have_child: bool) -> bool {
3161 match expr_kind {
3162 ast::ExprKind::Index(child, ..) | ast::ExprKind::Member(child, ..) => {
3163 is_call_chain(&child.kind, false)
3164 }
3165 ast::ExprKind::Tuple(exprs) if let [SpannedOption::Some(child)] = exprs.as_ref() => {
3166 is_call_chain(&child.kind, must_have_child)
3167 }
3168 _ => !must_have_child && is_call(expr_kind),
3169 }
3170}
3171
3172fn call_chain_contains_options(expr: &ast::Expr<'_>) -> bool {
3173 match &expr.peel_parens().kind {
3174 ast::ExprKind::CallOptions(..) => true,
3175 ast::ExprKind::Call(expr, ..)
3176 | ast::ExprKind::Index(expr, ..)
3177 | ast::ExprKind::Member(expr, ..) => call_chain_contains_options(expr),
3178 _ => false,
3179 }
3180}
3181
3182fn is_call_with_opts_and_args(expr_kind: &ast::ExprKind<'_>) -> bool {
3183 if let ast::ExprKind::Call(call_expr, call_args) = expr_kind {
3184 matches!(call_expr.kind, ast::ExprKind::CallOptions(..)) && !call_args.is_empty()
3185 } else {
3186 false
3187 }
3188}
3189
3190#[derive(Debug)]
3191struct Decision {
3192 outcome: bool,
3193 is_cached: bool,
3194}
3195
3196#[derive(Clone, Copy, PartialEq, Eq)]
3197pub(crate) enum BinOpGroup {
3198 Arithmetic,
3199 Bitwise,
3200 Comparison,
3201 Logical,
3202}
3203
3204trait BinOpExt {
3205 fn group(&self) -> BinOpGroup;
3206}
3207
3208impl BinOpExt for ast::BinOpKind {
3209 fn group(&self) -> BinOpGroup {
3210 match self {
3211 Self::Or | Self::And => BinOpGroup::Logical,
3212 Self::Eq | Self::Ne | Self::Lt | Self::Le | Self::Gt | Self::Ge => {
3213 BinOpGroup::Comparison
3214 }
3215 Self::BitOr | Self::BitXor | Self::BitAnd | Self::Shl | Self::Shr | Self::Sar => {
3216 BinOpGroup::Bitwise
3217 }
3218 Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Rem | Self::Pow => {
3219 BinOpGroup::Arithmetic
3220 }
3221 }
3222 }
3223}
3224
3225pub(super) fn get_callee_head_size(callee: &ast::Expr<'_>) -> usize {
3234 match &callee.kind {
3235 ast::ExprKind::Ident(id) => id.as_str().len(),
3236 ast::ExprKind::Type(ast::Type { kind: ast::TypeKind::Elementary(ty), .. }) => {
3237 ty.to_abi_str().len()
3238 }
3239 ast::ExprKind::Index(base, idx) => {
3240 let idx_len = match idx {
3241 ast::IndexKind::Index(expr) => expr.as_ref().map_or(0, |e| get_callee_head_size(e)),
3242 ast::IndexKind::Range(e1, e2) => {
3243 1 + e1.as_ref().map_or(0, |e| get_callee_head_size(e))
3244 + e2.as_ref().map_or(0, |e| get_callee_head_size(e))
3245 }
3246 };
3247 get_callee_head_size(base) + 2 + idx_len
3248 }
3249 ast::ExprKind::Member(base, member_ident) => {
3250 match &base.kind {
3251 ast::ExprKind::Ident(..) | ast::ExprKind::Type(..) => {
3252 get_callee_head_size(base) + 1 + member_ident.as_str().len()
3253 }
3254
3255 ast::ExprKind::Member(child, ..)
3257 if !matches!(&child.kind, ast::ExprKind::Call(..)) =>
3258 {
3259 get_callee_head_size(base) + 1 + member_ident.as_str().len()
3260 }
3261 _ => member_ident.as_str().len(),
3262 }
3263 }
3264 ast::ExprKind::Binary(lhs, _, _) => get_callee_head_size(lhs),
3265
3266 _ => 0,
3268 }
3269}
3270
3271#[cfg(test)]
3272mod tests {
3273 use super::*;
3274 use crate::{FormatterConfig, InlineConfig};
3275 use foundry_common::comments::Comments;
3276 use solar::{
3277 interface::{Session, source_map::FileName},
3278 sema::Compiler,
3279 };
3280 use std::sync::Arc;
3281
3282 fn parse_and_test<F>(source: &str, test_fn: F)
3284 where
3285 F: FnOnce(&mut State<'_, '_>, &ast::ItemFunction<'_>) + Send,
3286 {
3287 let session = Session::builder().with_buffer_emitter(Default::default()).build();
3288 let mut compiler = Compiler::new(session);
3289
3290 compiler
3291 .enter_mut(|c| -> solar::interface::Result<()> {
3292 let mut pcx = c.parse();
3293 pcx.set_resolve_imports(false);
3294
3295 let file = c
3297 .sess()
3298 .source_map()
3299 .new_source_file(FileName::Stdin, source)
3300 .map_err(|e| c.sess().dcx.err(e.to_string()).emit())?;
3301
3302 pcx.add_file(file.clone());
3303 pcx.parse();
3304 c.dcx().has_errors()?;
3305
3306 let gcx = c.gcx();
3308 let (_, source_obj) = gcx.get_ast_source(&file.name).expect("Failed to get AST");
3309 let ast = source_obj.ast.as_ref().expect("No AST found");
3310 let comments =
3311 Comments::new(&source_obj.file, gcx.sess.source_map(), true, false, None);
3312 let config = Arc::new(FormatterConfig::default());
3313 let inline_config = InlineConfig::default();
3314 let mut state = State::new(gcx.sess.source_map(), config, inline_config, comments);
3315
3316 let func = ast
3318 .items
3319 .iter()
3320 .find_map(|item| match &item.kind {
3321 ast::ItemKind::Function(func) => Some(func),
3322 ast::ItemKind::Contract(contract) => {
3323 contract.body.iter().find_map(|contract_item| {
3324 match &contract_item.kind {
3325 ast::ItemKind::Function(func) => Some(func),
3326 _ => None,
3327 }
3328 })
3329 }
3330 _ => None,
3331 })
3332 .expect("No function found in source");
3333
3334 test_fn(&mut state, func);
3336
3337 Ok(())
3338 })
3339 .expect("Test failed");
3340 }
3341
3342 #[test]
3343 fn test_estimate_header_sizes() {
3344 let test_cases = [
3345 ("function foo();", 14, 15),
3346 ("function foo() {}", 14, 16),
3347 ("function foo() public {}", 14, 23),
3348 ("function foo(uint256 a) public {}", 23, 32),
3349 ("function foo(uint256 a, address b, bool c) public {}", 42, 51),
3350 ("function foo() public pure {}", 14, 28),
3351 ("function foo() public virtual {}", 14, 31),
3352 ("function foo() public override {}", 14, 32),
3353 ("function foo() public onlyOwner {}", 14, 33),
3354 ("function foo() public returns(uint256) {}", 14, 40),
3355 ("function foo() public returns(uint256, address) {}", 14, 49),
3356 ("function foo(uint256 a) public virtual override returns(uint256) {}", 23, 66),
3357 ("function foo() external payable {}", 14, 33),
3358 ("contract C { constructor() {} }", 13, 15),
3360 ("contract C { constructor(uint256 a) {} }", 22, 24),
3361 ("contract C { modifier onlyOwner() {} }", 20, 22),
3362 ("contract C { modifier onlyRole(bytes32 role) {} }", 31, 33),
3363 ("contract C { fallback() external payable {} }", 10, 29),
3364 ("contract C { receive() external payable {} }", 9, 28),
3365 ];
3366
3367 for (source, expected_params, expected_header) in &test_cases {
3368 parse_and_test(source, |state, func| {
3369 let params_size = state.estimate_header_params_size(func);
3370 assert_eq!(
3371 params_size, *expected_params,
3372 "Failed params size: expected {expected_params}, got {params_size} for source: {source}",
3373 );
3374
3375 let header_size = state.estimate_header_size(func);
3376 assert_eq!(
3377 header_size, *expected_header,
3378 "Failed header size: expected {expected_header}, got {header_size} for source: {source}",
3379 );
3380 });
3381 }
3382 }
3383}