1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright 2021-2025 Martin Pool
//! Convert a token stream back to (reasonably) pretty Rust code in a string.
use proc_macro2::{Delimiter, TokenTree};
use quote::ToTokens;
/// Convert something to a pretty-printed string.
pub(crate) trait ToPrettyString {
fn to_pretty_string(&self) -> String;
}
/// Convert a `TokenStream` representing some code to a reasonably formatted
/// string of Rust code.
///
/// `TokenStream` has a `to_string`, but it adds spaces in places that don't
/// look idiomatic, so this reimplements it in a way that looks better.
///
/// This is probably not correctly formatted for all Rust syntax, and only tries
/// to cover cases that can emerge from the code we generate.
impl<T> ToPrettyString for T
where
T: ToTokens,
{
fn to_pretty_string(&self) -> String {
use TokenTree::{Group, Ident, Literal, Punct};
let mut b = String::with_capacity(200);
let mut ts = self.to_token_stream().into_iter().peekable();
while let Some(tt) = ts.next() {
match tt {
Punct(p) => {
let pc = p.as_char();
if pc == '|' && !b.is_empty() && !b.ends_with(' ') && !b.ends_with('|') {
// Generally have spaces around '|' in match arms (or bitwise expressions)
b.push(' ');
}
b.push(pc);
if ts.peek().is_some() && (b.ends_with("->") || pc == ',' || pc == ';') {
b.push(' ');
}
}
Ident(_) | Literal(_) => {
if b.ends_with('=') || b.ends_with("=>") || b.ends_with('|') {
b.push(' ');
}
match tt {
Literal(l) => b.push_str(&l.to_string()),
Ident(i) => b.push_str(&i.to_string()),
_ => unreachable!(),
}
if let Some(next) = ts.peek() {
match next {
Ident(_) | Literal(_) => b.push(' '),
Punct(p) => match p.as_char() {
',' | ';' | '<' | '>' | ':' | '.' | '!' => (),
_ => b.push(' '),
},
Group(_) => (),
}
}
}
Group(g) => {
match g.delimiter() {
Delimiter::Brace => b.push('{'),
Delimiter::Bracket => b.push('['),
Delimiter::Parenthesis => b.push('('),
Delimiter::None => (),
}
b += &g.stream().to_pretty_string();
match g.delimiter() {
Delimiter::Brace => b.push('}'),
Delimiter::Bracket => b.push(']'),
Delimiter::Parenthesis => b.push(')'),
Delimiter::None => (),
}
}
}
}
debug_assert!(
!b.ends_with(' '),
"generated a trailing space: ts={ts:?}, b={b:?}",
ts = self.to_token_stream(),
);
b
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use quote::quote;
use super::ToPrettyString;
#[test]
fn pretty_format_examples() {
assert_eq!(
quote! {
// Nonsense rust but a big salad of syntax
<impl Iterator for MergeTrees < AE , BE , AIT , BIT > > :: next
-> Option < Self :: Item >
}
.to_pretty_string(),
"<impl Iterator for MergeTrees<AE, BE, AIT, BIT>>::next -> Option<Self::Item>"
);
assert_eq!(
quote! { Lex < 'buf >::take }.to_pretty_string(),
"Lex<'buf>::take"
);
}
#[test]
fn format_trait_with_assoc_type() {
assert_eq!(
quote! { impl Iterator < Item = String > }.to_pretty_string(),
"impl Iterator<Item = String>"
);
}
#[test]
fn pretty_match_guard() {
assert_eq!(
quote! { "warning"
| "error" }
.to_pretty_string(),
r#""warning" | "error""#,
);
}
#[test]
fn preserve_boolean_or() {
assert_eq!(quote! { fair || foul }.to_pretty_string(), "fair || foul");
}
#[test]
fn format_thick_arrow() {
assert_eq!(quote! { a => b }.to_pretty_string(), "a => b");
}
}