From 7ff6be61e1a1f3b86feca44dcb7096ac16926ba7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Aug 2026 17:52:02 +0200 Subject: [PATCH 1/2] rustdoc: Correctly handle when a macro generates multiple items in `--generate-macro-expansion` --- src/librustdoc/html/macro_expansion.rs | 9 +++++++- .../multiple-items-in-one-macro-call.rs | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/rustdoc-html/macro-expansion/multiple-items-in-one-macro-call.rs diff --git a/src/librustdoc/html/macro_expansion.rs b/src/librustdoc/html/macro_expansion.rs index 4c820415e273c..9688a9e3fd7cf 100644 --- a/src/librustdoc/html/macro_expansion.rs +++ b/src/librustdoc/html/macro_expansion.rs @@ -46,6 +46,7 @@ pub(crate) struct ExpandedCode { /// As we go through the HIR visitor, if any span overlaps with another, they will /// both be merged. struct ExpandedCodeInfo { + original_span: Span, /// Callsite of the macro. span: Span, /// Expanded macro source code (HTML escaped). @@ -73,7 +74,12 @@ impl<'ast> ExpandedCodeVisitor<'ast> { self.expanded_codes.iter().position(|info| info.span.overlaps(callsite_span)) { let info = &mut self.expanded_codes[index]; - if new_span.contains(info.expanded_span) { + // If the new span we got has the exact same span information as a span already in the + // list, it means it's generated from the same macro but is a different item, so we need + // to add it as well. + let has_same_macro_origin = + new_span == info.original_span && callsite_span == info.span; + if !has_same_macro_origin && new_span.contains(info.expanded_span) { // New macro expansion recursively contains the old one, so replace it. info.span = callsite_span; info.expanded_span = new_span; @@ -90,6 +96,7 @@ impl<'ast> ExpandedCodeVisitor<'ast> { } else { // We add a new item. self.expanded_codes.push(ExpandedCodeInfo { + original_span: new_span, span: callsite_span, code: f(), expanded_span: new_span, diff --git a/tests/rustdoc-html/macro-expansion/multiple-items-in-one-macro-call.rs b/tests/rustdoc-html/macro-expansion/multiple-items-in-one-macro-call.rs new file mode 100644 index 0000000000000..782500db9d581 --- /dev/null +++ b/tests/rustdoc-html/macro-expansion/multiple-items-in-one-macro-call.rs @@ -0,0 +1,22 @@ +// This test ensures that when a macro call expands to multiple items, +// all of them are present in the macro expansion. +// Regression test for . + +//@ compile-flags: -Zunstable-options --generate-macro-expansion + +#![crate_name = "foo"] + +macro_rules! print_skip { + ($($t: ty),* $(,)?) => {$ + (impl $t { fn f() {} })* + }; +} + +pub struct A; +pub struct B; + +//@ has 'src/foo/multiple-items-in-one-macro-call.rs.html' +// Both `impl A` and `impl B` should be here. +//@ matches - '//*[@class="expansion"]/*[@class="expanded"]' \ +// '^impl A \{\s+fn f\(\) \{\}\n\}\nimpl B \{\s+fn f\(\) \{\}\n\}$' +print_skip!(A, B); From c9cb963e080b5e3239789808356eb970cfa1e0fa Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Aug 2026 20:56:16 +0200 Subject: [PATCH 2/2] Fix incorrect `hi` Span bound in rustdoc macro expansion --- src/librustdoc/html/macro_expansion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/html/macro_expansion.rs b/src/librustdoc/html/macro_expansion.rs index 9688a9e3fd7cf..55ca93e601b70 100644 --- a/src/librustdoc/html/macro_expansion.rs +++ b/src/librustdoc/html/macro_expansion.rs @@ -90,7 +90,7 @@ impl<'ast> ExpandedCodeVisitor<'ast> { expanded_code.code.push('\n'); expanded_code.code.push_str(&f()); let lo = BytePos(expanded_code.expanded_span.lo().0.min(new_span.lo().0)); - let hi = BytePos(expanded_code.expanded_span.hi().0.min(new_span.hi().0)); + let hi = BytePos(expanded_code.expanded_span.hi().0.max(new_span.hi().0)); expanded_code.expanded_span = expanded_code.expanded_span.with_lo(lo).with_hi(hi); } } else {