rustdoc/clean/
render_macro_matchers.rs

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