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