1use crate::ast::{Expr, LogicalOp};
15use crate::trivia::{Comment, GapPiece, scan_gap, take_block_comment};
16
17struct Source<'a> {
21 text: &'a str,
22 lines: Vec<usize>,
23 bytes: Vec<usize>,
24}
25
26impl<'a> Source<'a> {
27 fn new(text: &'a str) -> Self {
29 let mut lines = vec![0];
30 let mut bytes = Vec::new();
31 for (character, (byte, ch)) in text.char_indices().enumerate() {
33 bytes.push(byte);
34 if ch == '\n' {
35 lines.push(character + 1);
36 }
37 }
38 bytes.push(text.len());
39 Self { text, lines, bytes }
40 }
41
42 fn byte(&self, position: proc_macro2::LineColumn) -> usize {
46 debug_assert!((1..=self.lines.len()).contains(&position.line));
47 let character = self.lines[position.line - 1] + position.column;
48 debug_assert!(
49 character
50 < self
51 .lines
52 .get(position.line)
53 .copied()
54 .unwrap_or(self.bytes.len())
55 );
56 self.bytes[character]
57 }
58
59 fn between(&self, from: proc_macro2::LineColumn, to: proc_macro2::LineColumn) -> &'a str {
63 if self.text.is_empty() {
64 return "";
65 }
66 self.text.get(self.byte(from)..self.byte(to)).unwrap_or("")
67 }
68}
69
70type Doc = pretty::RcDoc<'static, ()>;
72
73fn verbatim_doc(text: String) -> Doc {
77 let mut lines = text.split('\n');
78 let first = Doc::text(lines.next().unwrap_or_default().to_owned());
79 lines.fold(first, |doc, line| {
80 let line = Doc::text(line.to_owned());
81 doc.append(Doc::nesting(move |indent| {
82 let offset = isize::try_from(indent)
83 .expect("document indentation fits isize")
84 .checked_neg()
85 .expect("nonnegative indentation can be negated");
86 Doc::hardline().append(line.clone()).nest(offset)
87 }))
88 })
89}
90
91fn trivia_doc(text: String) -> Doc {
95 trivia_doc_at(text, 4)
96}
97
98fn trivia_doc_at(text: String, continuation: isize) -> Doc {
102 let mut lines = text.split('\n');
103 let first = Doc::text(lines.next().unwrap_or_default().to_owned());
104 lines.fold(first, |doc, line| {
105 doc.append(
106 Doc::hardline()
107 .append(Doc::text(
108 line.strip_prefix(" ").unwrap_or(line).to_owned(),
109 ))
110 .nest(continuation),
111 )
112 })
113}
114
115fn gap_doc(pieces: &[GapPiece], spacing: Spacing) -> Doc {
119 if pieces
120 .iter()
121 .any(|piece| matches!(piece, GapPiece::Comment(_)))
122 && let Some(index) = pieces
123 .iter()
124 .position(|piece| matches!(piece, GapPiece::Punct(_)))
125 && matches!(spacing, Spacing::Around | Spacing::CommaAfter)
126 {
127 let GapPiece::Punct(token) = pieces[index] else {
128 unreachable!()
129 };
130 let before = emit_gap(&pieces[..index], Spacing::None, 0);
131 let after = emit_gap(&pieces[index + 1..], Spacing::None, 0);
132 let after_empty = after.is_empty();
133 let before_line = matches!(
134 pieces.get(index.wrapping_sub(1)),
135 Some(GapPiece::Comment(Comment::Line(_)))
136 );
137 let after_line = matches!(pieces.last(), Some(GapPiece::Comment(Comment::Line(_))));
138 let prefix = if before_line {
139 trivia_doc_at(before, 0)
140 } else {
141 trivia_doc_at(before.trim_end().to_owned(), 0)
142 };
143 let suffix = if after_line {
144 trivia_doc_at(after, 0)
145 } else {
146 trivia_doc_at(after.trim_end().to_owned(), 0)
147 };
148 return match spacing {
149 Spacing::Around => prefix
150 .append(if before_line {
151 Doc::nil()
152 } else {
153 Doc::softline()
154 })
155 .append(token)
156 .append(if after_empty {
157 Doc::space()
158 } else {
159 suffix.append(if after_line { Doc::nil() } else { Doc::space() })
160 }),
161 Spacing::CommaAfter => prefix.append(token).append(if after_empty {
162 Doc::line()
163 } else {
164 suffix.append(if after_line { Doc::nil() } else { Doc::line() })
165 }),
166 Spacing::None => unreachable!(),
167 };
168 }
169 if pieces
170 .iter()
171 .all(|piece| matches!(piece, GapPiece::Punct(_)))
172 {
173 Doc::concat(pieces.iter().map(|piece| {
174 let GapPiece::Punct(token) = piece else {
175 unreachable!()
176 };
177 match spacing {
178 Spacing::Around => Doc::line().append(*token).append(" "),
179 Spacing::CommaAfter => Doc::text(*token).append(Doc::line()),
180 Spacing::None => Doc::text(*token),
181 }
182 }))
183 } else {
184 trivia_doc(emit_gap(pieces, spacing, 0))
185 }
186}
187
188fn binary_doc(lhs: Doc, gap: &[GapPiece], rhs: Doc) -> Doc {
192 lhs.append(gap_doc(gap, Spacing::Around).append(rhs).nest(4))
193 .group()
194}
195
196fn body_gap_doc(pieces: &[GapPiece], trim_start: bool) -> Doc {
202 let text = emit_gap(pieces, Spacing::Around, 0);
203 let text = if trim_start { text.trim_start() } else { &text };
204 if matches!(pieces.last(), Some(GapPiece::Comment(Comment::Line(_)))) {
205 trivia_doc_at(text.to_owned(), 0)
206 } else {
207 trivia_doc_at(text.trim_end().to_owned(), 0).append(Doc::line())
208 }
209}
210
211#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
214struct Level(u8);
215
216impl Level {
217 const RANGE: Level = Level(0);
218 const OR: Level = Level(1);
219 const AND: Level = Level(2);
220 const COMPARISON: Level = Level(3);
221 const BIT_OR: Level = Level(4);
222 const BIT_XOR: Level = Level(5);
223 const BIT_AND: Level = Level(6);
224 const SHIFT: Level = Level(7);
225 const ADDITIVE: Level = Level(8);
226 const MULTIPLICATIVE: Level = Level(9);
227 const CAST: Level = Level(10);
228 const UNARY: Level = Level(11);
229 const POSTFIX: Level = Level(12);
230 const PRIMARY: Level = Level(13);
231
232 fn tighter(self) -> Level {
234 Level(self.0 + 1)
235 }
236}
237
238fn indent(depth: usize) -> String {
242 " ".repeat(depth)
243}
244
245#[derive(Clone, Copy)]
247enum Spacing {
248 Around,
250 None,
252 CommaAfter,
254}
255
256fn emit_gap(pieces: &[GapPiece], spacing: Spacing, depth: usize) -> String {
267 let cont = indent(depth + 1);
268 let mut out = String::new();
269 for piece in pieces {
270 match piece {
271 GapPiece::Punct(tok) => {
272 match spacing {
273 Spacing::Around => {
274 if !out.ends_with(' ') {
278 out.push(' ');
279 }
280 out.push_str(tok);
281 out.push(' ');
282 }
283 Spacing::None => out.push_str(tok),
284 Spacing::CommaAfter => {
285 if out.ends_with(' ') && !out.ends_with(&cont) {
295 out.pop();
296 }
297 out.push_str(tok);
298 out.push(' ');
299 }
300 }
301 }
302 GapPiece::Comment(Comment::Block(text)) => {
303 if !out.ends_with(' ') {
307 out.push(' ');
308 }
309 out.push_str("/* ");
310 out.push_str(text);
311 out.push_str(" */ ");
312 }
313 GapPiece::Comment(Comment::Line(text)) => {
314 let continuation = out
317 .rsplit_once('\n')
318 .is_some_and(|(_, tail)| tail.trim().is_empty());
319 if continuation {
320 out.push_str("// ");
321 } else {
322 let trimmed_len = out.trim_end().len();
323 out.truncate(trimmed_len);
324 out.push_str(" // ");
325 }
326 out.push_str(text);
327 out.push('\n');
328 out.push_str(&cont);
329 }
330 }
331 }
332 out
333}
334
335fn binary_op_level(name: &str) -> Level {
344 match name {
345 "|" => Level::BIT_OR,
346 "^" => Level::BIT_XOR,
347 "&" => Level::BIT_AND,
348 "<<" | ">>" => Level::SHIFT,
349 "+" | "-" => Level::ADDITIVE,
350 "*" | "/" | "%" => Level::MULTIPLICATIVE,
351 "==" | "!=" | "<" | ">" | "<=" | ">=" => Level::COMPARISON,
352 other => unreachable!("binary_op_level called with unknown operator `{other}`"),
353 }
354}
355
356fn render_literal(span: crate::ExprSpan) -> String {
360 span.start.source_text().unwrap_or_default()
361}
362
363fn gap_between(
372 source: &Source<'_>,
373 prev_end: proc_macro2::Span,
374 next_start: proc_macro2::Span,
375 expected: &[&'static str],
376) -> Vec<GapPiece> {
377 scan_gap(source.between(prev_end.end(), next_start.start()), expected)
378}
379
380fn member_gap(
387 source: &Source<'_>,
388 receiver_end: proc_macro2::Span,
389 name_start: proc_macro2::Span,
390) -> Vec<GapPiece> {
391 grouping_gap(source, receiver_end.end(), name_start.start(), &["."])
392}
393
394fn format_postfix_base(
400 base: &Expr,
401 source: &Source<'_>,
402 depth: usize,
403 start: proc_macro2::LineColumn,
404) -> Doc {
405 let leading = grouping_gap(source, start, base.span().start.start(), &[]);
406 if leading.is_empty() {
407 format_at(base, source, depth, Level::POSTFIX)
408 } else {
409 let (text, _) = render(base, source, depth);
410 let prefix = emit_gap(&leading, Spacing::None, 0);
411 Doc::text("(")
412 .append(trivia_doc(prefix.trim_start().to_owned()))
413 .append(text)
414 .append(")")
415 }
416}
417
418fn emit_list(
439 source: &Source<'_>,
440 depth: usize,
441 delims: (&'static str, &'static str),
442 bounds: (proc_macro2::LineColumn, proc_macro2::LineColumn),
443 elements: &[Expr],
444 trailing_comma: bool,
445) -> Doc {
446 let (open, close) = delims;
447 let (open_pos, close_pos) = bounds;
448 let scan = |from: proc_macro2::LineColumn,
452 to: proc_macro2::LineColumn,
453 expected: &[&'static str]|
454 -> Vec<GapPiece> { scan_gap(source.between(from, to), expected) };
455
456 if elements.is_empty() {
457 let pieces = scan(open_pos, close_pos, &[open, close]);
459 let whole = emit_gap(&pieces, Spacing::None, 0);
460 return trivia_doc(glue_before_close(&glue_after_open(&whole, open), close));
461 }
462 let mut pre = grouping_gap(source, open_pos, elements[0].span().start.start(), &[open]);
467 if open == "(" {
468 pre.insert(0, GapPiece::Punct("("));
469 }
470 let mut out = trivia_doc(glue_after_open(&emit_gap(&pre, Spacing::None, 0), open));
471 let mut body = Doc::line_();
472 for (i, el) in elements.iter().enumerate() {
473 body = body.append(format_at(el, source, depth, Level::OR));
474 if i + 1 < elements.len() {
475 let pieces = grouping_gap(
476 source,
477 el.span().end.end(),
478 elements[i + 1].span().start.start(),
479 &[","],
480 );
481 body = body.append(gap_doc(&pieces, Spacing::CommaAfter));
482 }
483 }
484 if trailing_comma {
485 body = body.append(",");
486 }
487 let mut post = grouping_gap(
491 source,
492 elements[elements.len() - 1].span().end.end(),
493 close_pos,
494 &[close],
495 );
496 if close == ")" {
497 post.push(GapPiece::Punct(")"));
498 }
499 out = out.append(body.nest(4));
500 let tail = glue_before_close(&emit_gap(&post, Spacing::None, 0), close);
501 out.append(Doc::line_()).append(trivia_doc(tail)).group()
502}
503
504fn glue_after_open(gap: &str, open: &'static str) -> String {
508 match gap.strip_prefix(&format!("{open} ")) {
509 Some(rest) => format!("{open}{rest}"),
510 None => gap.to_string(),
511 }
512}
513
514fn glue_before_close(gap: &str, close: &'static str) -> String {
517 if let Some((_, last_line)) = gap.rsplit_once('\n') {
518 if let Some(rest) = last_line.strip_suffix(close)
524 && rest.chars().all(|c| c == ' ')
525 {
526 return gap.to_string();
527 }
528 }
529 match gap.strip_suffix(&format!(" {close}")) {
530 Some(rest) => format!("{rest}{close}"),
531 None => gap.to_string(),
532 }
533}
534
535fn binary_op_token(name: &str) -> &'static str {
539 match name {
540 "|" => "|",
541 "^" => "^",
542 "&" => "&",
543 "<<" => "<<",
544 ">>" => ">>",
545 "+" => "+",
546 "-" => "-",
547 "*" => "*",
548 "/" => "/",
549 "%" => "%",
550 "==" => "==",
551 "!=" => "!=",
552 "<" => "<",
553 ">" => ">",
554 "<=" => "<=",
555 ">=" => ">=",
556 other => unreachable!("binary_op_token called with unknown operator `{other}`"),
557 }
558}
559
560fn unary_op_token(name: &str) -> &'static str {
562 match name {
563 "-" => "-",
564 "!" => "!",
565 other => unreachable!("unary_op_token called with unknown operator `{other}`"),
566 }
567}
568
569fn render_type_expr(type_expr: &crate::TypeExpr, source: &Source<'_>) -> String {
578 let span = type_expr.span();
579 let original = source.between(span.start.start(), span.end.end());
580 if original.contains("/*") || original.contains("//") {
581 return original.to_owned();
582 }
583 let mut output = String::new();
584 write_type_expr(type_expr, &mut output);
585 output
586}
587
588fn write_type_expr(type_expr: &crate::TypeExpr, output: &mut String) {
592 match type_expr {
593 crate::TypeExpr::Named { name, args, .. } => {
594 output.push_str(name);
595 if !args.is_empty() {
596 write_type_list(args, output, false);
597 }
598 }
599 crate::TypeExpr::Array { element, .. } => {
600 output.push('[');
601 write_type_expr(element, output);
602 output.push(']');
603 }
604 crate::TypeExpr::Tuple { elements, .. } => {
605 write_type_list(elements, output, true);
606 }
607 }
608}
609
610fn write_type_list(elements: &[crate::TypeExpr], output: &mut String, tuple: bool) {
614 output.push('(');
615 for (index, element) in elements.iter().enumerate() {
617 if index != 0 {
618 output.push_str(", ");
619 }
620 write_type_expr(element, output);
621 }
622 if tuple && elements.len() == 1 {
623 output.push(',');
624 }
625 output.push(')');
626}
627
628fn render(expr: &Expr, source: &Source<'_>, depth: usize) -> (Doc, Level) {
637 match expr {
638 Expr::Struct {
639 name,
640 name_span,
641 fields,
642 span,
643 } => {
644 let mut out = Doc::text(name.clone());
645 let mut body = Doc::nil();
646 if let Some(first) = fields.first() {
647 let open = grouping_gap(
648 source,
649 name_span.end.end(),
650 first.name_span.start.start(),
651 &["{"],
652 );
653 body = body.append(body_gap_doc(&open, false));
654 for (index, field) in fields.iter().enumerate() {
655 body = body.append(Doc::text(field.name.clone()));
656 let colon = grouping_gap(
657 source,
658 field.name_span.end.end(),
659 field.value.span().start.start(),
660 &[":"],
661 );
662 body = body.append(trivia_doc(emit_gap(&colon, Spacing::CommaAfter, 0)));
663 body = body.append(format_at(&field.value, source, depth, Level::RANGE));
664 if let Some(next) = fields.get(index + 1) {
665 let comma = grouping_gap(
666 source,
667 field.value.span().end.end(),
668 next.name_span.start.start(),
669 &[","],
670 );
671 body = body.append(gap_doc(&comma, Spacing::CommaAfter));
672 }
673 }
674 let last = fields.last().expect("non-empty fields");
675 let mut tail = grouping_gap(
676 source,
677 last.value.span().end.end(),
678 span.end.end(),
679 &[",", "}"],
680 );
681 tail.retain(|piece| !matches!(piece, GapPiece::Punct(",")));
682 let closing = emit_gap(&tail, Spacing::Around, 0);
683 out = out
684 .append(body.nest(4))
685 .append(Doc::line())
686 .append(trivia_doc(closing.trim().to_owned()))
687 .group();
688 } else {
689 let tail = grouping_gap(source, name_span.end.end(), span.end.end(), &["{", "}"]);
690 out = out
691 .append(" ")
692 .append(trivia_doc(emit_gap(&tail, Spacing::None, 0)));
693 }
694 (out, Level::PRIMARY)
695 }
696 Expr::Literal { span, .. } => (verbatim_doc(render_literal(*span)), Level::PRIMARY),
697 Expr::Ident { name, span }
700 if name == "range_full"
701 && span
702 .start
703 .source_text()
704 .is_some_and(|text| matches!(text.as_str(), "." | "..")) =>
705 {
706 (Doc::text(".."), Level::RANGE)
707 }
708 Expr::Ident { name, .. } => (Doc::text(name.clone()), Level::PRIMARY),
709 Expr::Logical { op, lhs, rhs, .. } => {
710 let level = match op {
711 LogicalOp::Or => Level::OR,
712 LogicalOp::And => Level::AND,
713 };
714 let op_static: &'static str = match op {
715 LogicalOp::Or => "||",
716 LogicalOp::And => "&&",
717 };
718 let lhs_s = format_at(lhs, source, depth, level);
719 let rhs_s = format_at(rhs, source, depth, level.tighter());
720 let pieces = gap_between(source, lhs.span().end, rhs.span().start, &[op_static]);
721 (binary_doc(lhs_s, &pieces, rhs_s), level)
722 }
723 Expr::Op { operands, .. } if operands.is_empty() => {
724 (Doc::text(".."), Level::RANGE)
730 }
731 Expr::Op {
732 name,
733 operands,
734 span,
735 } if operands.len() == 1
736 && matches!(
737 name.as_str(),
738 "range_from" | "range_to" | "range_to_inclusive"
739 ) =>
740 {
741 let operand_s = format_at(&operands[0], source, depth, Level::RANGE.tighter());
746 let text = match name.as_str() {
747 "range_from" => {
748 let pieces = gap_between(source, operands[0].span().end, span.end, &[".."]);
749 operand_s.append(gap_doc(&pieces, Spacing::None))
750 }
751 "range_to" => {
752 let pieces = gap_between(source, span.start, operands[0].span().start, &[]);
758 Doc::text("..")
759 .append(gap_doc(&pieces, Spacing::None))
760 .append(operand_s)
761 }
762 "range_to_inclusive" => {
763 let pieces = gap_between(source, span.start, operands[0].span().start, &[]);
764 Doc::text("..=")
765 .append(gap_doc(&pieces, Spacing::None))
766 .append(operand_s)
767 }
768 _ => unreachable!("guarded by the outer match arm"),
769 };
770 (text, Level::RANGE)
771 }
772 Expr::Op {
773 name,
774 operands,
775 span,
776 } if operands.len() == 1 => {
777 let operand_s = format_at(&operands[0], source, depth, Level::UNARY);
778 let op_static = unary_op_token(name);
779 let pieces = gap_between(source, span.start, operands[0].span().start, &[]);
783 let default_sep = if matches!(&operands[0], Expr::Op { name, operands, .. }
785 if operands.len() == 1 && matches!(name.as_str(), "-" | "!"))
786 {
787 " "
788 } else {
789 ""
790 };
791 let gap = if pieces.iter().any(|p| matches!(p, GapPiece::Comment(_))) {
792 emit_gap(&pieces, Spacing::None, 0)
793 } else {
794 default_sep.to_string()
795 };
796 (
797 Doc::text(op_static)
798 .append(trivia_doc(gap))
799 .append(operand_s),
800 Level::UNARY,
801 )
802 }
803 Expr::Op { name, operands, .. } if name == "range" || name == "range_inclusive" => {
804 let lhs_s = format_at(&operands[0], source, depth, Level::RANGE.tighter());
807 let rhs_s = format_at(&operands[1], source, depth, Level::RANGE.tighter());
808 let op_static: &'static str = if name == "range_inclusive" {
809 "..="
810 } else {
811 ".."
812 };
813 let pieces = gap_between(
814 source,
815 operands[0].span().end,
816 operands[1].span().start,
817 &[op_static],
818 );
819 (
820 lhs_s
821 .append(
822 Doc::softline_()
823 .append(gap_doc(&pieces, Spacing::None))
824 .append(rhs_s)
825 .nest(4),
826 )
827 .group(),
828 Level::RANGE,
829 )
830 }
831 Expr::Op { name, operands, .. } => {
832 let level = binary_op_level(name);
833 let (lhs_min, rhs_min) = if level == Level::COMPARISON {
837 (level.tighter(), level.tighter())
838 } else {
839 (level, level.tighter())
840 };
841 let lhs_s = format_at(&operands[0], source, depth, lhs_min);
842 let rhs_s = format_at(&operands[1], source, depth, rhs_min);
843 let op_static = binary_op_token(name);
845 let pieces = gap_between(
846 source,
847 operands[0].span().end,
848 operands[1].span().start,
849 &[op_static],
850 );
851 (binary_doc(lhs_s, &pieces, rhs_s), level)
852 }
853 Expr::Cast {
854 expr,
855 type_name,
856 span,
857 } => {
858 let expr_s = format_at(expr, source, depth, Level::CAST);
862 let pieces = gap_between(source, expr.span().end, span.end, &["as"]);
866 let text = binary_doc(expr_s, &pieces, Doc::text(type_name.clone()));
867 (text, Level::CAST)
868 }
869 Expr::Apply { callee, args, span } => {
870 let mut callee_s = format_postfix_base(callee, source, depth, span.start.start());
871 if matches!(callee.as_ref(), Expr::Member { args: None, .. }) {
874 callee_s = Doc::text("(").append(callee_s).append(")");
875 }
876 let tail = grouping_gap(source, callee.span().end.end(), span.end.start(), &[]);
877 callee_s = callee_s.append(gap_doc(&tail, Spacing::None));
878 let list = emit_list(
881 source,
882 depth,
883 ("(", ")"),
884 (span.end.start(), span.end.end()),
885 args,
886 false,
887 );
888 (callee_s.append(list), Level::POSTFIX)
889 }
890 Expr::Member {
891 receiver,
892 name,
893 name_span,
894 args,
895 span,
896 } => {
897 let receiver_s = format_postfix_base(receiver, source, depth, span.start.start());
898 let gap = member_gap(source, receiver.span().end, name_span.start);
899 let member_gap = if gap.iter().all(|piece| matches!(piece, GapPiece::Punct(_))) {
900 Doc::line_().append(".")
901 } else if gap
902 .iter()
903 .any(|piece| matches!(piece, GapPiece::Comment(Comment::Line(_))))
904 {
905 trivia_doc_at(emit_gap(&gap, Spacing::None, 0), 0)
906 } else {
907 verbatim_doc(emit_gap(&gap, Spacing::None, 0))
908 };
909 let mut text = receiver_s
910 .append(member_gap.append(Doc::text(name.clone())).nest(4))
911 .group();
912 if let Some(args) = args {
913 text = text.append(emit_list(
914 source,
915 depth,
916 ("(", ")"),
917 (name_span.end.end(), span.end.end()),
918 args,
919 false,
920 ));
921 }
922 (text, Level::POSTFIX)
923 }
924 Expr::Tuple { elements, span } => {
925 let list = emit_list(
929 source,
930 depth,
931 ("(", ")"),
932 (span.start.start(), span.end.end()),
933 elements,
934 elements.len() == 1,
935 );
936 (list, Level::PRIMARY)
937 }
938 Expr::Array {
939 elements,
940 annotation_span,
941 type_annotation,
942 span,
943 } => {
944 let close_pos = annotation_span
949 .map(|annotation_span| annotation_span.start.start())
950 .unwrap_or_else(|| span.end.end());
951 let mut list = emit_list(
952 source,
953 depth,
954 ("[", "]"),
955 (span.start.start(), close_pos),
956 elements,
957 false,
958 );
959 if let Some(type_annotation) = type_annotation {
960 let comments = annotation_span
961 .map(|annotation| {
962 gap_between(source, annotation.start, type_annotation.span().start, &[])
963 })
964 .unwrap_or_default();
965 let gap = emit_gap(&comments, Spacing::None, 0);
966 list = list
967 .append(":")
968 .append(trivia_doc(if gap.is_empty() {
969 " ".to_owned()
970 } else {
971 gap
972 }))
973 .append(verbatim_doc(render_type_expr(type_annotation, source)));
974 }
975 (list, Level::PRIMARY)
976 }
977 Expr::TupleIndex { base, index, span } => (
978 format_postfix_base(base, source, depth, span.start.start())
979 .append(gap_doc(
980 &grouping_gap(source, base.span().end.end(), span.end.start(), &["."]),
981 Spacing::None,
982 ))
983 .append(Doc::text(index.to_string())),
984 Level::POSTFIX,
985 ),
986 Expr::If {
987 cond,
988 then_branch,
989 else_branch,
990 span,
991 } => {
992 let cond_s = restricted_doc(
1001 cond,
1002 source,
1003 depth,
1004 crate::ExpressionRestriction::NoStructLiteral,
1005 );
1006 let then_s = format_at(then_branch, source, depth, Level::OR);
1007 let if_gap = grouping_gap(source, span.start.start(), header_start(cond), &["if"]);
1008 let open_gap = grouping_gap(
1009 source,
1010 header_end(cond),
1011 then_branch.span().start.start(),
1012 &["{"],
1013 );
1014 let mut text = Doc::nil();
1015 text = text.append(trivia_doc(
1018 emit_gap(&if_gap, Spacing::Around, 0)
1019 .trim_start()
1020 .to_owned(),
1021 ));
1022 text = text.append(cond_s);
1023 text = text.append(body_gap_doc(&open_gap, false).append(then_s).nest(4));
1024 match else_branch {
1025 None => {
1026 let close_gap = gap_between(source, then_branch.span().end, span.end, &["}"]);
1029 text = text.append(Doc::line()).append(trivia_doc(
1033 emit_gap(&close_gap, Spacing::Around, 0).trim().to_owned(),
1034 ));
1035 }
1036 Some(else_branch) => {
1037 let is_chain = matches!(else_branch.as_ref(), Expr::If { .. });
1038 let expected: &[&'static str] = if is_chain {
1041 &["}", "else"]
1042 } else {
1043 &["}", "else", "{"]
1044 };
1045 let else_gap = gap_between(
1049 source,
1050 then_branch.span().end,
1051 else_branch.span().start,
1052 expected,
1053 );
1054 text = text.append(Doc::line());
1055 if is_chain {
1056 let (else_s, _) = render(else_branch, source, depth);
1060 text = text.append(body_gap_doc(&else_gap, true)).append(else_s);
1061 } else {
1062 let else_s = format_at(else_branch, source, depth, Level::OR);
1063 text = text.append(body_gap_doc(&else_gap, true).append(else_s).nest(4));
1064 let final_close_gap =
1065 gap_between(source, else_branch.span().end, span.end, &["}"]);
1066 text = text.append(Doc::line()).append(trivia_doc(
1067 emit_gap(&final_close_gap, Spacing::Around, 0)
1068 .trim()
1069 .to_owned(),
1070 ));
1071 }
1072 }
1073 }
1074 (text.group(), Level::PRIMARY)
1075 }
1076 Expr::Closure { params, body, span } => {
1077 let body_s = format_at(body, source, depth, Level::OR);
1078 let header = if params.is_empty() {
1079 Doc::text("||")
1080 } else {
1081 let leading = gap_between(source, span.start, params[0].name_span.start, &[]);
1082 let mut parameters =
1083 trivia_doc(emit_gap(&leading, Spacing::None, 0).trim_start().to_owned());
1084 for (index, param) in params.iter().enumerate() {
1085 if index > 0 {
1086 let comma = gap_between(
1087 source,
1088 params[index - 1].type_expr.span().end,
1089 param.name_span.start,
1090 &[","],
1091 );
1092 parameters = parameters.append(gap_doc(&comma, Spacing::CommaAfter));
1093 }
1094 let colon = gap_between(
1095 source,
1096 param.name_span.end,
1097 param.type_expr.span().start,
1098 &[":"],
1099 );
1100 parameters = parameters
1101 .append(Doc::text(param.name.clone()))
1102 .append(trivia_doc(emit_gap(&colon, Spacing::CommaAfter, 0)))
1103 .append(verbatim_doc(render_type_expr(¶m.type_expr, source)));
1104 }
1105 Doc::text("|")
1106 .append(Doc::line_().append(parameters).nest(4))
1107 .append(Doc::line_())
1108 .append("|")
1109 .group()
1110 };
1111 let (tail_start, expected): (proc_macro2::Span, &[&'static str]) = match params.last() {
1112 Some(last) => (last.type_expr.span().end, &["|"]),
1113 None => (span.start, &[]),
1114 };
1115 let pieces = gap_between(source, tail_start, body.span().start, expected);
1116 let comments: Vec<GapPiece> = pieces
1117 .into_iter()
1118 .filter(|p| matches!(p, GapPiece::Comment(_)))
1119 .collect();
1120 let gap = emit_gap(&comments, Spacing::None, 0);
1121 let has_line_comment = comments
1126 .iter()
1127 .any(|p| matches!(p, GapPiece::Comment(Comment::Line(_))));
1128 let sep = if has_line_comment {
1129 trivia_doc_at(gap, 0)
1130 } else if gap.trim().is_empty() {
1131 Doc::line()
1132 } else {
1133 trivia_doc(format!(" {} ", gap.trim()))
1134 };
1135 (
1137 header.append(sep.append(body_s).nest(4)).group(),
1138 Level::RANGE,
1139 )
1140 }
1141 }
1142}
1143
1144fn header_start(expr: &Expr) -> proc_macro2::LineColumn {
1151 match expr {
1152 Expr::Op { name, operands, .. }
1153 if operands.len() == 2 || (name == "range_from" && operands.len() == 1) =>
1154 {
1155 header_start(&operands[0])
1156 }
1157 Expr::Logical { lhs, .. } => header_start(lhs),
1158 Expr::Cast { expr, .. } => header_start(expr),
1159 _ => expr.span().start.start(),
1160 }
1161}
1162
1163fn header_end(expr: &Expr) -> proc_macro2::LineColumn {
1169 match expr {
1170 Expr::Op { name, operands, .. } if name != "range_from" && !operands.is_empty() => {
1171 header_end(operands.last().expect("non-empty operands"))
1172 }
1173 Expr::Logical { rhs, .. } => header_end(rhs),
1174 Expr::Closure { body, .. } => header_end(body),
1175 _ => expr.span().end.end(),
1176 }
1177}
1178
1179fn grouping_gap(
1185 source: &Source<'_>,
1186 from: proc_macro2::LineColumn,
1187 to: proc_macro2::LineColumn,
1188 expected: &[&'static str],
1189) -> Vec<GapPiece> {
1190 let gap = source.between(from, to);
1191 let mut rest = gap;
1192 let mut tokens = Vec::new();
1193 let mut next = 0;
1194 loop {
1195 rest = rest.trim_start();
1196 if let Some(after) = rest.strip_prefix("//") {
1197 rest = &after[after.find('\n').unwrap_or(after.len())..];
1198 } else if let Some((_, after)) = take_block_comment(rest) {
1199 rest = after;
1200 } else if let Some(after) = rest.strip_prefix('(') {
1201 tokens.push("(");
1202 rest = after;
1203 } else if let Some(after) = rest.strip_prefix(')') {
1204 tokens.push(")");
1205 rest = after;
1206 } else if next < expected.len() && rest.starts_with(expected[next]) {
1207 tokens.push(expected[next]);
1208 rest = &rest[expected[next].len()..];
1209 next += 1;
1210 } else {
1211 break;
1212 }
1213 }
1214 tokens.extend_from_slice(&expected[next..]);
1215 scan_gap(gap, &tokens)
1216 .into_iter()
1217 .filter(|piece| !matches!(piece, GapPiece::Punct("(" | ")")))
1218 .collect()
1219}
1220
1221fn header_contains_record(expr: &Expr) -> bool {
1225 match expr {
1226 Expr::Struct { .. } => true,
1227 Expr::Op { operands, .. } => operands.iter().any(header_contains_record),
1228 Expr::Logical { lhs, rhs, .. } => {
1229 header_contains_record(lhs) || header_contains_record(rhs)
1230 }
1231 Expr::Cast { expr, .. } | Expr::Closure { body: expr, .. } => header_contains_record(expr),
1232 Expr::Member { receiver, .. } => header_contains_record(receiver),
1233 Expr::TupleIndex { base, .. } => header_contains_record(base),
1234 Expr::Apply { callee, .. } => header_contains_record(callee),
1235 Expr::Literal { .. }
1236 | Expr::Ident { .. }
1237 | Expr::Tuple { .. }
1238 | Expr::Array { .. }
1239 | Expr::If { .. } => false,
1240 }
1241}
1242
1243pub fn format_expr_restricted(
1261 expr: &Expr,
1262 source: &str,
1263 depth: usize,
1264 restriction: crate::ExpressionRestriction,
1265) -> String {
1266 render_document(
1267 restricted_doc(expr, &Source::new(source), depth, restriction),
1268 depth,
1269 depth * 4,
1270 100,
1271 0,
1272 )
1273}
1274
1275fn restricted_doc(
1280 expr: &Expr,
1281 source: &Source<'_>,
1282 depth: usize,
1283 restriction: crate::ExpressionRestriction,
1284) -> Doc {
1285 let text = format_at(expr, source, depth, Level::RANGE);
1286 if restriction == crate::ExpressionRestriction::NoStructLiteral && header_contains_record(expr)
1287 {
1288 Doc::text("(").append(text).append(")")
1289 } else {
1290 text
1291 }
1292}
1293
1294pub fn format_expr_header(
1316 expr: &Expr,
1317 source: &str,
1318 depth: usize,
1319 bounds: (proc_macro2::LineColumn, proc_macro2::LineColumn),
1320) -> String {
1321 format_expr_header_with_layout(expr, source, depth, bounds, depth * 4, 100, 0)
1322}
1323
1324pub fn format_expr_header_with_layout(
1343 expr: &Expr,
1344 source: &str,
1345 depth: usize,
1346 bounds: (proc_macro2::LineColumn, proc_macro2::LineColumn),
1347 column: usize,
1348 width: usize,
1349 suffix_columns: usize,
1350) -> String {
1351 let source = &Source::new(source);
1352 let leading = grouping_gap(source, bounds.0, header_start(expr), &[]);
1353 let trailing = grouping_gap(source, header_end(expr), bounds.1, &[]);
1354 let mut text = trivia_doc(emit_gap(&leading, Spacing::None, 0).trim_start().to_owned());
1355 text = text.append(restricted_doc(
1356 expr,
1357 source,
1358 depth,
1359 crate::ExpressionRestriction::NoStructLiteral,
1360 ));
1361 let tail = emit_gap(&trailing, Spacing::None, 0);
1362 if matches!(trailing.last(), Some(GapPiece::Comment(Comment::Line(_)))) {
1363 text = text.append(trivia_doc(tail));
1364 } else {
1365 text = text.append(trivia_doc(tail.trim_end().to_owned()));
1366 }
1367 render_document(text, depth, column, width, suffix_columns)
1368}
1369
1370fn format_at(expr: &Expr, source: &Source<'_>, depth: usize, min_level: Level) -> Doc {
1377 let (text, level) = render(expr, source, depth);
1378 if level < min_level {
1379 Doc::text("(").append(text).append(")").group()
1380 } else {
1381 text
1382 }
1383}
1384
1385pub fn format_expr(expr: &Expr, source: &str, depth: usize) -> String {
1405 format_expr_with_layout(expr, source, depth, depth * 4, 100, 0)
1406}
1407
1408pub fn format_expr_with_layout(
1430 expr: &Expr,
1431 source: &str,
1432 depth: usize,
1433 column: usize,
1434 width: usize,
1435 suffix_columns: usize,
1436) -> String {
1437 render_document(
1438 format_at(expr, &Source::new(source), depth, Level::RANGE),
1439 depth,
1440 column,
1441 width,
1442 suffix_columns,
1443 )
1444}
1445
1446fn render_document(
1451 doc: Doc,
1452 depth: usize,
1453 column: usize,
1454 width: usize,
1455 suffix_columns: usize,
1456) -> String {
1457 let prefix = " ".repeat(column);
1458 let suffix = " ".repeat(suffix_columns);
1459 let doc = if column > depth * 4 + 4 {
1460 Doc::hardline()
1463 .append(" ")
1464 .flat_alt(Doc::nil())
1465 .group()
1466 .append(Doc::column(move |current| {
1467 if current == column {
1468 doc.clone()
1469 } else {
1470 doc.clone().nest(4)
1471 }
1472 }))
1473 } else {
1474 doc
1475 };
1476 let document = Doc::text(prefix)
1477 .append(doc.nest((depth * 4) as isize))
1478 .append(Doc::text(suffix));
1479 let mut out = String::new();
1480 document
1481 .render_fmt(width, &mut out)
1482 .expect("writing to String is infallible");
1483 out.truncate(out.len() - suffix_columns);
1484 out.drain(..column);
1485 out
1486}
1487
1488#[cfg(test)]
1489mod tests {
1490 use super::*;
1491 use crate::{AstContext, OpLookup, Parser};
1492
1493 #[test]
1495 fn wraps_long_expressions_with_leading_operators() {
1496 let source = std::iter::repeat_n("long_operand_name", 8)
1497 .collect::<Vec<_>>()
1498 .join(" + ");
1499 let text = fmt(&source);
1500 assert!(text.contains("\n + "), "{text}");
1501 assert!(text.lines().all(|line| line.len() <= 100), "{text}");
1502 assert_eq!(fmt(&text), text);
1503 }
1504
1505 #[test]
1507 fn wraps_binary_ranges_before_their_operators() {
1508 for operator in ["..", "..="] {
1509 let source = format!("{}{}{}", "a".repeat(45), operator, "b".repeat(45));
1510 let expr = parse(&source);
1511 let text = format_expr_with_layout(&expr, &source, 1, 13, 100, 1);
1512 assert_eq!(
1513 text,
1514 format!("{}\n {operator}{}", "a".repeat(45), "b".repeat(45))
1515 );
1516 assert_eq!(
1517 format_expr_with_layout(&parse(&text), &text, 1, 13, 100, 1),
1518 text
1519 );
1520 assert_eq!(
1521 fmt(&format!("left{operator}right")),
1522 format!("left{operator}right")
1523 );
1524 let call = format!("call({source})");
1525 let formatted = fmt(&call);
1526 assert!(
1527 formatted.lines().all(|line| line.len() <= 100),
1528 "{formatted}"
1529 );
1530 assert_eq!(fmt(&formatted), formatted);
1531 }
1532 }
1533
1534 #[test]
1536 fn empty_closure_can_break_before_its_header() {
1537 let source = "|| predicate";
1538 let expr = parse(source);
1539 let text = format_expr_with_layout(&expr, source, 1, 100, 100, 1);
1540 assert_eq!(text, "\n || predicate");
1541 assert_eq!(
1542 format_expr_with_layout(&parse(&text), &text, 1, 100, 100, 1),
1543 text
1544 );
1545 }
1546
1547 #[test]
1549 fn wraps_nested_calls_records_arrays_and_closures() {
1550 let name = "a_long_argument_name_that_occupies_thirty_columns";
1551 for source in [
1552 format!("outer(inner({name}, {name}), [{name}, {name}])"),
1553 format!("Record {{ first: ({name}, {name}), second: [{name}, {name}] }}"),
1554 format!("map([{name}, {name}], |value: i32| value + {name} + {name})"),
1555 ] {
1556 let text = fmt(&source);
1557 assert!(text.contains('\n'), "{text}");
1558 assert!(text.lines().all(|line| line.len() <= 100), "{text}");
1559 assert_eq!(fmt(&text), text);
1560 }
1561 }
1562
1563 #[test]
1565 fn wraps_closure_parameters_without_losing_comments() {
1566 let source = "|/* first */ a_long_parameter: i32 /* second */, another_long_parameter: i32, third_long_parameter: i32, fourth_long_parameter: i32| a_long_parameter";
1567 let text = fmt(source);
1568 for label in ["first", "second"] {
1569 assert_eq!(text.matches(label).count(), 1, "{text}");
1570 }
1571 assert!(text.lines().all(|line| line.len() <= 100), "{text}");
1572 assert_eq!(fmt(&text), text);
1573 }
1574
1575 #[test]
1577 fn wraps_commented_binary_gaps() {
1578 let operand = "a".repeat(45);
1579 let source = format!("{operand} /* keep */ + {operand} + {operand}");
1580 let text = fmt(&source);
1581 assert!(text.lines().all(|line| line.len() <= 100), "{text}");
1582 assert_eq!(text.matches("keep").count(), 1);
1583 assert_eq!(fmt(&text), text);
1584 }
1585
1586 #[test]
1588 fn consecutive_line_comments_remain_separate() {
1589 let text = fmt("1 + // first\n// second\n2");
1590 assert_eq!(text, "1 + // first\n // second\n 2");
1591 assert_eq!(fmt(&text), text);
1592 }
1593
1594 #[test]
1596 fn brace_opening_line_comments_keep_their_mandatory_breaks() {
1597 for source in [
1598 "Record { // first\n value: 1 }",
1599 "if condition { // first\n 1 } else { // second\n 2 }",
1600 ] {
1601 let text = fmt(source);
1602 assert!(text.contains("// first\n"), "{text}");
1603 assert_eq!(fmt(&text), text);
1604 }
1605 }
1606
1607 #[test]
1609 fn embedding_reserves_exact_prefix_and_suffix_columns() {
1610 let source = "first + second";
1611 let expr = parse(source);
1612 assert_eq!(format_expr_with_layout(&expr, source, 1, 12, 27, 1), source);
1613 assert_eq!(
1614 format_expr_with_layout(&expr, source, 1, 12, 26, 1),
1615 "first\n + second"
1616 );
1617 let bounds = (
1618 proc_macro2::LineColumn { line: 1, column: 0 },
1619 proc_macro2::LineColumn {
1620 line: 1,
1621 column: source.len(),
1622 },
1623 );
1624 assert_eq!(
1625 format_expr_header_with_layout(&expr, source, 1, bounds, 12, 28, 2),
1626 source
1627 );
1628 assert_eq!(
1629 format_expr_header_with_layout(&expr, source, 1, bounds, 12, 27, 2),
1630 "first\n + second"
1631 );
1632 }
1633
1634 #[test]
1636 fn multiline_literal_spelling_is_independent_of_layout_nesting() {
1637 let source = "call(r#\"first\n second\"#, 0xffi32)";
1638 let expr = parse(source);
1639 let text = format_expr_with_layout(&expr, source, 2, 20, 30, 1);
1640 assert!(text.contains("r#\"first\n second\"#"), "{text}");
1641 assert!(text.contains("0xffi32"));
1642 assert_eq!(
1643 format_expr_with_layout(&parse(&text), &text, 2, 20, 30, 1),
1644 text
1645 );
1646 }
1647
1648 #[test]
1650 fn narrow_layout_preserves_expression_semantics_and_reaches_a_fixed_point() {
1651 for source in [
1652 "-(first_long_name + second_long_name) as i32",
1653 "first_long_name - (second_long_name - third_long_name)",
1654 "(|value: i32| value + first_long_name)(second_long_name)",
1655 "(holder.long_member_name)(first_long_name, second_long_name)",
1656 "first_long_name..=second_long_name",
1657 "[first_long_name, second_long_name]: [i32]",
1658 "(first_long_name + second_long_name,)",
1659 "[(first_long_name, second_long_name), (third_long_name, fourth_long_name)]",
1660 "if (Record { value: first_long_name }).value { 0xffi32 } else { 1i32 }",
1661 "first_long_name /* keep\n block */ + second_long_name",
1662 "call(first_long_name, // keep line\n second_long_name)",
1663 "first_long_name + // first\n // second\n second_long_name",
1664 ] {
1665 let expr = parse(source);
1666 let text = format_expr_with_layout(&expr, source, 0, 0, 40, 0);
1667 let parsed = parse(&text);
1668 assert_eq!(
1669 format_expr_with_layout(&parsed, &text, 0, 0, 40, 0),
1670 text,
1671 "{source}"
1672 );
1673 assert_eq!(
1674 format_expr_with_layout(&parsed, &text, 0, 0, 10_000, 0),
1675 format_expr_with_layout(&expr, source, 0, 0, 10_000, 0),
1676 "{source} -> {text}"
1677 );
1678 }
1679 for (source, expected) in [
1680 ("-(10 + 3) as i32", -13),
1681 ("10 - (3 - 1)", 8),
1682 ("(|value: i32| value + 5)(7)", 12),
1683 ("(1 + 2,).0", 3),
1684 ("if true && false { 1 / 0 } else { 42 }", 42),
1685 ] {
1686 let text = format_expr_with_layout(&parse(source), source, 0, 0, 12, 0);
1687 let mut segment = crate::CELParser::new(OpLookup::new())
1688 .parse_str(&text)
1689 .unwrap();
1690 assert_eq!(
1691 segment.call0::<i32>().unwrap(),
1692 expected,
1693 "{source} -> {text}"
1694 );
1695 }
1696 }
1697
1698 #[test]
1700 fn source_index_preserves_character_boundaries() {
1701 let source = Source::new("é\nβ\n");
1702 assert_eq!(
1703 source.byte(proc_macro2::LineColumn { line: 1, column: 1 }),
1704 2
1705 );
1706 assert_eq!(
1707 source.byte(proc_macro2::LineColumn { line: 2, column: 1 }),
1708 5
1709 );
1710 assert_eq!(
1711 source.byte(proc_macro2::LineColumn { line: 3, column: 0 }),
1712 6
1713 );
1714 assert_eq!(
1715 Source::new("").byte(proc_macro2::LineColumn { line: 1, column: 0 }),
1716 0
1717 );
1718 }
1719
1720 fn parse(source: &str) -> Expr {
1722 Parser::<AstContext>::new(OpLookup::new())
1723 .parse_str_ast(source)
1724 .unwrap()
1725 }
1726
1727 fn fmt(source: &str) -> String {
1729 format_expr(&parse(source), source, 0)
1730 }
1731
1732 #[test]
1733 fn additive_and_multiplicative_reprint_without_extra_parens() {
1734 assert_eq!(fmt("1i32 + 2i32 * 3i32"), "1i32 + 2i32 * 3i32");
1735 }
1736
1737 #[test]
1738 fn explicit_grouping_that_changes_precedence_keeps_its_parens() {
1739 assert_eq!(fmt("(1i32 + 2i32) * 3i32"), "(1i32 + 2i32) * 3i32");
1740 }
1741
1742 #[test]
1743 fn left_associative_chain_at_the_same_precedence_has_no_parens() {
1744 assert_eq!(fmt("1i32 - 2i32 - 3i32"), "1i32 - 2i32 - 3i32");
1745 }
1746
1747 #[test]
1748 fn a_right_leaning_tree_at_the_same_precedence_needs_parens() {
1749 fn ident(name: &str) -> Expr {
1754 Expr::Ident {
1755 name: name.to_string(),
1756 span: point(),
1757 }
1758 }
1759 fn point() -> crate::ExprSpan {
1760 crate::ExprSpan {
1761 start: proc_macro2::Span::call_site(),
1762 end: proc_macro2::Span::call_site(),
1763 }
1764 }
1765 let expr = Expr::Op {
1766 name: "-".to_string(),
1767 operands: vec![
1768 ident("a"),
1769 Expr::Op {
1770 name: "-".to_string(),
1771 operands: vec![ident("b"), ident("c")],
1772 span: point(),
1773 },
1774 ],
1775 span: point(),
1776 };
1777 assert_eq!(format_expr(&expr, "", 0), "a - (b - c)");
1778 }
1779
1780 #[test]
1781 fn nested_comparison_needs_parens_on_both_sides() {
1782 fn ident(name: &str) -> Expr {
1786 Expr::Ident {
1787 name: name.to_string(),
1788 span: crate::ExprSpan {
1789 start: proc_macro2::Span::call_site(),
1790 end: proc_macro2::Span::call_site(),
1791 },
1792 }
1793 }
1794 let inner = Expr::Op {
1795 name: "==".to_string(),
1796 operands: vec![ident("a"), ident("b")],
1797 span: crate::ExprSpan {
1798 start: proc_macro2::Span::call_site(),
1799 end: proc_macro2::Span::call_site(),
1800 },
1801 };
1802 let expr = Expr::Op {
1803 name: "==".to_string(),
1804 operands: vec![inner, ident("c")],
1805 span: crate::ExprSpan {
1806 start: proc_macro2::Span::call_site(),
1807 end: proc_macro2::Span::call_site(),
1808 },
1809 };
1810 assert_eq!(format_expr(&expr, "", 0), "(a == b) == c");
1811 }
1812
1813 #[test]
1814 fn literal_notation_is_preserved_exactly() {
1815 assert_eq!(fmt("1920.0"), "1920.0");
1816 assert_eq!(fmt("1920.0f64"), "1920.0f64");
1817 assert_eq!(fmt("1i32"), "1i32");
1818 }
1819
1820 #[test]
1821 fn unary_minus_of_a_binary_expression_needs_parens() {
1822 assert_eq!(fmt("-(1i32 + 2i32)"), "-(1i32 + 2i32)");
1823 }
1824
1825 #[test]
1826 fn double_unary_minus_keeps_a_separating_space() {
1827 assert_eq!(fmt("- -1i32"), "- -1i32");
1828 }
1829
1830 #[test]
1831 fn cast_chain_reprints_without_extra_parens() {
1832 assert_eq!(fmt("x as i32 as f64"), "x as i32 as f64");
1833 }
1834
1835 #[test]
1836 fn unary_minus_before_a_cast_needs_no_parens() {
1837 assert_eq!(fmt("-x as f64"), "-x as f64");
1840 }
1841
1842 #[test]
1843 fn explicit_grouping_before_a_cast_keeps_its_parens() {
1844 assert_eq!(fmt("(a + b) as i32"), "(a + b) as i32");
1847 }
1848
1849 #[test]
1850 fn cast_operand_of_an_additive_expression_needs_no_parens() {
1851 assert_eq!(fmt("a + b as i32"), "a + b as i32");
1854 }
1855
1856 #[test]
1857 fn explicit_grouping_around_a_cast_before_multiplicative_is_redundant_and_dropped() {
1858 assert_eq!(fmt("(x as i32) * y"), "x as i32 * y");
1862 }
1863
1864 #[test]
1865 fn one_tuple_keeps_its_trailing_comma() {
1866 assert_eq!(fmt("(1i32,)"), "(1i32,)");
1867 }
1868
1869 #[test]
1870 fn multi_element_tuple_has_no_trailing_comma() {
1871 assert_eq!(fmt("(1i32, 2i32)"), "(1i32, 2i32)");
1872 }
1873
1874 #[test]
1875 fn arrays_format_with_brackets_and_normalized_commas() {
1876 assert_eq!(fmt("[1i32,2i32]"), "[1i32, 2i32]");
1877 assert_eq!(fmt("[[1i32], [2i32]]"), "[[1i32], [2i32]]");
1878 }
1879
1880 #[test]
1881 fn typed_arrays_format_with_a_type_ascription() {
1882 assert_eq!(fmt("[1i32,2i32]:[i32]"), "[1i32, 2i32]: [i32]");
1883 assert_eq!(fmt("[]:[[i32]]"), "[]: [[i32]]");
1884 }
1885
1886 #[test]
1887 fn format_array_annotation_with_a_generic_builtin_element_type() {
1888 let source = "[]: [RangeInclusive(f64)]";
1889 let expr = parse(source);
1890 let formatted = format_expr(&expr, source, 0);
1891 assert_eq!(formatted, source);
1892 }
1893
1894 #[test]
1895 fn array_comments_remain_in_source_order() {
1896 assert_eq!(
1897 fmt("[1i32 /* first */, /* second */ 2i32]"),
1898 "[1i32 /* first */, /* second */ 2i32]"
1899 );
1900 }
1901
1902 #[test]
1903 fn comments_in_a_call_argument_list_are_preserved() {
1904 assert_eq!(
1905 fmt("f(/* a */ 1i32, /* b */ 2i32)"),
1906 "f(/* a */ 1i32, /* b */ 2i32)"
1907 );
1908 }
1909
1910 #[test]
1911 fn a_comment_before_a_closing_call_paren_is_preserved() {
1912 assert_eq!(fmt("f(1i32 /* end */)"), "f(1i32 /* end */)");
1913 }
1914
1915 #[test]
1916 fn comments_in_a_tuple_are_preserved() {
1917 assert_eq!(fmt("(1i32, /* mid */ 2i32)"), "(1i32, /* mid */ 2i32)");
1918 }
1919
1920 #[test]
1921 fn comment_free_calls_and_tuples_are_unchanged() {
1922 assert_eq!(fmt("f(1i32, 2i32)"), "f(1i32, 2i32)");
1923 assert_eq!(fmt("(1i32, 2i32)"), "(1i32, 2i32)");
1924 assert_eq!(fmt("(1i32,)"), "(1i32,)");
1925 assert_eq!(fmt("(1i32, 2i32).1"), "(1i32, 2i32).1");
1926 }
1927
1928 #[test]
1929 fn if_without_else_omits_the_else_clause() {
1930 assert_eq!(fmt("if true { 1i32 }"), "if true { 1i32 }");
1931 }
1932
1933 #[test]
1934 fn if_else_reprints_both_branches() {
1935 assert_eq!(
1936 fmt("if true { 1i32 } else { 2i32 }"),
1937 "if true { 1i32 } else { 2i32 }"
1938 );
1939 }
1940
1941 #[test]
1942 fn else_if_chain_has_no_braces_around_the_nested_if() {
1943 let source = "if true { 1i32 } else if false { 2i32 } else { 3i32 }";
1944 assert_eq!(fmt(source), source);
1945 }
1946
1947 #[test]
1948 fn a_comment_after_if_condition_is_preserved() {
1949 assert_eq!(fmt("if a /* c */ { 1i32 }"), "if a /* c */ { 1i32 }");
1950 }
1951
1952 #[test]
1953 fn a_comment_before_else_is_preserved() {
1954 assert_eq!(
1955 fmt("if a { 1i32 } /* e */ else { 2i32 }"),
1956 "if a { 1i32 } /* e */ else { 2i32 }"
1957 );
1958 }
1959
1960 #[test]
1961 fn logical_or_and_and_are_not_desugared_and_need_no_extra_parens() {
1962 assert_eq!(fmt("a || b && c"), "a || b && c");
1963 }
1964
1965 #[test]
1966 fn a_comment_around_a_logical_operator_is_preserved() {
1967 assert_eq!(fmt("a /* x */ || b"), "a /* x */ || b");
1968 assert_eq!(fmt("a && /* y */ b"), "a && /* y */ b");
1969 }
1970
1971 #[test]
1972 fn format_is_idempotent_through_a_reparse() {
1973 let source = "(1i32 + 2i32) * 3i32 - -4i32";
1974 let once = fmt(source);
1975 let twice = format_expr(&parse(&once), &once, 0);
1976 assert_eq!(once, twice);
1977 }
1978
1979 #[test]
1980 fn typed_array_annotation_round_trips_through_reparse() {
1981 let source = "[1.0, 42.5]: [f64]";
1982 let once = fmt(source);
1983 let twice = format_expr(&parse(&once), &once, 0);
1984 assert_eq!(once, twice);
1985 }
1986
1987 #[test]
1988 fn closure_with_one_param_reprints_with_its_type() {
1989 assert_eq!(fmt("|x: i32| x + 1i32"), "|x: i32| x + 1i32");
1990 }
1991
1992 #[test]
1993 fn closure_with_no_params_reprints_with_double_pipe() {
1994 assert_eq!(fmt("|| 1i32"), "|| 1i32");
1995 }
1996
1997 #[test]
1998 fn closure_with_multiple_params_joins_them_with_commas() {
1999 assert_eq!(fmt("|x: i32, y: i32| x + y"), "|x: i32, y: i32| x + y");
2000 }
2001
2002 #[test]
2003 fn closure_with_a_tuple_typed_param_reprints_the_tuple_type() {
2004 assert_eq!(fmt("|x: (i32, f64)| x.0"), "|x: (i32, f64)| x.0");
2005 }
2006
2007 #[test]
2008 fn a_comment_before_a_closure_body_is_preserved() {
2009 assert_eq!(
2010 fmt("|x: i32| /* b */ x + 1i32"),
2011 "|x: i32| /* b */ x + 1i32"
2012 );
2013 }
2014
2015 #[test]
2016 fn a_line_comment_before_a_closure_body_wraps_the_expression() {
2017 let source = "|x: i32| // c\n x + 1i32";
2021 let once = fmt(source);
2022 assert!(once.contains("// c"), "comment must survive: {once:?}");
2023 assert!(once.contains("x + 1i32"), "body must survive: {once:?}");
2024 let twice = format_expr(&parse(&once), &once, 0);
2025 assert_eq!(once, twice, "formatting must be idempotent");
2026 }
2027
2028 #[test]
2029 fn range_inclusive_reprints_without_spaces() {
2030 assert_eq!(fmt("1i32..=5i32"), "1i32..=5i32");
2031 }
2032
2033 #[test]
2034 fn range_reprints_without_spaces() {
2035 assert_eq!(fmt("1i32..5i32"), "1i32..5i32");
2036 }
2037
2038 #[test]
2039 fn range_from_reprints_without_spaces() {
2040 assert_eq!(fmt("1i32.."), "1i32..");
2041 }
2042
2043 #[test]
2044 fn range_to_reprints_without_spaces() {
2045 assert_eq!(fmt("..5i32"), "..5i32");
2046 }
2047
2048 #[test]
2049 fn range_to_inclusive_reprints_without_spaces() {
2050 assert_eq!(fmt("..=5i32"), "..=5i32");
2051 }
2052
2053 #[test]
2054 fn range_full_reprints_as_two_dots() {
2055 assert_eq!(fmt(".."), "..");
2056 }
2057
2058 #[test]
2059 fn range_endpoints_that_are_comparisons_need_no_parens() {
2060 assert_eq!(fmt("a == b..c == d"), "a == b..c == d");
2064 }
2065
2066 #[test]
2067 fn range_endpoints_that_are_arithmetic_need_no_parens() {
2068 assert_eq!(fmt("1i32 + 2i32..3i32 * 4i32"), "1i32 + 2i32..3i32 * 4i32");
2071 }
2072
2073 #[test]
2074 fn emit_gap_around_a_plain_operator_uses_single_spaces() {
2075 let pieces = vec![GapPiece::Punct("+")];
2076 assert_eq!(emit_gap(&pieces, Spacing::Around, 0), " + ");
2077 }
2078
2079 #[test]
2080 fn emit_gap_none_spacing_glues_a_range_operator() {
2081 let pieces = vec![GapPiece::Punct("..")];
2082 assert_eq!(emit_gap(&pieces, Spacing::None, 0), "..");
2083 }
2084
2085 #[test]
2086 fn emit_gap_comma_after_puts_one_trailing_space() {
2087 let pieces = vec![GapPiece::Punct(",")];
2088 assert_eq!(emit_gap(&pieces, Spacing::CommaAfter, 0), ", ");
2089 }
2090
2091 #[test]
2092 fn emit_gap_inlines_a_block_comment_before_an_operator() {
2093 let pieces = vec![
2094 GapPiece::Comment(Comment::Block("a".to_string())),
2095 GapPiece::Punct("+"),
2096 ];
2097 assert_eq!(emit_gap(&pieces, Spacing::Around, 0), " /* a */ + ");
2098 }
2099
2100 #[test]
2101 fn emit_gap_inlines_a_block_comment_after_an_operator() {
2102 let pieces = vec![
2103 GapPiece::Punct("+"),
2104 GapPiece::Comment(Comment::Block("b".to_string())),
2105 ];
2106 assert_eq!(emit_gap(&pieces, Spacing::Around, 0), " + /* b */ ");
2107 }
2108
2109 #[test]
2110 fn emit_gap_wraps_after_a_line_comment_to_the_continuation_indent() {
2111 let pieces = vec![
2112 GapPiece::Punct("+"),
2113 GapPiece::Comment(Comment::Line("why".to_string())),
2114 ];
2115 assert_eq!(emit_gap(&pieces, Spacing::Around, 0), " + // why\n ");
2118 }
2119
2120 #[test]
2121 fn emit_gap_comma_after_a_line_comment_keeps_the_full_continuation_indent() {
2122 let pieces = vec![
2126 GapPiece::Comment(Comment::Line("why".to_string())),
2127 GapPiece::Punct(","),
2128 ];
2129 assert_eq!(emit_gap(&pieces, Spacing::CommaAfter, 0), " // why\n , ");
2130 }
2131
2132 #[test]
2133 fn a_block_comment_before_a_binary_operator_is_preserved() {
2134 let source = "1i32 /* a */ + 2i32";
2135 assert_eq!(fmt(source), "1i32 /* a */ + 2i32");
2136 }
2137
2138 #[test]
2139 fn a_block_comment_after_a_binary_operator_is_preserved() {
2140 let source = "1i32 + /* b */ 2i32";
2141 assert_eq!(fmt(source), "1i32 + /* b */ 2i32");
2142 }
2143
2144 #[test]
2145 fn comments_on_both_sides_of_a_binary_operator_are_preserved() {
2146 let source = "1i32 /* a */ + /* b */ 2i32";
2147 assert_eq!(fmt(source), "1i32 /* a */ + /* b */ 2i32");
2148 }
2149
2150 #[test]
2151 fn a_comment_after_unary_minus_is_preserved() {
2152 assert_eq!(fmt("- /* neg */ 1i32"), "- /* neg */ 1i32");
2153 }
2154
2155 #[test]
2156 fn a_comment_around_a_cast_operator_is_preserved() {
2157 assert_eq!(fmt("x /* c */ as i32"), "x /* c */ as i32");
2158 }
2159
2160 #[test]
2161 fn a_comment_inside_a_range_is_preserved() {
2162 assert_eq!(fmt("1i32 /* r */ ..5i32"), "1i32 /* r */ ..5i32");
2163 }
2164
2165 #[test]
2166 fn comment_free_unary_cast_and_range_are_unchanged() {
2167 assert_eq!(fmt("-1i32"), "-1i32");
2168 assert_eq!(fmt("x as i32"), "x as i32");
2169 assert_eq!(fmt("1i32..5i32"), "1i32..5i32");
2170 }
2171
2172 #[test]
2173 fn a_line_comment_after_a_cast_operator_wraps_the_expression() {
2174 let source = "x as // c\n i32";
2178 let once = fmt(source);
2179 assert!(once.contains("// c"), "comment must survive: {once:?}");
2180 assert!(once.contains("i32"), "type name must survive: {once:?}");
2181 let twice = format_expr(&parse(&once), &once, 0);
2182 assert_eq!(once, twice, "formatting must be idempotent");
2183 }
2184
2185 #[test]
2186 fn issue_201_line_comment_inside_an_expression_round_trips() {
2187 let source = "1i32 +\n // why 2\n 2i32";
2189 let once = fmt(source);
2190 assert_eq!(once, "1i32 + // why 2\n 2i32");
2191 assert!(once.contains("// why 2"), "comment must survive: {once:?}");
2192 let twice = format_expr(&parse(&once), &once, 0);
2193 assert_eq!(once, twice, "formatting must be idempotent");
2194 }
2195
2196 #[test]
2197 fn the_full_inline_comment_example_round_trips() {
2198 let source = "/* p */ 1i32 /* q */ + // hello\n2i32 /* r */";
2206 let once = fmt(source);
2207 assert!(
2208 once.contains("/* q */") && once.contains("// hello"),
2209 "the in-scope operand/operator comments survive: {once:?}"
2210 );
2211 let twice = format_expr(&parse(&once), &once, 0);
2212 assert_eq!(once, twice);
2213 }
2214}