Correctly generate bang macro declaration in docs for attr/derive kinds

This commit is contained in:
Guillaume Gomez
2026-04-18 01:09:49 +02:00
parent 28cfe804a9
commit d0ff143b06
2 changed files with 60 additions and 15 deletions
+42 -12
View File
@@ -585,38 +585,68 @@ pub(crate) fn has_doc_flag<F: Fn(&DocAttribute) -> bool>(
/// Render a sequence of macro arms in a format suitable for displaying to the user
/// as part of an item declaration.
fn render_macro_arms<'a>(
fn render_macro_arms(
tcx: TyCtxt<'_>,
matchers: impl Iterator<Item = &'a TokenTree>,
tokens: &rustc_ast::tokenstream::TokenStream,
arm_delim: &str,
) -> String {
let mut tokens = tokens.iter();
let mut out = String::new();
for matcher in matchers {
while let Some(mut token) = tokens.next() {
// If this an attr/derive rule, it looks like `attr() () => {}`, so the token needs to be
// handled at the same time as the actual matcher.
//
// Without that, we would end up with `attr()` on one line and the matcher `()` on another.
let pre = if matches!(token, TokenTree::Token(..)) {
let pre = format!("{}() ", render_macro_matcher(tcx, token));
// Skipping the always empty `()` following the attr/derive ident.
tokens.next();
let Some(next) = tokens.next() else {
return out;
};
token = next;
pre
} else {
String::new()
};
writeln!(
out,
" {matcher} => {{ ... }}{arm_delim}",
matcher = render_macro_matcher(tcx, matcher),
" {pre}{matcher} => {{ ... }}{arm_delim}",
matcher = render_macro_matcher(tcx, token),
)
.unwrap();
// We skip the `=>`, macro "body" and the delimiter closing that "body" since we don't
// render them.
tokens.next();
tokens.next();
tokens.next();
}
out
}
pub(super) fn display_macro_source(tcx: TyCtxt<'_>, name: Symbol, def: &ast::MacroDef) -> String {
// Extract the spans of all matchers. They represent the "interface" of the macro.
let matchers = def.body.tokens.chunks(4).map(|arm| &arm[0]);
if def.macro_rules {
format!("macro_rules! {name} {{\n{arms}}}", arms = render_macro_arms(tcx, matchers, ";"))
format!(
"macro_rules! {name} {{\n{arms}}}",
arms = render_macro_arms(tcx, &def.body.tokens, ";")
)
} else {
if matchers.len() <= 1 {
if def.body.tokens.len() <= 4 {
format!(
"macro {name}{matchers} {{\n ...\n}}",
matchers =
matchers.map(|matcher| render_macro_matcher(tcx, matcher)).collect::<String>(),
matchers = def
.body
.tokens
.get(0)
.map(|matcher| render_macro_matcher(tcx, matcher))
.unwrap_or_default(),
)
} else {
format!("macro {name} {{\n{arms}}}", arms = render_macro_arms(tcx, matchers, ","))
format!(
"macro {name} {{\n{arms}}}",
arms = render_macro_arms(tcx, &def.body.tokens, ",")
)
}
}
}
+18 -3
View File
@@ -7,7 +7,7 @@ assert-text: ("#rustdoc-modnav .block.macro .current", "b")
define-function: (
"check_macro",
[name, info],
[name, info, kind],
block {
// It should be present twice in the sidebar.
assert-count: ("#rustdoc-modnav a[href='macro." + |name| + ".html']", 2)
@@ -24,11 +24,16 @@ define-function: (
assert-count: ("#rustdoc-modnav .current", 2)
// We check it has the expected information.
assert-text: ("h3.macro-info", "ⓘ This is " + |info| + "/function macro")
// We check how the item declaration looks like.
assert-text: (".item-decl", "macro_rules! " + |name| + " {
" + |kind| + "() () => { ... };
() => { ... };
}")
}
)
call-function: ("check_macro", {"name": "attr_macro", "info": "an attribute"})
call-function: ("check_macro", {"name": "derive_macro", "info": "a derive"})
call-function: ("check_macro", {"name": "attr_macro", "info": "an attribute", "kind": "attr"})
call-function: ("check_macro", {"name": "derive_macro", "info": "a derive", "kind": "derive"})
define-function: (
"crate_page",
@@ -74,3 +79,13 @@ define-function: (
go-to: "file://" + |DOC_PATH| + "/test_docs/all.html"
call-function: ("all_items_page", {"name": "attr_macro", "section_id": "attribute-macros"})
call-function: ("all_items_page", {"name": "derive_macro", "section_id": "derives"})
// We now check a macro with all 3 different kinds.
go-to: "file://" + |DOC_PATH| + "/test_docs/macro.one_for_all_macro.html"
assert-text: (".item-decl", "macro_rules! one_for_all_macro {
attr() () => { ... };
derive() () => { ... };
() => { ... };
}")
// We check it has the expected information.
assert-text: ("h3.macro-info", "ⓘ This is an attribute/derive/function macro")