Skip to content

Commit 0904614

Browse files
committed
Render more readable macro matchers in rustdoc
1 parent d950c1b commit 0904614

File tree

3 files changed

+152
-9
lines changed

3 files changed

+152
-9
lines changed

compiler/rustc_ast_pretty/src/pp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ impl Printer {
599599
self.break_offset(n, 0)
600600
}
601601

602-
crate fn zerobreak(&mut self) {
602+
pub fn zerobreak(&mut self) {
603603
self.spaces(0)
604604
}
605605

src/librustdoc/clean/utils.rs

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use crate::formats::item_type::ItemType;
99
use crate::visit_lib::LibEmbargoVisitor;
1010

1111
use rustc_ast as ast;
12-
use rustc_ast::tokenstream::TokenTree;
12+
use rustc_ast::token::{self, BinOpToken, DelimToken};
13+
use rustc_ast::tokenstream::{TokenStream, TokenTree};
14+
use rustc_ast_pretty::pprust::state::State as Printer;
15+
use rustc_ast_pretty::pprust::PrintState;
1316
use rustc_data_structures::thin_vec::ThinVec;
1417
use rustc_hir as hir;
1518
use rustc_hir::def::{DefKind, Res};
@@ -504,10 +507,44 @@ pub(super) fn render_macro_arms<'a>(
504507
/// as part of an item declaration.
505508
pub(super) fn render_macro_matcher(tcx: TyCtxt<'_>, matcher: &TokenTree) -> String {
506509
if let Some(snippet) = snippet_equal_to_token(tcx, matcher) {
507-
snippet
508-
} else {
509-
rustc_ast_pretty::pprust::tt_to_string(matcher)
510+
// If the original source code is known, we display the matcher exactly
511+
// as present in the source code.
512+
return snippet;
513+
}
514+
515+
// If the matcher is macro-generated or some other reason the source code
516+
// snippet is not available, we attempt to nicely render the token tree.
517+
let mut printer = Printer::new();
518+
519+
// If the inner ibox fits on one line, we get:
520+
//
521+
// macro_rules! macroname {
522+
// (the matcher) => {...};
523+
// }
524+
//
525+
// If the inner ibox gets wrapped, the cbox will break and get indented:
526+
//
527+
// macro_rules! macroname {
528+
// (
529+
// the matcher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
530+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!
531+
// ) => {...};
532+
// }
533+
printer.cbox(8);
534+
printer.word("(");
535+
printer.zerobreak();
536+
printer.ibox(0);
537+
match matcher {
538+
TokenTree::Delimited(_span, _delim, tts) => print_tts(&mut printer, tts),
539+
// Matcher which is not a Delimited is unexpected and should've failed
540+
// to compile, but we render whatever it is wrapped in parens.
541+
TokenTree::Token(_) => print_tt(&mut printer, matcher),
510542
}
543+
printer.end();
544+
printer.break_offset_if_not_bol(0, -4);
545+
printer.word(")");
546+
printer.end();
547+
printer.s.eof()
511548
}
512549

513550
/// 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<String
551588
if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None }
552589
}
553590

591+
fn print_tt(printer: &mut Printer<'_>, tt: &TokenTree) {
592+
match tt {
593+
TokenTree::Token(token) => {
594+
let token_str = printer.token_to_string(token);
595+
printer.word(token_str);
596+
if let token::DocComment(..) = token.kind {
597+
printer.hardbreak()
598+
}
599+
}
600+
TokenTree::Delimited(_span, delim, tts) => {
601+
let open_delim = printer.token_kind_to_string(&token::OpenDelim(*delim));
602+
printer.word(open_delim);
603+
if !tts.is_empty() {
604+
if *delim == DelimToken::Brace {
605+
printer.space();
606+
}
607+
print_tts(printer, tts);
608+
if *delim == DelimToken::Brace {
609+
printer.space();
610+
}
611+
}
612+
let close_delim = printer.token_kind_to_string(&token::CloseDelim(*delim));
613+
printer.word(close_delim);
614+
}
615+
}
616+
}
617+
618+
fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) {
619+
#[derive(Copy, Clone, PartialEq)]
620+
enum State {
621+
Start,
622+
Dollar,
623+
DollarIdent,
624+
DollarIdentColon,
625+
DollarParen,
626+
DollarParenSep,
627+
Pound,
628+
PoundBang,
629+
Ident,
630+
Other,
631+
}
632+
633+
use State::*;
634+
635+
let mut state = Start;
636+
for tt in tts.trees() {
637+
let (needs_space, next_state) = match &tt {
638+
TokenTree::Token(tt) => match (state, &tt.kind) {
639+
(Dollar, token::Ident(..)) => (false, DollarIdent),
640+
(DollarIdent, token::Colon) => (false, DollarIdentColon),
641+
(DollarIdentColon, token::Ident(..)) => (false, Other),
642+
(
643+
DollarParen,
644+
token::BinOp(BinOpToken::Plus | BinOpToken::Star) | token::Question,
645+
) => (false, Other),
646+
(DollarParen, _) => (false, DollarParenSep),
647+
(DollarParenSep, token::BinOp(BinOpToken::Plus | BinOpToken::Star)) => {
648+
(false, Other)
649+
}
650+
(Pound, token::Not) => (false, PoundBang),
651+
(_, token::Ident(symbol, /* is_raw */ false))
652+
if !usually_needs_space_between_keyword_and_open_delim(*symbol) =>
653+
{
654+
(true, Ident)
655+
}
656+
(_, token::Comma | token::Semi) => (false, Other),
657+
(_, token::Dollar) => (true, Dollar),
658+
(_, token::Pound) => (true, Pound),
659+
(_, _) => (true, Other),
660+
},
661+
TokenTree::Delimited(_, delim, _) => match (state, delim) {
662+
(Dollar, DelimToken::Paren) => (false, DollarParen),
663+
(Pound | PoundBang, DelimToken::Bracket) => (false, Other),
664+
(Ident, DelimToken::Paren | DelimToken::Bracket) => (false, Other),
665+
(_, _) => (true, Other),
666+
},
667+
};
668+
if state != Start && needs_space {
669+
printer.space();
670+
}
671+
print_tt(printer, &tt);
672+
state = next_state;
673+
}
674+
}
675+
676+
// This rough subset of keywords is listed here to distinguish tokens resembling
677+
// `f(0)` (no space between ident and paren) from tokens resembling `if let (0,
678+
// 0) = x` (space between ident and paren).
679+
fn usually_needs_space_between_keyword_and_open_delim(symbol: Symbol) -> bool {
680+
match symbol.as_str() {
681+
"as" | "box" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern"
682+
| "for" | "if" | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move"
683+
| "mut" | "ref" | "return" | "static" | "struct" | "trait" | "type" | "unsafe" | "use"
684+
| "where" | "while" | "yield" => true,
685+
_ => false,
686+
}
687+
}
688+
554689
pub(super) fn display_macro_source(
555690
cx: &mut DocContext<'_>,
556691
name: Symbol,

src/test/rustdoc/macro-generated-macro.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,22 @@ macro_rules! make_macro {
88
}
99

1010
// @has macro_generated_macro/macro.interpolations.html //pre 'macro_rules! interpolations {'
11-
// @has - //pre '(<= type $($i : ident) :: * + $e : expr =>) => { ... };'
11+
// @has - //pre '(<= type $($i:ident)::* + $e:expr =>) => { ... };'
1212
make_macro!(interpolations type $($i:ident)::* + $e:expr);
1313
interpolations!(<= type foo::bar + x.sort() =>);
1414

1515
// @has macro_generated_macro/macro.attributes.html //pre 'macro_rules! attributes {'
16-
// @has - //pre '(<= #! [no_std] #[inline] =>) => { ... };'
17-
make_macro!(attributes #![no_std] #[inline]);
16+
// @has - //pre '(<= #![no_std] #[cfg(feature = "alloc")] =>) => { ... };'
17+
make_macro!(attributes #![no_std] #[cfg(feature = "alloc")]);
1818

1919
// @has macro_generated_macro/macro.groups.html //pre 'macro_rules! groups {'
20-
// @has - //pre '(<= fn {} () { foo [0] } =>) => { ... };'
20+
// @has - //pre '(<= fn {} () { foo[0] } =>) => { ... };'
2121
make_macro!(groups fn {}() {foo[0]});
22+
23+
// @has macro_generated_macro/macro.linebreak.html //pre 'macro_rules! linebreak {'
24+
// @has - //pre ' ('
25+
// @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'
26+
// @has - //pre ' 26 27 28 =>'
27+
// @has - //pre ' ) => { ... };'
28+
// @has - //pre '};'
29+
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);

0 commit comments

Comments
 (0)