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::Symbol;
10 /// Render a macro matcher in a format suitable for displaying to the user
11 /// as part of an item declaration.
12 pub(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.
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();
23 // If the inner ibox fits on one line, we get:
25 // macro_rules! macroname {
26 // (the matcher) => {...};
29 // If the inner ibox gets wrapped, the cbox will break and get indented:
31 // macro_rules! macroname {
33 // the matcher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
42 TokenTree::Delimited(_span, _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),
48 printer.break_offset_if_not_bol(0, -4);
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.
56 fn 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()?;
65 let sess = ParseSess::new(FilePathMapping::empty());
66 let file_name = source_map.span_to_filename(span);
68 match rustc_parse::maybe_new_parser_from_source_str(&sess, file_name, snippet.clone()) {
71 for mut diagnostic in diagnostics {
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(mut diagnostic) => {
86 if reparsed_trees.len() != 1 {
89 let reparsed_tree = reparsed_trees.pop().unwrap();
91 // Compare against the original tree.
92 if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None }
95 fn print_tt(printer: &mut Printer<'_>, tt: &TokenTree) {
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 {
104 TokenTree::Delimited(_span, delim, tts) => {
105 let open_delim = printer.token_kind_to_string(&token::OpenDelim(*delim));
106 printer.word(open_delim);
108 if *delim == DelimToken::Brace {
111 print_tts(printer, tts);
112 if *delim == DelimToken::Brace {
116 let close_delim = printer.token_kind_to_string(&token::CloseDelim(*delim));
117 printer.word(close_delim);
122 fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) {
123 #[derive(Copy, Clone, PartialEq)]
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),
148 token::BinOp(BinOpToken::Plus | BinOpToken::Star) | token::Question,
150 (DollarParen, _) => (false, DollarParenSep),
151 (DollarParenSep, token::BinOp(BinOpToken::Plus | BinOpToken::Star)) => {
154 (Pound, token::Not) => (false, PoundBang),
155 (_, token::Ident(symbol, /* is_raw */ false))
156 if !usually_needs_space_between_keyword_and_open_delim(*symbol) =>
160 (_, token::Comma | token::Semi) => (false, Other),
161 (_, token::Dollar) => (true, Dollar),
162 (_, token::Pound) => (true, Pound),
163 (_, _) => (true, Other),
165 TokenTree::Delimited(_, delim, _) => match (state, delim) {
166 (Dollar, DelimToken::Paren) => (false, DollarParen),
167 (Pound | PoundBang, DelimToken::Bracket) => (false, Other),
168 (Ident, DelimToken::Paren | DelimToken::Bracket) => (false, Other),
169 (_, _) => (true, Other),
172 if state != Start && needs_space {
175 print_tt(printer, &tt);
180 // This rough subset of keywords is listed here to distinguish tokens resembling
181 // `f(0)` (no space between ident and paren) from tokens resembling `if let (0,
182 // 0) = x` (space between ident and paren).
183 fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol) -> bool {
184 match symbol.as_str() {
185 "as" | "box" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern"
186 | "for" | "if" | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move"
187 | "mut" | "ref" | "return" | "static" | "struct" | "trait" | "type" | "unsafe" | "use"
188 | "where" | "while" | "yield" => true,