Skip to content

Commit 8a2bd0f

Browse files
authored
Rollup merge of #153964 - GuillaumeGomez:fix-trait-impl-doc-cfg, r=lolbinarycat
Fix `doc_cfg` not working as expected on trait impls Fixes #153655. I spent waaaaay too much time on this fix. So the current issue is that rustdoc gets its items in two passes: 1. All items 2. Trait/blanket/auto impls Because of that, the trait impls are not stored "correctly" in the rustdoc AST, meaning that the `propagate_doc_cfg` pass doesn't work correctly on them. So initially, I tried to "clean" the impls at the same time as the other items. However, it created a monstruous amount of bugs and issues and after two days, I decided to give up on this approach (might be worth fixing that in the future!). You can see what I tried [here](https://github.com/rust-lang/rust/compare/main...GuillaumeGomez:trait-impls-doc_cfg?expand=1). So instead, since the impls are stored at the end, I create placeholders for impls and in `propagate_doc_cfg`, I store the `cfg` "context" (more clear when reading the code 😛) and re-use it later on when the "real" impl comes up. r? @lolbinarycat
2 parents 6f22f61 + 19876d1 commit 8a2bd0f

File tree

15 files changed

+275
-19
lines changed

15 files changed

+275
-19
lines changed

src/librustdoc/clean/mod.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2776,7 +2776,12 @@ fn clean_maybe_renamed_item<'tcx>(
27762776
// These kinds of item either don't need a `name` or accept a `None` one so we handle them
27772777
// before.
27782778
match item.kind {
2779-
ItemKind::Impl(ref impl_) => return clean_impl(impl_, item.owner_id.def_id, cx),
2779+
ItemKind::Impl(ref impl_) => {
2780+
// If `renamed` is `Some()` for an `impl`, it means it's been inlined because we use
2781+
// it as a marker to indicate that this is an inlined impl and that we should
2782+
// generate an impl placeholder and not a "real" impl item.
2783+
return clean_impl(impl_, item.owner_id.def_id, cx, renamed.is_some());
2784+
}
27802785
ItemKind::Use(path, kind) => {
27812786
return clean_use_statement(
27822787
item,
@@ -2909,10 +2914,27 @@ fn clean_impl<'tcx>(
29092914
impl_: &hir::Impl<'tcx>,
29102915
def_id: LocalDefId,
29112916
cx: &mut DocContext<'tcx>,
2917+
// If true, this is an inlined impl and it will be handled later on in the code.
2918+
// In here, we will generate a placeholder for it in order to be able to compute its
2919+
// `doc_cfg` info.
2920+
is_inlined: bool,
29122921
) -> Vec<Item> {
29132922
let tcx = cx.tcx;
29142923
let mut ret = Vec::new();
2915-
let trait_ = impl_.of_trait.map(|t| clean_trait_ref(&t.trait_ref, cx));
2924+
let trait_ = match impl_.of_trait {
2925+
Some(t) => {
2926+
if is_inlined {
2927+
return vec![Item::from_def_id_and_parts(
2928+
def_id.to_def_id(),
2929+
None,
2930+
PlaceholderImplItem,
2931+
cx,
2932+
)];
2933+
}
2934+
Some(clean_trait_ref(&t.trait_ref, cx))
2935+
}
2936+
None => None,
2937+
};
29162938
let items = impl_
29172939
.items
29182940
.iter()

src/librustdoc/clean/types.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,9 @@ pub(crate) enum ItemKind {
885885
TraitItem(Box<Trait>),
886886
TraitAliasItem(TraitAlias),
887887
ImplItem(Box<Impl>),
888+
/// This variant is used only as a placeholder for trait impls in order to correctly compute
889+
/// `doc_cfg` as trait impls are added to `clean::Crate` after we went through the whole tree.
890+
PlaceholderImplItem,
888891
/// A required method in a trait declaration meaning it's only a function signature.
889892
RequiredMethodItem(Box<Function>, Defaultness),
890893
/// A method in a trait impl or a provided method in a trait declaration.
@@ -964,7 +967,8 @@ impl ItemKind {
964967
| AssocTypeItem(..)
965968
| StrippedItem(_)
966969
| KeywordItem
967-
| AttributeItem => [].iter(),
970+
| AttributeItem
971+
| PlaceholderImplItem => [].iter(),
968972
}
969973
}
970974
}

src/librustdoc/fold.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ pub(crate) trait DocFolder: Sized {
9797
| RequiredAssocTypeItem(..)
9898
| AssocTypeItem(..)
9999
| KeywordItem
100-
| AttributeItem => kind,
100+
| AttributeItem
101+
| PlaceholderImplItem => kind,
101102
}
102103
}
103104

src/librustdoc/formats/cache.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ impl DocFolder for CacheBuilder<'_, '_> {
389389
// So would rather leave them to an expert,
390390
// as at least the list is better than `_ => {}`.
391391
}
392+
393+
clean::PlaceholderImplItem => return None,
392394
}
393395

394396
// Maintain the parent stack.

src/librustdoc/formats/item_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl<'a> From<&'a clean::Item> for ItemType {
122122
clean::StaticItem(..) => ItemType::Static,
123123
clean::ConstantItem(..) => ItemType::Constant,
124124
clean::TraitItem(..) => ItemType::Trait,
125-
clean::ImplItem(..) => ItemType::Impl,
125+
clean::ImplItem(..) | clean::PlaceholderImplItem => ItemType::Impl,
126126
clean::RequiredMethodItem(..) => ItemType::TyMethod,
127127
clean::MethodItem(..) => ItemType::Method,
128128
clean::StructFieldItem(..) => ItemType::StructField,

src/librustdoc/json/conversions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum
353353
name: name.as_ref().unwrap().to_string(),
354354
rename: src.map(|x| x.to_string()),
355355
},
356+
// All placeholder impl items should have been removed in the stripper passes.
357+
PlaceholderImplItem => unreachable!(),
356358
}
357359
}
358360

src/librustdoc/passes/calculate_doc_coverage.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ impl DocVisitor<'_> for CoverageCalculator<'_, '_> {
203203
// don't count items in stripped modules
204204
return;
205205
}
206+
clean::PlaceholderImplItem => {
207+
// The "real" impl items are handled below.
208+
return;
209+
}
206210
// docs on `use` and `extern crate` statements are not displayed, so they're not
207211
// worth counting
208212
clean::ImportItem(..) | clean::ExternCrateItem { .. } => {}

src/librustdoc/passes/check_doc_test_visibility.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -
8080
| clean::ImplAssocConstItem(..)
8181
| clean::RequiredAssocTypeItem(..)
8282
| clean::ImplItem(_)
83+
| clean::PlaceholderImplItem
8384
)
8485
{
8586
return false;

src/librustdoc/passes/propagate_doc_cfg.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.
22
3+
use rustc_data_structures::fx::FxHashMap;
34
use rustc_hir::Attribute;
45
use rustc_hir::attrs::{AttributeKind, DocAttribute};
56

67
use crate::clean::inline::{load_attrs, merge_attrs};
7-
use crate::clean::{CfgInfo, Crate, Item, ItemKind};
8+
use crate::clean::{CfgInfo, Crate, Item, ItemId, ItemKind};
89
use crate::core::DocContext;
910
use crate::fold::DocFolder;
1011
use crate::passes::Pass;
@@ -17,7 +18,8 @@ pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass {
1718

1819
pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
1920
if cx.tcx.features().doc_cfg() {
20-
CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr)
21+
CfgPropagator { cx, cfg_info: CfgInfo::default(), impl_cfg_info: FxHashMap::default() }
22+
.fold_crate(cr)
2123
} else {
2224
cr
2325
}
@@ -26,6 +28,10 @@ pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
2628
struct CfgPropagator<'a, 'tcx> {
2729
cx: &'a mut DocContext<'tcx>,
2830
cfg_info: CfgInfo,
31+
32+
/// To ensure the `doc_cfg` feature works with how `rustdoc` handles impls, we need to store
33+
/// the `cfg` info of `impl`s placeholder to use them later on the "real" impl item.
34+
impl_cfg_info: FxHashMap<ItemId, CfgInfo>,
2935
}
3036

3137
/// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from
@@ -78,7 +84,22 @@ impl DocFolder for CfgPropagator<'_, '_> {
7884
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
7985
let old_cfg_info = self.cfg_info.clone();
8086

81-
self.merge_with_parent_attributes(&mut item);
87+
// If we have an impl, we check if it has an associated `cfg` "context", and if so we will
88+
// use that context instead of the actual (wrong) one.
89+
if let ItemKind::ImplItem(_) = item.kind
90+
&& let Some(cfg_info) = self.impl_cfg_info.remove(&item.item_id)
91+
{
92+
self.cfg_info = cfg_info;
93+
}
94+
95+
if let ItemKind::PlaceholderImplItem = item.kind {
96+
// If we have a placeholder impl, we store the current `cfg` "context" to be used
97+
// on the actual impl later on (the impls are generated after we go through the whole
98+
// AST so they're stored in the `krate` object at the end).
99+
self.impl_cfg_info.insert(item.item_id, self.cfg_info.clone());
100+
} else {
101+
self.merge_with_parent_attributes(&mut item);
102+
}
82103

83104
let result = self.fold_item_recur(item);
84105
self.cfg_info = old_cfg_info;

src/librustdoc/passes/propagate_stability.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ impl DocFolder for StabilityPropagator<'_, '_> {
107107
| ItemKind::AssocTypeItem(..)
108108
| ItemKind::PrimitiveItem(..)
109109
| ItemKind::KeywordItem
110-
| ItemKind::AttributeItem => own_stability,
110+
| ItemKind::AttributeItem
111+
| ItemKind::PlaceholderImplItem => own_stability,
111112

112113
ItemKind::StrippedItem(..) => unreachable!(),
113114
}

0 commit comments

Comments
 (0)