diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 3293da27d2912..5b1846fcbfdb7 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 { @@ -4407,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 6a33cb38edf98..1b25575c41aa6 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,7 +46,7 @@ impl EarlyParsedState { if !self.cfg_trace.is_empty() { attributes.push(Attribute::Parsed(AttributeKind::CfgTrace(self.cfg_trace))); } - if self.cfg_attr_trace { + if !self.cfg_attr_trace.is_empty() { attributes.push(Attribute::Parsed(AttributeKind::CfgAttrTrace)); } } 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..6ce48cb7dbe3d 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -910,7 +910,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> { span, path, }; - let items = match expander.expand(self.cx, span, &meta, item, is_const) { + 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) => { // Reassemble the original invocation for retrying. @@ -920,6 +921,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }); } }; + self.add_cfg_attrs_to_items(&mut items, cfg_attrs); + let fragment = fragment_kind.expect_from_annotatables(items); if macro_stats { update_derive_macro_stats( @@ -1002,6 +1005,91 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }) } + 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| { + // 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()) + }) + .filter_map(|attr| { + let mut attr = attr.clone(); + 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) + }) + .collect::>() + } + fn gate_proc_macro_attr_item(&self, span: Span, item: &Annotatable) { let kind = match item { Annotatable::Item(_) 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;