From d0ff143b06706641232a66d348ebcdfd59ca673a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 18 Apr 2026 01:09:49 +0200 Subject: [PATCH] Correctly generate bang macro declaration in docs for attr/derive kinds --- src/librustdoc/clean/utils.rs | 54 +++++++++++++++++++++++------- tests/rustdoc-gui/attr-macros.goml | 21 ++++++++++-- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index ecf20dd9754a..c5e942f1fe94 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -585,38 +585,68 @@ pub(crate) fn has_doc_flag 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, + 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::(), + 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, ",") + ) } } } diff --git a/tests/rustdoc-gui/attr-macros.goml b/tests/rustdoc-gui/attr-macros.goml index 8fde3d54fa7e..23ad12fb582e 100644 --- a/tests/rustdoc-gui/attr-macros.goml +++ b/tests/rustdoc-gui/attr-macros.goml @@ -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")