
By replacing them with `{Open,Close}{Param,Brace,Bracket,Invisible}`. PR #137902 made `ast::TokenKind` more like `lexer::TokenKind` by replacing the compound `BinOp{,Eq}(BinOpToken)` variants with fieldless variants `Plus`, `Minus`, `Star`, etc. This commit does a similar thing with delimiters. It also makes `ast::TokenKind` more similar to `parser::TokenType`. This requires a few new methods: - `TokenKind::is_{,open_,close_}delim()` replace various kinds of pattern matches. - `Delimiter::as_{open,close}_token_kind` are used to convert `Delimiter` values to `TokenKind`. Despite these additions, it's a net reduction in lines of code. This is because e.g. `token::OpenParen` is so much shorter than `token::OpenDelim(Delimiter::Parenthesis)` that many multi-line forms reduce to single line forms. And many places where the number of lines doesn't change are still easier to read, just because the names are shorter, e.g.: ``` - } else if self.token != token::CloseDelim(Delimiter::Brace) { + } else if self.token != token::CloseBrace { ```
228 lines
8 KiB
Rust
228 lines
8 KiB
Rust
use rustc_ast::token::{self, Delimiter, IdentIsRaw};
|
|
use rustc_ast::tokenstream::{TokenStream, TokenTree};
|
|
use rustc_ast_pretty::pprust::PrintState;
|
|
use rustc_ast_pretty::pprust::state::State as Printer;
|
|
use rustc_middle::ty::TyCtxt;
|
|
use rustc_session::parse::ParseSess;
|
|
use rustc_span::symbol::{Ident, Symbol, kw};
|
|
use rustc_span::{FileName, Span};
|
|
|
|
/// Render a macro matcher in a format suitable for displaying to the user
|
|
/// as part of an item declaration.
|
|
pub(super) fn render_macro_matcher(tcx: TyCtxt<'_>, matcher: &TokenTree) -> String {
|
|
if let Some(snippet) = snippet_equal_to_token(tcx, matcher) {
|
|
// If the original source code is known, we display the matcher exactly
|
|
// as present in the source code.
|
|
return snippet;
|
|
}
|
|
|
|
// If the matcher is macro-generated or some other reason the source code
|
|
// snippet is not available, we attempt to nicely render the token tree.
|
|
let mut printer = Printer::new();
|
|
|
|
// If the inner ibox fits on one line, we get:
|
|
//
|
|
// macro_rules! macroname {
|
|
// (the matcher) => {...};
|
|
// }
|
|
//
|
|
// If the inner ibox gets wrapped, the cbox will break and get indented:
|
|
//
|
|
// macro_rules! macroname {
|
|
// (
|
|
// the matcher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
|
|
// ) => {...};
|
|
// }
|
|
printer.cbox(8);
|
|
printer.word("(");
|
|
printer.zerobreak();
|
|
printer.ibox(0);
|
|
match matcher {
|
|
TokenTree::Delimited(_span, _spacing, _delim, tts) => print_tts(&mut printer, tts),
|
|
// Matcher which is not a Delimited is unexpected and should've failed
|
|
// to compile, but we render whatever it is wrapped in parens.
|
|
TokenTree::Token(..) => print_tt(&mut printer, matcher),
|
|
}
|
|
printer.end();
|
|
printer.break_offset_if_not_bol(0, -4);
|
|
printer.word(")");
|
|
printer.end();
|
|
printer.s.eof()
|
|
}
|
|
|
|
/// Find the source snippet for this token's Span, reparse it, and return the
|
|
/// snippet if the reparsed TokenTree matches the argument TokenTree.
|
|
fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option<String> {
|
|
// Find what rustc thinks is the source snippet.
|
|
// This may not actually be anything meaningful if this matcher was itself
|
|
// generated by a macro.
|
|
let source_map = tcx.sess.source_map();
|
|
let span = matcher.span();
|
|
let snippet = source_map.span_to_snippet(span).ok()?;
|
|
|
|
// Create a Parser.
|
|
let psess = ParseSess::new(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec());
|
|
let file_name = FileName::macro_expansion_source_code(&snippet);
|
|
let mut parser =
|
|
match rustc_parse::new_parser_from_source_str(&psess, file_name, snippet.clone()) {
|
|
Ok(parser) => parser,
|
|
Err(errs) => {
|
|
errs.into_iter().for_each(|err| err.cancel());
|
|
return None;
|
|
}
|
|
};
|
|
|
|
// Reparse a single token tree.
|
|
if parser.token == token::Eof {
|
|
return None;
|
|
}
|
|
let reparsed_tree = parser.parse_token_tree();
|
|
if parser.token != token::Eof {
|
|
return None;
|
|
}
|
|
|
|
// Compare against the original tree.
|
|
if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None }
|
|
}
|
|
|
|
fn print_tt(printer: &mut Printer<'_>, tt: &TokenTree) {
|
|
match tt {
|
|
TokenTree::Token(token, _) => {
|
|
let token_str = printer.token_to_string(token);
|
|
printer.word(token_str);
|
|
if let token::DocComment(..) = token.kind {
|
|
printer.hardbreak()
|
|
}
|
|
}
|
|
TokenTree::Delimited(_span, _spacing, delim, tts) => {
|
|
let open_delim = printer.token_kind_to_string(&delim.as_open_token_kind());
|
|
printer.word(open_delim);
|
|
if !tts.is_empty() {
|
|
if *delim == Delimiter::Brace {
|
|
printer.space();
|
|
}
|
|
print_tts(printer, tts);
|
|
if *delim == Delimiter::Brace {
|
|
printer.space();
|
|
}
|
|
}
|
|
let close_delim = printer.token_kind_to_string(&delim.as_close_token_kind());
|
|
printer.word(close_delim);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) {
|
|
#[derive(Copy, Clone, PartialEq)]
|
|
enum State {
|
|
Start,
|
|
Dollar,
|
|
DollarIdent,
|
|
DollarIdentColon,
|
|
DollarParen,
|
|
DollarParenSep,
|
|
Pound,
|
|
PoundBang,
|
|
Ident,
|
|
Other,
|
|
}
|
|
|
|
use State::*;
|
|
|
|
let mut state = Start;
|
|
for tt in tts.iter() {
|
|
let (needs_space, next_state) = match &tt {
|
|
TokenTree::Token(tt, _) => match (state, &tt.kind) {
|
|
(Dollar, token::Ident(..)) => (false, DollarIdent),
|
|
(DollarIdent, token::Colon) => (false, DollarIdentColon),
|
|
(DollarIdentColon, token::Ident(..)) => (false, Other),
|
|
(DollarParen, token::Plus | token::Star | token::Question) => (false, Other),
|
|
(DollarParen, _) => (false, DollarParenSep),
|
|
(DollarParenSep, token::Plus | token::Star) => (false, Other),
|
|
(Pound, token::Bang) => (false, PoundBang),
|
|
(_, token::Ident(symbol, IdentIsRaw::No))
|
|
if !usually_needs_space_between_keyword_and_open_delim(*symbol, tt.span) =>
|
|
{
|
|
(true, Ident)
|
|
}
|
|
(_, token::Comma | token::Semi) => (false, Other),
|
|
(_, token::Dollar) => (true, Dollar),
|
|
(_, token::Pound) => (true, Pound),
|
|
(_, _) => (true, Other),
|
|
},
|
|
TokenTree::Delimited(.., delim, _) => match (state, delim) {
|
|
(Dollar, Delimiter::Parenthesis) => (false, DollarParen),
|
|
(Pound | PoundBang, Delimiter::Bracket) => (false, Other),
|
|
(Ident, Delimiter::Parenthesis | Delimiter::Bracket) => (false, Other),
|
|
(_, _) => (true, Other),
|
|
},
|
|
};
|
|
if state != Start && needs_space {
|
|
printer.space();
|
|
}
|
|
print_tt(printer, tt);
|
|
state = next_state;
|
|
}
|
|
}
|
|
|
|
fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol, span: Span) -> bool {
|
|
let ident = Ident { name: symbol, span };
|
|
let is_keyword = ident.is_used_keyword() || ident.is_unused_keyword();
|
|
if !is_keyword {
|
|
// An identifier that is not a keyword usually does not need a space
|
|
// before an open delim. For example: `f(0)` or `f[0]`.
|
|
return false;
|
|
}
|
|
|
|
match symbol {
|
|
// No space after keywords that are syntactically an expression. For
|
|
// example: a tuple struct created with `let _ = Self(0, 0)`, or if
|
|
// someone has `impl Index<MyStruct> for bool` then `true[MyStruct]`.
|
|
kw::False | kw::SelfLower | kw::SelfUpper | kw::True => false,
|
|
|
|
// No space, as in `let _: fn();`
|
|
kw::Fn => false,
|
|
|
|
// No space, as in `pub(crate) type T;`
|
|
kw::Pub => false,
|
|
|
|
// No space for keywords that can end an expression, as in `fut.await()`
|
|
// where fut's Output type is `fn()`.
|
|
kw::Await => false,
|
|
|
|
// Otherwise space after keyword. Some examples:
|
|
//
|
|
// `expr as [T; 2]`
|
|
// ^
|
|
// `box (tuple,)`
|
|
// ^
|
|
// `break (tuple,)`
|
|
// ^
|
|
// `type T = dyn (Fn() -> dyn Trait) + Send;`
|
|
// ^
|
|
// `for (tuple,) in iter {}`
|
|
// ^
|
|
// `if (tuple,) == v {}`
|
|
// ^
|
|
// `impl [T] {}`
|
|
// ^
|
|
// `for x in [..] {}`
|
|
// ^
|
|
// `let () = unit;`
|
|
// ^
|
|
// `match [x, y] {...}`
|
|
// ^
|
|
// `&mut (x as T)`
|
|
// ^
|
|
// `return [];`
|
|
// ^
|
|
// `fn f<T>() where (): Into<T>`
|
|
// ^
|
|
// `while (a + b).what() {}`
|
|
// ^
|
|
// `yield [];`
|
|
// ^
|
|
_ => true,
|
|
}
|
|
}
|