1use crate::ast::{Expr, LogicalOp};
15use crate::trivia::{
16 Comment, GapPiece, line_column_to_byte, line_start_byte_offsets, scan_gap, take_block_comment,
17};
18
19type Doc = pretty::RcDoc<'static, ()>;
21
22fn verbatim_doc(text: String) -> Doc {
26 let mut lines = text.split('\n');
27 let first = Doc::text(lines.next().unwrap_or_default().to_owned());
28 lines.fold(first, |doc, line| {
29 let line = Doc::text(line.to_owned());
30 doc.append(Doc::nesting(move |indent| {
31 let offset = isize::try_from(indent)
32 .expect("document indentation fits isize")
33 .checked_neg()
34 .expect("nonnegative indentation can be negated");
35 Doc::hardline().append(line.clone()).nest(offset)
36 }))
37 })
38}
39
40fn trivia_doc(text: String) -> Doc {
44 trivia_doc_at(text, 4)
45}
46
47fn trivia_doc_at(text: String, continuation: isize) -> Doc {
51 let mut lines = text.split('\n');
52 let first = Doc::text(lines.next().unwrap_or_default().to_owned());
53 lines.fold(first, |doc, line| {
54 doc.append(
55 Doc::hardline()
56 .append(Doc::text(
57 line.strip_prefix(" ").unwrap_or(line).to_owned(),
58 ))
59 .nest(continuation),
60 )
61 })
62}
63
64fn gap_doc(pieces: &[GapPiece], spacing: Spacing) -> Doc {
68 if pieces
69 .iter()
70 .any(|piece| matches!(piece, GapPiece::Comment(_)))
71 && let Some(index) = pieces
72 .iter()
73 .position(|piece| matches!(piece, GapPiece::Punct(_)))
74 && matches!(spacing, Spacing::Around | Spacing::CommaAfter)
75 {
76 let GapPiece::Punct(token) = pieces[index] else {
77 unreachable!()
78 };
79 let before = emit_gap(&pieces[..index], Spacing::None, 0);
80 let after = emit_gap(&pieces[index + 1..], Spacing::None, 0);
81 let after_empty = after.is_empty();
82 let before_line = matches!(
83 pieces.get(index.wrapping_sub(1)),
84 Some(GapPiece::Comment(Comment::Line(_)))
85 );
86 let after_line = matches!(pieces.last(), Some(GapPiece::Comment(Comment::Line(_))));
87 let prefix = if before_line {
88 trivia_doc_at(before, 0)
89 } else {
90 trivia_doc_at(before.trim_end().to_owned(), 0)
91 };
92 let suffix = if after_line {
93 trivia_doc_at(after, 0)
94 } else {
95 trivia_doc_at(after.trim_end().to_owned(), 0)
96 };
97 return match spacing {
98 Spacing::Around => prefix
99 .append(if before_line {
100 Doc::nil()
101 } else {
102 Doc::softline()
103 })
104 .append(token)
105 .append(if after_empty {
106 Doc::space()
107 } else {
108 suffix.append(if after_line { Doc::nil() } else { Doc::space() })
109 }),
110 Spacing::CommaAfter => prefix.append(token).append(if after_empty {
111 Doc::line()
112 } else {
113 suffix.append(if after_line { Doc::nil() } else { Doc::line() })
114 }),
115 Spacing::None => unreachable!(),
116 };
117 }
118 if pieces
119 .iter()
120 .all(|piece| matches!(piece, GapPiece::Punct(_)))
121 {
122 Doc::concat(pieces.iter().map(|piece| {
123 let GapPiece::Punct(token) = piece else {
124 unreachable!()
125 };
126 match spacing {
127 Spacing::Around => Doc::line().append(*token).append(" "),
128 Spacing::CommaAfter => Doc::text(*token).append(Doc::line()),
129 Spacing::None => Doc::text(*token),
130 }
131 }))
132 } else {
133 trivia_doc(emit_gap(pieces, spacing, 0))
134 }
135}
136
137fn binary_doc(lhs: Doc, gap: &[GapPiece], rhs: Doc) -> Doc {
141 lhs.append(gap_doc(gap, Spacing::Around).append(rhs).nest(4))
142 .group()
143}
144
145fn body_gap_doc(pieces: &[GapPiece], trim_start: bool) -> Doc {
151 let text = emit_gap(pieces, Spacing::Around, 0);
152 let text = if trim_start { text.trim_start() } else { &text };
153 if matches!(pieces.last(), Some(GapPiece::Comment(Comment::Line(_)))) {
154 trivia_doc_at(text.to_owned(), 0)
155 } else {
156 trivia_doc_at(text.trim_end().to_owned(), 0).append(Doc::line())
157 }
158}
159
160#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
163struct Level(u8);
164
165impl Level {
166 const RANGE: Level = Level(0);
167 const OR: Level = Level(1);
168 const AND: Level = Level(2);
169 const COMPARISON: Level = Level(3);
170 const BIT_OR: Level = Level(4);
171 const BIT_XOR: Level = Level(5);
172 const BIT_AND: Level = Level(6);
173 const SHIFT: Level = Level(7);
174 const ADDITIVE: Level = Level(8);
175 const MULTIPLICATIVE: Level = Level(9);
176 const CAST: Level = Level(10);
177 const UNARY: Level = Level(11);
178 const POSTFIX: Level = Level(12);
179 const PRIMARY: Level = Level(13);
180
181 fn tighter(self) -> Level {
183 Level(self.0 + 1)
184 }
185}
186
187fn indent(depth: usize) -> String {
191 " ".repeat(depth)
192}
193
194#[derive(Clone, Copy)]
196enum Spacing {
197 Around,
199 None,
201 CommaAfter,
203}
204
205fn emit_gap(pieces: &[GapPiece], spacing: Spacing, depth: usize) -> String {
216 let cont = indent(depth + 1);
217 let mut out = String::new();
218 for piece in pieces {
219 match piece {
220 GapPiece::Punct(tok) => {
221 match spacing {
222 Spacing::Around => {
223 if !out.ends_with(' ') {
227 out.push(' ');
228 }
229 out.push_str(tok);
230 out.push(' ');
231 }
232 Spacing::None => out.push_str(tok),
233 Spacing::CommaAfter => {
234 if out.ends_with(' ') && !out.ends_with(&cont) {
244 out.pop();
245 }
246 out.push_str(tok);
247 out.push(' ');
248 }
249 }
250 }
251 GapPiece::Comment(Comment::Block(text)) => {
252 if !out.ends_with(' ') {
256 out.push(' ');
257 }
258 out.push_str("/* ");
259 out.push_str(text);
260 out.push_str(" */ ");
261 }
262 GapPiece::Comment(Comment::Line(text)) => {
263 let continuation = out
266 .rsplit_once('\n')
267 .is_some_and(|(_, tail)| tail.trim().is_empty());
268 if continuation {
269 out.push_str("// ");
270 } else {
271 let trimmed_len = out.trim_end().len();
272 out.truncate(trimmed_len);
273 out.push_str(" // ");
274 }
275 out.push_str(text);
276 out.push('\n');
277 out.push_str(&cont);
278 }
279 }
280 }
281 out
282}
283
284fn binary_op_level(name: &str) -> Level {
293 match name {
294 "|" => Level::BIT_OR,
295 "^" => Level::BIT_XOR,
296 "&" => Level::BIT_AND,
297 "<<" | ">>" => Level::SHIFT,
298 "+" | "-" => Level::ADDITIVE,
299 "*" | "/" | "%" => Level::MULTIPLICATIVE,
300 "==" | "!=" | "<" | ">" | "<=" | ">=" => Level::COMPARISON,
301 other => unreachable!("binary_op_level called with unknown operator `{other}`"),
302 }
303}
304
305fn render_literal(span: crate::ExprSpan) -> String {
309 span.start.source_text().unwrap_or_default()
310}
311
312fn gap_between(
322 source: &str,
323 prev_end: proc_macro2::Span,
324 next_start: proc_macro2::Span,
325 expected: &[&'static str],
326) -> Vec<GapPiece> {
327 if source.is_empty() {
328 return scan_gap("", expected);
329 }
330 let line_starts = line_start_byte_offsets(source);
331 let start = line_column_to_byte(source, &line_starts, prev_end.end());
332 let end = line_column_to_byte(source, &line_starts, next_start.start());
333 let gap = source.get(start..end).unwrap_or("");
334 scan_gap(gap, expected)
335}
336
337fn member_gap(
344 source: &str,
345 receiver_end: proc_macro2::Span,
346 name_start: proc_macro2::Span,
347) -> Vec<GapPiece> {
348 grouping_gap(source, receiver_end.end(), name_start.start(), &["."])
349}
350
351fn format_postfix_base(
357 base: &Expr,
358 source: &str,
359 depth: usize,
360 start: proc_macro2::LineColumn,
361) -> Doc {
362 let leading = grouping_gap(source, start, base.span().start.start(), &[]);
363 if leading.is_empty() {
364 format_at(base, source, depth, Level::POSTFIX)
365 } else {
366 let (text, _) = render(base, source, depth);
367 let prefix = emit_gap(&leading, Spacing::None, 0);
368 Doc::text("(")
369 .append(trivia_doc(prefix.trim_start().to_owned()))
370 .append(text)
371 .append(")")
372 }
373}
374
375fn emit_list(
396 source: &str,
397 depth: usize,
398 delims: (&'static str, &'static str),
399 bounds: (proc_macro2::LineColumn, proc_macro2::LineColumn),
400 elements: &[Expr],
401 trailing_comma: bool,
402) -> Doc {
403 let (open, close) = delims;
404 let (open_pos, close_pos) = bounds;
405 let scan = |from: proc_macro2::LineColumn,
409 to: proc_macro2::LineColumn,
410 expected: &[&'static str]|
411 -> Vec<GapPiece> {
412 if source.is_empty() {
413 return scan_gap("", expected);
414 }
415 let line_starts = line_start_byte_offsets(source);
416 let start = line_column_to_byte(source, &line_starts, from);
417 let end = line_column_to_byte(source, &line_starts, to);
418 let gap = source.get(start..end).unwrap_or("");
419 scan_gap(gap, expected)
420 };
421
422 if elements.is_empty() {
423 let pieces = scan(open_pos, close_pos, &[open, close]);
425 let whole = emit_gap(&pieces, Spacing::None, 0);
426 return trivia_doc(glue_before_close(&glue_after_open(&whole, open), close));
427 }
428 let mut pre = grouping_gap(source, open_pos, elements[0].span().start.start(), &[open]);
433 if open == "(" {
434 pre.insert(0, GapPiece::Punct("("));
435 }
436 let mut out = trivia_doc(glue_after_open(&emit_gap(&pre, Spacing::None, 0), open));
437 let mut body = Doc::line_();
438 for (i, el) in elements.iter().enumerate() {
439 body = body.append(format_at(el, source, depth, Level::OR));
440 if i + 1 < elements.len() {
441 let pieces = grouping_gap(
442 source,
443 el.span().end.end(),
444 elements[i + 1].span().start.start(),
445 &[","],
446 );
447 body = body.append(gap_doc(&pieces, Spacing::CommaAfter));
448 }
449 }
450 if trailing_comma {
451 body = body.append(",");
452 }
453 let mut post = grouping_gap(
457 source,
458 elements[elements.len() - 1].span().end.end(),
459 close_pos,
460 &[close],
461 );
462 if close == ")" {
463 post.push(GapPiece::Punct(")"));
464 }
465 out = out.append(body.nest(4));
466 let tail = glue_before_close(&emit_gap(&post, Spacing::None, 0), close);
467 out.append(Doc::line_()).append(trivia_doc(tail)).group()
468}
469
470fn glue_after_open(gap: &str, open: &'static str) -> String {
474 match gap.strip_prefix(&format!("{open} ")) {
475 Some(rest) => format!("{open}{rest}"),
476 None => gap.to_string(),
477 }
478}
479
480fn glue_before_close(gap: &str, close: &'static str) -> String {
483 if let Some((_, last_line)) = gap.rsplit_once('\n') {
484 if let Some(rest) = last_line.strip_suffix(close)
490 && rest.chars().all(|c| c == ' ')
491 {
492 return gap.to_string();
493 }
494 }
495 match gap.strip_suffix(&format!(" {close}")) {
496 Some(rest) => format!("{rest}{close}"),
497 None => gap.to_string(),
498 }
499}
500
501fn binary_op_token(name: &str) -> &'static str {
505 match name {
506 "|" => "|",
507 "^" => "^",
508 "&" => "&",
509 "<<" => "<<",
510 ">>" => ">>",
511 "+" => "+",
512 "-" => "-",
513 "*" => "*",
514 "/" => "/",
515 "%" => "%",
516 "==" => "==",
517 "!=" => "!=",
518 "<" => "<",
519 ">" => ">",
520 "<=" => "<=",
521 ">=" => ">=",
522 other => unreachable!("binary_op_token called with unknown operator `{other}`"),
523 }
524}
525
526fn unary_op_token(name: &str) -> &'static str {
528 match name {
529 "-" => "-",
530 "!" => "!",
531 other => unreachable!("unary_op_token called with unknown operator `{other}`"),
532 }
533}
534
535fn render_type_expr(type_expr: &crate::TypeExpr) -> String {
543 match type_expr {
544 crate::TypeExpr::Named { name, args, .. } => {
545 if args.is_empty() {
546 name.clone()
547 } else {
548 let inner = args
549 .iter()
550 .map(render_type_expr)
551 .collect::<Vec<_>>()
552 .join(", ");
553 format!("{name}({inner})")
554 }
555 }
556 crate::TypeExpr::Array { element, .. } => format!("[{}]", render_type_expr(element)),
557 crate::TypeExpr::Tuple { elements, .. } => {
558 let inner = elements
559 .iter()
560 .map(render_type_expr)
561 .collect::<Vec<_>>()
562 .join(", ");
563 match elements.len() {
564 0 => "()".to_string(),
565 1 => format!("({inner},)"),
566 _ => format!("({inner})"),
567 }
568 }
569 }
570}
571
572fn render(expr: &Expr, source: &str, depth: usize) -> (Doc, Level) {
580 match expr {
581 Expr::Struct {
582 name,
583 name_span,
584 fields,
585 span,
586 } => {
587 let mut out = Doc::text(name.clone());
588 let mut body = Doc::nil();
589 if let Some(first) = fields.first() {
590 let open = grouping_gap(
591 source,
592 name_span.end.end(),
593 first.name_span.start.start(),
594 &["{"],
595 );
596 body = body.append(body_gap_doc(&open, false));
597 for (index, field) in fields.iter().enumerate() {
598 body = body.append(Doc::text(field.name.clone()));
599 let colon = grouping_gap(
600 source,
601 field.name_span.end.end(),
602 field.value.span().start.start(),
603 &[":"],
604 );
605 body = body.append(trivia_doc(emit_gap(&colon, Spacing::CommaAfter, 0)));
606 body = body.append(format_at(&field.value, source, depth, Level::RANGE));
607 if let Some(next) = fields.get(index + 1) {
608 let comma = grouping_gap(
609 source,
610 field.value.span().end.end(),
611 next.name_span.start.start(),
612 &[","],
613 );
614 body = body.append(gap_doc(&comma, Spacing::CommaAfter));
615 }
616 }
617 let last = fields.last().expect("non-empty fields");
618 let mut tail = grouping_gap(
619 source,
620 last.value.span().end.end(),
621 span.end.end(),
622 &[",", "}"],
623 );
624 tail.retain(|piece| !matches!(piece, GapPiece::Punct(",")));
625 let closing = emit_gap(&tail, Spacing::Around, 0);
626 out = out
627 .append(body.nest(4))
628 .append(Doc::line())
629 .append(trivia_doc(closing.trim().to_owned()))
630 .group();
631 } else {
632 let tail = grouping_gap(source, name_span.end.end(), span.end.end(), &["{", "}"]);
633 out = out
634 .append(" ")
635 .append(trivia_doc(emit_gap(&tail, Spacing::None, 0)));
636 }
637 (out, Level::PRIMARY)
638 }
639 Expr::Literal { span, .. } => (verbatim_doc(render_literal(*span)), Level::PRIMARY),
640 Expr::Ident { name, .. } if name == "range_full" => (Doc::text(".."), Level::RANGE),
653 Expr::Ident { name, .. } => (Doc::text(name.clone()), Level::PRIMARY),
654 Expr::Logical { op, lhs, rhs, .. } => {
655 let level = match op {
656 LogicalOp::Or => Level::OR,
657 LogicalOp::And => Level::AND,
658 };
659 let op_static: &'static str = match op {
660 LogicalOp::Or => "||",
661 LogicalOp::And => "&&",
662 };
663 let lhs_s = format_at(lhs, source, depth, level);
664 let rhs_s = format_at(rhs, source, depth, level.tighter());
665 let pieces = gap_between(source, lhs.span().end, rhs.span().start, &[op_static]);
666 (binary_doc(lhs_s, &pieces, rhs_s), level)
667 }
668 Expr::Op { operands, .. } if operands.is_empty() => {
669 (Doc::text(".."), Level::RANGE)
675 }
676 Expr::Op {
677 name,
678 operands,
679 span,
680 } if operands.len() == 1
681 && matches!(
682 name.as_str(),
683 "range_from" | "range_to" | "range_to_inclusive"
684 ) =>
685 {
686 let operand_s = format_at(&operands[0], source, depth, Level::RANGE.tighter());
691 let text = match name.as_str() {
692 "range_from" => {
693 let pieces = gap_between(source, operands[0].span().end, span.end, &[".."]);
694 operand_s.append(gap_doc(&pieces, Spacing::None))
695 }
696 "range_to" => {
697 let pieces = gap_between(source, span.start, operands[0].span().start, &[]);
703 Doc::text("..")
704 .append(gap_doc(&pieces, Spacing::None))
705 .append(operand_s)
706 }
707 "range_to_inclusive" => {
708 let pieces = gap_between(source, span.start, operands[0].span().start, &[]);
709 Doc::text("..=")
710 .append(gap_doc(&pieces, Spacing::None))
711 .append(operand_s)
712 }
713 _ => unreachable!("guarded by the outer match arm"),
714 };
715 (text, Level::RANGE)
716 }
717 Expr::Op {
718 name,
719 operands,
720 span,
721 } if operands.len() == 1 => {
722 let operand_s = format_at(&operands[0], source, depth, Level::UNARY);
723 let op_static = unary_op_token(name);
724 let pieces = gap_between(source, span.start, operands[0].span().start, &[]);
728 let default_sep = if matches!(&operands[0], Expr::Op { name, operands, .. }
730 if operands.len() == 1 && matches!(name.as_str(), "-" | "!"))
731 {
732 " "
733 } else {
734 ""
735 };
736 let gap = if pieces.iter().any(|p| matches!(p, GapPiece::Comment(_))) {
737 emit_gap(&pieces, Spacing::None, 0)
738 } else {
739 default_sep.to_string()
740 };
741 (
742 Doc::text(op_static)
743 .append(trivia_doc(gap))
744 .append(operand_s),
745 Level::UNARY,
746 )
747 }
748 Expr::Op { name, operands, .. } if name == "range" || name == "range_inclusive" => {
749 let lhs_s = format_at(&operands[0], source, depth, Level::RANGE.tighter());
752 let rhs_s = format_at(&operands[1], source, depth, Level::RANGE.tighter());
753 let op_static: &'static str = if name == "range_inclusive" {
754 "..="
755 } else {
756 ".."
757 };
758 let pieces = gap_between(
759 source,
760 operands[0].span().end,
761 operands[1].span().start,
762 &[op_static],
763 );
764 (
765 lhs_s
766 .append(
767 Doc::softline_()
768 .append(gap_doc(&pieces, Spacing::None))
769 .append(rhs_s)
770 .nest(4),
771 )
772 .group(),
773 Level::RANGE,
774 )
775 }
776 Expr::Op { name, operands, .. } => {
777 let level = binary_op_level(name);
778 let (lhs_min, rhs_min) = if level == Level::COMPARISON {
782 (level.tighter(), level.tighter())
783 } else {
784 (level, level.tighter())
785 };
786 let lhs_s = format_at(&operands[0], source, depth, lhs_min);
787 let rhs_s = format_at(&operands[1], source, depth, rhs_min);
788 let op_static = binary_op_token(name);
790 let pieces = gap_between(
791 source,
792 operands[0].span().end,
793 operands[1].span().start,
794 &[op_static],
795 );
796 (binary_doc(lhs_s, &pieces, rhs_s), level)
797 }
798 Expr::Cast {
799 expr,
800 type_name,
801 span,
802 } => {
803 let expr_s = format_at(expr, source, depth, Level::CAST);
807 let pieces = gap_between(source, expr.span().end, span.end, &["as"]);
811 let text = binary_doc(expr_s, &pieces, Doc::text(type_name.clone()));
812 (text, Level::CAST)
813 }
814 Expr::Apply { callee, args, span } => {
815 let mut callee_s = format_postfix_base(callee, source, depth, span.start.start());
816 if matches!(callee.as_ref(), Expr::Member { args: None, .. }) {
819 callee_s = Doc::text("(").append(callee_s).append(")");
820 }
821 let tail = grouping_gap(source, callee.span().end.end(), span.end.start(), &[]);
822 callee_s = callee_s.append(gap_doc(&tail, Spacing::None));
823 let list = emit_list(
826 source,
827 depth,
828 ("(", ")"),
829 (span.end.start(), span.end.end()),
830 args,
831 false,
832 );
833 (callee_s.append(list), Level::POSTFIX)
834 }
835 Expr::Member {
836 receiver,
837 name,
838 name_span,
839 args,
840 span,
841 } => {
842 let receiver_s = format_postfix_base(receiver, source, depth, span.start.start());
843 let gap = member_gap(source, receiver.span().end, name_span.start);
844 let member_gap = if gap.iter().all(|piece| matches!(piece, GapPiece::Punct(_))) {
845 Doc::line_().append(".")
846 } else if gap
847 .iter()
848 .any(|piece| matches!(piece, GapPiece::Comment(Comment::Line(_))))
849 {
850 trivia_doc_at(emit_gap(&gap, Spacing::None, 0), 0)
851 } else {
852 verbatim_doc(emit_gap(&gap, Spacing::None, 0))
853 };
854 let mut text = receiver_s
855 .append(member_gap.append(Doc::text(name.clone())).nest(4))
856 .group();
857 if let Some(args) = args {
858 text = text.append(emit_list(
859 source,
860 depth,
861 ("(", ")"),
862 (name_span.end.end(), span.end.end()),
863 args,
864 false,
865 ));
866 }
867 (text, Level::POSTFIX)
868 }
869 Expr::Tuple { elements, span } => {
870 let list = emit_list(
874 source,
875 depth,
876 ("(", ")"),
877 (span.start.start(), span.end.end()),
878 elements,
879 elements.len() == 1,
880 );
881 (list, Level::PRIMARY)
882 }
883 Expr::Array {
884 elements,
885 annotation_span,
886 type_annotation,
887 span,
888 } => {
889 let close_pos = annotation_span
894 .map(|annotation_span| annotation_span.start.start())
895 .unwrap_or_else(|| span.end.end());
896 let mut list = emit_list(
897 source,
898 depth,
899 ("[", "]"),
900 (span.start.start(), close_pos),
901 elements,
902 false,
903 );
904 if let Some(type_annotation) = type_annotation {
905 list = list
906 .append(": ")
907 .append(Doc::text(render_type_expr(type_annotation)));
908 }
909 (list, Level::PRIMARY)
910 }
911 Expr::TupleIndex { base, index, span } => (
912 format_postfix_base(base, source, depth, span.start.start())
913 .append(Doc::text(format!(".{index}"))),
914 Level::POSTFIX,
915 ),
916 Expr::If {
917 cond,
918 then_branch,
919 else_branch,
920 span,
921 } => {
922 let cond_s = restricted_doc(
931 cond,
932 source,
933 depth,
934 crate::ExpressionRestriction::NoStructLiteral,
935 );
936 let then_s = format_at(then_branch, source, depth, Level::OR);
937 let if_gap = grouping_gap(source, span.start.start(), header_start(cond), &["if"]);
938 let open_gap = grouping_gap(
939 source,
940 header_end(cond),
941 then_branch.span().start.start(),
942 &["{"],
943 );
944 let mut text = Doc::nil();
945 text = text.append(trivia_doc(
948 emit_gap(&if_gap, Spacing::Around, 0)
949 .trim_start()
950 .to_owned(),
951 ));
952 text = text.append(cond_s);
953 text = text.append(body_gap_doc(&open_gap, false).append(then_s).nest(4));
954 match else_branch {
955 None => {
956 let close_gap = gap_between(source, then_branch.span().end, span.end, &["}"]);
959 text = text.append(Doc::line()).append(trivia_doc(
963 emit_gap(&close_gap, Spacing::Around, 0).trim().to_owned(),
964 ));
965 }
966 Some(else_branch) => {
967 let is_chain = matches!(else_branch.as_ref(), Expr::If { .. });
968 let expected: &[&'static str] = if is_chain {
971 &["}", "else"]
972 } else {
973 &["}", "else", "{"]
974 };
975 let else_gap = gap_between(
979 source,
980 then_branch.span().end,
981 else_branch.span().start,
982 expected,
983 );
984 text = text.append(Doc::line());
985 if is_chain {
986 let (else_s, _) = render(else_branch, source, depth);
990 text = text.append(body_gap_doc(&else_gap, true)).append(else_s);
991 } else {
992 let else_s = format_at(else_branch, source, depth, Level::OR);
993 text = text.append(body_gap_doc(&else_gap, true).append(else_s).nest(4));
994 let final_close_gap =
995 gap_between(source, else_branch.span().end, span.end, &["}"]);
996 text = text.append(Doc::line()).append(trivia_doc(
997 emit_gap(&final_close_gap, Spacing::Around, 0)
998 .trim()
999 .to_owned(),
1000 ));
1001 }
1002 }
1003 }
1004 (text.group(), Level::PRIMARY)
1005 }
1006 Expr::Closure { params, body, span } => {
1007 let body_s = format_at(body, source, depth, Level::OR);
1008 let header = if params.is_empty() {
1009 Doc::text("||")
1010 } else {
1011 let leading = gap_between(source, span.start, params[0].name_span.start, &[]);
1012 let mut parameters =
1013 trivia_doc(emit_gap(&leading, Spacing::None, 0).trim_start().to_owned());
1014 for (index, param) in params.iter().enumerate() {
1015 if index > 0 {
1016 let comma = gap_between(
1017 source,
1018 params[index - 1].type_expr.span().end,
1019 param.name_span.start,
1020 &[","],
1021 );
1022 parameters = parameters.append(gap_doc(&comma, Spacing::CommaAfter));
1023 }
1024 let colon = gap_between(
1025 source,
1026 param.name_span.end,
1027 param.type_expr.span().start,
1028 &[":"],
1029 );
1030 parameters = parameters
1031 .append(Doc::text(param.name.clone()))
1032 .append(trivia_doc(emit_gap(&colon, Spacing::CommaAfter, 0)))
1033 .append(Doc::text(render_type_expr(¶m.type_expr)));
1034 }
1035 Doc::text("|")
1036 .append(Doc::line_().append(parameters).nest(4))
1037 .append(Doc::line_())
1038 .append("|")
1039 .group()
1040 };
1041 let (tail_start, expected): (proc_macro2::Span, &[&'static str]) = match params.last() {
1042 Some(last) => (last.type_expr.span().end, &["|"]),
1043 None => (span.start, &[]),
1044 };
1045 let pieces = gap_between(source, tail_start, body.span().start, expected);
1046 let comments: Vec<GapPiece> = pieces
1047 .into_iter()
1048 .filter(|p| matches!(p, GapPiece::Comment(_)))
1049 .collect();
1050 let gap = emit_gap(&comments, Spacing::None, 0);
1051 let has_line_comment = comments
1056 .iter()
1057 .any(|p| matches!(p, GapPiece::Comment(Comment::Line(_))));
1058 let sep = if has_line_comment {
1059 trivia_doc_at(gap, 0)
1060 } else if gap.trim().is_empty() {
1061 Doc::line()
1062 } else {
1063 trivia_doc(format!(" {} ", gap.trim()))
1064 };
1065 (
1068 header.append(sep.append(body_s).nest(4)).group(),
1069 Level::RANGE,
1070 )
1071 }
1072 }
1073}
1074
1075fn header_start(expr: &Expr) -> proc_macro2::LineColumn {
1082 match expr {
1083 Expr::Op { name, operands, .. }
1084 if operands.len() == 2 || (name == "range_from" && operands.len() == 1) =>
1085 {
1086 header_start(&operands[0])
1087 }
1088 Expr::Logical { lhs, .. } => header_start(lhs),
1089 Expr::Cast { expr, .. } => header_start(expr),
1090 _ => expr.span().start.start(),
1091 }
1092}
1093
1094fn header_end(expr: &Expr) -> proc_macro2::LineColumn {
1100 match expr {
1101 Expr::Op { name, operands, .. } if name != "range_from" && !operands.is_empty() => {
1102 header_end(operands.last().expect("non-empty operands"))
1103 }
1104 Expr::Logical { rhs, .. } => header_end(rhs),
1105 Expr::Closure { body, .. } => header_end(body),
1106 _ => expr.span().end.end(),
1107 }
1108}
1109
1110fn grouping_gap(
1116 source: &str,
1117 from: proc_macro2::LineColumn,
1118 to: proc_macro2::LineColumn,
1119 expected: &[&'static str],
1120) -> Vec<GapPiece> {
1121 let starts = line_start_byte_offsets(source);
1122 let gap = if source.is_empty() {
1123 ""
1124 } else {
1125 let from = line_column_to_byte(source, &starts, from);
1126 let to = line_column_to_byte(source, &starts, to);
1127 source.get(from..to).unwrap_or("")
1128 };
1129 let mut rest = gap;
1130 let mut tokens = Vec::new();
1131 let mut next = 0;
1132 loop {
1133 rest = rest.trim_start();
1134 if let Some(after) = rest.strip_prefix("//") {
1135 rest = &after[after.find('\n').unwrap_or(after.len())..];
1136 } else if let Some((_, after)) = take_block_comment(rest) {
1137 rest = after;
1138 } else if let Some(after) = rest.strip_prefix('(') {
1139 tokens.push("(");
1140 rest = after;
1141 } else if let Some(after) = rest.strip_prefix(')') {
1142 tokens.push(")");
1143 rest = after;
1144 } else if next < expected.len() && rest.starts_with(expected[next]) {
1145 tokens.push(expected[next]);
1146 rest = &rest[expected[next].len()..];
1147 next += 1;
1148 } else {
1149 break;
1150 }
1151 }
1152 tokens.extend_from_slice(&expected[next..]);
1153 scan_gap(gap, &tokens)
1154 .into_iter()
1155 .filter(|piece| !matches!(piece, GapPiece::Punct("(" | ")")))
1156 .collect()
1157}
1158
1159fn header_contains_record(expr: &Expr) -> bool {
1163 match expr {
1164 Expr::Struct { .. } => true,
1165 Expr::Op { operands, .. } => operands.iter().any(header_contains_record),
1166 Expr::Logical { lhs, rhs, .. } => {
1167 header_contains_record(lhs) || header_contains_record(rhs)
1168 }
1169 Expr::Cast { expr, .. } | Expr::Closure { body: expr, .. } => header_contains_record(expr),
1170 Expr::Member { receiver, .. } => header_contains_record(receiver),
1171 Expr::TupleIndex { base, .. } => header_contains_record(base),
1172 Expr::Apply { callee, .. } => header_contains_record(callee),
1173 Expr::Literal { .. }
1174 | Expr::Ident { .. }
1175 | Expr::Tuple { .. }
1176 | Expr::Array { .. }
1177 | Expr::If { .. } => false,
1178 }
1179}
1180
1181pub fn format_expr_restricted(
1198 expr: &Expr,
1199 source: &str,
1200 depth: usize,
1201 restriction: crate::ExpressionRestriction,
1202) -> String {
1203 render_document(
1204 restricted_doc(expr, source, depth, restriction),
1205 depth,
1206 depth * 4,
1207 100,
1208 0,
1209 )
1210}
1211
1212fn restricted_doc(
1216 expr: &Expr,
1217 source: &str,
1218 depth: usize,
1219 restriction: crate::ExpressionRestriction,
1220) -> Doc {
1221 let text = format_at(expr, source, depth, Level::RANGE);
1222 if restriction == crate::ExpressionRestriction::NoStructLiteral && header_contains_record(expr)
1223 {
1224 Doc::text("(").append(text).append(")")
1225 } else {
1226 text
1227 }
1228}
1229
1230pub fn format_expr_header(
1251 expr: &Expr,
1252 source: &str,
1253 depth: usize,
1254 bounds: (proc_macro2::LineColumn, proc_macro2::LineColumn),
1255) -> String {
1256 format_expr_header_with_layout(expr, source, depth, bounds, depth * 4, 100, 0)
1257}
1258
1259pub fn format_expr_header_with_layout(
1277 expr: &Expr,
1278 source: &str,
1279 depth: usize,
1280 bounds: (proc_macro2::LineColumn, proc_macro2::LineColumn),
1281 column: usize,
1282 width: usize,
1283 suffix_columns: usize,
1284) -> String {
1285 let leading = grouping_gap(source, bounds.0, header_start(expr), &[]);
1286 let trailing = grouping_gap(source, header_end(expr), bounds.1, &[]);
1287 let mut text = trivia_doc(emit_gap(&leading, Spacing::None, 0).trim_start().to_owned());
1288 text = text.append(restricted_doc(
1289 expr,
1290 source,
1291 depth,
1292 crate::ExpressionRestriction::NoStructLiteral,
1293 ));
1294 let tail = emit_gap(&trailing, Spacing::None, 0);
1295 if matches!(trailing.last(), Some(GapPiece::Comment(Comment::Line(_)))) {
1296 text = text.append(trivia_doc(tail));
1297 } else {
1298 text = text.append(trivia_doc(tail.trim_end().to_owned()));
1299 }
1300 render_document(text, depth, column, width, suffix_columns)
1301}
1302
1303fn format_at(expr: &Expr, source: &str, depth: usize, min_level: Level) -> Doc {
1309 let (text, level) = render(expr, source, depth);
1310 if level < min_level {
1311 Doc::text("(").append(text).append(")").group()
1312 } else {
1313 text
1314 }
1315}
1316
1317pub fn format_expr(expr: &Expr, source: &str, depth: usize) -> String {
1336 format_expr_with_layout(expr, source, depth, depth * 4, 100, 0)
1337}
1338
1339pub fn format_expr_with_layout(
1360 expr: &Expr,
1361 source: &str,
1362 depth: usize,
1363 column: usize,
1364 width: usize,
1365 suffix_columns: usize,
1366) -> String {
1367 render_document(
1368 format_at(expr, source, depth, Level::RANGE),
1369 depth,
1370 column,
1371 width,
1372 suffix_columns,
1373 )
1374}
1375
1376fn render_document(
1381 doc: Doc,
1382 depth: usize,
1383 column: usize,
1384 width: usize,
1385 suffix_columns: usize,
1386) -> String {
1387 let prefix = " ".repeat(column);
1388 let suffix = " ".repeat(suffix_columns);
1389 let doc = if column > depth * 4 + 4 {
1390 Doc::hardline()
1393 .append(" ")
1394 .flat_alt(Doc::nil())
1395 .group()
1396 .append(Doc::column(move |current| {
1397 if current == column {
1398 doc.clone()
1399 } else {
1400 doc.clone().nest(4)
1401 }
1402 }))
1403 } else {
1404 doc
1405 };
1406 let document = Doc::text(prefix)
1407 .append(doc.nest((depth * 4) as isize))
1408 .append(Doc::text(suffix));
1409 let mut out = String::new();
1410 document
1411 .render_fmt(width, &mut out)
1412 .expect("writing to String is infallible");
1413 out.truncate(out.len() - suffix_columns);
1414 out.drain(..column);
1415 out
1416}
1417
1418#[cfg(test)]
1419mod tests {
1420 use super::*;
1421 use crate::{AstContext, OpLookup, Parser};
1422
1423 #[test]
1425 fn wraps_long_expressions_with_leading_operators() {
1426 let source = std::iter::repeat_n("long_operand_name", 8)
1427 .collect::<Vec<_>>()
1428 .join(" + ");
1429 let text = fmt(&source);
1430 assert!(text.contains("\n + "), "{text}");
1431 assert!(text.lines().all(|line| line.len() <= 100), "{text}");
1432 assert_eq!(fmt(&text), text);
1433 }
1434
1435 #[test]
1437 fn wraps_binary_ranges_before_their_operators() {
1438 for operator in ["..", "..="] {
1439 let source = format!("{}{}{}", "a".repeat(45), operator, "b".repeat(45));
1440 let expr = parse(&source);
1441 let text = format_expr_with_layout(&expr, &source, 1, 13, 100, 1);
1442 assert_eq!(
1443 text,
1444 format!("{}\n {operator}{}", "a".repeat(45), "b".repeat(45))
1445 );
1446 assert_eq!(
1447 format_expr_with_layout(&parse(&text), &text, 1, 13, 100, 1),
1448 text
1449 );
1450 assert_eq!(
1451 fmt(&format!("left{operator}right")),
1452 format!("left{operator}right")
1453 );
1454 let call = format!("call({source})");
1455 let formatted = fmt(&call);
1456 assert!(
1457 formatted.lines().all(|line| line.len() <= 100),
1458 "{formatted}"
1459 );
1460 assert_eq!(fmt(&formatted), formatted);
1461 }
1462 }
1463
1464 #[test]
1466 fn empty_closure_can_break_before_its_header() {
1467 let source = "|| predicate";
1468 let expr = parse(source);
1469 let text = format_expr_with_layout(&expr, source, 1, 100, 100, 1);
1470 assert_eq!(text, "\n || predicate");
1471 assert_eq!(
1472 format_expr_with_layout(&parse(&text), &text, 1, 100, 100, 1),
1473 text
1474 );
1475 }
1476
1477 #[test]
1479 fn wraps_nested_calls_records_arrays_and_closures() {
1480 let name = "a_long_argument_name_that_occupies_thirty_columns";
1481 for source in [
1482 format!("outer(inner({name}, {name}), [{name}, {name}])"),
1483 format!("Record {{ first: ({name}, {name}), second: [{name}, {name}] }}"),
1484 format!("map([{name}, {name}], |value: i32| value + {name} + {name})"),
1485 ] {
1486 let text = fmt(&source);
1487 assert!(text.contains('\n'), "{text}");
1488 assert!(text.lines().all(|line| line.len() <= 100), "{text}");
1489 assert_eq!(fmt(&text), text);
1490 }
1491 }
1492
1493 #[test]
1495 fn wraps_closure_parameters_without_losing_comments() {
1496 let source = "|/* first */ a_long_parameter: i32 /* second */, another_long_parameter: i32, third_long_parameter: i32, fourth_long_parameter: i32| a_long_parameter";
1497 let text = fmt(source);
1498 for label in ["first", "second"] {
1499 assert_eq!(text.matches(label).count(), 1, "{text}");
1500 }
1501 assert!(text.lines().all(|line| line.len() <= 100), "{text}");
1502 assert_eq!(fmt(&text), text);
1503 }
1504
1505 #[test]
1507 fn wraps_commented_binary_gaps() {
1508 let operand = "a".repeat(45);
1509 let source = format!("{operand} /* keep */ + {operand} + {operand}");
1510 let text = fmt(&source);
1511 assert!(text.lines().all(|line| line.len() <= 100), "{text}");
1512 assert_eq!(text.matches("keep").count(), 1);
1513 assert_eq!(fmt(&text), text);
1514 }
1515
1516 #[test]
1518 fn consecutive_line_comments_remain_separate() {
1519 let text = fmt("1 + // first\n// second\n2");
1520 assert_eq!(text, "1 + // first\n // second\n 2");
1521 assert_eq!(fmt(&text), text);
1522 }
1523
1524 #[test]
1526 fn brace_opening_line_comments_keep_their_mandatory_breaks() {
1527 for source in [
1528 "Record { // first\n value: 1 }",
1529 "if condition { // first\n 1 } else { // second\n 2 }",
1530 ] {
1531 let text = fmt(source);
1532 assert!(text.contains("// first\n"), "{text}");
1533 assert_eq!(fmt(&text), text);
1534 }
1535 }
1536
1537 #[test]
1539 fn embedding_reserves_exact_prefix_and_suffix_columns() {
1540 let source = "first + second";
1541 let expr = parse(source);
1542 assert_eq!(format_expr_with_layout(&expr, source, 1, 12, 27, 1), source);
1543 assert_eq!(
1544 format_expr_with_layout(&expr, source, 1, 12, 26, 1),
1545 "first\n + second"
1546 );
1547 let bounds = (
1548 proc_macro2::LineColumn { line: 1, column: 0 },
1549 proc_macro2::LineColumn {
1550 line: 1,
1551 column: source.len(),
1552 },
1553 );
1554 assert_eq!(
1555 format_expr_header_with_layout(&expr, source, 1, bounds, 12, 28, 2),
1556 source
1557 );
1558 assert_eq!(
1559 format_expr_header_with_layout(&expr, source, 1, bounds, 12, 27, 2),
1560 "first\n + second"
1561 );
1562 }
1563
1564 #[test]
1566 fn multiline_literal_spelling_is_independent_of_layout_nesting() {
1567 let source = "call(r#\"first\n second\"#, 0xffi32)";
1568 let expr = parse(source);
1569 let text = format_expr_with_layout(&expr, source, 2, 20, 30, 1);
1570 assert!(text.contains("r#\"first\n second\"#"), "{text}");
1571 assert!(text.contains("0xffi32"));
1572 assert_eq!(
1573 format_expr_with_layout(&parse(&text), &text, 2, 20, 30, 1),
1574 text
1575 );
1576 }
1577
1578 #[test]
1580 fn narrow_layout_preserves_expression_semantics_and_reaches_a_fixed_point() {
1581 for source in [
1582 "-(first_long_name + second_long_name) as i32",
1583 "first_long_name - (second_long_name - third_long_name)",
1584 "(|value: i32| value + first_long_name)(second_long_name)",
1585 "(holder.long_member_name)(first_long_name, second_long_name)",
1586 "first_long_name..=second_long_name",
1587 "[first_long_name, second_long_name]: [i32]",
1588 "(first_long_name + second_long_name,)",
1589 "[(first_long_name, second_long_name), (third_long_name, fourth_long_name)]",
1590 "if (Record { value: first_long_name }).value { 0xffi32 } else { 1i32 }",
1591 "first_long_name /* keep\n block */ + second_long_name",
1592 "call(first_long_name, // keep line\n second_long_name)",
1593 "first_long_name + // first\n // second\n second_long_name",
1594 ] {
1595 let expr = parse(source);
1596 let text = format_expr_with_layout(&expr, source, 0, 0, 40, 0);
1597 let parsed = parse(&text);
1598 assert_eq!(
1599 format_expr_with_layout(&parsed, &text, 0, 0, 40, 0),
1600 text,
1601 "{source}"
1602 );
1603 assert_eq!(
1604 format_expr_with_layout(&parsed, &text, 0, 0, 10_000, 0),
1605 format_expr_with_layout(&expr, source, 0, 0, 10_000, 0),
1606 "{source} -> {text}"
1607 );
1608 }
1609 }
1610
1611 fn parse(source: &str) -> Expr {
1612 Parser::<AstContext>::new(OpLookup::new())
1613 .parse_str_ast(source)
1614 .unwrap()
1615 }
1616
1617 fn fmt(source: &str) -> String {
1618 format_expr(&parse(source), source, 0)
1619 }
1620
1621 #[test]
1622 fn additive_and_multiplicative_reprint_without_extra_parens() {
1623 assert_eq!(fmt("1i32 + 2i32 * 3i32"), "1i32 + 2i32 * 3i32");
1624 }
1625
1626 #[test]
1627 fn explicit_grouping_that_changes_precedence_keeps_its_parens() {
1628 assert_eq!(fmt("(1i32 + 2i32) * 3i32"), "(1i32 + 2i32) * 3i32");
1629 }
1630
1631 #[test]
1632 fn left_associative_chain_at_the_same_precedence_has_no_parens() {
1633 assert_eq!(fmt("1i32 - 2i32 - 3i32"), "1i32 - 2i32 - 3i32");
1634 }
1635
1636 #[test]
1637 fn a_right_leaning_tree_at_the_same_precedence_needs_parens() {
1638 fn ident(name: &str) -> Expr {
1643 Expr::Ident {
1644 name: name.to_string(),
1645 span: point(),
1646 }
1647 }
1648 fn point() -> crate::ExprSpan {
1649 crate::ExprSpan {
1650 start: proc_macro2::Span::call_site(),
1651 end: proc_macro2::Span::call_site(),
1652 }
1653 }
1654 let expr = Expr::Op {
1655 name: "-".to_string(),
1656 operands: vec![
1657 ident("a"),
1658 Expr::Op {
1659 name: "-".to_string(),
1660 operands: vec![ident("b"), ident("c")],
1661 span: point(),
1662 },
1663 ],
1664 span: point(),
1665 };
1666 assert_eq!(format_expr(&expr, "", 0), "a - (b - c)");
1667 }
1668
1669 #[test]
1670 fn nested_comparison_needs_parens_on_both_sides() {
1671 fn ident(name: &str) -> Expr {
1675 Expr::Ident {
1676 name: name.to_string(),
1677 span: crate::ExprSpan {
1678 start: proc_macro2::Span::call_site(),
1679 end: proc_macro2::Span::call_site(),
1680 },
1681 }
1682 }
1683 let inner = Expr::Op {
1684 name: "==".to_string(),
1685 operands: vec![ident("a"), ident("b")],
1686 span: crate::ExprSpan {
1687 start: proc_macro2::Span::call_site(),
1688 end: proc_macro2::Span::call_site(),
1689 },
1690 };
1691 let expr = Expr::Op {
1692 name: "==".to_string(),
1693 operands: vec![inner, ident("c")],
1694 span: crate::ExprSpan {
1695 start: proc_macro2::Span::call_site(),
1696 end: proc_macro2::Span::call_site(),
1697 },
1698 };
1699 assert_eq!(format_expr(&expr, "", 0), "(a == b) == c");
1700 }
1701
1702 #[test]
1703 fn literal_notation_is_preserved_exactly() {
1704 assert_eq!(fmt("1920.0"), "1920.0");
1705 assert_eq!(fmt("1920.0f64"), "1920.0f64");
1706 assert_eq!(fmt("1i32"), "1i32");
1707 }
1708
1709 #[test]
1710 fn unary_minus_of_a_binary_expression_needs_parens() {
1711 assert_eq!(fmt("-(1i32 + 2i32)"), "-(1i32 + 2i32)");
1712 }
1713
1714 #[test]
1715 fn double_unary_minus_keeps_a_separating_space() {
1716 assert_eq!(fmt("- -1i32"), "- -1i32");
1717 }
1718
1719 #[test]
1720 fn cast_chain_reprints_without_extra_parens() {
1721 assert_eq!(fmt("x as i32 as f64"), "x as i32 as f64");
1722 }
1723
1724 #[test]
1725 fn unary_minus_before_a_cast_needs_no_parens() {
1726 assert_eq!(fmt("-x as f64"), "-x as f64");
1729 }
1730
1731 #[test]
1732 fn explicit_grouping_before_a_cast_keeps_its_parens() {
1733 assert_eq!(fmt("(a + b) as i32"), "(a + b) as i32");
1736 }
1737
1738 #[test]
1739 fn cast_operand_of_an_additive_expression_needs_no_parens() {
1740 assert_eq!(fmt("a + b as i32"), "a + b as i32");
1743 }
1744
1745 #[test]
1746 fn explicit_grouping_around_a_cast_before_multiplicative_is_redundant_and_dropped() {
1747 assert_eq!(fmt("(x as i32) * y"), "x as i32 * y");
1751 }
1752
1753 #[test]
1754 fn one_tuple_keeps_its_trailing_comma() {
1755 assert_eq!(fmt("(1i32,)"), "(1i32,)");
1756 }
1757
1758 #[test]
1759 fn multi_element_tuple_has_no_trailing_comma() {
1760 assert_eq!(fmt("(1i32, 2i32)"), "(1i32, 2i32)");
1761 }
1762
1763 #[test]
1764 fn arrays_format_with_brackets_and_normalized_commas() {
1765 assert_eq!(fmt("[1i32,2i32]"), "[1i32, 2i32]");
1766 assert_eq!(fmt("[[1i32], [2i32]]"), "[[1i32], [2i32]]");
1767 }
1768
1769 #[test]
1770 fn typed_arrays_format_with_a_type_ascription() {
1771 assert_eq!(fmt("[1i32,2i32]:[i32]"), "[1i32, 2i32]: [i32]");
1772 assert_eq!(fmt("[]:[[i32]]"), "[]: [[i32]]");
1773 }
1774
1775 #[test]
1776 fn format_array_annotation_with_a_generic_builtin_element_type() {
1777 let source = "[]: [RangeInclusive(f64)]";
1778 let expr = parse(source);
1779 let formatted = format_expr(&expr, source, 0);
1780 assert_eq!(formatted, source);
1781 }
1782
1783 #[test]
1784 fn array_comments_remain_in_source_order() {
1785 assert_eq!(
1786 fmt("[1i32 /* first */, /* second */ 2i32]"),
1787 "[1i32 /* first */, /* second */ 2i32]"
1788 );
1789 }
1790
1791 #[test]
1792 fn comments_in_a_call_argument_list_are_preserved() {
1793 assert_eq!(
1794 fmt("f(/* a */ 1i32, /* b */ 2i32)"),
1795 "f(/* a */ 1i32, /* b */ 2i32)"
1796 );
1797 }
1798
1799 #[test]
1800 fn a_comment_before_a_closing_call_paren_is_preserved() {
1801 assert_eq!(fmt("f(1i32 /* end */)"), "f(1i32 /* end */)");
1802 }
1803
1804 #[test]
1805 fn comments_in_a_tuple_are_preserved() {
1806 assert_eq!(fmt("(1i32, /* mid */ 2i32)"), "(1i32, /* mid */ 2i32)");
1807 }
1808
1809 #[test]
1810 fn comment_free_calls_and_tuples_are_unchanged() {
1811 assert_eq!(fmt("f(1i32, 2i32)"), "f(1i32, 2i32)");
1812 assert_eq!(fmt("(1i32, 2i32)"), "(1i32, 2i32)");
1813 assert_eq!(fmt("(1i32,)"), "(1i32,)");
1814 assert_eq!(fmt("(1i32, 2i32).1"), "(1i32, 2i32).1");
1815 }
1816
1817 #[test]
1818 fn if_without_else_omits_the_else_clause() {
1819 assert_eq!(fmt("if true { 1i32 }"), "if true { 1i32 }");
1820 }
1821
1822 #[test]
1823 fn if_else_reprints_both_branches() {
1824 assert_eq!(
1825 fmt("if true { 1i32 } else { 2i32 }"),
1826 "if true { 1i32 } else { 2i32 }"
1827 );
1828 }
1829
1830 #[test]
1831 fn else_if_chain_has_no_braces_around_the_nested_if() {
1832 let source = "if true { 1i32 } else if false { 2i32 } else { 3i32 }";
1833 assert_eq!(fmt(source), source);
1834 }
1835
1836 #[test]
1837 fn a_comment_after_if_condition_is_preserved() {
1838 assert_eq!(fmt("if a /* c */ { 1i32 }"), "if a /* c */ { 1i32 }");
1839 }
1840
1841 #[test]
1842 fn a_comment_before_else_is_preserved() {
1843 assert_eq!(
1844 fmt("if a { 1i32 } /* e */ else { 2i32 }"),
1845 "if a { 1i32 } /* e */ else { 2i32 }"
1846 );
1847 }
1848
1849 #[test]
1850 fn comment_free_if_else_is_unchanged() {
1851 assert_eq!(
1852 fmt("if true { 1i32 } else { 2i32 }"),
1853 "if true { 1i32 } else { 2i32 }"
1854 );
1855 assert_eq!(fmt("if true { 1i32 }"), "if true { 1i32 }");
1856 }
1857
1858 #[test]
1859 fn logical_or_and_and_are_not_desugared_and_need_no_extra_parens() {
1860 assert_eq!(fmt("a || b && c"), "a || b && c");
1861 }
1862
1863 #[test]
1864 fn a_comment_around_a_logical_operator_is_preserved() {
1865 assert_eq!(fmt("a /* x */ || b"), "a /* x */ || b");
1866 assert_eq!(fmt("a && /* y */ b"), "a && /* y */ b");
1867 }
1868
1869 #[test]
1870 fn comment_free_logical_is_unchanged() {
1871 assert_eq!(fmt("a || b && c"), "a || b && c");
1872 }
1873
1874 #[test]
1875 fn format_is_idempotent_through_a_reparse() {
1876 let source = "(1i32 + 2i32) * 3i32 - -4i32";
1877 let once = fmt(source);
1878 let twice = format_expr(&parse(&once), &once, 0);
1879 assert_eq!(once, twice);
1880 }
1881
1882 #[test]
1883 fn typed_array_annotation_round_trips_through_reparse() {
1884 let source = "[1.0, 42.5]: [f64]";
1885 let once = fmt(source);
1886 let twice = format_expr(&parse(&once), &once, 0);
1887 assert_eq!(once, twice);
1888 }
1889
1890 #[test]
1891 fn closure_with_one_param_reprints_with_its_type() {
1892 assert_eq!(fmt("|x: i32| x + 1i32"), "|x: i32| x + 1i32");
1893 }
1894
1895 #[test]
1896 fn closure_with_no_params_reprints_with_double_pipe() {
1897 assert_eq!(fmt("|| 1i32"), "|| 1i32");
1898 }
1899
1900 #[test]
1901 fn closure_with_multiple_params_joins_them_with_commas() {
1902 assert_eq!(fmt("|x: i32, y: i32| x + y"), "|x: i32, y: i32| x + y");
1903 }
1904
1905 #[test]
1906 fn closure_with_a_tuple_typed_param_reprints_the_tuple_type() {
1907 assert_eq!(fmt("|x: (i32, f64)| x.0"), "|x: (i32, f64)| x.0");
1908 }
1909
1910 #[test]
1911 fn a_comment_before_a_closure_body_is_preserved() {
1912 assert_eq!(
1913 fmt("|x: i32| /* b */ x + 1i32"),
1914 "|x: i32| /* b */ x + 1i32"
1915 );
1916 }
1917
1918 #[test]
1919 fn comment_free_closures_are_unchanged() {
1920 assert_eq!(fmt("|x: i32| x + 1i32"), "|x: i32| x + 1i32");
1921 assert_eq!(fmt("|| 1i32"), "|| 1i32");
1922 assert_eq!(fmt("|x: i32, y: i32| x + y"), "|x: i32, y: i32| x + y");
1923 }
1924
1925 #[test]
1926 fn a_line_comment_before_a_closure_body_wraps_the_expression() {
1927 let source = "|x: i32| // c\n x + 1i32";
1931 let once = fmt(source);
1932 assert!(once.contains("// c"), "comment must survive: {once:?}");
1933 assert!(once.contains("x + 1i32"), "body must survive: {once:?}");
1934 let twice = format_expr(&parse(&once), &once, 0);
1935 assert_eq!(once, twice, "formatting must be idempotent");
1936 }
1937
1938 #[test]
1939 fn range_inclusive_reprints_without_spaces() {
1940 assert_eq!(fmt("1i32..=5i32"), "1i32..=5i32");
1941 }
1942
1943 #[test]
1944 fn range_reprints_without_spaces() {
1945 assert_eq!(fmt("1i32..5i32"), "1i32..5i32");
1946 }
1947
1948 #[test]
1949 fn range_from_reprints_without_spaces() {
1950 assert_eq!(fmt("1i32.."), "1i32..");
1951 }
1952
1953 #[test]
1954 fn range_to_reprints_without_spaces() {
1955 assert_eq!(fmt("..5i32"), "..5i32");
1956 }
1957
1958 #[test]
1959 fn range_to_inclusive_reprints_without_spaces() {
1960 assert_eq!(fmt("..=5i32"), "..=5i32");
1961 }
1962
1963 #[test]
1964 fn range_full_reprints_as_two_dots() {
1965 assert_eq!(fmt(".."), "..");
1966 }
1967
1968 #[test]
1969 fn range_endpoints_that_are_comparisons_need_no_parens() {
1970 assert_eq!(fmt("a == b..c == d"), "a == b..c == d");
1974 }
1975
1976 #[test]
1977 fn range_endpoints_that_are_arithmetic_need_no_parens() {
1978 assert_eq!(fmt("1i32 + 2i32..3i32 * 4i32"), "1i32 + 2i32..3i32 * 4i32");
1981 }
1982
1983 #[test]
1984 fn emit_gap_around_a_plain_operator_uses_single_spaces() {
1985 let pieces = vec![GapPiece::Punct("+")];
1986 assert_eq!(emit_gap(&pieces, Spacing::Around, 0), " + ");
1987 }
1988
1989 #[test]
1990 fn emit_gap_none_spacing_glues_a_range_operator() {
1991 let pieces = vec![GapPiece::Punct("..")];
1992 assert_eq!(emit_gap(&pieces, Spacing::None, 0), "..");
1993 }
1994
1995 #[test]
1996 fn emit_gap_comma_after_puts_one_trailing_space() {
1997 let pieces = vec![GapPiece::Punct(",")];
1998 assert_eq!(emit_gap(&pieces, Spacing::CommaAfter, 0), ", ");
1999 }
2000
2001 #[test]
2002 fn emit_gap_inlines_a_block_comment_before_an_operator() {
2003 let pieces = vec![
2004 GapPiece::Comment(Comment::Block("a".to_string())),
2005 GapPiece::Punct("+"),
2006 ];
2007 assert_eq!(emit_gap(&pieces, Spacing::Around, 0), " /* a */ + ");
2008 }
2009
2010 #[test]
2011 fn emit_gap_inlines_a_block_comment_after_an_operator() {
2012 let pieces = vec![
2013 GapPiece::Punct("+"),
2014 GapPiece::Comment(Comment::Block("b".to_string())),
2015 ];
2016 assert_eq!(emit_gap(&pieces, Spacing::Around, 0), " + /* b */ ");
2017 }
2018
2019 #[test]
2020 fn emit_gap_wraps_after_a_line_comment_to_the_continuation_indent() {
2021 let pieces = vec![
2022 GapPiece::Punct("+"),
2023 GapPiece::Comment(Comment::Line("why".to_string())),
2024 ];
2025 assert_eq!(emit_gap(&pieces, Spacing::Around, 0), " + // why\n ");
2028 }
2029
2030 #[test]
2031 fn emit_gap_comma_after_a_line_comment_keeps_the_full_continuation_indent() {
2032 let pieces = vec![
2036 GapPiece::Comment(Comment::Line("why".to_string())),
2037 GapPiece::Punct(","),
2038 ];
2039 assert_eq!(emit_gap(&pieces, Spacing::CommaAfter, 0), " // why\n , ");
2040 }
2041
2042 #[test]
2043 fn a_block_comment_before_a_binary_operator_is_preserved() {
2044 let source = "1i32 /* a */ + 2i32";
2045 assert_eq!(fmt(source), "1i32 /* a */ + 2i32");
2046 }
2047
2048 #[test]
2049 fn a_block_comment_after_a_binary_operator_is_preserved() {
2050 let source = "1i32 + /* b */ 2i32";
2051 assert_eq!(fmt(source), "1i32 + /* b */ 2i32");
2052 }
2053
2054 #[test]
2055 fn comments_on_both_sides_of_a_binary_operator_are_preserved() {
2056 let source = "1i32 /* a */ + /* b */ 2i32";
2057 assert_eq!(fmt(source), "1i32 /* a */ + /* b */ 2i32");
2058 }
2059
2060 #[test]
2061 fn a_line_comment_after_a_binary_operator_wraps_the_expression() {
2062 let source = "1i32 +\n // why 2\n 2i32";
2063 assert_eq!(fmt(source), "1i32 + // why 2\n 2i32");
2064 }
2065
2066 #[test]
2067 fn a_comment_free_binary_op_still_prints_on_one_line() {
2068 assert_eq!(fmt("1i32 + 2i32 * 3i32"), "1i32 + 2i32 * 3i32");
2069 }
2070
2071 #[test]
2072 fn a_comment_after_unary_minus_is_preserved() {
2073 assert_eq!(fmt("- /* neg */ 1i32"), "- /* neg */ 1i32");
2074 }
2075
2076 #[test]
2077 fn a_comment_around_a_cast_operator_is_preserved() {
2078 assert_eq!(fmt("x /* c */ as i32"), "x /* c */ as i32");
2079 }
2080
2081 #[test]
2082 fn a_comment_inside_a_range_is_preserved() {
2083 assert_eq!(fmt("1i32 /* r */ ..5i32"), "1i32 /* r */ ..5i32");
2084 }
2085
2086 #[test]
2087 fn comment_free_unary_cast_and_range_are_unchanged() {
2088 assert_eq!(fmt("-1i32"), "-1i32");
2089 assert_eq!(fmt("x as i32"), "x as i32");
2090 assert_eq!(fmt("1i32..5i32"), "1i32..5i32");
2091 }
2092
2093 #[test]
2094 fn a_line_comment_after_a_cast_operator_wraps_the_expression() {
2095 let source = "x as // c\n i32";
2099 let once = fmt(source);
2100 assert!(once.contains("// c"), "comment must survive: {once:?}");
2101 assert!(once.contains("i32"), "type name must survive: {once:?}");
2102 let twice = format_expr(&parse(&once), &once, 0);
2103 assert_eq!(once, twice, "formatting must be idempotent");
2104 }
2105
2106 #[test]
2107 fn issue_201_line_comment_inside_an_expression_round_trips() {
2108 let source = "1i32 +\n // why 2\n 2i32";
2110 let once = fmt(source);
2111 assert!(once.contains("// why 2"), "comment must survive: {once:?}");
2112 let twice = format_expr(&parse(&once), &once, 0);
2113 assert_eq!(once, twice, "formatting must be idempotent");
2114 }
2115
2116 #[test]
2117 fn the_full_inline_comment_example_round_trips() {
2118 let source = "/* p */ 1i32 /* q */ + // hello\n2i32 /* r */";
2126 let once = fmt(source);
2127 assert!(
2128 once.contains("/* q */") && once.contains("// hello"),
2129 "the in-scope operand/operator comments survive: {once:?}"
2130 );
2131 let twice = format_expr(&parse(&once), &once, 0);
2132 assert_eq!(once, twice);
2133 }
2134}