diff --git a/compiler/rustc_ast_pretty/src/pp.rs b/compiler/rustc_ast_pretty/src/pp.rs index ad9d15f1ce3..7fba80a98dc 100644 --- a/compiler/rustc_ast_pretty/src/pp.rs +++ b/compiler/rustc_ast_pretty/src/pp.rs @@ -599,7 +599,7 @@ impl Printer { self.break_offset(n, 0) } - crate fn zerobreak(&mut self) { + pub fn zerobreak(&mut self) { self.spaces(0) } diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 38da9a4635d..a88e11b667a 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -9,7 +9,10 @@ use crate::formats::item_type::ItemType; use crate::visit_lib::LibEmbargoVisitor; use rustc_ast as ast; -use rustc_ast::tokenstream::TokenTree; +use rustc_ast::token::{self, BinOpToken, DelimToken}; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_ast_pretty::pprust::state::State as Printer; +use rustc_ast_pretty::pprust::PrintState; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -504,10 +507,44 @@ pub(super) fn render_macro_arms<'a>( /// 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) { - snippet - } else { - rustc_ast_pretty::pprust::tt_to_string(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, _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 @@ -551,6 +588,104 @@ fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option, 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, delim, tts) => { + let open_delim = printer.token_kind_to_string(&token::OpenDelim(*delim)); + printer.word(open_delim); + if !tts.is_empty() { + if *delim == DelimToken::Brace { + printer.space(); + } + print_tts(printer, tts); + if *delim == DelimToken::Brace { + printer.space(); + } + } + let close_delim = printer.token_kind_to_string(&token::CloseDelim(*delim)); + 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.trees() { + 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::BinOp(BinOpToken::Plus | BinOpToken::Star) | token::Question, + ) => (false, Other), + (DollarParen, _) => (false, DollarParenSep), + (DollarParenSep, token::BinOp(BinOpToken::Plus | BinOpToken::Star)) => { + (false, Other) + } + (Pound, token::Not) => (false, PoundBang), + (_, token::Ident(symbol, /* is_raw */ false)) + if !usually_needs_space_between_keyword_and_open_delim(*symbol) => + { + (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, DelimToken::Paren) => (false, DollarParen), + (Pound | PoundBang, DelimToken::Bracket) => (false, Other), + (Ident, DelimToken::Paren | DelimToken::Bracket) => (false, Other), + (_, _) => (true, Other), + }, + }; + if state != Start && needs_space { + printer.space(); + } + print_tt(printer, &tt); + state = next_state; + } +} + +// This rough subset of keywords is listed here to distinguish tokens resembling +// `f(0)` (no space between ident and paren) from tokens resembling `if let (0, +// 0) = x` (space between ident and paren). +fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol) -> bool { + match symbol.as_str() { + "as" | "box" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern" + | "for" | "if" | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" + | "mut" | "ref" | "return" | "static" | "struct" | "trait" | "type" | "unsafe" | "use" + | "where" | "while" | "yield" => true, + _ => false, + } +} + pub(super) fn display_macro_source( cx: &mut DocContext<'_>, name: Symbol, diff --git a/src/test/rustdoc/macro-generated-macro.rs b/src/test/rustdoc/macro-generated-macro.rs index 942d8afaa50..9f155439b7e 100644 --- a/src/test/rustdoc/macro-generated-macro.rs +++ b/src/test/rustdoc/macro-generated-macro.rs @@ -8,14 +8,22 @@ macro_rules! make_macro { } // @has macro_generated_macro/macro.interpolations.html //pre 'macro_rules! interpolations {' -// @has - //pre '(<= type $($i : ident) :: * + $e : expr =>) => { ... };' +// @has - //pre '(<= type $($i:ident)::* + $e:expr =>) => { ... };' make_macro!(interpolations type $($i:ident)::* + $e:expr); interpolations!(<= type foo::bar + x.sort() =>); // @has macro_generated_macro/macro.attributes.html //pre 'macro_rules! attributes {' -// @has - //pre '(<= #! [no_std] #[inline] =>) => { ... };' -make_macro!(attributes #![no_std] #[inline]); +// @has - //pre '(<= #![no_std] #[cfg(feature = "alloc")] =>) => { ... };' +make_macro!(attributes #![no_std] #[cfg(feature = "alloc")]); // @has macro_generated_macro/macro.groups.html //pre 'macro_rules! groups {' -// @has - //pre '(<= fn {} () { foo [0] } =>) => { ... };' +// @has - //pre '(<= fn {} () { foo[0] } =>) => { ... };' make_macro!(groups fn {}() {foo[0]}); + +// @has macro_generated_macro/macro.linebreak.html //pre 'macro_rules! linebreak {' +// @has - //pre ' (' +// @has - //pre ' <= 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25' +// @has - //pre ' 26 27 28 =>' +// @has - //pre ' ) => { ... };' +// @has - //pre '};' +make_macro!(linebreak 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28);