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 let mut previous_mixed = false;
770 for cmnt in pre_cmnts {
771 if previous_mixed
773 && cmnt.style.is_trailing()
774 && matches!(cmnt.kind, ast::CommentKind::Line)
775 {
776 self.hardbreak_if_not_bol();
777 }
778 previous_mixed = cmnt.style.is_mixed();
779 let Some(cmnt) = self.handle_comment(cmnt, false) else {
780 continue;
781 };
782 self.print_comment(
783 cmnt,
784 CommentConfig::default().mixed_no_break_post().mixed_prev_space(),
785 );
786 }
787 for cmnt in inner_cmnts.into_iter().rev() {
790 self.comments.push_front(cmnt);
791 }
792 let enabled = if self.handle_span(span, false) {
793 false
794 } else {
795 if !self.is_bol_or_only_ind() {
796 self.space();
797 }
798 self.ibox(0);
799 print_fn(self);
800 self.cursor.advance_to(span.hi(), true);
801 true
802 };
803 for cmnt in post_cmnts {
805 let Some(cmnt) = self.handle_comment(cmnt, false) else {
806 continue;
807 };
808 self.print_comment(cmnt, CommentConfig::default().mixed_prev_space());
809 }
810 if enabled {
811 self.end();
812 }
813 }
814 None => {
816 if !self.is_bol_or_only_ind() {
817 self.space();
818 }
819 print_fn(self);
820 self.cursor.advance_to(span.hi(), true);
821 }
822 }
823 }
824
825 fn is_modifier_a_base_contract(
826 &self,
827 kind: ast::FunctionKind,
828 modifier: &'ast ast::Modifier<'ast>,
829 ) -> bool {
830 let is_contract_base = self.contract.is_some_and(|contract| {
835 contract
836 .bases
837 .iter()
838 .any(|contract_base| contract_base.name.to_string() == modifier.name.to_string())
839 });
840 let is_constructor = matches!(kind, ast::FunctionKind::Constructor);
843 is_contract_base
845 || (is_constructor
846 && modifier.name.first().name.as_str().starts_with(char::is_uppercase))
847 }
848
849 fn print_error(&mut self, err: &'ast ast::ItemError<'ast>) {
850 let ast::ItemError { name, parameters } = err;
851 self.word("error ");
852 self.print_ident(name);
853 self.print_parameter_list(
854 parameters,
855 parameters.span,
856 if self.config.prefer_compact.errors() {
857 ListFormat::compact()
858 } else {
859 ListFormat::consistent()
860 },
861 );
862 self.word(";");
863 }
864
865 fn print_event(&mut self, event: &'ast ast::ItemEvent<'ast>) {
866 let ast::ItemEvent { name, parameters, anonymous } = event;
867 self.word("event ");
868 self.print_ident(name);
869 self.print_parameter_list(
870 parameters,
871 parameters.span,
872 if self.config.prefer_compact.events() {
873 ListFormat::compact().break_cmnts()
874 } else {
875 ListFormat::consistent().break_cmnts()
876 },
877 );
878 if *anonymous {
879 self.word(" anonymous");
880 }
881 self.word(";");
882 }
883
884 fn print_var_def(&mut self, var: &'ast ast::VariableDefinition<'ast>) {
885 self.print_var(var, true);
886 self.word(";");
887 }
888
889 fn print_assign_rhs(
891 &mut self,
892 rhs: &'ast ast::Expr<'ast>,
893 lhs_size: usize,
894 space_left: usize,
895 ty: Option<&ast::TypeKind<'ast>>,
896 cache: bool,
897 ) {
898 let rhs_size = if is_binary_expr(&rhs.kind)
901 && !self.has_comment_between(rhs.span.lo(), rhs.span.hi())
902 {
903 self.estimate_binary_size(rhs)
904 } else if assignment_member_depth(rhs) >= 2
905 && self.peek_comment_before(rhs.span.lo()).is_none()
906 {
907 self.estimate_assignment_size(rhs.span)
910 } else {
911 self.estimate_size(rhs.span)
912 };
913 let overflows = lhs_size + rhs_size >= space_left;
914 let fits_alone = rhs_size + self.config.tab_width < space_left;
915 let fits_alone_no_cmnts =
916 fits_alone && !self.has_comment_between(rhs.span.lo(), rhs.span.hi());
917 let force_break = overflows && fits_alone_no_cmnts;
918
919 if lhs_size <= space_left {
920 self.neverbreak();
921 }
922
923 if let Some(cmnt) = self.peek_comment_before(rhs.span.lo())
925 && self.inline_config.is_disabled(cmnt.span)
926 {
927 self.print_sep(Separator::Nbsp);
928 }
929 if self
930 .print_comments(
931 rhs.span.lo(),
932 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
933 )
934 .is_some_and(|cmnt| cmnt.is_trailing())
935 {
936 self.break_offset_if_not_bol(SIZE_INFINITY as usize, self.ind, false);
937 }
938
939 match &rhs.kind {
941 ast::ExprKind::Lit(lit, ..) if lit.is_str_concatenation() => {
942 self.print_sep(Separator::Nbsp);
944 self.neverbreak();
945 self.s.ibox(self.ind);
946 self.print_expr(rhs);
947 self.end();
948 }
949 ast::ExprKind::Lit(..) if ty.is_none() && !fits_alone => {
950 self.print_sep(Separator::Space);
952 self.s.offset(self.ind);
953 self.print_expr(rhs);
954 }
955 ast::ExprKind::Binary(lhs, op, _) => {
956 let print_inline = |this: &mut Self| {
957 this.print_sep(Separator::Nbsp);
958 this.neverbreak();
959 this.print_expr(rhs);
960 };
961 let print_with_break = |this: &mut Self, force_break: bool| {
962 if !this.is_bol_or_only_ind() {
963 if force_break {
964 this.print_sep(Separator::Hardbreak);
965 } else {
966 this.print_sep(Separator::Space);
967 }
968 }
969 this.s.offset(this.ind);
970 this.s.ibox(this.ind);
971 this.print_expr(rhs);
972 this.end();
973 };
974
975 if force_break {
977 print_with_break(self, true);
978 } else if self.estimate_lhs_size(rhs, op) + lhs_size > space_left {
979 if has_complex_successor(&rhs.kind, true)
980 && get_callee_head_size(lhs) + lhs_size <= space_left
981 {
982 if is_call(&lhs.kind) {
984 self.s.ibox(-self.ind);
985 print_inline(self);
986 self.end();
987 } else {
988 print_inline(self);
989 }
990 } else {
991 print_with_break(self, false);
992 }
993 }
994 else {
996 print_inline(self);
997 }
998 }
999 _ => {
1000 let callee_doesnt_fit = if let ast::ExprKind::Call(call_expr, ..) = &rhs.kind {
1002 let callee_size = get_callee_head_size(call_expr);
1003 callee_size + lhs_size > space_left
1004 && callee_size + self.config.tab_width < space_left
1005 } else {
1006 false
1007 };
1008
1009 if (lhs_size + 1 >= space_left && !is_call_chain(&rhs.kind, false))
1010 || callee_doesnt_fit
1011 {
1012 self.s.ibox(self.ind);
1013 } else {
1014 self.s.ibox(0);
1015 };
1016
1017 if has_complex_successor(&rhs.kind, true)
1018 && !matches!(&rhs.kind, ast::ExprKind::Member(..))
1019 {
1020 if !self.is_bol_or_only_ind() {
1022 let needs_offset = !callee_doesnt_fit
1023 && rhs_size + lhs_size + 1 >= space_left
1024 && fits_alone_no_cmnts;
1025 let separator = if callee_doesnt_fit || needs_offset {
1026 Separator::Space
1027 } else {
1028 Separator::Nbsp
1029 };
1030 self.print_sep(separator);
1031 if needs_offset {
1032 self.s.offset(self.ind);
1033 }
1034 }
1035 } else {
1036 if !self.is_bol_or_only_ind() {
1037 self.print_sep_unhandled(Separator::Space);
1038 }
1039 if let Some(ty) = ty
1041 && matches!(ty, ast::TypeKind::Elementary(..) | ast::TypeKind::Mapping(..))
1042 {
1043 self.s.offset(self.ind);
1044 }
1045 }
1046 self.print_expr(rhs);
1047 self.end();
1048 }
1049 }
1050
1051 self.var_init = cache;
1052 }
1053
1054 fn print_var(&mut self, var: &'ast ast::VariableDefinition<'ast>, is_var_def: bool) {
1055 let ast::VariableDefinition {
1056 span,
1057 ty,
1058 visibility,
1059 mutability,
1060 data_location,
1061 override_,
1062 indexed,
1063 name,
1064 initializer,
1065 } = var;
1066
1067 if self.handle_span(*span, false) {
1068 return;
1069 }
1070
1071 let init_space_left = self.space_left();
1076 let mut pre_init_size = self.estimate_size(ty.span);
1077
1078 self.s.ibox(0);
1080 if override_.is_some() {
1081 self.s.cbox(self.ind);
1082 } else {
1083 self.s.ibox(self.ind);
1084 }
1085 self.print_ty(ty);
1086
1087 self.print_attribute(visibility.map(|v| v.to_str()), is_var_def, &mut pre_init_size);
1088 self.print_attribute(mutability.map(|m| m.to_str()), is_var_def, &mut pre_init_size);
1089 self.print_attribute(data_location.map(|d| d.to_str()), is_var_def, &mut pre_init_size);
1090
1091 if let Some(override_) = override_ {
1092 if self
1093 .print_comments(override_.span.lo(), CommentConfig::skip_ws().mixed_prev_space())
1094 .is_none()
1095 {
1096 self.print_sep(Separator::SpaceOrNbsp(is_var_def));
1097 }
1098 self.ibox(0);
1099 self.print_override(override_);
1100 pre_init_size += self.estimate_size(override_.span) + 1;
1101 }
1102
1103 if *indexed {
1104 self.print_attribute(indexed.then_some("indexed"), is_var_def, &mut pre_init_size);
1105 }
1106
1107 if let Some(ident) = name {
1108 self.print_sep(Separator::SpaceOrNbsp(is_var_def && override_.is_none()));
1109 self.print_comments(
1110 ident.span.lo(),
1111 CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
1112 );
1113 self.print_ident(ident);
1114 pre_init_size += self.estimate_size(ident.span) + 1;
1115 }
1116 if let Some(init) = initializer {
1117 let cache = self.var_init;
1118 self.var_init = true;
1119
1120 pre_init_size += 2;
1121 self.print_word(" =");
1122 if override_.is_some() {
1123 self.end();
1124 }
1125 self.end();
1126
1127 self.print_assign_rhs(init, pre_init_size, init_space_left, Some(&ty.kind), cache);
1128 } else {
1129 if override_.is_some() {
1130 self.end();
1131 }
1132 self.end();
1133 }
1134 self.end();
1135 }
1136
1137 fn print_attribute(
1138 &mut self,
1139 attribute: Option<&'static str>,
1140 is_var_def: bool,
1141 size: &mut usize,
1142 ) {
1143 if let Some(s) = attribute {
1144 self.print_sep(Separator::SpaceOrNbsp(is_var_def));
1145 self.print_word(s);
1146 *size += s.len() + 1;
1147 }
1148 }
1149
1150 fn print_parameter_list(
1151 &mut self,
1152 parameters: &'ast [ast::VariableDefinition<'ast>],
1153 span: Span,
1154 format: ListFormat,
1155 ) {
1156 if self.handle_span(span, false) {
1157 return;
1158 }
1159
1160 self.print_tuple(
1161 parameters,
1162 span.lo(),
1163 span.hi(),
1164 |fmt, var| fmt.print_var(var, false),
1165 get_span!(),
1166 format,
1167 );
1168 }
1169
1170 fn print_ident_or_strlit(&mut self, value: &'ast ast::IdentOrStrLit) {
1171 match value {
1172 ast::IdentOrStrLit::Ident(ident) => self.print_ident(ident),
1173 ast::IdentOrStrLit::StrLit(strlit) => self.print_ast_str_lit(strlit),
1174 }
1175 }
1176
1177 fn print_ast_str_lit(&mut self, strlit: &'ast ast::StrLit) {
1179 self.print_str_lit(ast::StrKind::Str, strlit.span.lo(), strlit.value.as_str());
1180 }
1181
1182 fn print_ty(&mut self, ty: &'ast ast::Type<'ast>) {
1183 if self.handle_span(ty.span, false) {
1184 return;
1185 }
1186
1187 match &ty.kind {
1188 &ast::TypeKind::Elementary(ty) => 'b: {
1189 match ty {
1190 ast::ElementaryType::Address(true) => {
1192 self.word("address payable");
1193 break 'b;
1194 }
1195 ast::ElementaryType::Int(size) | ast::ElementaryType::UInt(size) => {
1197 match (self.config.int_types, size.bits_raw()) {
1198 (config::IntTypes::Short, 0 | 256)
1199 | (config::IntTypes::Preserve, 0) => {
1200 let short = match ty {
1201 ast::ElementaryType::Int(_) => "int",
1202 ast::ElementaryType::UInt(_) => "uint",
1203 _ => unreachable!(),
1204 };
1205 self.word(short);
1206 break 'b;
1207 }
1208 _ => {}
1209 }
1210 }
1211 _ => {}
1212 }
1213 self.word(ty.to_abi_str());
1214 }
1215 ast::TypeKind::Array(ast::TypeArray { element, size }) => {
1216 self.print_ty(element);
1217 let open_bracket = self
1218 .find_uncommented_char(Span::new(element.span.hi(), ty.span.hi()), '[')
1219 .unwrap();
1220 self.print_comments(
1221 open_bracket,
1222 CommentConfig::skip_ws().mixed_prev_space().mixed_post_nbsp(),
1223 );
1224 if let Some(size) = size {
1225 self.word("[");
1226 self.print_expr(size);
1227 self.word("]");
1228 } else {
1229 self.word("[]");
1230 }
1231 }
1232 ast::TypeKind::Function(ast::TypeFunction {
1233 parameters,
1234 visibility,
1235 state_mutability,
1236 returns,
1237 }) => {
1238 self.cbox(0);
1239 self.word("function");
1240 self.print_parameter_list(parameters, parameters.span, ListFormat::inline());
1241
1242 if let Some(v) = visibility {
1243 self.space();
1244 self.word(v.to_str());
1245 }
1246 if let Some(sm) = state_mutability
1247 && !matches!(**sm, ast::StateMutability::NonPayable)
1248 {
1249 self.space();
1250 self.word(sm.to_str());
1251 }
1252 if let Some(ret) = returns
1253 && !ret.is_empty()
1254 {
1255 self.nbsp();
1256 self.word("returns");
1257 self.nbsp();
1258 self.print_parameter_list(
1259 ret,
1260 ret.span,
1261 ListFormat::consistent(), );
1263 }
1264 self.end();
1265 }
1266 ast::TypeKind::Mapping(ast::TypeMapping { key, key_name, value, value_name }) => {
1267 self.word("mapping(");
1268 self.s.cbox(0);
1269 if let Some(cmnt) = self.peek_comment_before(key.span.lo()) {
1270 if cmnt.style.is_mixed() {
1271 self.print_comments(
1272 key.span.lo(),
1273 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1274 );
1275 self.break_offset_if_not_bol(SIZE_INFINITY as usize, 0, false);
1276 } else {
1277 self.print_comments(key.span.lo(), CommentConfig::skip_ws());
1278 }
1279 }
1280 else if 18
1284 + self.estimate_type_size(key)
1285 + key_name.map(|k| self.estimate_size(k.span)).unwrap_or(0)
1286 + self.estimate_type_size(value)
1287 + value_name.map(|v| self.estimate_size(v.span)).unwrap_or(0)
1288 >= self.space_left()
1289 {
1290 self.hardbreak();
1291 } else {
1292 self.zerobreak();
1293 }
1294 self.s.cbox(0);
1295 self.print_ty(key);
1296 if let Some(ident) = key_name {
1297 if self
1298 .print_comments(
1299 ident.span.lo(),
1300 CommentConfig::skip_ws()
1301 .mixed_no_break()
1302 .mixed_prev_space()
1303 .mixed_post_nbsp(),
1304 )
1305 .is_none()
1306 {
1307 self.nbsp();
1308 }
1309 self.print_ident(ident);
1310 }
1311 self.print_comments(
1314 value.span.lo(),
1315 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1316 );
1317 if !self.is_bol_or_only_ind() {
1318 self.space();
1319 }
1320 self.s.offset(self.ind);
1321 self.word("=> ");
1322 self.s.ibox(self.ind);
1323 self.print_ty(value);
1324 if let Some(ident) = value_name {
1325 self.neverbreak();
1326 if self
1327 .print_comments(
1328 ident.span.lo(),
1329 CommentConfig::skip_ws()
1330 .mixed_no_break()
1331 .mixed_prev_space()
1332 .mixed_post_nbsp(),
1333 )
1334 .is_none()
1335 {
1336 self.nbsp();
1337 }
1338 self.print_ident(ident);
1339 if self
1340 .peek_comment_before(ty.span.hi())
1341 .is_some_and(|cmnt| cmnt.style.is_mixed())
1342 {
1343 self.neverbreak();
1344 self.print_comments(
1345 value.span.lo(),
1346 CommentConfig::skip_ws().mixed_no_break(),
1347 );
1348 }
1349 }
1350 self.end();
1351 self.end();
1352 if self
1353 .print_comments(
1354 ty.span.hi(),
1355 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1356 )
1357 .is_some_and(|cmnt| !cmnt.is_mixed())
1358 {
1359 self.break_offset_if_not_bol(0, -self.ind, false);
1360 } else {
1361 self.zerobreak();
1362 self.s.offset(-self.ind);
1363 }
1364 self.end();
1365 self.word(")");
1366 }
1367 ast::TypeKind::Custom(path) => self.print_path(path, false),
1368 }
1369 }
1370
1371 fn print_override(&mut self, override_: &'ast ast::Override<'ast>) {
1372 let ast::Override { span, paths } = override_;
1373 if self.handle_span(*span, false) {
1374 return;
1375 }
1376 self.word("override");
1377 if !paths.is_empty() {
1378 if self.config.override_spacing {
1379 self.nbsp();
1380 }
1381 self.print_tuple(
1382 paths,
1383 span.lo(),
1384 span.hi(),
1385 |this, path| this.print_path(path, false),
1386 get_span!(()),
1387 ListFormat::consistent(), );
1389 }
1390 }
1391
1392 fn print_expr(&mut self, expr: &'ast ast::Expr<'ast>) {
1396 let ast::Expr { span, ref kind } = *expr;
1397 if self.handle_span(span, false) {
1398 return;
1399 }
1400
1401 match kind {
1402 ast::ExprKind::Array(exprs) => {
1403 self.print_array(exprs, expr.span, |this, e| this.print_expr(e), get_span!())
1404 }
1405 ast::ExprKind::Assign(lhs, None, rhs) => self.print_assign_expr(lhs, rhs),
1406 ast::ExprKind::Assign(lhs, Some(op), rhs) => self.print_bin_expr(lhs, op, rhs, true),
1407 ast::ExprKind::Binary(lhs, op, rhs) => self.print_bin_expr(lhs, op, rhs, false),
1408 ast::ExprKind::Call(call_expr, call_args) => {
1409 let cache = self.call_with_opts_and_args;
1410 let chained_named_call_cache = self.chained_named_call;
1411 let keep_inline = chained_named_call_cache
1414 .is_some_and(|call| call.keep_inline && call.callee.contains(expr.span))
1415 && !self.has_comments_between_elements(call_args.span, call_args.exprs());
1416 self.call_with_opts_and_args = is_call_with_opts_and_args(&expr.kind);
1417 let named_args_size = if call_args.is_empty() {
1418 4 + usize::from(self.config.bracket_spacing)
1419 } else {
1420 2
1421 };
1422 self.chained_named_call = (matches!(call_args.kind, ast::CallArgsKind::Named(_))
1423 && is_call_chain(&call_expr.kind, true))
1424 .then(|| ChainedNamedCall {
1425 callee: call_expr.span,
1426 keep_inline: !call_chain_contains_options(call_expr)
1427 && !self.has_comment_between(call_expr.span.lo(), call_expr.span.hi())
1428 && self
1429 .estimate_call_chain_size(call_expr)
1430 .is_some_and(|size| size + named_args_size <= self.space_left()),
1431 })
1432 .or_else(|| {
1433 chained_named_call_cache.filter(|call| call.callee.contains(expr.span))
1434 });
1435 let list_format = if keep_inline {
1436 ListFormat::inline()
1437 } else {
1438 ListFormat::compact().break_cmnts().break_single(true)
1439 };
1440 let terminal_callee = call_expr.peel_parens();
1441 let callee_has_breakable_comment = self
1442 .has_breakable_comment_between(call_expr.span.lo(), terminal_callee.span.lo())
1443 || self.has_breakable_comment_between(
1444 terminal_callee.span.hi(),
1445 call_expr.span.hi(),
1446 )
1447 || if let ast::ExprKind::Member(member_expr, ident) = &terminal_callee.kind {
1448 self.has_breakable_comment_between(member_expr.span.hi(), ident.span.lo())
1449 } else {
1450 false
1451 };
1452 self.print_member_or_call_chain(
1453 call_expr,
1454 MemberOrCallArgs::CallArgs(
1455 self.estimate_call_args_size(call_args.span),
1456 self.has_comments_between_elements(call_args.span, call_args.exprs()),
1457 ),
1458 |s| {
1459 let callee_suffix_can_break = callee_has_breakable_comment
1460 || match &terminal_callee.kind {
1461 ast::ExprKind::Member(member_expr, _) => {
1462 s.member_suffix_emits_break(terminal_callee, member_expr)
1463 }
1464 ast::ExprKind::Index(..) => !s.skip_index_break,
1465 _ => false,
1466 };
1467 s.print_call_args(
1468 call_args,
1469 list_format.without_ind(s.return_bin_expr).with_delimiters(
1470 !s.call_with_opts_and_args
1471 || s.call_stack
1472 .last()
1473 .is_some_and(|call| call.is_chained() && call.has_indent),
1474 ),
1475 get_callee_head_size(call_expr),
1476 callee_suffix_can_break,
1477 );
1478 },
1479 );
1480 self.call_with_opts_and_args = cache;
1481 self.chained_named_call = chained_named_call_cache;
1482 }
1483 ast::ExprKind::CallOptions(expr, named_args) => {
1484 let cache = self.call_with_opts_and_args;
1486 self.call_with_opts_and_args = false;
1487
1488 self.print_expr(expr);
1489 self.print_named_args(named_args, span.hi(), false);
1490
1491 self.call_with_opts_and_args = cache;
1493 }
1494 ast::ExprKind::Delete(expr) => {
1495 self.word("delete ");
1496 self.print_expr(expr);
1497 }
1498 ast::ExprKind::Ident(ident) => self.print_ident(ident),
1499 ast::ExprKind::Index(expr, kind) => self.print_index_expr(span, expr, kind),
1500 ast::ExprKind::Lit(lit, unit) => {
1501 self.print_lit_inner(lit, false);
1502 if let Some(unit) = unit {
1503 self.nbsp();
1504 self.word(unit.to_str());
1505 }
1506 }
1507 ast::ExprKind::Member(member_expr, ident) => {
1508 self.print_member_or_call_chain(
1509 member_expr,
1510 MemberOrCallArgs::Member(self.estimate_size(ident.span)),
1511 |s| {
1512 let has_mixed_comment = s
1513 .peek_comment_between(member_expr.span.hi(), ident.span.lo())
1514 .is_some_and(|comment| comment.style.is_mixed());
1515 let break_before_suffix = if has_mixed_comment {
1516 s.print_comments(
1517 ident.span.lo(),
1518 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1519 );
1520 true
1521 } else {
1522 !s.print_trailing_comment(member_expr.span.hi(), Some(ident.span.lo()))
1523 && s.peek_comment_between(member_expr.span.hi(), ident.span.lo())
1524 .is_none()
1525 && s.member_suffix_emits_break(expr, member_expr)
1526 };
1527 if break_before_suffix {
1528 s.zerobreak();
1529 }
1530 s.word(".");
1531 s.print_ident(ident);
1532 },
1533 );
1534 }
1535 ast::ExprKind::New(ty) => {
1536 self.word("new ");
1537 self.print_ty(ty);
1538 }
1539 ast::ExprKind::Payable(args) => {
1540 self.word("payable");
1541 self.print_call_args(args, ListFormat::compact().break_cmnts(), 7, false);
1542 }
1543 ast::ExprKind::Ternary(cond, then, els) => self.print_ternary_expr(cond, then, els),
1544 ast::ExprKind::Tuple(exprs) => self.print_tuple(
1545 exprs,
1546 span.lo(),
1547 span.hi(),
1548 |this, expr| match expr.as_ref() {
1549 SpannedOption::Some(expr) => this.print_expr(expr),
1550 SpannedOption::None(span) => {
1551 this.print_comments(
1552 span.hi(),
1553 CommentConfig::skip_ws().mixed_no_break_post(),
1554 );
1555 }
1556 },
1557 |expr| match expr.as_ref() {
1558 SpannedOption::Some(expr) => expr.span,
1559 SpannedOption::None(..) => Span::DUMMY,
1561 },
1562 ListFormat::compact().break_single(is_binary_expr(&expr.kind)),
1563 ),
1564 ast::ExprKind::TypeCall(ty) => {
1565 self.word("type");
1566 self.print_tuple(
1567 std::slice::from_ref(ty),
1568 span.lo(),
1569 span.hi(),
1570 Self::print_ty,
1571 get_span!(),
1572 ListFormat::consistent(),
1573 );
1574 }
1575 ast::ExprKind::Type(ty) => self.print_ty(ty),
1576 ast::ExprKind::Unary(un_op, expr) => {
1577 let prefix = un_op.kind.is_prefix();
1578 let op = un_op.kind.to_str();
1579 if prefix {
1580 self.word(op);
1581 }
1582 self.print_expr(expr);
1583 if !prefix {
1584 debug_assert!(un_op.kind.is_postfix());
1585 self.word(op);
1586 }
1587 }
1588 ast::ExprKind::Err(_) => self.print_span(span),
1589 }
1590 self.cursor.advance_to(span.hi(), true);
1591 }
1592
1593 fn print_assign_expr(&mut self, lhs: &'ast ast::Expr<'ast>, rhs: &'ast ast::Expr<'ast>) {
1595 let cache = self.var_init;
1596 self.var_init = true;
1597
1598 let space_left = self.space_left();
1599 let lhs_size = self.estimate_size(lhs.span);
1600 let lhs_size = if matches!(lhs.kind, ast::ExprKind::Index(..))
1603 && lhs_size + 2 <= space_left
1604 && !self.has_comment_between(lhs.span.lo(), rhs.span.lo())
1605 {
1606 self.estimate_assignment_size(lhs.span)
1607 } else {
1608 lhs_size
1609 };
1610 self.print_expr(lhs);
1611 self.word(" =");
1612 self.print_assign_rhs(rhs, lhs_size + 2, space_left, None, cache);
1613 }
1614
1615 fn print_bin_expr(
1617 &mut self,
1618 lhs: &'ast ast::Expr<'ast>,
1619 bin_op: &ast::BinOp,
1620 rhs: &'ast ast::Expr<'ast>,
1621 is_assign: bool,
1622 ) {
1623 let prev_chain = self.binary_expr;
1624 let is_chain = prev_chain.is_some_and(|prev| prev == bin_op.kind.group());
1625
1626 if !is_chain {
1628 self.binary_expr = Some(bin_op.kind.group());
1629
1630 let indent = if (is_assign && has_complex_successor(&rhs.kind, true))
1631 || self.call_stack.is_nested()
1632 && is_call_chain(&lhs.kind, false)
1633 && self.estimate_size(lhs.span) >= self.space_left()
1634 {
1635 0
1636 } else {
1637 self.ind
1638 };
1639 self.s.ibox(indent);
1640 }
1641
1642 self.print_expr(lhs);
1644
1645 let no_trailing_comment = !self.print_trailing_comment(lhs.span.hi(), Some(rhs.span.lo()));
1647 if is_assign {
1648 if no_trailing_comment {
1649 self.nbsp();
1650 }
1651 self.word(bin_op.kind.to_str());
1652 self.word("= ");
1653 } else {
1654 if no_trailing_comment
1655 && self
1656 .print_comments(
1657 bin_op.span.lo(),
1658 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1659 )
1660 .is_none_or(|cmnt| cmnt.is_mixed())
1661 {
1662 if !self.config.pow_no_space || !matches!(bin_op.kind, ast::BinOpKind::Pow) {
1663 self.space_if_not_bol();
1664 } else if !self.is_bol_or_only_ind() && !self.last_token_is_break() {
1665 self.zerobreak();
1666 }
1667 }
1668
1669 self.word(bin_op.kind.to_str());
1670
1671 if !self.config.pow_no_space || !matches!(bin_op.kind, ast::BinOpKind::Pow) {
1672 self.nbsp();
1673 }
1674 }
1675
1676 let rhs_has_mixed_comment =
1678 self.peek_comment_before(rhs.span.lo()).is_some_and(|cmnt| cmnt.style.is_mixed());
1679 if rhs_has_mixed_comment {
1680 self.ibox(0);
1681 self.print_expr(rhs);
1682 self.end();
1683 } else {
1684 self.print_expr(rhs);
1685 }
1686
1687 if !is_chain {
1689 self.binary_expr = prev_chain;
1690 self.end();
1691 }
1692 }
1693
1694 fn print_index_expr(
1696 &mut self,
1697 span: Span,
1698 expr: &'ast ast::Expr<'ast>,
1699 kind: &'ast ast::IndexKind<'ast>,
1700 ) {
1701 self.print_expr(expr);
1702 self.word("[");
1703 self.s.cbox(self.ind);
1704
1705 let mut skip_break = false;
1706 let mut zerobreak = |this: &mut Self| {
1707 if this.skip_index_break {
1708 skip_break = true;
1709 } else {
1710 this.zerobreak();
1711 }
1712 };
1713 match kind {
1714 ast::IndexKind::Index(Some(inner_expr)) => {
1715 zerobreak(self);
1716 self.print_expr(inner_expr);
1717 }
1718 ast::IndexKind::Index(None) => {}
1719 ast::IndexKind::Range(start, end) => {
1720 if let Some(start_expr) = start {
1721 if self
1722 .print_comments(start_expr.span.lo(), CommentConfig::skip_ws())
1723 .is_none_or(|s| s.is_mixed())
1724 {
1725 zerobreak(self);
1726 }
1727 self.print_expr(start_expr);
1728 } else {
1729 zerobreak(self);
1730 }
1731
1732 self.word(":");
1733
1734 if let Some(end_expr) = end {
1735 self.s.ibox(self.ind);
1736 if start.is_some() {
1737 zerobreak(self);
1738 }
1739 self.print_comments(
1740 end_expr.span.lo(),
1741 CommentConfig::skip_ws()
1742 .mixed_prev_space()
1743 .mixed_no_break()
1744 .mixed_post_nbsp(),
1745 );
1746 self.print_expr(end_expr);
1747 }
1748
1749 let is_trailing = if let Some(style) = self.print_comments(
1751 span.hi(),
1752 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space(),
1753 ) {
1754 skip_break = true;
1755 style.is_trailing()
1756 } else {
1757 false
1758 };
1759
1760 match (skip_break, end.is_some()) {
1762 (true, true) => {
1763 self.break_offset_if_not_bol(0, -2 * self.ind, false);
1764 self.end();
1765 if !is_trailing {
1766 self.break_offset_if_not_bol(0, -self.ind, false);
1767 }
1768 }
1769 (true, false) => {
1770 self.break_offset_if_not_bol(0, -self.ind, false);
1771 }
1772 (false, true) => {
1773 self.end();
1774 }
1775 _ => {}
1776 }
1777 }
1778 }
1779
1780 if !skip_break {
1781 self.zerobreak();
1782 self.s.offset(-self.ind);
1783 }
1784
1785 self.end();
1786 self.word("]");
1787 }
1788
1789 fn print_ternary_expr(
1791 &mut self,
1792 cond: &'ast ast::Expr<'ast>,
1793 then: &'ast ast::Expr<'ast>,
1794 els: &'ast ast::Expr<'ast>,
1795 ) {
1796 self.s.cbox(self.ind);
1797 self.s.ibox(0);
1798
1799 let print_sub_expr = |this: &mut Self, span_lo, prefix, expr: &'ast ast::Expr<'ast>| {
1800 match prefix {
1801 Some(prefix) => {
1802 if this.peek_comment_before(span_lo).is_some() {
1803 this.space();
1804 }
1805 this.print_comments(span_lo, CommentConfig::skip_ws());
1806 this.end();
1807 if !this.is_bol_or_only_ind() {
1808 this.space();
1809 }
1810 this.s.ibox(0);
1811 this.word(prefix);
1812 }
1813 None => {
1814 this.print_comments(expr.span.lo(), CommentConfig::skip_ws());
1815 }
1816 };
1817 this.print_expr(expr);
1818 };
1819
1820 self.s.ibox(-self.ind);
1822 print_sub_expr(self, then.span.lo(), None, cond);
1823 self.end();
1824 print_sub_expr(self, then.span.lo(), Some("? "), then);
1826 print_sub_expr(self, els.span.lo(), Some(": "), els);
1828
1829 self.end();
1830 self.neverbreak();
1831 self.s.offset(-self.ind);
1832 self.end();
1833 }
1834
1835 fn print_modifier_call(
1837 &mut self,
1838 modifier: &'ast ast::Modifier<'ast>,
1839 add_parens_if_empty: bool,
1840 ) {
1841 let ast::Modifier { name, arguments } = modifier;
1842 self.print_path(name, false);
1843 if !arguments.is_empty() || add_parens_if_empty {
1844 self.print_call_args(
1845 arguments,
1846 ListFormat::compact().break_cmnts(),
1847 name.to_string().len(),
1848 false,
1849 );
1850 }
1851 }
1852
1853 fn member_suffix_emits_break(&self, expr: &ast::Expr<'_>, member_expr: &ast::Expr<'_>) -> bool {
1854 match member_expr.kind {
1855 ast::ExprKind::Ident(_) | ast::ExprKind::Type(_) => false,
1856 ast::ExprKind::Index(..) if self.skip_index_break => false,
1857 _ if self
1858 .chained_named_call
1859 .is_some_and(|call| call.keep_inline && call.callee.contains(expr.span)) =>
1860 {
1861 false
1862 }
1863 _ if is_call_with_named_args(&member_expr.kind) => false,
1868 _ => true,
1869 }
1870 }
1871
1872 fn print_member_or_call_chain<F>(
1873 &mut self,
1874 child_expr: &'ast ast::Expr<'ast>,
1875 member_or_args: MemberOrCallArgs,
1876 print_suffix: F,
1877 ) where
1878 F: FnOnce(&mut Self),
1879 {
1880 fn member_depth(depth: usize, expr: &ast::Expr<'_>) -> usize {
1881 if let ast::ExprKind::Member(child, ..) = &expr.kind {
1882 member_depth(depth + 1, child)
1883 } else {
1884 depth
1885 }
1886 }
1887
1888 let (mut extra_box, skip_cache) = (false, self.skip_index_break);
1889 let parent_is_chain = self.call_stack.last().copied().is_some_and(|call| call.is_chained());
1890 if !parent_is_chain {
1891 let callee_size = get_callee_head_size(child_expr) + member_or_args.member_size();
1893 let expr_size = self.estimate_size(child_expr.span);
1894
1895 let callee_fits_line = self.space_left() > callee_size + 1;
1896 let total_fits_line = self.space_left() > expr_size + member_or_args.size() + 2;
1897 let no_cmnt_or_mixed =
1898 self.peek_comment_before(child_expr.span.hi()).is_none_or(|c| c.style.is_mixed());
1899
1900 if self.call_with_opts_and_args {
1902 self.cbox(0);
1903 extra_box = true;
1904 }
1905
1906 let keep_chain_inline = self
1908 .chained_named_call
1909 .is_some_and(|call| call.keep_inline && call.callee.contains(child_expr.span));
1910 let chain_has_indent = !keep_chain_inline
1911 && (is_call_chain(&child_expr.kind, true)
1912 || !(no_cmnt_or_mixed
1913 || matches!(&child_expr.kind, ast::ExprKind::CallOptions(..)))
1914 || !callee_fits_line
1915 || (member_depth(0, child_expr) >= 2
1916 && (!total_fits_line || member_or_args.has_comments())));
1917
1918 if is_call_chain(&child_expr.kind, false) {
1920 self.call_stack.push(CallContext::chained(callee_size, chain_has_indent));
1921 }
1922
1923 if chain_has_indent {
1924 self.s.cbox(self.ind);
1925 } else {
1926 self.skip_index_break = true;
1927 self.cbox(0);
1928 }
1929 }
1930
1931 self.print_expr(child_expr);
1933
1934 if extra_box {
1936 self.end();
1937 }
1938
1939 print_suffix(self);
1941
1942 if !parent_is_chain {
1944 if is_call_chain(&child_expr.kind, false) {
1945 self.call_stack.pop();
1946 }
1947 self.end();
1948 }
1949
1950 if self.skip_index_break {
1952 self.skip_index_break = skip_cache;
1953 }
1954 }
1955
1956 fn print_call_args(
1957 &mut self,
1958 args: &'ast ast::CallArgs<'ast>,
1959 format: ListFormat,
1960 callee_size: usize,
1961 callee_suffix_can_break: bool,
1962 ) {
1963 let ast::CallArgs { span, ref kind } = *args;
1964 if self.handle_span(span, true) {
1965 return;
1966 }
1967
1968 self.call_stack.push(CallContext::nested(callee_size));
1969
1970 let cache = self.binary_expr.take();
1972
1973 match kind {
1974 ast::CallArgsKind::Unnamed(exprs) => {
1975 self.print_tuple(
1976 exprs,
1977 span.lo(),
1978 span.hi(),
1979 |this, e| this.print_expr(e),
1980 get_span!(),
1981 format,
1982 );
1983 }
1984 ast::CallArgsKind::Named(named_args) => {
1985 let without_ind =
1986 self.call_stack.has_indented_parent_chain() && !callee_suffix_can_break;
1987 self.print_inside_parens(|state| {
1988 state.print_named_args(named_args, span.hi(), without_ind)
1989 });
1990 }
1991 }
1992
1993 self.binary_expr = cache;
1995 self.call_stack.pop();
1996 }
1997
1998 fn print_named_args(
1999 &mut self,
2000 args: &'ast [ast::NamedArg<'ast>],
2001 pos_hi: BytePos,
2002 without_ind: bool,
2003 ) {
2004 let list_format = match (self.config.bracket_spacing, self.config.prefer_compact.calls()) {
2005 (false, true) => ListFormat::compact(),
2006 (false, false) => ListFormat::consistent(),
2007 (true, true) => ListFormat::compact().with_space(),
2008 (true, false) => ListFormat::consistent().with_space(),
2009 };
2010
2011 self.word("{");
2012 if let Some(first_arg) = args.first() {
2014 let list_lo = first_arg.name.span.lo();
2015 self.commasep(
2016 args,
2017 list_lo,
2018 pos_hi,
2019 |s, arg| {
2021 s.cbox(0);
2022 s.print_ident(&arg.name);
2023 s.word(":");
2024 if s.same_source_line(arg.name.span.hi(), arg.value.span.hi())
2025 || !s.print_trailing_comment(arg.name.span.hi(), None)
2026 {
2027 s.nbsp();
2028 }
2029 s.print_comments(
2030 arg.value.span.lo(),
2031 CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
2032 );
2033 s.print_expr(arg.value);
2034 s.end();
2035 },
2036 |arg| arg.name.span.until(arg.value.span),
2037 list_format
2038 .break_cmnts()
2039 .break_single(true)
2040 .without_ind(without_ind)
2041 .with_delimiters(!self.call_with_opts_and_args),
2042 );
2043 } else if self.config.bracket_spacing {
2044 self.nbsp();
2045 }
2046 self.word("}");
2047 }
2048
2049 fn print_stmt(&mut self, stmt: &'ast ast::Stmt<'ast>) {
2053 self.print_stmt_bound(stmt, None);
2054 }
2055
2056 fn print_stmt_bound(&mut self, stmt: &'ast ast::Stmt<'ast>, next_pos: Option<BytePos>) {
2058 let ast::Stmt { ref docs, span, ref kind } = *stmt;
2059 self.print_docs(docs);
2060
2061 if self.handle_span(span, false) {
2063 self.print_trailing_comment_no_break(stmt.span.hi(), next_pos);
2064 return;
2065 }
2066
2067 let force_break = matches!(kind, ast::StmtKind::Return(..))
2069 && self.peek_comment_before(span.lo()).is_some_and(|cmnt| cmnt.style.is_mixed());
2070
2071 match kind {
2072 ast::StmtKind::Assembly(ast::StmtAssembly { dialect, flags, block }) => {
2073 self.print_assembly_stmt(span, dialect, flags, block)
2074 }
2075 ast::StmtKind::DeclSingle(var) => self.print_var(var, true),
2076 ast::StmtKind::DeclMulti(vars, init_expr) => {
2077 self.print_multi_decl_stmt(span, vars, init_expr)
2078 }
2079 ast::StmtKind::Block(stmts) => self.print_block(stmts, span),
2080 ast::StmtKind::Break => self.word("break"),
2081 ast::StmtKind::Continue => self.word("continue"),
2082 ast::StmtKind::DoWhile(stmt, cond) => {
2083 self.word("do ");
2084 self.print_stmt_as_block(stmt, cond.span.lo(), false);
2085 self.nbsp();
2086 self.print_if_cond("while", cond, cond.span.hi());
2087 }
2088 ast::StmtKind::Emit(path, args) => self.print_emit_or_revert("emit", path, args),
2089 ast::StmtKind::Expr(expr) => self.print_expr(expr),
2090 ast::StmtKind::For { init, cond, next, body } => {
2091 self.print_for_stmt(span, init, cond, next, body)
2092 }
2093 ast::StmtKind::If(cond, then, els_opt) => self.print_if_stmt(span, cond, then, els_opt),
2094 ast::StmtKind::Return(expr) => self.print_return_stmt(force_break, expr),
2095 ast::StmtKind::Revert(path, args) => self.print_emit_or_revert("revert", path, args),
2096 ast::StmtKind::Try(ast::StmtTry { expr, clauses }) => {
2097 self.print_try_stmt(expr, clauses)
2098 }
2099 ast::StmtKind::UncheckedBlock(block) => {
2100 self.word("unchecked ");
2101 self.print_block(block, stmt.span);
2102 }
2103 ast::StmtKind::While(cond, stmt) => {
2104 let inline = self.is_single_line_block(span.lo(), cond, stmt, None);
2106 if !inline.is_cached && self.single_line_stmt.is_none() {
2107 self.single_line_stmt = Some(inline.outcome);
2108 }
2109
2110 self.print_if_cond("while", cond, stmt.span.lo());
2112 self.nbsp();
2113 self.print_stmt_as_block(stmt, stmt.span.hi(), inline.outcome);
2114
2115 if !inline.is_cached && self.single_line_stmt.is_some() {
2117 self.single_line_stmt = None;
2118 }
2119 }
2120 ast::StmtKind::Placeholder => self.word("_"),
2121 }
2122 if stmt_needs_semi(kind) {
2123 self.neverbreak(); self.word(";");
2125 self.cursor.advance_to(span.hi(), true);
2126 }
2127 let ends_with_line_comment = self
2129 .comments
2130 .iter()
2131 .take_while(|cmnt| cmnt.pos() < stmt.span.hi())
2132 .filter(|cmnt| !cmnt.style.is_blank())
2133 .last()
2134 .is_some_and(|cmnt| {
2135 cmnt.style.is_trailing() && matches!(cmnt.kind, ast::CommentKind::Line)
2136 });
2137 self.print_comments(
2138 stmt.span.hi(),
2139 CommentConfig::skip_trailing_ws()
2140 .trailing_no_break()
2141 .mixed_no_break()
2142 .mixed_prev_space(),
2143 );
2144 if ends_with_line_comment && self.peek_comment().is_some() {
2145 self.hardbreak_if_not_bol();
2146 }
2147 self.print_trailing_comment_no_break(stmt.span.hi(), next_pos);
2148 }
2149
2150 fn print_assembly_stmt(
2153 &mut self,
2154 span: Span,
2155 dialect: &'ast Option<ast::StrLit>,
2156 flags: &'ast [ast::StrLit],
2157 block: &'ast ast::yul::Block<'ast>,
2158 ) {
2159 _ = self.handle_span(self.cursor.span(span.lo()), false);
2160 if !self.handle_span(span.until(block.span), false) {
2161 self.cursor.advance_to(span.lo(), true);
2162 self.print_word("assembly "); if let Some(dialect) = dialect {
2164 self.print_ast_str_lit(dialect);
2165 self.print_sep(Separator::Nbsp);
2166 }
2167 if !flags.is_empty() {
2168 self.print_tuple(
2169 flags,
2170 span.lo(),
2171 block.span.lo(),
2172 Self::print_ast_str_lit,
2173 get_span!(),
2174 ListFormat::consistent(),
2175 );
2176 self.print_sep(Separator::Nbsp);
2177 }
2178 }
2179 self.print_yul_block(block, block.span, false, 9);
2180 }
2181
2182 fn print_multi_decl_stmt(
2185 &mut self,
2186 span: Span,
2187 vars: &'ast BoxSlice<'ast, SpannedOption<ast::VariableDefinition<'ast>>>,
2188 init_expr: &'ast ast::Expr<'ast>,
2189 ) {
2190 let space_left = self.space_left();
2191
2192 self.s.ibox(self.ind);
2193 self.s.ibox(-self.ind);
2194 self.print_tuple(
2195 vars,
2196 span.lo(),
2197 init_expr.span.lo(),
2198 |this, var| match var {
2199 SpannedOption::Some(var) => this.print_var(var, true),
2200 SpannedOption::None(span) => {
2201 this.print_comments(span.hi(), CommentConfig::skip_ws().mixed_no_break_post());
2202 }
2203 },
2204 |var| match var {
2205 SpannedOption::Some(var) => var.span,
2206 SpannedOption::None(..) => Span::DUMMY,
2208 },
2209 ListFormat::consistent(),
2210 );
2211 self.end();
2212 self.word(" =");
2213
2214 if self.estimate_size(init_expr.span) + self.config.tab_width
2215 <= std::cmp::max(space_left, self.space_left())
2216 {
2217 self.print_sep(Separator::Space);
2218 self.ibox(0);
2219 } else {
2220 self.print_sep(Separator::Nbsp);
2221 self.neverbreak();
2222 self.s.ibox(-self.ind);
2223 }
2224 self.print_expr(init_expr);
2225 self.end();
2226 self.end();
2227 }
2228
2229 fn print_for_stmt(
2232 &mut self,
2233 span: Span,
2234 init: &'ast Option<&mut ast::Stmt<'ast>>,
2235 cond: &'ast Option<&mut ast::Expr<'ast>>,
2236 next: &'ast Option<&mut ast::Expr<'ast>>,
2237 body: &'ast ast::Stmt<'ast>,
2238 ) {
2239 self.cbox(0);
2240 self.s.ibox(self.ind);
2241 let open_paren = self.find_uncommented_char(span, '(').unwrap();
2242 self.print_word("for");
2243 if self
2244 .print_comments(
2245 open_paren,
2246 CommentConfig::skip_ws().mixed_prev_space().mixed_post_nbsp(),
2247 )
2248 .is_none()
2249 {
2250 self.nbsp();
2251 }
2252 self.cursor.advance_to(open_paren, true);
2253 self.print_word("(");
2254 let init_has_leading_comment =
2255 init.as_ref().is_some_and(|stmt| self.peek_comment_before(stmt.span.lo()).is_some());
2256 if !init_has_leading_comment {
2257 self.zerobreak();
2258 }
2259
2260 self.s.cbox(0);
2262 let init_trailing_comment = match init {
2263 Some(init_stmt) => {
2264 let has_trailing_comment = cond.as_ref().is_some_and(|cond| {
2265 self.comments
2266 .iter()
2267 .skip_while(|cmnt| cmnt.pos() < init_stmt.span.hi())
2268 .take_while(|cmnt| cmnt.pos() < cond.span.lo())
2269 .any(|cmnt| cmnt.style.is_trailing())
2270 });
2271 self.print_stmt_bound(init_stmt, Some(init_stmt.span.hi()));
2272 has_trailing_comment
2273 }
2274 None => {
2275 self.print_word(";");
2276 false
2277 }
2278 };
2279
2280 match cond {
2282 Some(cond_expr) => {
2283 if init_trailing_comment {
2284 self.hardbreak_if_not_bol();
2285 }
2286 self.print_sep(Separator::Space);
2287 self.print_expr(cond_expr);
2288 }
2289 None => self.zerobreak(),
2290 }
2291 self.print_word(";");
2292
2293 match next {
2295 Some(next_expr) => {
2296 self.space();
2297 self.print_expr(next_expr);
2298 }
2299 None => self.zerobreak(),
2300 }
2301
2302 self.break_offset_if_not_bol(0, -self.ind, false);
2304 self.end();
2305 self.print_word(") ");
2306 self.neverbreak();
2307 self.end();
2308
2309 self.print_comments(body.span.lo(), CommentConfig::skip_ws());
2311 self.print_stmt_as_block(body, span.hi(), false);
2312 self.end();
2313 }
2314
2315 fn print_if_stmt(
2318 &mut self,
2319 span: Span,
2320 cond: &'ast ast::Expr<'ast>,
2321 then: &'ast ast::Stmt<'ast>,
2322 els_opt: &'ast Option<&mut ast::Stmt<'ast>>,
2323 ) {
2324 let inline = self.is_single_line_block(span.lo(), cond, then, els_opt.as_ref());
2326 let set_inline_cache = !inline.is_cached && self.single_line_stmt.is_none();
2327 if set_inline_cache {
2328 self.single_line_stmt = Some(inline.outcome);
2329 }
2330
2331 self.cbox(0);
2332 self.ibox(0);
2333 self.print_if_no_else(cond, then, inline.outcome);
2335
2336 let mut current_else = els_opt.as_deref();
2338 while let Some(els) = current_else {
2339 if self.ends_with('}') {
2340 if self.has_comment_before_with(els.span.lo(), |cmnt| !cmnt.style.is_mixed()) {
2342 if self
2344 .print_comments(els.span.lo(), CommentConfig::skip_ws().mixed_no_break())
2345 .is_some_and(|cmnt| cmnt.is_mixed())
2346 {
2347 self.hardbreak();
2348 }
2349 }
2350 else if self
2352 .print_comments(
2353 els.span.lo(),
2354 CommentConfig::skip_ws()
2355 .mixed_no_break()
2356 .mixed_prev_space()
2357 .mixed_post_nbsp(),
2358 )
2359 .is_none()
2360 {
2361 self.nbsp();
2362 }
2363 } else {
2364 self.hardbreak_if_not_bol();
2365 if self
2366 .print_comments(els.span.lo(), CommentConfig::skip_ws())
2367 .is_some_and(|cmnt| cmnt.is_mixed())
2368 {
2369 self.hardbreak();
2370 };
2371 }
2372
2373 self.ibox(0);
2374 self.print_word("else ");
2375 match &els.kind {
2376 ast::StmtKind::If(cond, then, next_else) => {
2377 self.print_if_no_else(cond, then, inline.outcome);
2378 current_else = next_else.as_deref();
2379 }
2380 _ => {
2381 self.print_stmt_as_block(els, span.hi(), inline.outcome);
2382 self.end(); break;
2384 }
2385 }
2386 }
2387 self.end();
2388
2389 if set_inline_cache {
2391 self.single_line_stmt = None;
2392 }
2393 }
2394
2395 fn print_return_stmt(&mut self, force_break: bool, expr: &'ast Option<&mut ast::Expr<'ast>>) {
2398 if force_break {
2399 self.hardbreak_if_not_bol();
2400 }
2401
2402 let space_left = self.space_left();
2403 let expr_size = expr.as_ref().map_or(0, |expr| self.estimate_size(expr.span));
2404
2405 let overflows = space_left < 8 + expr_size;
2407 let fits_alone = space_left > expr_size;
2408
2409 if let Some(expr) = expr {
2410 let is_simple = matches!(expr.kind, ast::ExprKind::Lit(..) | ast::ExprKind::Ident(..));
2411 let allow_break = overflows && fits_alone;
2412
2413 self.return_bin_expr = is_binary_expr(&expr.kind);
2414 self.s.ibox(if is_simple || allow_break { self.ind } else { 0 });
2415
2416 self.print_word("return");
2417
2418 match self.print_comments(
2419 expr.span.lo(),
2420 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space().mixed_post_nbsp(),
2421 ) {
2422 Some(_) if !is_simple => self.s.offset(self.ind),
2423 None => self.print_sep(Separator::SpaceOrNbsp(allow_break)),
2424 _ => {}
2425 }
2426
2427 self.print_expr(expr);
2428 self.end();
2429 self.return_bin_expr = false;
2430 } else {
2431 self.print_word("return");
2432 }
2433 }
2434
2435 fn print_try_stmt(
2438 &mut self,
2439 expr: &'ast ast::Expr<'ast>,
2440 clauses: &'ast [ast::TryCatchClause<'ast>],
2441 ) {
2442 self.cbox(0);
2443 if let Some((first, other)) = clauses.split_first() {
2444 let ast::TryCatchClause { args, block, span: try_span, .. } = first;
2446 self.cbox(0);
2447 self.ibox(0);
2448 self.print_word("try ");
2449 self.print_comments(expr.span.lo(), CommentConfig::skip_ws());
2450 self.print_expr(expr);
2451
2452 self.print_comments(
2454 args.first().map(|p| p.span.lo()).unwrap_or_else(|| expr.span.lo()),
2455 CommentConfig::skip_ws(),
2456 );
2457 if !self.is_beginning_of_line() {
2458 self.nbsp();
2459 }
2460
2461 if args.is_empty() {
2462 self.end();
2463 } else {
2464 self.print_word("returns ");
2465 self.print_word("(");
2466 self.zerobreak();
2467 self.end();
2468 let span = args.span.with_hi(block.span.lo());
2469 self.commasep(
2470 args,
2471 span.lo(),
2472 span.hi(),
2473 |fmt, var| fmt.print_var(var, false),
2474 get_span!(),
2475 ListFormat::compact().with_delimiters(false),
2476 );
2477 self.print_word(")");
2478 self.nbsp();
2479 }
2480 if block.is_empty() {
2481 self.print_block(block, *try_span);
2482 self.end();
2483 } else {
2484 self.print_word("{");
2485 self.end();
2486 self.neverbreak();
2487 self.print_trailing_comment_no_break(try_span.lo(), None);
2488 self.print_block_without_braces(block, try_span.hi(), Some(self.ind));
2489 if self.cursor.enabled || self.cursor.pos < try_span.hi() {
2490 self.print_word("}");
2491 self.cursor.advance_to(try_span.hi(), true);
2492 }
2493 }
2494
2495 let mut skip_ind = false;
2496 if self.print_trailing_comment(try_span.hi(), other.first().map(|c| c.span.lo())) {
2497 self.break_offset_if_not_bol(0, self.ind, false);
2500 skip_ind = true;
2501 };
2502
2503 let mut prev_block_multiline = self.is_multiline_block(block, false, true);
2504
2505 for (pos, ast::TryCatchClause { name, args, block, span: catch_span }) in
2507 other.iter().delimited()
2508 {
2509 let current_block_multiline = self.is_multiline_block(block, false, true);
2510 if !pos.is_first || !skip_ind {
2511 if (pos.is_first && block.is_empty() && is_call_with_named_args(&expr.kind))
2512 || (prev_block_multiline && (current_block_multiline || pos.is_last))
2513 {
2514 self.nbsp();
2515 } else {
2516 self.space();
2517 if !current_block_multiline {
2518 self.s.offset(self.ind);
2519 }
2520 }
2521 }
2522 self.s.ibox(self.ind);
2523 self.print_comments(
2524 catch_span.lo(),
2525 CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
2526 );
2527
2528 self.print_word("catch ");
2529 if !args.is_empty() {
2530 self.print_comments(
2531 args[0].span.lo(),
2532 CommentConfig::skip_ws().mixed_no_break().mixed_post_nbsp(),
2533 );
2534 if let Some(name) = name {
2535 self.print_ident(name);
2536 }
2537 self.print_parameter_list(
2538 args,
2539 args.span.with_hi(block.span.lo()),
2540 ListFormat::inline(),
2541 );
2542 self.nbsp();
2543 }
2544 self.print_word("{");
2545 self.end();
2546 if !block.is_empty() {
2547 self.print_trailing_comment_no_break(catch_span.lo(), None);
2548 }
2549 self.print_block_without_braces(block, catch_span.hi(), Some(self.ind));
2550 if self.cursor.enabled || self.cursor.pos < try_span.hi() {
2551 self.print_word("}");
2552 self.cursor.advance_to(catch_span.hi(), true);
2553 }
2554
2555 prev_block_multiline = current_block_multiline;
2556 }
2557 }
2558 self.end();
2559 }
2560
2561 fn print_if_no_else(
2562 &mut self,
2563 cond: &'ast ast::Expr<'ast>,
2564 then: &'ast ast::Stmt<'ast>,
2565 inline: bool,
2566 ) {
2567 if !self.handle_span(cond.span.until(then.span), true) {
2568 self.print_if_cond("if", cond, then.span.lo());
2569 if let ast::StmtKind::Block(block) = &then.kind
2571 && block.is_empty()
2572 && self.peek_comment_before(then.span.hi()).is_none()
2573 {
2574 self.neverbreak();
2575 self.print_sep(Separator::Nbsp);
2576 } else if inline
2577 && is_call(&cond.kind)
2578 && matches!(
2579 self.config.single_line_statement_blocks,
2580 config::SingleLineBlockStyle::Preserve
2581 )
2582 {
2583 self.print_sep(Separator::Nbsp);
2586 } else {
2587 self.print_sep(Separator::Space);
2588 }
2589 }
2590 self.end();
2591 self.print_stmt_as_block(then, then.span.hi(), inline);
2592 self.cursor.advance_to(then.span.hi(), true);
2593 }
2594
2595 fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) {
2596 self.print_word(kw);
2597 self.print_sep_unhandled(Separator::Nbsp);
2598 self.print_tuple(
2599 std::slice::from_ref(cond),
2600 cond.span.lo(),
2601 pos_hi,
2602 Self::print_expr,
2603 get_span!(),
2604 ListFormat::compact().break_cmnts().break_single(is_binary_expr(&cond.kind)),
2605 );
2606 }
2607
2608 fn print_emit_or_revert(
2609 &mut self,
2610 kw: &'static str,
2611 path: &'ast ast::PathSlice,
2612 args: &'ast ast::CallArgs<'ast>,
2613 ) {
2614 self.word(kw);
2615 if self
2616 .print_comments(
2617 path.span().lo(),
2618 CommentConfig::skip_ws().mixed_no_break().mixed_prev_space().mixed_post_nbsp(),
2619 )
2620 .is_none()
2621 {
2622 self.nbsp();
2623 };
2624 self.s.cbox(0);
2625 self.emit_or_revert = path.segments().len() > 1;
2626 self.print_path(path, false);
2627 let format = if self.config.prefer_compact.calls() {
2628 ListFormat::compact()
2629 } else {
2630 ListFormat::consistent()
2631 };
2632 self.print_call_args(args, format.break_cmnts(), path.to_string().len(), false);
2633 self.emit_or_revert = false;
2634 self.end();
2635 }
2636
2637 fn print_block(&mut self, block: &'ast [ast::Stmt<'ast>], span: Span) {
2638 self.print_block_inner(
2639 block,
2640 BlockFormat::Regular,
2641 Self::print_stmt,
2642 |b| b.span,
2643 span.hi(),
2644 );
2645 }
2646
2647 fn print_block_without_braces(
2648 &mut self,
2649 block: &'ast [ast::Stmt<'ast>],
2650 pos_hi: BytePos,
2651 offset: Option<isize>,
2652 ) {
2653 self.print_block_inner(
2654 block,
2655 BlockFormat::NoBraces(offset),
2656 Self::print_stmt,
2657 |b| b.span,
2658 pos_hi,
2659 );
2660 }
2661
2662 fn print_stmt_as_block(&mut self, stmt: &'ast ast::Stmt<'ast>, pos_hi: BytePos, inline: bool) {
2664 if self.handle_span(stmt.span, false) {
2665 return;
2666 }
2667
2668 let stmts = if let ast::StmtKind::Block(stmts) = &stmt.kind {
2669 stmts
2670 } else {
2671 std::slice::from_ref(stmt)
2672 };
2673
2674 if inline && stmts.len() == 1 {
2675 self.neverbreak();
2676 self.print_block_without_braces(stmts, pos_hi, None);
2677 } else {
2678 let inline_parent = self.single_line_stmt.take();
2680
2681 self.print_word("{");
2682 self.print_block_without_braces(stmts, pos_hi, Some(self.ind));
2683 self.print_word("}");
2684
2685 self.single_line_stmt = inline_parent;
2687 }
2688 }
2689
2690 fn is_single_line_block(
2699 &mut self,
2700 stmt_span_lo: BytePos,
2701 cond: &'ast ast::Expr<'ast>,
2702 then: &'ast ast::Stmt<'ast>,
2703 els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>,
2704 ) -> Decision {
2705 if Self::then_block_can_capture_trailing_else(then, els_opt.is_some()) {
2708 return Decision { outcome: false, is_cached: false };
2709 }
2710
2711 if let Some(cached_decision) = self.single_line_stmt {
2713 return Decision { outcome: cached_decision, is_cached: true };
2714 }
2715
2716 if std::slice::from_ref(then).is_empty() {
2718 return Decision { outcome: false, is_cached: false };
2719 }
2720
2721 if self.peek_comment_between(stmt_span_lo, then.span.lo()).is_some() {
2723 return Decision { outcome: false, is_cached: false };
2724 }
2725
2726 match self.config.single_line_statement_blocks {
2728 config::SingleLineBlockStyle::Preserve => {
2729 if self.is_stmt_in_new_line(cond, then) || self.is_multiline_block_stmt(then, true)
2730 {
2731 return Decision { outcome: false, is_cached: false };
2732 }
2733 }
2734 config::SingleLineBlockStyle::Single => {
2735 if self.is_multiline_block_stmt(then, true) {
2736 return Decision { outcome: false, is_cached: false };
2737 }
2738 }
2739 config::SingleLineBlockStyle::Multi => {
2740 return Decision { outcome: false, is_cached: false };
2741 }
2742 };
2743
2744 if !self.can_stmts_be_inlined(cond, then, els_opt) {
2747 return Decision { outcome: false, is_cached: false };
2748 }
2749
2750 if let ast::StmtKind::If(child_cond, child_then, child_els_opt) = &then.kind {
2752 let child_decision = self.is_single_line_block(
2753 then.span.lo(),
2754 child_cond,
2755 child_then,
2756 child_els_opt.as_ref(),
2757 );
2758 if !child_decision.outcome {
2759 return child_decision;
2760 }
2761 }
2762 if let Some(stmt) = els_opt {
2763 if let ast::StmtKind::If(child_cond, child_then, child_els_opt) = &stmt.kind {
2764 return self.is_single_line_block(
2765 stmt.span.lo(),
2766 child_cond,
2767 child_then,
2768 child_els_opt.as_ref(),
2769 );
2770 } else if self.is_multiline_block_stmt(stmt, true) {
2771 return Decision { outcome: false, is_cached: false };
2772 }
2773 }
2774
2775 Decision { outcome: true, is_cached: false }
2777 }
2778
2779 fn is_inline_stmt(&self, stmt: &'ast ast::Stmt<'ast>, cond_len: usize) -> bool {
2780 if let ast::StmtKind::If(cond, then, els_opt) = &stmt.kind {
2781 let if_span = cond.span.to(then.span);
2782 if !self.same_source_line(if_span.lo(), if_span.hi())
2783 && matches!(
2784 self.config.single_line_statement_blocks,
2785 config::SingleLineBlockStyle::Preserve
2786 )
2787 {
2788 return false;
2789 }
2790 if cond_len + self.estimate_size(if_span) >= self.space_left() {
2791 return false;
2792 }
2793 if let Some(els) = els_opt
2794 && !self.is_inline_stmt(els, 6)
2795 {
2796 return false;
2797 }
2798 } else {
2799 if matches!(
2800 self.config.single_line_statement_blocks,
2801 config::SingleLineBlockStyle::Preserve
2802 ) && !self.same_source_line(stmt.span.lo(), stmt.span.hi())
2803 {
2804 return false;
2805 }
2806 if cond_len + self.estimate_size(stmt.span) >= self.space_left() {
2807 return false;
2808 }
2809 }
2810 true
2811 }
2812
2813 fn is_stmt_in_new_line(
2815 &self,
2816 cond: &'ast ast::Expr<'ast>,
2817 then: &'ast ast::Stmt<'ast>,
2818 ) -> bool {
2819 let span_between = cond.span.between(then.span);
2820 if let Some(snip) = self.snippet(span_between) {
2821 if let Some((_, after_paren)) = snip.split_once(')') {
2823 return after_paren.lines().count() > 1;
2824 }
2825 }
2826 false
2827 }
2828
2829 fn then_block_can_capture_trailing_else(
2832 then: &'ast ast::Stmt<'ast>,
2833 has_outer_else: bool,
2834 ) -> bool {
2835 let ast::StmtKind::Block(block) = &then.kind else { return false };
2836 if block.stmts.len() != 1 {
2837 return false;
2838 }
2839 match &block.stmts[0].kind {
2840 ast::StmtKind::If(_, _, inner_else) => has_outer_else || inner_else.is_some(),
2841 ast::StmtKind::While(..) | ast::StmtKind::For { .. } => has_outer_else,
2842 _ => false,
2843 }
2844 }
2845
2846 fn is_multiline_block_stmt(
2848 &mut self,
2849 stmt: &'ast ast::Stmt<'ast>,
2850 empty_as_multiline: bool,
2851 ) -> bool {
2852 match &stmt.kind {
2853 ast::StmtKind::Block(block) => {
2854 self.is_multiline_block(block, empty_as_multiline, false)
2855 }
2856 ast::StmtKind::While(cond, body) => {
2857 !self.is_single_line_block(stmt.span.lo(), cond, body, None).outcome
2858 }
2859 ast::StmtKind::For { body, .. } => {
2860 if let ast::StmtKind::Block(block) = &body.kind {
2863 self.is_multiline_block(block, empty_as_multiline, true)
2864 } else {
2865 true
2866 }
2867 }
2868
2869 ast::StmtKind::If(_, _, Some(_)) => true,
2870 ast::StmtKind::If(_, then, None) => {
2871 self.is_multiline_block_stmt(then, empty_as_multiline)
2872 }
2873
2874 ast::StmtKind::Assembly(_)
2876 | ast::StmtKind::DoWhile(_, _)
2877 | ast::StmtKind::Try(_)
2878 | ast::StmtKind::UncheckedBlock(_) => true,
2879
2880 ast::StmtKind::Break
2881 | ast::StmtKind::Continue
2882 | ast::StmtKind::DeclMulti(_, _)
2883 | ast::StmtKind::DeclSingle(_)
2884 | ast::StmtKind::Emit(_, _)
2885 | ast::StmtKind::Expr(_)
2886 | ast::StmtKind::Return(_)
2887 | ast::StmtKind::Revert(_, _)
2888 | ast::StmtKind::Placeholder => false,
2889 }
2890 }
2891
2892 fn is_multiline_block(
2895 &mut self,
2896 block: &'ast ast::Block<'ast>,
2897 empty_as_multiline: bool,
2898 force_single_as_multiline: bool,
2899 ) -> bool {
2900 if block.stmts.is_empty() {
2901 return empty_as_multiline;
2902 }
2903 if block.stmts.len() > 1 {
2906 return true;
2907 }
2908
2909 if force_single_as_multiline {
2910 return true;
2911 }
2912
2913 if !self.same_source_line(block.span.lo(), block.span.hi())
2916 && let Some(snip) = self.snippet(block.span)
2917 {
2918 let code_lines = snip.lines().filter(|line| {
2919 let trimmed = line.trim();
2920 if empty_as_multiline {
2922 !trimmed.is_empty() && trimmed != "{" && trimmed != "}"
2923 } else {
2924 !trimmed.is_empty()
2925 }
2926 });
2927 if code_lines.count() > 1 {
2928 return true;
2929 }
2930 }
2931
2932 let stmt = &block.stmts[0];
2933
2934 if self.peek_comment_between(block.span.lo(), stmt.span.lo()).is_some() {
2937 return true;
2938 }
2939
2940 self.is_multiline_block_stmt(stmt, empty_as_multiline)
2941 }
2942
2943 fn can_stmts_be_inlined(
2945 &mut self,
2946 cond: &'ast ast::Expr<'ast>,
2947 then: &'ast ast::Stmt<'ast>,
2948 els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>,
2949 ) -> bool {
2950 let cond_len = self.estimate_size(cond.span);
2951
2952 let then_margin = if 6 + cond_len < self.space_left() { 6 + cond_len } else { 2 };
2955
2956 if !self.is_inline_stmt(then, then_margin) {
2957 return false;
2958 }
2959
2960 els_opt.is_none_or(|els| self.is_inline_stmt(els, 6))
2962 }
2963
2964 fn can_header_be_inlined(&mut self, func: &ast::ItemFunction<'_>) -> bool {
2965 self.estimate_header_size(func) <= self.space_left()
2966 }
2967
2968 fn can_header_params_be_inlined(&mut self, func: &ast::ItemFunction<'_>) -> bool {
2969 self.estimate_header_params_size(func) <= self.space_left()
2970 }
2971
2972 fn estimate_header_size(&mut self, func: &ast::ItemFunction<'_>) -> usize {
2973 let ast::ItemFunction { kind: _, ref header, ref body, body_span: _ } = *func;
2974
2975 let visibility = header.visibility.map_or(0, |v| self.estimate_size(v.span) + 1);
2977 let mutability = header.state_mutability.map_or(0, |sm| self.estimate_size(sm.span) + 1);
2979 let m = header.modifiers.iter().fold(0, |len, m| len + self.estimate_size(m.span()));
2981 let modifiers = if m != 0 { m + 1 } else { 0 };
2982 let override_ = header.override_.as_ref().map_or(0, |o| self.estimate_size(o.span) + 1);
2984 let virtual_ = if header.virtual_.is_none() { 0 } else { 8 };
2986 let returns = header.returns.as_ref().map_or(0, |ret| {
2988 ret.vars
2989 .iter()
2990 .fold(0, |len, p| if len != 0 { len + 2 } else { 10 } + self.estimate_size(p.span))
2991 });
2992 let end = if body.is_some() { 2 } else { 1 };
2994
2995 self.estimate_header_params_size(func)
2996 + visibility
2997 + mutability
2998 + modifiers
2999 + override_
3000 + virtual_
3001 + returns
3002 + end
3003 }
3004
3005 fn estimate_header_params_size(&mut self, func: &ast::ItemFunction<'_>) -> usize {
3006 let ast::ItemFunction { kind, ref header, body: _, body_span: _ } = *func;
3007
3008 let kw = match kind {
3009 ast::FunctionKind::Constructor => 11, ast::FunctionKind::Function => 9, ast::FunctionKind::Modifier => 9, ast::FunctionKind::Fallback => 8, ast::FunctionKind::Receive => 7, };
3015
3016 let params = header
3018 .parameters
3019 .vars
3020 .iter()
3021 .fold(0, |len, p| if len != 0 { len + 2 } else { 2 } + self.estimate_size(p.span));
3022
3023 kw + header.name.map_or(0, |name| self.estimate_size(name.span)) + std::cmp::max(2, params)
3024 }
3025
3026 fn estimate_binary_size(&self, expr: &ast::Expr<'_>) -> usize {
3028 match &expr.kind {
3029 ast::ExprKind::Binary(lhs, op, rhs) => {
3030 let spaces = if self.config.pow_no_space && matches!(op.kind, ast::BinOpKind::Pow) {
3031 0
3032 } else {
3033 2
3034 };
3035 self.estimate_binary_size(lhs)
3036 + op.kind.to_str().len()
3037 + spaces
3038 + self.estimate_binary_size(rhs)
3039 }
3040 ast::ExprKind::Tuple(exprs) if let [SpannedOption::Some(inner)] = exprs.as_ref() => {
3041 self.estimate_binary_size(inner) + 2
3042 }
3043 _ => self.estimate_size(expr.span),
3044 }
3045 }
3046
3047 fn estimate_lhs_size(&self, expr: &ast::Expr<'_>, parent_op: &ast::BinOp) -> usize {
3048 match &expr.kind {
3049 ast::ExprKind::Binary(lhs, op, _) if op.kind.group() == parent_op.kind.group() => {
3050 self.estimate_lhs_size(lhs, op)
3051 }
3052 _ => self.estimate_size(expr.span),
3053 }
3054 }
3055
3056 fn estimate_call_chain_size(&self, expr: &ast::Expr<'_>) -> Option<usize> {
3057 match &expr.kind {
3058 ast::ExprKind::Call(callee, args) => {
3059 let ast::CallArgsKind::Unnamed(args) = &args.kind else { return None };
3060 let mut size = self.estimate_call_chain_size(callee)? + 2;
3061 for arg in args.iter() {
3062 size += self.estimate_call_chain_size(arg)?;
3063 }
3064 Some(size + args.len().saturating_sub(1) * 2)
3065 }
3066 ast::ExprKind::Ident(ident) => Some(ident.to_string().len()),
3067 ast::ExprKind::Index(expr, kind) => {
3068 let index_size = match kind {
3069 ast::IndexKind::Index(Some(index)) => self.estimate_call_chain_size(index)?,
3070 ast::IndexKind::Index(None) => 0,
3071 ast::IndexKind::Range(start, end) => {
3072 let start = match start {
3073 Some(start) => self.estimate_call_chain_size(start)?,
3074 None => 0,
3075 };
3076 let end = match end {
3077 Some(end) => self.estimate_call_chain_size(end)?,
3078 None => 0,
3079 };
3080 start + end + 1
3081 }
3082 };
3083 Some(self.estimate_call_chain_size(expr)? + index_size + 2)
3084 }
3085 ast::ExprKind::Lit(lit, None)
3087 if matches!(lit.kind, ast::LitKind::Number(_)) && lit.symbol.as_str() == "0" =>
3088 {
3089 Some(1)
3090 }
3091 ast::ExprKind::Member(expr, ident) => {
3092 Some(self.estimate_call_chain_size(expr)? + ident.to_string().len() + 1)
3093 }
3094 ast::ExprKind::Tuple(exprs) if let [SpannedOption::Some(expr)] = exprs.as_ref() => {
3095 Some(self.estimate_call_chain_size(expr)? + 2)
3096 }
3097 _ => None,
3098 }
3099 }
3100
3101 fn has_comments_between_elements<I>(&self, limits: Span, elements: I) -> bool
3102 where
3103 I: IntoIterator<Item = &'ast ast::Expr<'ast>>,
3104 {
3105 let mut last_span_end = limits.lo();
3106 for expr in elements {
3107 if self.has_comment_between(last_span_end, expr.span.lo()) {
3108 return true;
3109 }
3110 last_span_end = expr.span.hi();
3111 }
3112
3113 self.has_comment_between(last_span_end, limits.hi())
3114 }
3115}
3116
3117#[derive(Debug)]
3120enum MemberOrCallArgs {
3121 Member(usize),
3122 CallArgs(usize, bool),
3123}
3124
3125impl MemberOrCallArgs {
3126 const fn size(&self) -> usize {
3127 match self {
3128 Self::CallArgs(size, ..) | Self::Member(size) => *size,
3129 }
3130 }
3131
3132 const fn member_size(&self) -> usize {
3133 match self {
3134 Self::CallArgs(..) => 0,
3135 Self::Member(size) => *size,
3136 }
3137 }
3138
3139 const fn has_comments(&self) -> bool {
3140 matches!(self, Self::CallArgs(.., true))
3141 }
3142}
3143
3144#[derive(Debug, Clone)]
3145#[expect(dead_code)]
3146enum AttributeKind<'ast> {
3147 Visibility(ast::Visibility),
3148 StateMutability(ast::StateMutability),
3149 Virtual,
3150 Override(&'ast ast::Override<'ast>),
3151 Modifier(&'ast ast::Modifier<'ast>),
3152}
3153
3154type AttributeCommentMap = HashMap<BytePos, (Vec<Comment>, Vec<Comment>, Vec<Comment>)>;
3155
3156#[derive(Debug, Clone)]
3157struct AttributeInfo<'ast> {
3158 kind: AttributeKind<'ast>,
3159 span: Span,
3160}
3161
3162struct AttributeCommentMapper<'ast> {
3164 limit_pos: BytePos,
3165 comments: Vec<Comment>,
3166 attributes: Vec<AttributeInfo<'ast>>,
3167}
3168
3169impl<'ast> AttributeCommentMapper<'ast> {
3170 fn new(returns: Option<&'ast ast::ParameterList<'ast>>, body_pos: BytePos) -> Self {
3171 Self {
3172 comments: Vec::new(),
3173 attributes: Vec::new(),
3174 limit_pos: returns.as_ref().map_or(body_pos, |ret| ret.span.lo()),
3175 }
3176 }
3177
3178 #[allow(clippy::type_complexity)]
3179 fn build(
3180 mut self,
3181 state: &mut State<'_, 'ast>,
3182 header: &'ast ast::FunctionHeader<'ast>,
3183 ) -> (AttributeCommentMap, Vec<AttributeInfo<'ast>>, BytePos) {
3184 let first_attr = self.collect_attributes(header);
3185 if !self.attributes.is_empty() {
3186 self.cache_comments(state);
3187 }
3188 (self.map(), self.attributes, first_attr)
3189 }
3190
3191 fn map(&mut self) -> AttributeCommentMap {
3192 let mut map = HashMap::new();
3193 for a in 0..self.attributes.len() {
3194 let is_last = a == self.attributes.len() - 1;
3195 let (mut before, mut inner, mut after) = (Vec::new(), Vec::new(), Vec::new());
3196
3197 let before_limit = self.attributes[a].span.lo();
3198 let inner_limit = self.attributes[a].span.hi();
3199 let after_limit =
3200 if is_last { self.limit_pos } else { self.attributes[a + 1].span.lo() };
3201
3202 let mut c = 0;
3203 while c < self.comments.len() {
3204 if self.comments[c].pos() <= before_limit {
3205 before.push(self.comments.remove(c));
3206 } else if self.comments[c].pos() <= inner_limit {
3207 inner.push(self.comments.remove(c));
3208 } else if (after.is_empty() || is_last) && self.comments[c].pos() <= after_limit {
3209 after.push(self.comments.remove(c));
3210 } else {
3211 c += 1;
3212 }
3213 }
3214 map.insert(before_limit, (before, inner, after));
3215 }
3216 map
3217 }
3218
3219 fn collect_attributes(&mut self, header: &'ast ast::FunctionHeader<'ast>) -> BytePos {
3220 let mut first_pos = BytePos(u32::MAX);
3221 if let Some(v) = header.visibility {
3222 if v.span.lo() < first_pos {
3223 first_pos = v.span.lo()
3224 }
3225 self.attributes
3226 .push(AttributeInfo { kind: AttributeKind::Visibility(*v), span: v.span });
3227 }
3228 if let Some(sm) = header.state_mutability {
3229 if sm.span.lo() < first_pos {
3230 first_pos = sm.span.lo()
3231 }
3232 self.attributes
3233 .push(AttributeInfo { kind: AttributeKind::StateMutability(*sm), span: sm.span });
3234 }
3235 if let Some(span) = header.virtual_ {
3236 if span.lo() < first_pos {
3237 first_pos = span.lo()
3238 }
3239 self.attributes.push(AttributeInfo { kind: AttributeKind::Virtual, span });
3240 }
3241 if let Some(ref o) = header.override_ {
3242 if o.span.lo() < first_pos {
3243 first_pos = o.span.lo()
3244 }
3245 self.attributes.push(AttributeInfo { kind: AttributeKind::Override(o), span: o.span });
3246 }
3247 for m in header.modifiers.iter() {
3248 if m.span().lo() < first_pos {
3249 first_pos = m.span().lo()
3250 }
3251 self.attributes
3252 .push(AttributeInfo { kind: AttributeKind::Modifier(m), span: m.span() });
3253 }
3254 self.attributes.sort_by_key(|attr| attr.span.lo());
3255 first_pos
3256 }
3257
3258 fn cache_comments(&mut self, state: &mut State<'_, 'ast>) {
3259 let mut pending = None;
3260 for cmnt in state.comments.iter() {
3261 if cmnt.pos() >= self.limit_pos {
3262 break;
3263 }
3264 match pending {
3265 Some(ref p) => pending = Some(p + 1),
3266 None => pending = Some(0),
3267 }
3268 }
3269 while let Some(p) = pending {
3270 if p == 0 {
3271 pending = None;
3272 } else {
3273 pending = Some(p - 1);
3274 }
3275 let cmnt = state.next_comment().unwrap();
3276 if cmnt.style.is_blank() {
3277 continue;
3278 }
3279 self.comments.push(cmnt);
3280 }
3281 }
3282}
3283
3284const fn stmt_needs_semi(stmt: &ast::StmtKind<'_>) -> bool {
3285 match stmt {
3286 ast::StmtKind::Assembly { .. }
3287 | ast::StmtKind::Block { .. }
3288 | ast::StmtKind::For { .. }
3289 | ast::StmtKind::If { .. }
3290 | ast::StmtKind::Try { .. }
3291 | ast::StmtKind::UncheckedBlock { .. }
3292 | ast::StmtKind::While { .. } => false,
3293
3294 ast::StmtKind::DeclSingle { .. }
3295 | ast::StmtKind::DeclMulti { .. }
3296 | ast::StmtKind::Break { .. }
3297 | ast::StmtKind::Continue { .. }
3298 | ast::StmtKind::DoWhile { .. }
3299 | ast::StmtKind::Emit { .. }
3300 | ast::StmtKind::Expr { .. }
3301 | ast::StmtKind::Return { .. }
3302 | ast::StmtKind::Revert { .. }
3303 | ast::StmtKind::Placeholder { .. } => true,
3304 }
3305}
3306
3307fn item_needs_iso(item: &ast::ItemKind<'_>) -> bool {
3309 match item {
3310 ast::ItemKind::Pragma(..)
3311 | ast::ItemKind::Import(..)
3312 | ast::ItemKind::Using(..)
3313 | ast::ItemKind::Variable(..)
3314 | ast::ItemKind::Udvt(..)
3315 | ast::ItemKind::Enum(..)
3316 | ast::ItemKind::Error(..)
3317 | ast::ItemKind::Event(..) => false,
3318
3319 ast::ItemKind::Contract(..) => true,
3320
3321 ast::ItemKind::Struct(strukt) => !strukt.fields.is_empty(),
3322 ast::ItemKind::Function(func) => {
3323 func.body.as_ref().is_some_and(|b| !b.is_empty())
3324 && !matches!(func.kind, ast::FunctionKind::Modifier)
3325 }
3326 }
3327}
3328
3329const fn is_binary_expr(expr_kind: &ast::ExprKind<'_>) -> bool {
3330 matches!(expr_kind, ast::ExprKind::Binary(..))
3331}
3332
3333fn has_complex_successor(expr_kind: &ast::ExprKind<'_>, left: bool) -> bool {
3334 match expr_kind {
3335 ast::ExprKind::Binary(lhs, _, rhs) => {
3336 if left {
3337 has_complex_successor(&lhs.kind, left)
3338 } else {
3339 has_complex_successor(&rhs.kind, left)
3340 }
3341 }
3342 ast::ExprKind::Unary(_, expr) => has_complex_successor(&expr.kind, left),
3343 ast::ExprKind::Lit(..) | ast::ExprKind::Ident(_) => false,
3344 ast::ExprKind::Tuple(..) => false,
3345 _ => true,
3346 }
3347}
3348
3349const fn is_call(expr_kind: &ast::ExprKind<'_>) -> bool {
3350 matches!(expr_kind, ast::ExprKind::Call(..))
3351}
3352
3353const fn is_call_with_named_args(expr_kind: &ast::ExprKind<'_>) -> bool {
3358 if let ast::ExprKind::Call(_, args) = expr_kind {
3359 matches!(args.kind, ast::CallArgsKind::Named(_))
3360 } else {
3361 false
3362 }
3363}
3364
3365fn is_call_chain(expr_kind: &ast::ExprKind<'_>, must_have_child: bool) -> bool {
3366 match expr_kind {
3367 ast::ExprKind::Index(child, ..) | ast::ExprKind::Member(child, ..) => {
3368 is_call_chain(&child.kind, false)
3369 }
3370 ast::ExprKind::Tuple(exprs) if let [SpannedOption::Some(child)] = exprs.as_ref() => {
3371 is_call_chain(&child.kind, must_have_child)
3372 }
3373 _ => !must_have_child && is_call(expr_kind),
3374 }
3375}
3376
3377fn call_chain_contains_options(expr: &ast::Expr<'_>) -> bool {
3378 match &expr.peel_parens().kind {
3379 ast::ExprKind::CallOptions(..) => true,
3380 ast::ExprKind::Call(expr, ..)
3381 | ast::ExprKind::Index(expr, ..)
3382 | ast::ExprKind::Member(expr, ..) => call_chain_contains_options(expr),
3383 _ => false,
3384 }
3385}
3386
3387fn is_call_with_opts_and_args(expr_kind: &ast::ExprKind<'_>) -> bool {
3388 if let ast::ExprKind::Call(call_expr, call_args) = expr_kind {
3389 matches!(call_expr.kind, ast::ExprKind::CallOptions(..)) && !call_args.is_empty()
3390 } else {
3391 false
3392 }
3393}
3394
3395#[derive(Debug)]
3396struct Decision {
3397 outcome: bool,
3398 is_cached: bool,
3399}
3400
3401#[derive(Clone, Copy, PartialEq, Eq)]
3402pub(crate) enum BinOpGroup {
3403 Arithmetic,
3404 Bitwise,
3405 Comparison,
3406 Logical,
3407}
3408
3409trait BinOpExt {
3410 fn group(&self) -> BinOpGroup;
3411}
3412
3413impl BinOpExt for ast::BinOpKind {
3414 fn group(&self) -> BinOpGroup {
3415 match self {
3416 Self::Or | Self::And => BinOpGroup::Logical,
3417 Self::Eq | Self::Ne | Self::Lt | Self::Le | Self::Gt | Self::Ge => {
3418 BinOpGroup::Comparison
3419 }
3420 Self::BitOr | Self::BitXor | Self::BitAnd | Self::Shl | Self::Shr | Self::Sar => {
3421 BinOpGroup::Bitwise
3422 }
3423 Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Rem | Self::Pow => {
3424 BinOpGroup::Arithmetic
3425 }
3426 }
3427 }
3428}
3429
3430pub(super) fn get_callee_head_size(callee: &ast::Expr<'_>) -> usize {
3439 match &callee.kind {
3440 ast::ExprKind::Ident(id) => id.as_str().len(),
3441 ast::ExprKind::Type(ast::Type { kind: ast::TypeKind::Elementary(ty), .. }) => {
3442 ty.to_abi_str().len()
3443 }
3444 ast::ExprKind::Index(base, idx) => {
3445 let idx_len = match idx {
3446 ast::IndexKind::Index(expr) => expr.as_ref().map_or(0, |e| get_callee_head_size(e)),
3447 ast::IndexKind::Range(e1, e2) => {
3448 1 + e1.as_ref().map_or(0, |e| get_callee_head_size(e))
3449 + e2.as_ref().map_or(0, |e| get_callee_head_size(e))
3450 }
3451 };
3452 get_callee_head_size(base) + 2 + idx_len
3453 }
3454 ast::ExprKind::Member(base, member_ident) => {
3455 match &base.kind {
3456 ast::ExprKind::Ident(..) | ast::ExprKind::Type(..) => {
3457 get_callee_head_size(base) + 1 + member_ident.as_str().len()
3458 }
3459
3460 ast::ExprKind::Member(child, ..) if !is_call(&child.kind) => {
3462 get_callee_head_size(base) + 1 + member_ident.as_str().len()
3463 }
3464 _ => member_ident.as_str().len(),
3465 }
3466 }
3467 ast::ExprKind::Binary(lhs, _, _) => get_callee_head_size(lhs),
3468
3469 _ => 0,
3471 }
3472}
3473
3474fn assignment_member_depth(expr: &ast::Expr<'_>) -> usize {
3476 match &expr.kind {
3477 ast::ExprKind::Member(child, _) => 1 + assignment_member_depth(child),
3478 ast::ExprKind::Call(child, _) | ast::ExprKind::Index(child, _) => {
3479 assignment_member_depth(child)
3480 }
3481 _ => 0,
3482 }
3483}
3484
3485#[cfg(test)]
3486mod tests {
3487 use super::*;
3488 use crate::{FormatterConfig, InlineConfig};
3489 use foundry_common::comments::Comments;
3490 use solar::{
3491 interface::{Session, source_map::FileName},
3492 sema::Compiler,
3493 };
3494 use std::sync::Arc;
3495
3496 fn parse_and_test<F>(source: &str, test_fn: F)
3498 where
3499 F: FnOnce(&mut State<'_, '_>, &ast::ItemFunction<'_>) + Send,
3500 {
3501 let session = Session::builder().with_buffer_emitter(Default::default()).build();
3502 let mut compiler = Compiler::new(session);
3503
3504 compiler
3505 .enter_mut(|c| -> solar::interface::Result<()> {
3506 let mut pcx = c.parse();
3507 pcx.set_resolve_imports(false);
3508
3509 let file = c
3511 .sess()
3512 .source_map()
3513 .new_source_file(FileName::Stdin, source)
3514 .map_err(|e| c.sess().dcx.err(e.to_string()).emit())?;
3515
3516 pcx.add_file(file.clone());
3517 pcx.parse();
3518 c.dcx().has_errors()?;
3519
3520 let gcx = c.gcx();
3522 let (_, source_obj) = gcx.get_ast_source(&file.name).expect("Failed to get AST");
3523 let ast = source_obj.ast.as_ref().expect("No AST found");
3524 let comments =
3525 Comments::new(&source_obj.file, gcx.sess.source_map(), true, false, None);
3526 let config = Arc::new(FormatterConfig::default());
3527 let inline_config = InlineConfig::default();
3528 let mut state = State::new(&source_obj.file, config, inline_config, comments);
3529
3530 let func = ast
3532 .items
3533 .iter()
3534 .find_map(|item| match &item.kind {
3535 ast::ItemKind::Function(func) => Some(func),
3536 ast::ItemKind::Contract(contract) => {
3537 contract.body.iter().find_map(|contract_item| {
3538 match &contract_item.kind {
3539 ast::ItemKind::Function(func) => Some(func),
3540 _ => None,
3541 }
3542 })
3543 }
3544 _ => None,
3545 })
3546 .expect("No function found in source");
3547
3548 test_fn(&mut state, func);
3550
3551 Ok(())
3552 })
3553 .expect("Test failed");
3554 }
3555
3556 #[test]
3557 fn test_estimate_header_sizes() {
3558 let test_cases = [
3559 ("function foo();", 14, 15),
3560 ("function foo() {}", 14, 16),
3561 ("function foo() public {}", 14, 23),
3562 ("function foo(uint256 a) public {}", 23, 32),
3563 ("function foo(uint256 a, address b, bool c) public {}", 42, 51),
3564 ("function foo() public pure {}", 14, 28),
3565 ("function foo() public virtual {}", 14, 31),
3566 ("function foo() public override {}", 14, 32),
3567 ("function foo() public onlyOwner {}", 14, 33),
3568 ("function foo() public returns(uint256) {}", 14, 40),
3569 ("function foo() public returns(uint256, address) {}", 14, 49),
3570 ("function foo(uint256 a) public virtual override returns(uint256) {}", 23, 66),
3571 ("function foo() external payable {}", 14, 33),
3572 ("contract C { constructor() {} }", 13, 15),
3574 ("contract C { constructor(uint256 a) {} }", 22, 24),
3575 ("contract C { modifier onlyOwner() {} }", 20, 22),
3576 ("contract C { modifier onlyRole(bytes32 role) {} }", 31, 33),
3577 ("contract C { fallback() external payable {} }", 10, 29),
3578 ("contract C { receive() external payable {} }", 9, 28),
3579 ];
3580
3581 for (source, expected_params, expected_header) in &test_cases {
3582 parse_and_test(source, |state, func| {
3583 let params_size = state.estimate_header_params_size(func);
3584 assert_eq!(
3585 params_size, *expected_params,
3586 "Failed params size: expected {expected_params}, got {params_size} for source: {source}",
3587 );
3588
3589 let header_size = state.estimate_header_size(func);
3590 assert_eq!(
3591 header_size, *expected_header,
3592 "Failed header size: expected {expected_header}, got {header_size} for source: {source}",
3593 );
3594 });
3595 }
3596 }
3597}