1use rustc_ast::token::{self, BinOpToken, 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::Span;
8use rustc_span::symbol::{Ident, Symbol, kw};
9
10pub(super) fn render_macro_matcher(tcx: TyCtxt<'_>, matcher: &TokenTree) -> String {
13 if let Some(snippet) = snippet_equal_to_token(tcx, matcher) {
14 return snippet;
17 }
18
19 let mut printer = Printer::new();
22
23 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 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
54fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option<String> {
57 let source_map = tcx.sess.source_map();
61 let span = matcher.span();
62 let snippet = source_map.span_to_snippet(span).ok()?;
63
64 let psess = ParseSess::new(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec());
66 let file_name = source_map.span_to_filename(span);
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 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 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 (
141 DollarParen,
142 token::BinOp(BinOpToken::Plus | BinOpToken::Star) | token::Question,
143 ) => (false, Other),
144 (DollarParen, _) => (false, DollarParenSep),
145 (DollarParenSep, token::BinOp(BinOpToken::Plus | BinOpToken::Star)) => {
146 (false, Other)
147 }
148 (Pound, token::Not) => (false, PoundBang),
149 (_, token::Ident(symbol, IdentIsRaw::No))
150 if !usually_needs_space_between_keyword_and_open_delim(*symbol, tt.span) =>
151 {
152 (true, Ident)
153 }
154 (_, token::Comma | token::Semi) => (false, Other),
155 (_, token::Dollar) => (true, Dollar),
156 (_, token::Pound) => (true, Pound),
157 (_, _) => (true, Other),
158 },
159 TokenTree::Delimited(.., delim, _) => match (state, delim) {
160 (Dollar, Delimiter::Parenthesis) => (false, DollarParen),
161 (Pound | PoundBang, Delimiter::Bracket) => (false, Other),
162 (Ident, Delimiter::Parenthesis | Delimiter::Bracket) => (false, Other),
163 (_, _) => (true, Other),
164 },
165 };
166 if state != Start && needs_space {
167 printer.space();
168 }
169 print_tt(printer, tt);
170 state = next_state;
171 }
172}
173
174fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol, span: Span) -> bool {
175 let ident = Ident { name: symbol, span };
176 let is_keyword = ident.is_used_keyword() || ident.is_unused_keyword();
177 if !is_keyword {
178 return false;
181 }
182
183 match symbol {
184 kw::False | kw::SelfLower | kw::SelfUpper | kw::True => false,
188
189 kw::Fn => false,
191
192 kw::Pub => false,
194
195 kw::Await => false,
198
199 _ => true,
232 }
233}