From c89edba25f69ce2085de3f591a42c7ade453962a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 8 Jul 2026 23:58:52 +0200 Subject: [PATCH 1/4] Keep `cfg_attr` cfg predicates when expanding proc-macros so it can be used by the `doc_cfg` feature --- compiler/rustc_ast/src/ast.rs | 3 ++- .../rustc_attr_parsing/src/early_parsed.rs | 14 +++++----- .../src/deriving/generic/mod.rs | 2 +- compiler/rustc_expand/src/config.rs | 18 ++++++++++--- compiler/rustc_expand/src/expand.rs | 26 ++++++++++++++++++- .../rustc_hir/src/attrs/data_structures.rs | 4 ++- .../rustc_hir/src/attrs/encode_cross_crate.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 2 +- src/librustdoc/clean/cfg.rs | 22 ++++++++++++---- src/librustdoc/clean/mod.rs | 4 +++ src/librustdoc/passes/propagate_doc_cfg.rs | 14 ++++++++++ tests/rustdoc-html/doc-cfg/cfg-attr.rs | 15 +++++++++++ 12 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 tests/rustdoc-html/doc-cfg/cfg-attr.rs diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 3293da27d2912..150907c93c8cb 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3490,7 +3490,8 @@ impl AttrItemKind { #[derive(Clone, Encodable, Decodable, Debug, StableHash)] pub enum EarlyParsedAttribute { CfgTrace(CfgEntry), - CfgAttrTrace, + /// The value is the `cfg` predicate of this `cfg_attr`. + CfgAttrTrace(CfgEntry), } impl AttrItem { diff --git a/compiler/rustc_attr_parsing/src/early_parsed.rs b/compiler/rustc_attr_parsing/src/early_parsed.rs index 6a33cb38edf98..21770dbd3b768 100644 --- a/compiler/rustc_attr_parsing/src/early_parsed.rs +++ b/compiler/rustc_attr_parsing/src/early_parsed.rs @@ -18,9 +18,7 @@ pub(crate) struct EarlyParsedState { cfg_trace: ThinVec<(CfgEntry, Span)>, /// Attribute state for `#[cfg_attr]` trace attributes - /// The arguments of these attributes is no longer relevant for any later passes, only their presence. - /// So we discard the arguments here. - cfg_attr_trace: bool, + cfg_attr_trace: ThinVec<(CfgEntry, Span)>, } impl EarlyParsedState { @@ -36,8 +34,10 @@ impl EarlyParsedState { cfg.lower_spans(lower_span); self.cfg_trace.push((cfg, attr_span)); } - EarlyParsedAttribute::CfgAttrTrace => { - self.cfg_attr_trace = true; + EarlyParsedAttribute::CfgAttrTrace(cfg) => { + let mut cfg = cfg.clone(); + cfg.lower_spans(lower_span); + self.cfg_attr_trace.push((cfg, attr_span)); } } } @@ -46,8 +46,8 @@ impl EarlyParsedState { if !self.cfg_trace.is_empty() { attributes.push(Attribute::Parsed(AttributeKind::CfgTrace(self.cfg_trace))); } - if self.cfg_attr_trace { - attributes.push(Attribute::Parsed(AttributeKind::CfgAttrTrace)); + if !self.cfg_attr_trace.is_empty() { + attributes.push(Attribute::Parsed(AttributeKind::CfgAttrTrace(self.cfg_attr_trace))); } } } diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index f9ec93a1115dd..4bbcb5364f63e 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -796,7 +796,7 @@ impl<'a> TraitDef<'a> { let rustc_const_unstable = cx.path_ident(self.span, Ident::new(sym::rustc_const_unstable, self.span)); - let mut attrs = thin_vec![cx.attr_word(sym::automatically_derived, self.span),]; + let mut attrs = thin_vec![cx.attr_word(sym::automatically_derived, self.span)]; // Only add `rustc_const_unstable` attributes if `derive_const` is used within libcore/libstd, // Other crates don't need stability attributes, so adding them is not useful, but libcore needs them diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index b3582494f226e..1f204a0b48592 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -2,6 +2,7 @@ use std::iter; +use rustc_ast::attr::data_structures::CfgEntry; use rustc_ast::token::{Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::{ AttrTokenStream, AttrTokenTree, LazyAttrTokenStream, Spacing, TokenTree, WithTokens, @@ -264,8 +265,6 @@ impl<'a> StripUnconfigured<'a> { // A trace attribute left in AST in place of the original `cfg_attr` attribute. // It can later be used by lints or other diagnostics. let mut trace_attr = cfg_attr.clone(); - trace_attr.replace_args(AttrItemKind::Parsed(EarlyParsedAttribute::CfgAttrTrace)); - let trace_attr = attr_into_trace(trace_attr, sym::cfg_attr_trace); let Some((cfg_predicate, expanded_attrs)) = rustc_attr_parsing::parse_cfg_attr( cfg_attr, @@ -273,7 +272,10 @@ impl<'a> StripUnconfigured<'a> { self.features, self.lint_node_id, ) else { - return vec![trace_attr]; + trace_attr.replace_args(AttrItemKind::Parsed(EarlyParsedAttribute::CfgAttrTrace( + CfgEntry::Bool(false, cfg_attr.span), + ))); + return vec![attr_into_trace(trace_attr, sym::cfg_attr_trace)]; }; // Lint on zero attributes in source. @@ -287,9 +289,17 @@ impl<'a> StripUnconfigured<'a> { } if !attr::eval_config_entry(self.sess, &cfg_predicate).as_bool() { - return vec![trace_attr]; + trace_attr.replace_args(AttrItemKind::Parsed(EarlyParsedAttribute::CfgAttrTrace( + CfgEntry::Bool(false, cfg_attr.span), + ))); + return vec![attr_into_trace(trace_attr, sym::cfg_attr_trace)]; } + // We add the `cfg` predicate into the trace. + trace_attr + .replace_args(AttrItemKind::Parsed(EarlyParsedAttribute::CfgAttrTrace(cfg_predicate))); + let trace_attr = attr_into_trace(trace_attr, sym::cfg_attr_trace); + if recursive { // We call `process_cfg_attr` recursively in case there's a // `cfg_attr` inside of another `cfg_attr`. E.g. diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 6dbac58cc6dbe..9300f4511dbb0 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -910,7 +910,22 @@ impl<'a, 'b> MacroExpander<'a, 'b> { span, path, }; - let items = match expander.expand(self.cx, span, &meta, item, is_const) { + let cfg_attrs = if let Annotatable::Item(ref item) = item { + item.attrs + .iter() + .filter(|attr| { + // FIXME(GuillaumeGomez): Should we convert the `cfg_attr_trace` + // attributes into `cfg_trace` to prevent having to handle two + // different attributes for the same information? + attr.name().is_some_and(|name| name == sym::cfg_attr_trace) + && self.cx.call_site().overlaps(attr.span) + }) + .cloned() + .collect::>() + } else { + Vec::new() + }; + let mut items = match expander.expand(self.cx, span, &meta, item, is_const) { ExpandResult::Ready(items) => items, ExpandResult::Retry(item) => { // Reassemble the original invocation for retrying. @@ -920,6 +935,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }); } }; + + if !cfg_attrs.is_empty() { + for item in &mut items { + if let Annotatable::Item(item) = item { + item.attrs.extend(cfg_attrs.clone()); + } + } + } + let fragment = fragment_kind.expect_from_annotatables(items); if macro_stats { update_derive_macro_stats( diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 17d00863d99d5..2328f76a522a0 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1011,7 +1011,9 @@ pub enum AttributeKind { AutomaticallyDerived, /// Represents the trace attribute of `#[cfg_attr]` - CfgAttrTrace, + /// + /// The value is the `cfg` predicate of the `cfg_attr`. + CfgAttrTrace(ThinVec<(CfgEntry, Span)>), /// Represents the trace attribute of `#[cfg]` CfgTrace(ThinVec<(CfgEntry, Span)>), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index f5a6eeec07406..169f4cbc5ab6b 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -21,7 +21,7 @@ impl AttributeKind { AllowInternalUnsafe(..) => Yes, AllowInternalUnstable(..) => Yes, AutomaticallyDerived => Yes, - CfgAttrTrace => Yes, + CfgAttrTrace(..) => Yes, CfgTrace(..) => Yes, CfiEncoding { .. } => Yes, Cold => No, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 863e4d88872c9..b3564ff3ecb90 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -246,7 +246,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { // All of the following attributes have no specific checks. // tidy-alphabetical-start AttributeKind::AutomaticallyDerived => (), - AttributeKind::CfgAttrTrace => (), + AttributeKind::CfgAttrTrace(..) => (), AttributeKind::CfgTrace(..) => (), AttributeKind::CfiEncoding { .. } => (), AttributeKind::Cold => (), diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 74a04b4451040..43b447936c87e 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -845,6 +845,8 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator let mut changed_auto_active_status = None; + let mut cfg_attr_trace = Vec::new(); + let mut is_automatically_derived = false; // We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes. for attr in attrs { if let Attribute::Parsed(AttributeKind::Doc(d)) = attr { @@ -884,15 +886,25 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator }); } continue; - } else if !cfg_info.parent_is_doc_cfg - && let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr - { - for (new_cfg, _) in cfgs { - cfg_info.current_cfg &= Cfg(new_cfg.clone()); + } else if !cfg_info.parent_is_doc_cfg { + if let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr { + for (new_cfg, _) in cfgs { + cfg_info.current_cfg &= Cfg(new_cfg.clone()); + } + } else if let hir::Attribute::Parsed(AttributeKind::CfgAttrTrace(cfgs)) = attr { + cfg_attr_trace.push(cfgs); + } else if matches!(attr, hir::Attribute::Parsed(AttributeKind::AutomaticallyDerived)) { + is_automatically_derived = true; } } } + if is_automatically_derived { + for (new_cfg, _) in cfg_attr_trace.into_iter().flatten() { + cfg_info.current_cfg &= Cfg(new_cfg.clone()); + } + } + // If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing // to be done here. if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg { diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index c7387bf157d10..4b689c55d4cd7 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2808,6 +2808,8 @@ fn add_without_unwanted_attributes<'hir>( is_inline: bool, import_parent: Option, ) { + let is_automatically_derived = + rustc_hir::find_attr!(new_attrs, AttributeKind::AutomaticallyDerived); for attr in new_attrs { match attr { hir::Attribute::Parsed(AttributeKind::DocComment { .. }) => { @@ -2855,6 +2857,8 @@ fn add_without_unwanted_attributes<'hir>( // We discard `#[cfg(...)]` attributes unless we're inlining hir::Attribute::Parsed(AttributeKind::CfgTrace(..)) if !is_inline => {} + hir::Attribute::Parsed(AttributeKind::CfgAttrTrace(..)) + if !is_inline || !is_automatically_derived => {} // We keep all other attributes _ => { attrs.push((Cow::Borrowed(attr), import_parent)); diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 213b8060a8512..51e911d1204fd 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -37,6 +37,8 @@ struct CfgPropagator<'a, 'tcx> { /// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from /// it and put them into `attrs`. fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) { + let is_automatically_derived = + rustc_hir::find_attr!(new_attrs, AttributeKind::AutomaticallyDerived); for attr in new_attrs { if let Attribute::Parsed(AttributeKind::Doc(d)) = attr && !d.cfg.is_empty() @@ -47,6 +49,11 @@ fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) } else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr { // If it's a `cfg()` attribute, we keep it. attrs.push(attr.clone()); + } else if let Attribute::Parsed(AttributeKind::CfgAttrTrace(..)) = attr + && is_automatically_derived + { + // If it's a `cfg()` attribute, we keep it. + attrs.push(attr.clone()); } } } @@ -54,6 +61,8 @@ fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) /// This function goes through the attributes list (`new_attrs`) and extracts the attributes that /// affect the cfg state propagated to detached items. fn add_cfg_state_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) { + let is_automatically_derived = + rustc_hir::find_attr!(new_attrs, AttributeKind::AutomaticallyDerived); for attr in new_attrs { if let Attribute::Parsed(AttributeKind::Doc(d)) = attr && (!d.cfg.is_empty() || !d.auto_cfg.is_empty() || !d.auto_cfg_change.is_empty()) @@ -66,6 +75,11 @@ fn add_cfg_state_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) } else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr { // If it's a `cfg()` attribute, we keep it. attrs.push(attr.clone()); + } else if let Attribute::Parsed(AttributeKind::CfgAttrTrace(..)) = attr + && is_automatically_derived + { + // If it's a `cfg()` attribute, we keep it. + attrs.push(attr.clone()); } } } diff --git a/tests/rustdoc-html/doc-cfg/cfg-attr.rs b/tests/rustdoc-html/doc-cfg/cfg-attr.rs new file mode 100644 index 0000000000000..01aacd7278829 --- /dev/null +++ b/tests/rustdoc-html/doc-cfg/cfg-attr.rs @@ -0,0 +1,15 @@ +// This test ensures that the `cfg_attr` cfg predicates are correctly kept to be used +// by the `doc_cfg` feature. + +#![crate_name = "foo"] +#![feature(doc_cfg)] + +//@ has 'foo/struct.Test.html' +//@ has - '//*[@id="impl-Debug-for-Test"]/*[@class="item-info"]/*[@class="stab portability"]' \ +// 'Available on non-crate feature debug only.' +//@ has - '//*[@id="impl-Clone-for-Test"]/*[@class="item-info"]/*[@class="stab portability"]' \ +// 'Available on non-crate feature aa and non-crate feature bb only.' + +#[cfg_attr(not(feature = "debug"), derive(Debug))] +#[cfg_attr(not(feature = "aa"), cfg_attr(not(feature = "bb"), derive(Clone)))] +pub struct Test; From ad9b4298da3480f6c641fb474c33fa8b6d0bece7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 12 Jul 2026 00:44:52 +0200 Subject: [PATCH 2/4] Replace `cfg_attr_trace` with `cfg_trace` when propagating it in derive expansions --- compiler/rustc_ast/src/ast.rs | 2 +- .../rustc_attr_parsing/src/early_parsed.rs | 2 +- compiler/rustc_expand/src/expand.rs | 73 +++++++++++++++---- .../rustc_hir/src/attrs/data_structures.rs | 4 +- .../rustc_hir/src/attrs/encode_cross_crate.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 2 +- src/librustdoc/clean/cfg.rs | 12 --- src/librustdoc/clean/mod.rs | 4 - src/librustdoc/passes/propagate_doc_cfg.rs | 14 ---- 9 files changed, 62 insertions(+), 53 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 150907c93c8cb..5b1846fcbfdb7 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -4408,7 +4408,7 @@ mod size_asserts { static_assert_size!(MetaItem, 80); static_assert_size!(MetaItemKind, 40); static_assert_size!(MetaItemLit, 40); - static_assert_size!(NormalAttr, 72); + static_assert_size!(NormalAttr, 80); static_assert_size!(Param, 40); static_assert_size!(Pat, 64); static_assert_size!(PatKind, 48); diff --git a/compiler/rustc_attr_parsing/src/early_parsed.rs b/compiler/rustc_attr_parsing/src/early_parsed.rs index 21770dbd3b768..1b25575c41aa6 100644 --- a/compiler/rustc_attr_parsing/src/early_parsed.rs +++ b/compiler/rustc_attr_parsing/src/early_parsed.rs @@ -47,7 +47,7 @@ impl EarlyParsedState { attributes.push(Attribute::Parsed(AttributeKind::CfgTrace(self.cfg_trace))); } if !self.cfg_attr_trace.is_empty() { - attributes.push(Attribute::Parsed(AttributeKind::CfgAttrTrace(self.cfg_attr_trace))); + attributes.push(Attribute::Parsed(AttributeKind::CfgAttrTrace)); } } } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 9300f4511dbb0..f84072105dc23 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -910,20 +910,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> { span, path, }; - let cfg_attrs = if let Annotatable::Item(ref item) = item { - item.attrs - .iter() - .filter(|attr| { - // FIXME(GuillaumeGomez): Should we convert the `cfg_attr_trace` - // attributes into `cfg_trace` to prevent having to handle two - // different attributes for the same information? - attr.name().is_some_and(|name| name == sym::cfg_attr_trace) - && self.cx.call_site().overlaps(attr.span) - }) - .cloned() - .collect::>() - } else { - Vec::new() + let cfg_attrs = match item { + Annotatable::Item(ref item) => { + self.propagate_cfg_attr_predicate_to_expanded_item(item) + } + Annotatable::Stmt(ref stmt) if let StmtKind::Item(ref item) = stmt.kind => { + self.propagate_cfg_attr_predicate_to_expanded_item(item) + } + _ => unreachable!(), }; let mut items = match expander.expand(self.cx, span, &meta, item, is_const) { ExpandResult::Ready(items) => items, @@ -938,8 +932,16 @@ impl<'a, 'b> MacroExpander<'a, 'b> { if !cfg_attrs.is_empty() { for item in &mut items { - if let Annotatable::Item(item) = item { - item.attrs.extend(cfg_attrs.clone()); + match item { + Annotatable::Item(item) => { + item.attrs.extend(cfg_attrs.clone()); + } + Annotatable::Stmt(stmt) => { + if let StmtKind::Item(item) = &mut stmt.kind { + item.attrs.extend(cfg_attrs.clone()); + } + } + _ => unreachable!(), } } } @@ -1026,6 +1028,45 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }) } + fn propagate_cfg_attr_predicate_to_expanded_item( + &self, + item: &rustc_ast::Item, + ) -> Vec { + item.attrs + .iter() + .filter(|attr| { + // FIXME(GuillaumeGomez): Find a better way to propagate `cfg_attr` to expanded items + // as it doesn't work nicely with `include!` and equivalents... + // We need to filter to ensure only the `cfg_attr`s from which the item was expanded + // are taken into account. + attr.name().is_some_and(|name| name == sym::cfg_attr_trace) + && attr.span.contains(self.cx.call_site()) + }) + .map(|attr| { + let mut attr = attr.clone(); + if let rustc_ast::AttrKind::Normal(attr) = &mut attr.kind { + let cfg = if let rustc_ast::ast::AttrItemKind::Parsed( + EarlyParsedAttribute::CfgAttrTrace(cfg), + ) = &mut attr.item.args + { + std::mem::replace( + cfg, + rustc_ast::attr::data_structures::CfgEntry::Bool( + false, + rustc_span::DUMMY_SP, + ), + ) + } else { + unreachable!() + }; + attr.item.args = + rustc_ast::ast::AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg)); + } + attr + }) + .collect::>() + } + fn gate_proc_macro_attr_item(&self, span: Span, item: &Annotatable) { let kind = match item { Annotatable::Item(_) diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 2328f76a522a0..17d00863d99d5 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1011,9 +1011,7 @@ pub enum AttributeKind { AutomaticallyDerived, /// Represents the trace attribute of `#[cfg_attr]` - /// - /// The value is the `cfg` predicate of the `cfg_attr`. - CfgAttrTrace(ThinVec<(CfgEntry, Span)>), + CfgAttrTrace, /// Represents the trace attribute of `#[cfg]` CfgTrace(ThinVec<(CfgEntry, Span)>), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 169f4cbc5ab6b..f5a6eeec07406 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -21,7 +21,7 @@ impl AttributeKind { AllowInternalUnsafe(..) => Yes, AllowInternalUnstable(..) => Yes, AutomaticallyDerived => Yes, - CfgAttrTrace(..) => Yes, + CfgAttrTrace => Yes, CfgTrace(..) => Yes, CfiEncoding { .. } => Yes, Cold => No, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index b3564ff3ecb90..863e4d88872c9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -246,7 +246,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { // All of the following attributes have no specific checks. // tidy-alphabetical-start AttributeKind::AutomaticallyDerived => (), - AttributeKind::CfgAttrTrace(..) => (), + AttributeKind::CfgAttrTrace => (), AttributeKind::CfgTrace(..) => (), AttributeKind::CfiEncoding { .. } => (), AttributeKind::Cold => (), diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 43b447936c87e..3c5ae82183283 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -845,8 +845,6 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator let mut changed_auto_active_status = None; - let mut cfg_attr_trace = Vec::new(); - let mut is_automatically_derived = false; // We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes. for attr in attrs { if let Attribute::Parsed(AttributeKind::Doc(d)) = attr { @@ -891,20 +889,10 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator for (new_cfg, _) in cfgs { cfg_info.current_cfg &= Cfg(new_cfg.clone()); } - } else if let hir::Attribute::Parsed(AttributeKind::CfgAttrTrace(cfgs)) = attr { - cfg_attr_trace.push(cfgs); - } else if matches!(attr, hir::Attribute::Parsed(AttributeKind::AutomaticallyDerived)) { - is_automatically_derived = true; } } } - if is_automatically_derived { - for (new_cfg, _) in cfg_attr_trace.into_iter().flatten() { - cfg_info.current_cfg &= Cfg(new_cfg.clone()); - } - } - // If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing // to be done here. if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg { diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 4b689c55d4cd7..c7387bf157d10 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2808,8 +2808,6 @@ fn add_without_unwanted_attributes<'hir>( is_inline: bool, import_parent: Option, ) { - let is_automatically_derived = - rustc_hir::find_attr!(new_attrs, AttributeKind::AutomaticallyDerived); for attr in new_attrs { match attr { hir::Attribute::Parsed(AttributeKind::DocComment { .. }) => { @@ -2857,8 +2855,6 @@ fn add_without_unwanted_attributes<'hir>( // We discard `#[cfg(...)]` attributes unless we're inlining hir::Attribute::Parsed(AttributeKind::CfgTrace(..)) if !is_inline => {} - hir::Attribute::Parsed(AttributeKind::CfgAttrTrace(..)) - if !is_inline || !is_automatically_derived => {} // We keep all other attributes _ => { attrs.push((Cow::Borrowed(attr), import_parent)); diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 51e911d1204fd..213b8060a8512 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -37,8 +37,6 @@ struct CfgPropagator<'a, 'tcx> { /// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from /// it and put them into `attrs`. fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) { - let is_automatically_derived = - rustc_hir::find_attr!(new_attrs, AttributeKind::AutomaticallyDerived); for attr in new_attrs { if let Attribute::Parsed(AttributeKind::Doc(d)) = attr && !d.cfg.is_empty() @@ -49,11 +47,6 @@ fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) } else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr { // If it's a `cfg()` attribute, we keep it. attrs.push(attr.clone()); - } else if let Attribute::Parsed(AttributeKind::CfgAttrTrace(..)) = attr - && is_automatically_derived - { - // If it's a `cfg()` attribute, we keep it. - attrs.push(attr.clone()); } } } @@ -61,8 +54,6 @@ fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) /// This function goes through the attributes list (`new_attrs`) and extracts the attributes that /// affect the cfg state propagated to detached items. fn add_cfg_state_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) { - let is_automatically_derived = - rustc_hir::find_attr!(new_attrs, AttributeKind::AutomaticallyDerived); for attr in new_attrs { if let Attribute::Parsed(AttributeKind::Doc(d)) = attr && (!d.cfg.is_empty() || !d.auto_cfg.is_empty() || !d.auto_cfg_change.is_empty()) @@ -75,11 +66,6 @@ fn add_cfg_state_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) } else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr { // If it's a `cfg()` attribute, we keep it. attrs.push(attr.clone()); - } else if let Attribute::Parsed(AttributeKind::CfgAttrTrace(..)) = attr - && is_automatically_derived - { - // If it's a `cfg()` attribute, we keep it. - attrs.push(attr.clone()); } } } From 7a8d6a89b9be981e1b041f329d485bf806fd7604 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 12 Jul 2026 22:05:14 +0200 Subject: [PATCH 3/4] Don't make `cfg_attr` expansion code use `unreachable!()` until `rustdoc-json` stops entering it --- compiler/rustc_expand/src/expand.rs | 16 +++++++++++----- src/librustdoc/clean/cfg.rs | 10 +++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index f84072105dc23..dd37ecfa5f0ea 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -917,7 +917,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> { Annotatable::Stmt(ref stmt) if let StmtKind::Item(ref item) = stmt.kind => { self.propagate_cfg_attr_predicate_to_expanded_item(item) } - _ => unreachable!(), + // FIXME: Should be `unreachable!()` but `rustdoc-json-types` is trying + // to expand items it shouldn't. + _ => Vec::new(), }; let mut items = match expander.expand(self.cx, span, &meta, item, is_const) { ExpandResult::Ready(items) => items, @@ -941,7 +943,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> { item.attrs.extend(cfg_attrs.clone()); } } - _ => unreachable!(), + // FIXME: Should be `unreachable!()` but `rustdoc-json-types` is + // trying to expand items it shouldn't. + _ => {} } } } @@ -1042,7 +1046,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { attr.name().is_some_and(|name| name == sym::cfg_attr_trace) && attr.span.contains(self.cx.call_site()) }) - .map(|attr| { + .filter_map(|attr| { let mut attr = attr.clone(); if let rustc_ast::AttrKind::Normal(attr) = &mut attr.kind { let cfg = if let rustc_ast::ast::AttrItemKind::Parsed( @@ -1057,12 +1061,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> { ), ) } else { - unreachable!() + // FIXME(GuillaumeGomez): How is it possible for `rustdoc-json-types` to + // enter this condition? + return None; }; attr.item.args = rustc_ast::ast::AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg)); } - attr + Some(attr) }) .collect::>() } diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 3c5ae82183283..74a04b4451040 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -884,11 +884,11 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator }); } continue; - } else if !cfg_info.parent_is_doc_cfg { - if let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr { - for (new_cfg, _) in cfgs { - cfg_info.current_cfg &= Cfg(new_cfg.clone()); - } + } else if !cfg_info.parent_is_doc_cfg + && let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr + { + for (new_cfg, _) in cfgs { + cfg_info.current_cfg &= Cfg(new_cfg.clone()); } } } From 751bb059a8c1f24bf3bd6e75d11937fb7626d894 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 14 Jul 2026 18:04:47 +0200 Subject: [PATCH 4/4] Improve derive attribute expansion code and correctly update `cfg_attr_trace` into `cfg_trace` to correctly handle the `rustdoc-json-types` situation --- compiler/rustc_expand/src/expand.rs | 121 ++++++++++++++++------------ 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index dd37ecfa5f0ea..6ce48cb7dbe3d 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -910,17 +910,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { span, path, }; - let cfg_attrs = match item { - Annotatable::Item(ref item) => { - self.propagate_cfg_attr_predicate_to_expanded_item(item) - } - Annotatable::Stmt(ref stmt) if let StmtKind::Item(ref item) = stmt.kind => { - self.propagate_cfg_attr_predicate_to_expanded_item(item) - } - // FIXME: Should be `unreachable!()` but `rustdoc-json-types` is trying - // to expand items it shouldn't. - _ => Vec::new(), - }; + let cfg_attrs = self.get_item_cfg_attr_traces(&item); let mut items = match expander.expand(self.cx, span, &meta, item, is_const) { ExpandResult::Ready(items) => items, ExpandResult::Retry(item) => { @@ -931,24 +921,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }); } }; - - if !cfg_attrs.is_empty() { - for item in &mut items { - match item { - Annotatable::Item(item) => { - item.attrs.extend(cfg_attrs.clone()); - } - Annotatable::Stmt(stmt) => { - if let StmtKind::Item(item) = &mut stmt.kind { - item.attrs.extend(cfg_attrs.clone()); - } - } - // FIXME: Should be `unreachable!()` but `rustdoc-json-types` is - // trying to expand items it shouldn't. - _ => {} - } - } - } + self.add_cfg_attrs_to_items(&mut items, cfg_attrs); let fragment = fragment_kind.expect_from_annotatables(items); if macro_stats { @@ -1032,10 +1005,49 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }) } - fn propagate_cfg_attr_predicate_to_expanded_item( - &self, - item: &rustc_ast::Item, - ) -> Vec { + fn add_cfg_attrs_to_items(&self, items: &mut Vec, cfg_attrs: Vec) { + if cfg_attrs.is_empty() { + return; + } + for item in items { + match item { + Annotatable::Item(item) => { + item.attrs.extend(cfg_attrs.clone()); + } + Annotatable::Stmt(stmt) => { + if let StmtKind::Item(item) = &mut stmt.kind { + item.attrs.extend(cfg_attrs.clone()); + } + } + Annotatable::GenericParam(_) => { + self.cx.dcx().span_delayed_bug( + item.span(), + "trying to get derive attributes on a `GenericParam`", + ); + } + _ => unreachable!(), + } + } + } + + fn get_item_cfg_attr_traces(&self, item: &Annotatable) -> Vec { + match item { + Annotatable::Item(item) => self.cfg_attr_traces_for_derive(item), + Annotatable::Stmt(stmt) if let StmtKind::Item(ref item) = stmt.kind => { + self.cfg_attr_traces_for_derive(item) + } + Annotatable::GenericParam(_) => { + self.cx.dcx().span_delayed_bug( + item.span(), + "trying to get derive attributes on a `GenericParam`", + ); + Vec::new() + } + _ => unreachable!(), + } + } + + fn cfg_attr_traces_for_derive(&self, item: &ast::Item) -> Vec { item.attrs .iter() .filter(|attr| { @@ -1048,25 +1060,30 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }) .filter_map(|attr| { let mut attr = attr.clone(); - if let rustc_ast::AttrKind::Normal(attr) = &mut attr.kind { - let cfg = if let rustc_ast::ast::AttrItemKind::Parsed( - EarlyParsedAttribute::CfgAttrTrace(cfg), - ) = &mut attr.item.args - { - std::mem::replace( - cfg, - rustc_ast::attr::data_structures::CfgEntry::Bool( - false, - rustc_span::DUMMY_SP, - ), - ) - } else { - // FIXME(GuillaumeGomez): How is it possible for `rustdoc-json-types` to - // enter this condition? - return None; - }; - attr.item.args = - rustc_ast::ast::AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg)); + let ast::AttrKind::Normal(normal_attr) = &mut attr.kind else { unreachable!() }; + let cfg = if let ast::ast::AttrItemKind::Parsed( + EarlyParsedAttribute::CfgAttrTrace(cfg), + ) = &mut normal_attr.item.args + { + std::mem::replace( + cfg, + rustc_ast::attr::data_structures::CfgEntry::Bool( + false, + rustc_span::DUMMY_SP, + ), + ) + } else { + unreachable!() + }; + normal_attr.item.args = + ast::ast::AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg)); + // We also need to change the attribute because `rustdoc-json-types` is using the + // already processed attributes. So if we don't make this change, we will have an + // attribute with the name `cfg_attr_trace` but of type + // `AttrItemKind::Parsed(EarlyParsedAttribute::CfgAttrTrace())`, entering the + // `unreachable!()`. + if let Some(name) = normal_attr.item.path.segments.first_mut() { + name.ident.name = sym::cfg_trace; } Some(attr) })