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