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_parse::lexer::StripTokens;
7use rustc_session::parse::ParseSess;
8use rustc_span::symbol::{Ident, Symbol, kw};
9use rustc_span::{FileName, Span};
10
11pub(super) fn render_macro_matcher(tcx: TyCtxt<'_>, matcher: &TokenTree) -> String {
14 if let Some(snippet) = snippet_equal_to_token(tcx, matcher) {
15 return snippet;
18 }
19
20 let mut printer = Printer::new();
23
24 let cb = printer.cbox(8);
39 printer.word("(");
40 printer.zerobreak();
41 let ib = printer.ibox(0);
42 match matcher {
43 TokenTree::Delimited(_span, _spacing, _delim, tts) => print_tts(&mut printer, tts),
44 TokenTree::Token(..) => print_tt(&mut printer, matcher),
47 }
48 printer.end(ib);
49 printer.break_offset_if_not_bol(0, -4);
50 printer.word(")");
51 printer.end(cb);
52 printer.s.eof()
53}
54
55fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option<String> {
58 let source_map = tcx.sess.source_map();
62 let span = matcher.span();
63 let snippet = source_map.span_to_snippet(span).ok()?;
64
65 let psess = ParseSess::new(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec());
67 let file_name = FileName::macro_expansion_source_code(&snippet);
68 let mut parser = match rustc_parse::new_parser_from_source_str(
69 &psess,
70 file_name,
71 snippet.clone(),
72 StripTokens::Nothing,
73 ) {
74 Ok(parser) => parser,
75 Err(errs) => {
76 errs.into_iter().for_each(|err| err.cancel());
77 return None;
78 }
79 };
80
81 if parser.token == token::Eof {
83 return None;
84 }
85 let reparsed_tree = parser.parse_token_tree();
86 if parser.token != token::Eof {
87 return None;
88 }
89
90 if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None }
92}
93
94fn 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, _spacing, delim, tts) => {
104 let open_delim = printer.token_kind_to_string(&delim.as_open_token_kind());
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(&delim.as_close_token_kind());
116 printer.word(close_delim);
117 }
118 }
119}
120
121fn 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.iter() {
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 (DollarParen, token::Plus | token::Star | token::Question) => (false, Other),
146 (DollarParen, _) => (false, DollarParenSep),
147 (DollarParenSep, token::Plus | token::Star) => (false, Other),
148 (Pound, token::Bang) => (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::new(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}