Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0913cbd

Browse files
committedApr 1, 2025
Implement RFC 3631
1 parent 19f42cb commit 0913cbd

File tree

16 files changed

+528
-227
lines changed

16 files changed

+528
-227
lines changed
 

‎compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,6 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
165165

166166
gate_doc!(
167167
"experimental" {
168-
cfg => doc_cfg
169-
cfg_hide => doc_cfg_hide
170168
masked => doc_masked
171169
notable_trait => doc_notable_trait
172170
}

‎compiler/rustc_passes/messages.ftl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,14 @@ passes_doc_alias_start_end =
182182
passes_doc_attr_not_crate_level =
183183
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
184184
185-
passes_doc_cfg_hide_takes_list =
186-
`#[doc(cfg_hide(...))]` takes a list of attributes
185+
passes_doc_auto_cfg_expects_hide_or_show =
186+
`only "hide" or "show" are allowed in "#[doc(auto_cfg(...))]"`
187+
188+
passes_doc_auto_cfg_hide_show_expects_list =
189+
`#![doc(auto_cfg({$attr_name}(...)))]` only expects a list of items
190+
191+
passes_doc_auto_cfg_wrong_literal =
192+
`expected boolean for #[doc(auto_cfg = ...)]`
187193
188194
passes_doc_expect_str =
189195
doc {$attr_name} attribute expects a string: #[doc({$attr_name} = "a")]

‎compiler/rustc_passes/src/check_attr.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,16 +1259,43 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
12591259
}
12601260
}
12611261

1262-
/// Check that the `#![doc(cfg_hide(...))]` attribute only contains a list of attributes.
1263-
///
1264-
fn check_doc_cfg_hide(&self, meta: &MetaItemInner, hir_id: HirId) {
1265-
if meta.meta_item_list().is_none() {
1266-
self.tcx.emit_node_span_lint(
1267-
INVALID_DOC_ATTRIBUTES,
1268-
hir_id,
1269-
meta.span(),
1270-
errors::DocCfgHideTakesList,
1271-
);
1262+
/// Check that the `#![doc(auto_cfg(..))]` attribute has expected input.
1263+
fn check_doc_auto_cfg(&self, meta: &MetaItemInner, hir_id: HirId) {
1264+
let MetaItemInner::MetaItem(meta) = meta else {
1265+
unreachable!();
1266+
};
1267+
match &meta.kind {
1268+
MetaItemKind::Word => {}
1269+
MetaItemKind::NameValue(lit) => {
1270+
if !matches!(lit.kind, LitKind::Bool(_)) {
1271+
self.tcx.emit_node_span_lint(
1272+
INVALID_DOC_ATTRIBUTES,
1273+
hir_id,
1274+
meta.span,
1275+
errors::DocAutoCfgWrongLiteral,
1276+
);
1277+
}
1278+
}
1279+
MetaItemKind::List(list) => {
1280+
for item in list {
1281+
let attr_name = item.name_or_empty();
1282+
if attr_name != sym::hide && attr_name != sym::show {
1283+
self.tcx.emit_node_span_lint(
1284+
INVALID_DOC_ATTRIBUTES,
1285+
hir_id,
1286+
meta.span,
1287+
errors::DocAutoCfgExpectsHideOrShow,
1288+
);
1289+
} else if item.meta_item_list().is_none() {
1290+
self.tcx.emit_node_span_lint(
1291+
INVALID_DOC_ATTRIBUTES,
1292+
hir_id,
1293+
meta.span,
1294+
errors::DocAutoCfgHideShowExpectsList { attr_name: attr_name.as_str() },
1295+
);
1296+
}
1297+
}
1298+
}
12721299
}
12731300
}
12741301

@@ -1329,10 +1356,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
13291356
self.check_attr_crate_level(attr, meta, hir_id);
13301357
}
13311358

1332-
sym::cfg_hide => {
1333-
if self.check_attr_crate_level(attr, meta, hir_id) {
1334-
self.check_doc_cfg_hide(meta, hir_id);
1335-
}
1359+
sym::auto_cfg => {
1360+
self.check_doc_auto_cfg(meta, hir_id);
13361361
}
13371362

13381363
sym::inline | sym::no_inline => {

‎compiler/rustc_passes/src/errors.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,18 @@ pub(crate) struct DocTestLiteral;
333333
pub(crate) struct DocTestTakesList;
334334

335335
#[derive(LintDiagnostic)]
336-
#[diag(passes_doc_cfg_hide_takes_list)]
337-
pub(crate) struct DocCfgHideTakesList;
336+
#[diag(passes_doc_auto_cfg_wrong_literal)]
337+
pub(crate) struct DocAutoCfgWrongLiteral;
338+
339+
#[derive(LintDiagnostic)]
340+
#[diag(passes_doc_auto_cfg_expects_hide_or_show)]
341+
pub(crate) struct DocAutoCfgExpectsHideOrShow;
342+
343+
#[derive(LintDiagnostic)]
344+
#[diag(passes_doc_auto_cfg_hide_show_expects_list)]
345+
pub(crate) struct DocAutoCfgHideShowExpectsList<'a> {
346+
pub attr_name: &'a str,
347+
}
338348

339349
#[derive(LintDiagnostic)]
340350
#[diag(passes_doc_test_unknown_any)]

‎compiler/rustc_span/src/symbol.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ symbols! {
534534
attributes,
535535
audit_that,
536536
augmented_assignments,
537+
auto_cfg,
537538
auto_traits,
538539
autodiff,
539540
automatically_derived,
@@ -1093,6 +1094,8 @@ symbols! {
10931094
hashset_iter_ty,
10941095
hexagon_target_feature,
10951096
hidden,
1097+
hidden_cfg,
1098+
hide,
10961099
hint,
10971100
homogeneous_aggregate,
10981101
host,
@@ -1878,6 +1881,7 @@ symbols! {
18781881
shl_assign,
18791882
shorter_tail_lifetimes,
18801883
should_panic,
1884+
show,
18811885
shr,
18821886
shr_assign,
18831887
sig_dfl,

‎library/alloc/src/lib.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,30 @@
6464
issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
6565
test(no_crate_inject, attr(allow(unused_variables), deny(warnings)))
6666
)]
67-
#![doc(cfg_hide(
68-
not(test),
69-
not(any(test, bootstrap)),
70-
no_global_oom_handling,
71-
not(no_global_oom_handling),
72-
not(no_rc),
73-
not(no_sync),
74-
target_has_atomic = "ptr"
75-
))]
67+
#![cfg_attr(
68+
bootstrap,
69+
doc(cfg_hide(
70+
not(test),
71+
not(any(test, bootstrap)),
72+
no_global_oom_handling,
73+
not(no_global_oom_handling),
74+
not(no_rc),
75+
not(no_sync),
76+
target_has_atomic = "ptr"
77+
))
78+
)]
79+
#![cfg_attr(
80+
not(bootstrap),
81+
doc(auto_cfg(hide(
82+
not(test),
83+
not(any(test, bootstrap)),
84+
no_global_oom_handling,
85+
not(no_global_oom_handling),
86+
not(no_rc),
87+
not(no_sync),
88+
target_has_atomic = "ptr"
89+
)))
90+
)]
7691
#![doc(rust_logo)]
7792
#![feature(rustdoc_internals)]
7893
#![no_std]

‎library/core/src/lib.rs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,27 +51,54 @@
5151
test(attr(allow(dead_code, deprecated, unused_variables, unused_mut)))
5252
)]
5353
#![doc(rust_logo)]
54-
#![doc(cfg_hide(
55-
no_fp_fmt_parse,
56-
target_pointer_width = "16",
57-
target_pointer_width = "32",
58-
target_pointer_width = "64",
59-
target_has_atomic = "8",
60-
target_has_atomic = "16",
61-
target_has_atomic = "32",
62-
target_has_atomic = "64",
63-
target_has_atomic = "ptr",
64-
target_has_atomic_equal_alignment = "8",
65-
target_has_atomic_equal_alignment = "16",
66-
target_has_atomic_equal_alignment = "32",
67-
target_has_atomic_equal_alignment = "64",
68-
target_has_atomic_equal_alignment = "ptr",
69-
target_has_atomic_load_store = "8",
70-
target_has_atomic_load_store = "16",
71-
target_has_atomic_load_store = "32",
72-
target_has_atomic_load_store = "64",
73-
target_has_atomic_load_store = "ptr",
74-
))]
54+
#![cfg_attr(
55+
bootstrap,
56+
doc(cfg_hide(
57+
no_fp_fmt_parse,
58+
target_pointer_width = "16",
59+
target_pointer_width = "32",
60+
target_pointer_width = "64",
61+
target_has_atomic = "8",
62+
target_has_atomic = "16",
63+
target_has_atomic = "32",
64+
target_has_atomic = "64",
65+
target_has_atomic = "ptr",
66+
target_has_atomic_equal_alignment = "8",
67+
target_has_atomic_equal_alignment = "16",
68+
target_has_atomic_equal_alignment = "32",
69+
target_has_atomic_equal_alignment = "64",
70+
target_has_atomic_equal_alignment = "ptr",
71+
target_has_atomic_load_store = "8",
72+
target_has_atomic_load_store = "16",
73+
target_has_atomic_load_store = "32",
74+
target_has_atomic_load_store = "64",
75+
target_has_atomic_load_store = "ptr",
76+
))
77+
)]
78+
#![cfg_attr(
79+
not(bootstrap),
80+
doc(auto_cfg(hide(
81+
no_fp_fmt_parse,
82+
target_pointer_width = "16",
83+
target_pointer_width = "32",
84+
target_pointer_width = "64",
85+
target_has_atomic = "8",
86+
target_has_atomic = "16",
87+
target_has_atomic = "32",
88+
target_has_atomic = "64",
89+
target_has_atomic = "ptr",
90+
target_has_atomic_equal_alignment = "8",
91+
target_has_atomic_equal_alignment = "16",
92+
target_has_atomic_equal_alignment = "32",
93+
target_has_atomic_equal_alignment = "64",
94+
target_has_atomic_equal_alignment = "ptr",
95+
target_has_atomic_load_store = "8",
96+
target_has_atomic_load_store = "16",
97+
target_has_atomic_load_store = "32",
98+
target_has_atomic_load_store = "64",
99+
target_has_atomic_load_store = "ptr",
100+
)))
101+
)]
75102
#![no_core]
76103
#![rustc_coherence_is_core]
77104
#![rustc_preserve_ub_checks]

‎library/std/src/lib.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,24 @@
235235
test(attr(allow(dead_code, deprecated, unused_variables, unused_mut)))
236236
)]
237237
#![doc(rust_logo)]
238-
#![doc(cfg_hide(
239-
not(test),
240-
not(any(test, bootstrap)),
241-
no_global_oom_handling,
242-
not(no_global_oom_handling)
243-
))]
238+
#![cfg_attr(
239+
bootstrap,
240+
doc(cfg_hide(
241+
not(test),
242+
not(any(test, bootstrap)),
243+
no_global_oom_handling,
244+
not(no_global_oom_handling)
245+
))
246+
)]
247+
#![cfg_attr(
248+
not(bootstrap),
249+
doc(auto_cfg(hide(
250+
not(test),
251+
not(any(test, bootstrap)),
252+
no_global_oom_handling,
253+
not(no_global_oom_handling)
254+
)))
255+
)]
244256
// Don't link to std. We are std.
245257
#![no_std]
246258
// Tell the compiler to link to either panic_abort or panic_unwind

‎src/librustdoc/clean/cfg.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,36 @@ impl Cfg {
244244
Some(self.clone())
245245
}
246246
}
247+
248+
pub(crate) fn strip_hidden(&self, hidden: &FxHashSet<Cfg>) -> Option<Self> {
249+
match self {
250+
Self::True | Self::False => Some(self.clone()),
251+
Self::Cfg(..) => {
252+
if !hidden.contains(self) {
253+
Some(self.clone())
254+
} else {
255+
None
256+
}
257+
}
258+
Self::Not(cfg) => {
259+
if let Some(cfg) = cfg.strip_hidden(hidden) {
260+
Some(Self::Not(Box::new(cfg)))
261+
} else {
262+
None
263+
}
264+
}
265+
Self::Any(cfgs) => {
266+
let cfgs =
267+
cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>();
268+
if cfgs.is_empty() { None } else { Some(Self::Any(cfgs)) }
269+
}
270+
Self::All(cfgs) => {
271+
let cfgs =
272+
cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>();
273+
if cfgs.is_empty() { None } else { Some(Self::All(cfgs)) }
274+
}
275+
}
276+
}
247277
}
248278

249279
impl ops::Not for Cfg {

‎src/librustdoc/clean/inline.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ use tracing::{debug, trace};
1919

2020
use super::{Item, extract_cfg_from_attrs};
2121
use crate::clean::{
22-
self, Attributes, ImplKind, ItemId, Type, clean_bound_vars, clean_generics, clean_impl_item,
23-
clean_middle_assoc_item, clean_middle_field, clean_middle_ty, clean_poly_fn_sig,
24-
clean_trait_ref_with_constraints, clean_ty, clean_ty_alias_inner_type, clean_ty_generics,
25-
clean_variant_def, utils,
22+
self, Attributes, CfgInfo, ImplKind, ItemId, Type, clean_bound_vars, clean_generics,
23+
clean_impl_item, clean_middle_assoc_item, clean_middle_field, clean_middle_ty,
24+
clean_poly_fn_sig, clean_trait_ref_with_constraints, clean_ty, clean_ty_alias_inner_type,
25+
clean_ty_generics, clean_variant_def, utils,
2626
};
2727
use crate::core::DocContext;
2828
use crate::formats::item_type::ItemType;
@@ -384,6 +384,7 @@ pub(crate) fn merge_attrs(
384384
cx: &mut DocContext<'_>,
385385
old_attrs: &[hir::Attribute],
386386
new_attrs: Option<(&[hir::Attribute], Option<LocalDefId>)>,
387+
cfg_info: &mut CfgInfo,
387388
) -> (clean::Attributes, Option<Arc<clean::cfg::Cfg>>) {
388389
// NOTE: If we have additional attributes (from a re-export),
389390
// always insert them first. This ensure that re-export
@@ -398,12 +399,12 @@ pub(crate) fn merge_attrs(
398399
} else {
399400
Attributes::from_hir(&both)
400401
},
401-
extract_cfg_from_attrs(both.iter(), cx.tcx, &cx.cache.hidden_cfg),
402+
extract_cfg_from_attrs(both.iter(), cx.tcx, cfg_info),
402403
)
403404
} else {
404405
(
405406
Attributes::from_hir(old_attrs),
406-
extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg),
407+
extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, cfg_info),
407408
)
408409
}
409410
}
@@ -582,7 +583,7 @@ pub(crate) fn build_impl(
582583
});
583584
}
584585

585-
let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs);
586+
let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs, &mut CfgInfo::default());
586587
trace!("merged_attrs={merged_attrs:?}");
587588

588589
trace!(

‎src/librustdoc/clean/mod.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -198,18 +198,10 @@ fn generate_item_with_correct_attrs(
198198
// We only keep the item's attributes.
199199
target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect()
200200
};
201-
let cfg = extract_cfg_from_attrs(
202-
attrs.iter().map(move |(attr, _)| match attr {
203-
Cow::Borrowed(attr) => *attr,
204-
Cow::Owned(attr) => attr,
205-
}),
206-
cx.tcx,
207-
&cx.cache.hidden_cfg,
208-
);
209201
let attrs = Attributes::from_hir_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false);
210202

211203
let name = renamed.or(Some(name));
212-
let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, cfg);
204+
let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, None);
213205
item.inner.inline_stmt_id = import_id;
214206
item
215207
}

‎src/librustdoc/clean/types.rs

Lines changed: 239 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use std::{fmt, iter};
55

66
use arrayvec::ArrayVec;
77
use rustc_abi::{ExternAbi, VariantIdx};
8+
use rustc_ast::ast::{LitKind, MetaItemInner, MetaItemKind};
89
use rustc_attr_parsing::{AttributeKind, ConstStability, Deprecation, Stability, StableSince};
9-
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
10+
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
1011
use rustc_hir::def::{CtorKind, DefKind, Res};
1112
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId};
1213
use rustc_hir::lang_items::LangItem;
@@ -479,7 +480,7 @@ impl Item {
479480
name,
480481
kind,
481482
Attributes::from_hir(hir_attrs),
482-
extract_cfg_from_attrs(hir_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg),
483+
None,
483484
)
484485
}
485486

@@ -974,30 +975,6 @@ impl ItemKind {
974975
| KeywordItem => [].iter(),
975976
}
976977
}
977-
978-
/// Returns `true` if this item does not appear inside an impl block.
979-
pub(crate) fn is_non_assoc(&self) -> bool {
980-
matches!(
981-
self,
982-
StructItem(_)
983-
| UnionItem(_)
984-
| EnumItem(_)
985-
| TraitItem(_)
986-
| ModuleItem(_)
987-
| ExternCrateItem { .. }
988-
| FunctionItem(_)
989-
| TypeAliasItem(_)
990-
| StaticItem(_)
991-
| ConstantItem(_)
992-
| TraitAliasItem(_)
993-
| ForeignFunctionItem(_, _)
994-
| ForeignStaticItem(_, _)
995-
| ForeignTypeItem
996-
| MacroItem(_)
997-
| ProcMacroItem(_)
998-
| PrimitiveItem(_)
999-
)
1000-
}
1001978
}
1002979

1003980
#[derive(Clone, Debug)]
@@ -1017,14 +994,85 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator<Item = &'a hir::Attribute>>(
1017994
.flatten()
1018995
}
1019996

997+
#[derive(Clone, Debug)]
998+
pub(crate) struct CfgInfo {
999+
hidden_cfg: FxHashSet<Cfg>,
1000+
current_cfg: Cfg,
1001+
doc_auto_cfg_active: bool,
1002+
parent_is_doc_cfg: bool,
1003+
}
1004+
1005+
impl Default for CfgInfo {
1006+
fn default() -> Self {
1007+
Self {
1008+
hidden_cfg: [
1009+
Cfg::Cfg(sym::test, None),
1010+
Cfg::Cfg(sym::doc, None),
1011+
Cfg::Cfg(sym::doctest, None),
1012+
]
1013+
.into_iter()
1014+
.collect(),
1015+
current_cfg: Cfg::True,
1016+
doc_auto_cfg_active: true,
1017+
parent_is_doc_cfg: false,
1018+
}
1019+
}
1020+
}
1021+
1022+
fn show_hide_show_conflict_error(
1023+
tcx: TyCtxt<'_>,
1024+
item_span: rustc_span::Span,
1025+
previous: rustc_span::Span,
1026+
) {
1027+
let mut diag = tcx.sess.dcx().struct_span_err(
1028+
item_span,
1029+
format!(
1030+
"same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item"
1031+
),
1032+
);
1033+
diag.span_note(previous, "first change was here");
1034+
diag.emit();
1035+
}
1036+
1037+
fn handle_auto_cfg_hide_show(
1038+
tcx: TyCtxt<'_>,
1039+
cfg_info: &mut CfgInfo,
1040+
sub_attr: &MetaItemInner,
1041+
is_show: bool,
1042+
new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
1043+
new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
1044+
) {
1045+
if let MetaItemInner::MetaItem(item) = sub_attr
1046+
&& let MetaItemKind::List(items) = &item.kind
1047+
{
1048+
for item in items {
1049+
// Cfg parsing errors should already have been reported in `rustc_passes::check_attr`.
1050+
if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) {
1051+
if is_show {
1052+
if let Some(span) = new_hide_attrs.get(&(key, value)) {
1053+
show_hide_show_conflict_error(tcx, item.span(), *span);
1054+
} else {
1055+
new_show_attrs.insert((key, value), item.span());
1056+
}
1057+
cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value));
1058+
} else {
1059+
if let Some(span) = new_show_attrs.get(&(key, value)) {
1060+
show_hide_show_conflict_error(tcx, item.span(), *span);
1061+
} else {
1062+
new_hide_attrs.insert((key, value), item.span());
1063+
}
1064+
cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value));
1065+
}
1066+
}
1067+
}
1068+
}
1069+
}
1070+
10201071
pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>(
10211072
attrs: I,
10221073
tcx: TyCtxt<'_>,
1023-
hidden_cfg: &FxHashSet<Cfg>,
1074+
cfg_info: &mut CfgInfo,
10241075
) -> Option<Arc<Cfg>> {
1025-
let doc_cfg_active = tcx.features().doc_cfg();
1026-
let doc_auto_cfg_active = tcx.features().doc_auto_cfg();
1027-
10281076
fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
10291077
let mut iter = it.into_iter();
10301078
let item = iter.next()?;
@@ -1034,67 +1082,176 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
10341082
Some(item)
10351083
}
10361084

1037-
let mut cfg = if doc_cfg_active || doc_auto_cfg_active {
1038-
let mut doc_cfg = attrs
1039-
.clone()
1040-
.filter(|attr| attr.has_name(sym::doc))
1041-
.flat_map(|attr| attr.meta_item_list().unwrap_or_default())
1042-
.filter(|attr| attr.has_name(sym::cfg))
1043-
.peekable();
1044-
if doc_cfg.peek().is_some() && doc_cfg_active {
1045-
let sess = tcx.sess;
1046-
doc_cfg.fold(Cfg::True, |mut cfg, item| {
1047-
if let Some(cfg_mi) =
1048-
item.meta_item().and_then(|item| rustc_expand::config::parse_cfg(item, sess))
1085+
let mut new_show_attrs = FxHashMap::default();
1086+
let mut new_hide_attrs = FxHashMap::default();
1087+
1088+
let mut doc_cfg = attrs
1089+
.clone()
1090+
.filter(|attr| attr.has_name(sym::doc))
1091+
.flat_map(|attr| attr.meta_item_list().unwrap_or_default())
1092+
.filter(|attr| attr.has_name(sym::cfg))
1093+
.peekable();
1094+
// If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes.
1095+
if doc_cfg.peek().is_some() {
1096+
let sess = tcx.sess;
1097+
// We overwrite existing `cfg`.
1098+
if !cfg_info.parent_is_doc_cfg {
1099+
cfg_info.current_cfg = Cfg::True;
1100+
cfg_info.parent_is_doc_cfg = true;
1101+
}
1102+
for attr in doc_cfg {
1103+
if let Some(cfg_mi) =
1104+
attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg(attr, sess))
1105+
{
1106+
// The result is unused here but we can gate unstable predicates
1107+
rustc_attr_parsing::cfg_matches(
1108+
cfg_mi,
1109+
tcx.sess,
1110+
rustc_ast::CRATE_NODE_ID,
1111+
Some(tcx.features()),
1112+
);
1113+
match Cfg::parse(cfg_mi) {
1114+
Ok(new_cfg) => cfg_info.current_cfg &= new_cfg,
1115+
Err(e) => {
1116+
sess.dcx().span_err(e.span, e.msg);
1117+
}
1118+
}
1119+
}
1120+
}
1121+
} else {
1122+
cfg_info.parent_is_doc_cfg = false;
1123+
}
1124+
1125+
let mut changed_auto_active_status = None;
1126+
1127+
// First we get all `doc(auto_cfg)` attributes.
1128+
for attr in attrs.clone() {
1129+
if let Some(ident) = attr.ident()
1130+
&& ident.name == sym::doc
1131+
&& let Some(attrs) = attr.meta_item_list()
1132+
{
1133+
for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) {
1134+
let MetaItemInner::MetaItem(attr) = attr else {
1135+
continue;
1136+
};
1137+
match &attr.kind {
1138+
MetaItemKind::Word => {
1139+
if let Some(first_change) = changed_auto_active_status {
1140+
if !cfg_info.doc_auto_cfg_active {
1141+
tcx.sess.dcx().struct_span_err(
1142+
vec![first_change, attr.span],
1143+
"`auto_cfg` was disabled and enabled more than once on the same item",
1144+
).emit();
1145+
return None;
1146+
}
1147+
} else {
1148+
changed_auto_active_status = Some(attr.span);
1149+
}
1150+
cfg_info.doc_auto_cfg_active = true;
1151+
}
1152+
MetaItemKind::NameValue(lit) => {
1153+
if let LitKind::Bool(value) = lit.kind {
1154+
if let Some(first_change) = changed_auto_active_status {
1155+
if cfg_info.doc_auto_cfg_active != value {
1156+
tcx.sess.dcx().struct_span_err(
1157+
vec![first_change, attr.span],
1158+
"`auto_cfg` was disabled and enabled more than once on the same item",
1159+
).emit();
1160+
return None;
1161+
}
1162+
} else {
1163+
changed_auto_active_status = Some(attr.span);
1164+
}
1165+
cfg_info.doc_auto_cfg_active = value;
1166+
}
1167+
}
1168+
MetaItemKind::List(sub_attrs) => {
1169+
if let Some(first_change) = changed_auto_active_status {
1170+
if !cfg_info.doc_auto_cfg_active {
1171+
tcx.sess.dcx().struct_span_err(
1172+
vec![first_change, attr.span],
1173+
"`auto_cfg` was disabled and enabled more than once on the same item",
1174+
).emit();
1175+
return None;
1176+
}
1177+
} else {
1178+
changed_auto_active_status = Some(attr.span);
1179+
}
1180+
// Whatever happens next, the feature is enabled again.
1181+
cfg_info.doc_auto_cfg_active = true;
1182+
for sub_attr in sub_attrs.iter() {
1183+
if let Some(ident) = sub_attr.ident()
1184+
&& (ident.name == sym::show || ident.name == sym::hide)
1185+
{
1186+
handle_auto_cfg_hide_show(
1187+
tcx,
1188+
cfg_info,
1189+
&sub_attr,
1190+
ident.name == sym::show,
1191+
&mut new_show_attrs,
1192+
&mut new_hide_attrs,
1193+
);
1194+
}
1195+
}
1196+
}
1197+
}
1198+
}
1199+
}
1200+
}
1201+
1202+
// If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because
1203+
// `doc(cfg())` overrides `cfg()`).
1204+
for attr in attrs {
1205+
let Some(ident) = attr.ident() else { continue };
1206+
match ident.name {
1207+
sym::cfg | sym::cfg_trace if !cfg_info.parent_is_doc_cfg => {
1208+
if let Some(attr) = single(attr.meta_item_list()?)
1209+
&& let Ok(new_cfg) = Cfg::parse(&attr)
10491210
{
1050-
// The result is unused here but we can gate unstable predicates
1051-
rustc_attr_parsing::cfg_matches(
1052-
cfg_mi,
1053-
tcx.sess,
1054-
rustc_ast::CRATE_NODE_ID,
1055-
Some(tcx.features()),
1056-
);
1057-
match Cfg::parse(cfg_mi) {
1058-
Ok(new_cfg) => cfg &= new_cfg,
1059-
Err(e) => {
1060-
sess.dcx().span_err(e.span, e.msg);
1211+
cfg_info.current_cfg &= new_cfg;
1212+
}
1213+
}
1214+
// treat #[target_feature(enable = "feat")] attributes as if they were
1215+
// #[doc(cfg(target_feature = "feat"))] attributes as well
1216+
sym::target_feature
1217+
if !cfg_info.parent_is_doc_cfg
1218+
&& let Some(attrs) = attr.meta_item_list() =>
1219+
{
1220+
for attr in attrs {
1221+
if attr.has_name(sym::enable) && attr.value_str().is_some() {
1222+
// Clone `enable = "feat"`, change to `target_feature = "feat"`.
1223+
// Unwrap is safe because `value_str` succeeded above.
1224+
let mut meta = attr.meta_item().unwrap().clone();
1225+
meta.path =
1226+
ast::Path::from_ident(Ident::with_dummy_span(sym::target_feature));
1227+
1228+
if let Ok(feat_cfg) = Cfg::parse(&ast::MetaItemInner::MetaItem(meta)) {
1229+
cfg_info.current_cfg &= feat_cfg;
10611230
}
10621231
}
10631232
}
1064-
cfg
1065-
})
1066-
} else if doc_auto_cfg_active {
1067-
// If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because
1068-
// `doc(cfg())` overrides `cfg()`).
1069-
attrs
1070-
.clone()
1071-
.filter(|attr| attr.has_name(sym::cfg_trace))
1072-
.filter_map(|attr| single(attr.meta_item_list()?))
1073-
.filter_map(|attr| Cfg::parse_without(attr.meta_item()?, hidden_cfg).ok().flatten())
1074-
.fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg)
1233+
}
1234+
_ => {}
1235+
}
1236+
}
1237+
1238+
// If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing
1239+
// to be done here.
1240+
if !cfg_info.doc_auto_cfg_active && !cfg_info.parent_is_doc_cfg {
1241+
None
1242+
} else if cfg_info.parent_is_doc_cfg {
1243+
if cfg_info.current_cfg == Cfg::True {
1244+
None
10751245
} else {
1076-
Cfg::True
1246+
Some(Arc::new(cfg_info.current_cfg.clone()))
10771247
}
10781248
} else {
1079-
Cfg::True
1080-
};
1081-
1082-
// treat #[target_feature(enable = "feat")] attributes as if they were
1083-
// #[doc(cfg(target_feature = "feat"))] attributes as well
1084-
for attr in hir_attr_lists(attrs, sym::target_feature) {
1085-
if attr.has_name(sym::enable) && attr.value_str().is_some() {
1086-
// Clone `enable = "feat"`, change to `target_feature = "feat"`.
1087-
// Unwrap is safe because `value_str` succeeded above.
1088-
let mut meta = attr.meta_item().unwrap().clone();
1089-
meta.path = ast::Path::from_ident(Ident::with_dummy_span(sym::target_feature));
1090-
1091-
if let Ok(feat_cfg) = Cfg::parse(&ast::MetaItemInner::MetaItem(meta)) {
1092-
cfg &= feat_cfg;
1093-
}
1249+
// Since we always want to collect all `cfg` items, we remove the hidden ones afterward.
1250+
match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) {
1251+
None | Some(Cfg::True) => None,
1252+
Some(cfg) => Some(Arc::new(cfg)),
10941253
}
10951254
}
1096-
1097-
if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) }
10981255
}
10991256

11001257
pub(crate) trait NestedAttributesExt {

‎src/librustdoc/doctest/rust.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
use std::env;
44
use std::sync::Arc;
55

6-
use rustc_data_structures::fx::FxHashSet;
76
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
87
use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit};
98
use rustc_middle::hir::nested_filter;
@@ -13,7 +12,7 @@ use rustc_span::source_map::SourceMap;
1312
use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span};
1413

1514
use super::{DocTestVisitor, ScrapedDocTest};
16-
use crate::clean::{Attributes, extract_cfg_from_attrs};
15+
use crate::clean::{Attributes, CfgInfo, extract_cfg_from_attrs};
1716
use crate::html::markdown::{self, ErrorCodes, LangString, MdRelLine};
1817

1918
struct RustCollector {
@@ -97,7 +96,7 @@ impl HirCollector<'_> {
9796
) {
9897
let ast_attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id));
9998
if let Some(ref cfg) =
100-
extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &FxHashSet::default())
99+
extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &mut CfgInfo::default())
101100
&& !cfg.matches(&self.tcx.sess.psess)
102101
{
103102
return;

‎src/librustdoc/formats/cache.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,6 @@ pub(crate) struct Cache {
125125
///
126126
/// Links are indexed by the DefId of the item they document.
127127
pub(crate) intra_doc_links: FxHashMap<ItemId, FxIndexSet<clean::ItemLink>>,
128-
/// Cfg that have been hidden via #![doc(cfg_hide(...))]
129-
pub(crate) hidden_cfg: FxHashSet<clean::cfg::Cfg>,
130128

131129
/// Contains the list of `DefId`s which have been inlined. It is used when generating files
132130
/// to check if a stripped item should get its file generated or not: if it's inside a

‎src/librustdoc/passes/propagate_doc_cfg.rs

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
33
use std::sync::Arc;
44

5+
use rustc_ast::token::{Token, TokenKind};
6+
use rustc_ast::tokenstream::{TokenStream, TokenTree};
57
use rustc_hir::def_id::LocalDefId;
8+
use rustc_hir::{AttrArgs, Attribute};
9+
use rustc_span::symbol::sym;
610

711
use crate::clean::cfg::Cfg;
812
use crate::clean::inline::{load_attrs, merge_attrs};
9-
use crate::clean::{Crate, Item, ItemKind};
13+
use crate::clean::{CfgInfo, Crate, Item, ItemKind};
1014
use crate::core::DocContext;
1115
use crate::fold::DocFolder;
1216
use crate::passes::Pass;
@@ -18,70 +22,119 @@ pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass {
1822
};
1923

2024
pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
21-
CfgPropagator { parent_cfg: None, parent: None, cx }.fold_crate(cr)
25+
CfgPropagator { parent_cfg: None, parent: None, cx, cfg_info: CfgInfo::default() }
26+
.fold_crate(cr)
2227
}
2328

2429
struct CfgPropagator<'a, 'tcx> {
2530
parent_cfg: Option<Arc<Cfg>>,
2631
parent: Option<LocalDefId>,
2732
cx: &'a mut DocContext<'tcx>,
33+
cfg_info: CfgInfo,
2834
}
2935

30-
impl CfgPropagator<'_, '_> {
31-
// Some items need to merge their attributes with their parents' otherwise a few of them
32-
// (mostly `cfg` ones) will be missing.
33-
fn merge_with_parent_attributes(&mut self, item: &mut Item) {
34-
let check_parent = match &item.kind {
35-
// impl blocks can be in different modules with different cfg and we need to get them
36-
// as well.
37-
ItemKind::ImplItem(_) => false,
38-
kind if kind.is_non_assoc() => true,
39-
_ => return,
40-
};
36+
fn should_retain(token: &TokenTree) -> bool {
37+
// We only keep `doc(cfg)` items.
38+
matches!(
39+
token,
40+
TokenTree::Token(
41+
Token {
42+
kind: TokenKind::Ident(
43+
ident,
44+
_,
45+
),
46+
..
47+
},
48+
_,
49+
) if *ident == sym::cfg,
50+
)
51+
}
4152

42-
let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else {
43-
return;
44-
};
53+
fn filter_tokens_from_list(args_tokens: &TokenStream) -> Vec<TokenTree> {
54+
let mut tokens = Vec::with_capacity(args_tokens.len());
55+
let mut skip_next_delimited = false;
56+
for token in args_tokens.iter() {
57+
match token {
58+
TokenTree::Delimited(..) => {
59+
if !skip_next_delimited {
60+
tokens.push(token.clone());
61+
}
62+
skip_next_delimited = false;
63+
}
64+
token if should_retain(token) => {
65+
skip_next_delimited = false;
66+
tokens.push(token.clone());
67+
}
68+
_ => {
69+
skip_next_delimited = true;
70+
}
71+
}
72+
}
73+
tokens
74+
}
4575

46-
if check_parent {
47-
let expected_parent = self.cx.tcx.opt_local_parent(def_id);
48-
// If parents are different, it means that `item` is a reexport and we need
49-
// to compute the actual `cfg` by iterating through its "real" parents.
50-
if self.parent.is_some() && self.parent == expected_parent {
51-
return;
76+
// We only care about `#[cfg()]` and `#[doc(cfg())]`, we discard everything else.
77+
fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute]) {
78+
for attr in new_attrs {
79+
if attr.is_doc_comment() {
80+
continue;
81+
}
82+
let mut attr = attr.clone();
83+
if let Attribute::Unparsed(ref mut normal) = attr
84+
&& let [ident] = &*normal.path.segments
85+
{
86+
let ident = ident.name;
87+
if ident == sym::doc
88+
&& let AttrArgs::Delimited(args) = &mut normal.args
89+
{
90+
let tokens = filter_tokens_from_list(&args.tokens);
91+
args.tokens = TokenStream::new(tokens);
92+
attrs.push(attr);
93+
} else if ident == sym::cfg_trace {
94+
// If it's a `cfg()` attribute, we keep it.
95+
attrs.push(attr);
5296
}
5397
}
98+
}
99+
}
54100

101+
impl CfgPropagator<'_, '_> {
102+
// Some items need to merge their attributes with their parents' otherwise a few of them
103+
// (mostly `cfg` ones) will be missing.
104+
fn merge_with_parent_attributes(&mut self, item: &mut Item) {
55105
let mut attrs = Vec::new();
56-
let mut next_def_id = def_id;
57-
while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) {
58-
attrs.extend_from_slice(load_attrs(self.cx, parent_def_id.to_def_id()));
59-
next_def_id = parent_def_id;
106+
// We only need to merge an item attributes with its parent's in case it's an impl as an
107+
// impl might not be defined in the same module as the item it implements.
108+
//
109+
// Otherwise, `cfg_info` already tracks everything we need so nothing else to do!
110+
if matches!(item.kind, ItemKind::ImplItem(_))
111+
&& let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local())
112+
{
113+
let mut next_def_id = def_id;
114+
while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) {
115+
let x = load_attrs(self.cx, parent_def_id.to_def_id());
116+
add_only_cfg_attributes(&mut attrs, x);
117+
next_def_id = parent_def_id;
118+
}
60119
}
61120

62-
let (_, cfg) =
63-
merge_attrs(self.cx, item.attrs.other_attrs.as_slice(), Some((&attrs, None)));
121+
let (_, cfg) = merge_attrs(
122+
self.cx,
123+
item.attrs.other_attrs.as_slice(),
124+
Some((&attrs, None)),
125+
&mut self.cfg_info,
126+
);
64127
item.inner.cfg = cfg;
65128
}
66129
}
67130

68131
impl DocFolder for CfgPropagator<'_, '_> {
69132
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
133+
let old_cfg_info = self.cfg_info.clone();
70134
let old_parent_cfg = self.parent_cfg.clone();
71135

72136
self.merge_with_parent_attributes(&mut item);
73-
74-
let new_cfg = match (self.parent_cfg.take(), item.inner.cfg.take()) {
75-
(None, None) => None,
76-
(Some(rc), None) | (None, Some(rc)) => Some(rc),
77-
(Some(mut a), Some(b)) => {
78-
let b = Arc::try_unwrap(b).unwrap_or_else(|rc| Cfg::clone(&rc));
79-
*Arc::make_mut(&mut a) &= b;
80-
Some(a)
81-
}
82-
};
83-
self.parent_cfg = new_cfg.clone();
84-
item.inner.cfg = new_cfg;
137+
self.parent_cfg = item.inner.cfg.clone();
85138

86139
let old_parent =
87140
if let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) {
@@ -90,6 +143,7 @@ impl DocFolder for CfgPropagator<'_, '_> {
90143
self.parent.take()
91144
};
92145
let result = self.fold_item_recur(item);
146+
self.cfg_info = old_cfg_info;
93147
self.parent_cfg = old_parent_cfg;
94148
self.parent = old_parent;
95149

‎src/librustdoc/visit_ast.rs

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ use std::mem;
55

66
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
77
use rustc_hir as hir;
8+
use rustc_hir::Node;
89
use rustc_hir::def::{DefKind, Res};
910
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
1011
use rustc_hir::intravisit::{Visitor, walk_body, walk_item};
11-
use rustc_hir::{CRATE_HIR_ID, Node};
1212
use rustc_middle::hir::nested_filter;
1313
use rustc_middle::ty::TyCtxt;
1414
use rustc_span::Span;
@@ -17,7 +17,6 @@ use rustc_span::hygiene::MacroKind;
1717
use rustc_span::symbol::{Symbol, kw, sym};
1818
use tracing::debug;
1919

20-
use crate::clean::cfg::Cfg;
2120
use crate::clean::utils::{inherits_doc_hidden, should_ignore_res};
2221
use crate::clean::{NestedAttributesExt, hir_attr_lists, reexport_chain};
2322
use crate::core;
@@ -149,32 +148,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
149148
}
150149
}
151150

152-
self.cx.cache.hidden_cfg = self
153-
.cx
154-
.tcx
155-
.hir_attrs(CRATE_HIR_ID)
156-
.iter()
157-
.filter(|attr| attr.has_name(sym::doc))
158-
.flat_map(|attr| attr.meta_item_list().into_iter().flatten())
159-
.filter(|attr| attr.has_name(sym::cfg_hide))
160-
.flat_map(|attr| {
161-
attr.meta_item_list()
162-
.unwrap_or(&[])
163-
.iter()
164-
.filter_map(|attr| {
165-
Cfg::parse(attr)
166-
.map_err(|e| self.cx.sess().dcx().span_err(e.span, e.msg))
167-
.ok()
168-
})
169-
.collect::<Vec<_>>()
170-
})
171-
.chain([
172-
Cfg::Cfg(sym::test, None),
173-
Cfg::Cfg(sym::doc, None),
174-
Cfg::Cfg(sym::doctest, None),
175-
])
176-
.collect();
177-
178151
self.cx.cache.exact_paths = self.exact_paths;
179152
top_level_module
180153
}

0 commit comments

Comments
 (0)
Please sign in to comment.