1 use rustc_ast::token::{self, BinOpToken, Delimiter};
2 use rustc_ast::tokenstream::{TokenStream, TokenTree};
3 use rustc_ast_pretty::pprust::state::State as Printer;
4 use rustc_ast_pretty::pprust::PrintState;
5 use rustc_middle::ty::TyCtxt;
6 use rustc_session::parse::ParseSess;
7 use rustc_span::source_map::FilePathMapping;
8 use rustc_span::symbol::{kw, Ident, Symbol};
9 use rustc_span::Span;
10
11 /// Render a macro matcher in a format suitable for displaying to the user
12 /// as part of an item declaration.
render_macro_matcher(tcx: TyCtxt<'_>, matcher: &TokenTree) -> String13 pub(super) fn render_macro_matcher(tcx: TyCtxt<'_>, matcher: &TokenTree) -> String {
14 if let Some(snippet) = snippet_equal_to_token(tcx, matcher) {
15 // If the original source code is known, we display the matcher exactly
16 // as present in the source code.
17 return snippet;
18 }
19
20 // If the matcher is macro-generated or some other reason the source code
21 // snippet is not available, we attempt to nicely render the token tree.
22 let mut printer = Printer::new();
23
24 // If the inner ibox fits on one line, we get:
25 //
26 // macro_rules! macroname {
27 // (the matcher) => {...};
28 // }
29 //
30 // If the inner ibox gets wrapped, the cbox will break and get indented:
31 //
32 // macro_rules! macroname {
33 // (
34 // the matcher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
36 // ) => {...};
37 // }
38 printer.cbox(8);
39 printer.word("(");
40 printer.zerobreak();
41 printer.ibox(0);
42 match matcher {
43 TokenTree::Delimited(_span, _delim, tts) => print_tts(&mut printer, tts),
44 // Matcher which is not a Delimited is unexpected and should've failed
45 // to compile, but we render whatever it is wrapped in parens.
46 TokenTree::Token(..) => print_tt(&mut printer, matcher),
47 }
48 printer.end();
49 printer.break_offset_if_not_bol(0, -4);
50 printer.word(")");
51 printer.end();
52 printer.s.eof()
53 }
54
55 /// Find the source snippet for this token's Span, reparse it, and return the
56 /// snippet if the reparsed TokenTree matches the argument TokenTree.
snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option<String>57 fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option<String> {
58 // Find what rustc thinks is the source snippet.
59 // This may not actually be anything meaningful if this matcher was itself
60 // generated by a macro.
61 let source_map = tcx.sess.source_map();
62 let span = matcher.span();
63 let snippet = source_map.span_to_snippet(span).ok()?;
64
65 // Create a Parser.
66 let sess =
67 ParseSess::new(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), FilePathMapping::empty());
68 let file_name = source_map.span_to_filename(span);
69 let mut parser =
70 match rustc_parse::maybe_new_parser_from_source_str(&sess, file_name, snippet.clone()) {
71 Ok(parser) => parser,
72 Err(diagnostics) => {
73 drop(diagnostics);
74 return None;
75 }
76 };
77
78 // Reparse a single token tree.
79 let mut reparsed_trees = match parser.parse_all_token_trees() {
80 Ok(reparsed_trees) => reparsed_trees,
81 Err(diagnostic) => {
82 diagnostic.cancel();
83 return None;
84 }
85 };
86 if reparsed_trees.len() != 1 {
87 return None;
88 }
89 let reparsed_tree = reparsed_trees.pop().unwrap();
90
91 // Compare against the original tree.
92 if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None }
93 }
94
print_tt(printer: &mut Printer<'_>, tt: &TokenTree)95 fn print_tt(printer: &mut Printer<'_>, tt: &TokenTree) {
96 match tt {
97 TokenTree::Token(token, _) => {
98 let token_str = printer.token_to_string(token);
99 printer.word(token_str);
100 if let token::DocComment(..) = token.kind {
101 printer.hardbreak()
102 }
103 }
104 TokenTree::Delimited(_span, delim, tts) => {
105 let open_delim = printer.token_kind_to_string(&token::OpenDelim(*delim));
106 printer.word(open_delim);
107 if !tts.is_empty() {
108 if *delim == Delimiter::Brace {
109 printer.space();
110 }
111 print_tts(printer, tts);
112 if *delim == Delimiter::Brace {
113 printer.space();
114 }
115 }
116 let close_delim = printer.token_kind_to_string(&token::CloseDelim(*delim));
117 printer.word(close_delim);
118 }
119 }
120 }
121
print_tts(printer: &mut Printer<'_>, tts: &TokenStream)122 fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) {
123 #[derive(Copy, Clone, PartialEq)]
124 enum State {
125 Start,
126 Dollar,
127 DollarIdent,
128 DollarIdentColon,
129 DollarParen,
130 DollarParenSep,
131 Pound,
132 PoundBang,
133 Ident,
134 Other,
135 }
136
137 use State::*;
138
139 let mut state = Start;
140 for tt in tts.trees() {
141 let (needs_space, next_state) = match &tt {
142 TokenTree::Token(tt, _) => match (state, &tt.kind) {
143 (Dollar, token::Ident(..)) => (false, DollarIdent),
144 (DollarIdent, token::Colon) => (false, DollarIdentColon),
145 (DollarIdentColon, token::Ident(..)) => (false, Other),
146 (
147 DollarParen,
148 token::BinOp(BinOpToken::Plus | BinOpToken::Star) | token::Question,
149 ) => (false, Other),
150 (DollarParen, _) => (false, DollarParenSep),
151 (DollarParenSep, token::BinOp(BinOpToken::Plus | BinOpToken::Star)) => {
152 (false, Other)
153 }
154 (Pound, token::Not) => (false, PoundBang),
155 (_, token::Ident(symbol, /* is_raw */ false))
156 if !usually_needs_space_between_keyword_and_open_delim(*symbol, tt.span) =>
157 {
158 (true, Ident)
159 }
160 (_, token::Comma | token::Semi) => (false, Other),
161 (_, token::Dollar) => (true, Dollar),
162 (_, token::Pound) => (true, Pound),
163 (_, _) => (true, Other),
164 },
165 TokenTree::Delimited(_, delim, _) => match (state, delim) {
166 (Dollar, Delimiter::Parenthesis) => (false, DollarParen),
167 (Pound | PoundBang, Delimiter::Bracket) => (false, Other),
168 (Ident, Delimiter::Parenthesis | Delimiter::Bracket) => (false, Other),
169 (_, _) => (true, Other),
170 },
171 };
172 if state != Start && needs_space {
173 printer.space();
174 }
175 print_tt(printer, tt);
176 state = next_state;
177 }
178 }
179
usually_needs_space_between_keyword_and_open_delim(symbol: Symbol, span: Span) -> bool180 fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol, span: Span) -> bool {
181 let ident = Ident { name: symbol, span };
182 let is_keyword = ident.is_used_keyword() || ident.is_unused_keyword();
183 if !is_keyword {
184 // An identifier that is not a keyword usually does not need a space
185 // before an open delim. For example: `f(0)` or `f[0]`.
186 return false;
187 }
188
189 match symbol {
190 // No space after keywords that are syntactically an expression. For
191 // example: a tuple struct created with `let _ = Self(0, 0)`, or if
192 // someone has `impl Index<MyStruct> for bool` then `true[MyStruct]`.
193 kw::False | kw::SelfLower | kw::SelfUpper | kw::True => false,
194
195 // No space, as in `let _: fn();`
196 kw::Fn => false,
197
198 // No space, as in `pub(crate) type T;`
199 kw::Pub => false,
200
201 // No space for keywords that can end an expression, as in `fut.await()`
202 // where fut's Output type is `fn()`.
203 kw::Await => false,
204
205 // Otherwise space after keyword. Some examples:
206 //
207 // `expr as [T; 2]`
208 // ^
209 // `box (tuple,)`
210 // ^
211 // `break (tuple,)`
212 // ^
213 // `type T = dyn (Fn() -> dyn Trait) + Send;`
214 // ^
215 // `for (tuple,) in iter {}`
216 // ^
217 // `if (tuple,) == v {}`
218 // ^
219 // `impl [T] {}`
220 // ^
221 // `for x in [..] {}`
222 // ^
223 // `let () = unit;`
224 // ^
225 // `match [x, y] {...}`
226 // ^
227 // `&mut (x as T)`
228 // ^
229 // `return [];`
230 // ^
231 // `fn f<T>() where (): Into<T>`
232 // ^
233 // `while (a + b).what() {}`
234 // ^
235 // `yield [];`
236 // ^
237 _ => true,
238 }
239 }
240