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