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