Skip to content

Commit 81b1e32

Browse files
committed
Move render_macro_matcher to own module
1 parent 0904614 commit 81b1e32

File tree

3 files changed

+194
-189
lines changed

3 files changed

+194
-189
lines changed

src/librustdoc/clean/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod auto_trait;
55
mod blanket_impl;
66
crate mod cfg;
77
crate mod inline;
8+
mod render_macro_matchers;
89
mod simplify;
910
crate mod types;
1011
crate mod utils;
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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;
9+
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.
16+
return snippet;
17+
}
18+
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();
22+
23+
// If the inner ibox fits on one line, we get:
24+
//
25+
// macro_rules! macroname {
26+
// (the matcher) => {...};
27+
// }
28+
//
29+
// If the inner ibox gets wrapped, the cbox will break and get indented:
30+
//
31+
// macro_rules! macroname {
32+
// (
33+
// the matcher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
35+
// ) => {...};
36+
// }
37+
printer.cbox(8);
38+
printer.word("(");
39+
printer.zerobreak();
40+
printer.ibox(0);
41+
match matcher {
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),
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+
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()?;
63+
64+
// Create a Parser.
65+
let sess = ParseSess::new(FilePathMapping::empty());
66+
let file_name = source_map.span_to_filename(span);
67+
let mut parser =
68+
match rustc_parse::maybe_new_parser_from_source_str(&sess, file_name, snippet.clone()) {
69+
Ok(parser) => parser,
70+
Err(diagnostics) => {
71+
for mut diagnostic in diagnostics {
72+
diagnostic.cancel();
73+
}
74+
return None;
75+
}
76+
};
77+
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) => {
82+
diagnostic.cancel();
83+
return None;
84+
}
85+
};
86+
if reparsed_trees.len() != 1 {
87+
return None;
88+
}
89+
let reparsed_tree = reparsed_trees.pop().unwrap();
90+
91+
// Compare against the original tree.
92+
if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None }
93+
}
94+
95+
fn print_tt(printer: &mut Printer<'_>, tt: &TokenTree) {
96+
match tt {
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 {
101+
printer.hardbreak()
102+
}
103+
}
104+
TokenTree::Delimited(_span, delim, tts) => {
105+
let open_delim = printer.token_kind_to_string(&token::OpenDelim(*delim));
106+
printer.word(open_delim);
107+
if !tts.is_empty() {
108+
if *delim == DelimToken::Brace {
109+
printer.space();
110+
}
111+
print_tts(printer, tts);
112+
if *delim == DelimToken::Brace {
113+
printer.space();
114+
}
115+
}
116+
let close_delim = printer.token_kind_to_string(&token::CloseDelim(*delim));
117+
printer.word(close_delim);
118+
}
119+
}
120+
}
121+
122+
fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) {
123+
#[derive(Copy, Clone, PartialEq)]
124+
enum State {
125+
Start,
126+
Dollar,
127+
DollarIdent,
128+
DollarIdentColon,
129+
DollarParen,
130+
DollarParenSep,
131+
Pound,
132+
PoundBang,
133+
Ident,
134+
Other,
135+
}
136+
137+
use State::*;
138+
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),
146+
(
147+
DollarParen,
148+
token::BinOp(BinOpToken::Plus | BinOpToken::Star) | token::Question,
149+
) => (false, Other),
150+
(DollarParen, _) => (false, DollarParenSep),
151+
(DollarParenSep, token::BinOp(BinOpToken::Plus | BinOpToken::Star)) => {
152+
(false, Other)
153+
}
154+
(Pound, token::Not) => (false, PoundBang),
155+
(_, token::Ident(symbol, /* is_raw */ false))
156+
if !usually_needs_space_between_keyword_and_open_delim(*symbol) =>
157+
{
158+
(true, Ident)
159+
}
160+
(_, token::Comma | token::Semi) => (false, Other),
161+
(_, token::Dollar) => (true, Dollar),
162+
(_, token::Pound) => (true, Pound),
163+
(_, _) => (true, Other),
164+
},
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),
170+
},
171+
};
172+
if state != Start && needs_space {
173+
printer.space();
174+
}
175+
print_tt(printer, &tt);
176+
state = next_state;
177+
}
178+
}
179+
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,
189+
_ => false,
190+
}
191+
}

0 commit comments

Comments
 (0)