]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/clean/render_macro_matchers.rs
Rollup merge of #103709 - cuviper:netbsd-9, r=pietroalbini
[rust.git] / src / librustdoc / clean / render_macro_matchers.rs
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.
13 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.
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 = ParseSess::new(FilePathMapping::empty());
67     let file_name = source_map.span_to_filename(span);
68     let mut parser =
69         match rustc_parse::maybe_new_parser_from_source_str(&sess, file_name, snippet.clone()) {
70             Ok(parser) => parser,
71             Err(diagnostics) => {
72                 drop(diagnostics);
73                 return None;
74             }
75         };
76
77     // Reparse a single token tree.
78     let mut reparsed_trees = match parser.parse_all_token_trees() {
79         Ok(reparsed_trees) => reparsed_trees,
80         Err(diagnostic) => {
81             diagnostic.cancel();
82             return None;
83         }
84     };
85     if reparsed_trees.len() != 1 {
86         return None;
87     }
88     let reparsed_tree = reparsed_trees.pop().unwrap();
89
90     // Compare against the original tree.
91     if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None }
92 }
93
94 fn print_tt(printer: &mut Printer<'_>, tt: &TokenTree) {
95     match tt {
96         TokenTree::Token(token, _) => {
97             let token_str = printer.token_to_string(token);
98             printer.word(token_str);
99             if let token::DocComment(..) = token.kind {
100                 printer.hardbreak()
101             }
102         }
103         TokenTree::Delimited(_span, delim, tts) => {
104             let open_delim = printer.token_kind_to_string(&token::OpenDelim(*delim));
105             printer.word(open_delim);
106             if !tts.is_empty() {
107                 if *delim == Delimiter::Brace {
108                     printer.space();
109                 }
110                 print_tts(printer, tts);
111                 if *delim == Delimiter::Brace {
112                     printer.space();
113                 }
114             }
115             let close_delim = printer.token_kind_to_string(&token::CloseDelim(*delim));
116             printer.word(close_delim);
117         }
118     }
119 }
120
121 fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) {
122     #[derive(Copy, Clone, PartialEq)]
123     enum State {
124         Start,
125         Dollar,
126         DollarIdent,
127         DollarIdentColon,
128         DollarParen,
129         DollarParenSep,
130         Pound,
131         PoundBang,
132         Ident,
133         Other,
134     }
135
136     use State::*;
137
138     let mut state = Start;
139     for tt in tts.trees() {
140         let (needs_space, next_state) = match &tt {
141             TokenTree::Token(tt, _) => match (state, &tt.kind) {
142                 (Dollar, token::Ident(..)) => (false, DollarIdent),
143                 (DollarIdent, token::Colon) => (false, DollarIdentColon),
144                 (DollarIdentColon, token::Ident(..)) => (false, Other),
145                 (
146                     DollarParen,
147                     token::BinOp(BinOpToken::Plus | BinOpToken::Star) | token::Question,
148                 ) => (false, Other),
149                 (DollarParen, _) => (false, DollarParenSep),
150                 (DollarParenSep, token::BinOp(BinOpToken::Plus | BinOpToken::Star)) => {
151                     (false, Other)
152                 }
153                 (Pound, token::Not) => (false, PoundBang),
154                 (_, token::Ident(symbol, /* is_raw */ false))
155                     if !usually_needs_space_between_keyword_and_open_delim(*symbol, tt.span) =>
156                 {
157                     (true, Ident)
158                 }
159                 (_, token::Comma | token::Semi) => (false, Other),
160                 (_, token::Dollar) => (true, Dollar),
161                 (_, token::Pound) => (true, Pound),
162                 (_, _) => (true, Other),
163             },
164             TokenTree::Delimited(_, delim, _) => match (state, delim) {
165                 (Dollar, Delimiter::Parenthesis) => (false, DollarParen),
166                 (Pound | PoundBang, Delimiter::Bracket) => (false, Other),
167                 (Ident, Delimiter::Parenthesis | Delimiter::Bracket) => (false, Other),
168                 (_, _) => (true, Other),
169             },
170         };
171         if state != Start && needs_space {
172             printer.space();
173         }
174         print_tt(printer, tt);
175         state = next_state;
176     }
177 }
178
179 fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol, span: Span) -> bool {
180     let ident = Ident { name: symbol, span };
181     let is_keyword = ident.is_used_keyword() || ident.is_unused_keyword();
182     if !is_keyword {
183         // An identifier that is not a keyword usually does not need a space
184         // before an open delim. For example: `f(0)` or `f[0]`.
185         return false;
186     }
187
188     match symbol {
189         // No space after keywords that are syntactically an expression. For
190         // example: a tuple struct created with `let _ = Self(0, 0)`, or if
191         // someone has `impl Index<MyStruct> for bool` then `true[MyStruct]`.
192         kw::False | kw::SelfLower | kw::SelfUpper | kw::True => false,
193
194         // No space, as in `let _: fn();`
195         kw::Fn => false,
196
197         // No space, as in `pub(crate) type T;`
198         kw::Pub => false,
199
200         // No space for keywords that can end an expression, as in `fut.await()`
201         // where fut's Output type is `fn()`.
202         kw::Await => false,
203
204         // Otherwise space after keyword. Some examples:
205         //
206         // `expr as [T; 2]`
207         //         ^
208         // `box (tuple,)`
209         //     ^
210         // `break (tuple,)`
211         //       ^
212         // `type T = dyn (Fn() -> dyn Trait) + Send;`
213         //              ^
214         // `for (tuple,) in iter {}`
215         //     ^
216         // `if (tuple,) == v {}`
217         //    ^
218         // `impl [T] {}`
219         //      ^
220         // `for x in [..] {}`
221         //          ^
222         // `let () = unit;`
223         //     ^
224         // `match [x, y] {...}`
225         //       ^
226         // `&mut (x as T)`
227         //      ^
228         // `return [];`
229         //        ^
230         // `fn f<T>() where (): Into<T>`
231         //                 ^
232         // `while (a + b).what() {}`
233         //       ^
234         // `yield [];`
235         //       ^
236         _ => true,
237     }
238 }