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