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