Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2776,7 +2776,12 @@ fn clean_maybe_renamed_item<'tcx>(
// These kinds of item either don't need a `name` or accept a `None` one so we handle them
// before.
match item.kind {
ItemKind::Impl(ref impl_) => return clean_impl(impl_, item.owner_id.def_id, cx),
ItemKind::Impl(ref impl_) => {
// If `renamed` is `Some()` for an `impl`, it means it's been inlined because we use
// it as a marker to indicate that this is an inlined impl and that we should
// generate an impl placeholder and not a "real" impl item.
return clean_impl(impl_, item.owner_id.def_id, cx, renamed.is_some());
}
ItemKind::Use(path, kind) => {
return clean_use_statement(
item,
Expand Down Expand Up @@ -2909,10 +2914,27 @@ fn clean_impl<'tcx>(
impl_: &hir::Impl<'tcx>,
def_id: LocalDefId,
cx: &mut DocContext<'tcx>,
// If true, this is an inlined impl and it will be handled later on in the code.
// In here, we will generate a placeholder for it in order to be able to compute its
// `doc_cfg` info.
is_inlined: bool,
) -> Vec<Item> {
let tcx = cx.tcx;
let mut ret = Vec::new();
let trait_ = impl_.of_trait.map(|t| clean_trait_ref(&t.trait_ref, cx));
let trait_ = match impl_.of_trait {
Some(t) => {
if is_inlined {
return vec![Item::from_def_id_and_parts(
def_id.to_def_id(),
None,
PlaceholderImplItem,
cx,
)];
}
Some(clean_trait_ref(&t.trait_ref, cx))
}
None => None,
};
let items = impl_
.items
.iter()
Expand Down
6 changes: 5 additions & 1 deletion src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,9 @@ pub(crate) enum ItemKind {
TraitItem(Box<Trait>),
TraitAliasItem(TraitAlias),
ImplItem(Box<Impl>),
/// This variant is used only as a placeholder for trait impls in order to correctly compute
/// `doc_cfg` as trait impls are added to `clean::Crate` after we went through the whole tree.
PlaceholderImplItem,
/// A required method in a trait declaration meaning it's only a function signature.
RequiredMethodItem(Box<Function>, Defaultness),
/// A method in a trait impl or a provided method in a trait declaration.
Expand Down Expand Up @@ -964,7 +967,8 @@ impl ItemKind {
| AssocTypeItem(..)
| StrippedItem(_)
| KeywordItem
| AttributeItem => [].iter(),
| AttributeItem
| PlaceholderImplItem => [].iter(),
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/librustdoc/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ pub(crate) trait DocFolder: Sized {
| RequiredAssocTypeItem(..)
| AssocTypeItem(..)
| KeywordItem
| AttributeItem => kind,
| AttributeItem
| PlaceholderImplItem => kind,
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/formats/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ impl DocFolder for CacheBuilder<'_, '_> {
// So would rather leave them to an expert,
// as at least the list is better than `_ => {}`.
}

clean::PlaceholderImplItem => return None,
}

// Maintain the parent stack.
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/formats/item_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl<'a> From<&'a clean::Item> for ItemType {
clean::StaticItem(..) => ItemType::Static,
clean::ConstantItem(..) => ItemType::Constant,
clean::TraitItem(..) => ItemType::Trait,
clean::ImplItem(..) => ItemType::Impl,
clean::ImplItem(..) | clean::PlaceholderImplItem => ItemType::Impl,
clean::RequiredMethodItem(..) => ItemType::TyMethod,
clean::MethodItem(..) => ItemType::Method,
clean::StructFieldItem(..) => ItemType::StructField,
Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/json/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum
name: name.as_ref().unwrap().to_string(),
rename: src.map(|x| x.to_string()),
},
// All placeholder impl items should have been removed in the stripper passes.
PlaceholderImplItem => unreachable!(),
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/librustdoc/passes/calculate_doc_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ impl DocVisitor<'_> for CoverageCalculator<'_, '_> {
// don't count items in stripped modules
return;
}
clean::PlaceholderImplItem => {
// The "real" impl items are handled below.
return;
}
// docs on `use` and `extern crate` statements are not displayed, so they're not
// worth counting
clean::ImportItem(..) | clean::ExternCrateItem { .. } => {}
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/passes/check_doc_test_visibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -
| clean::ImplAssocConstItem(..)
| clean::RequiredAssocTypeItem(..)
| clean::ImplItem(_)
| clean::PlaceholderImplItem
)
{
return false;
Expand Down
27 changes: 24 additions & 3 deletions src/librustdoc/passes/propagate_doc_cfg.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items.

use rustc_data_structures::fx::FxHashMap;
use rustc_hir::Attribute;
use rustc_hir::attrs::{AttributeKind, DocAttribute};

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

pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
if cx.tcx.features().doc_cfg() {
CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr)
CfgPropagator { cx, cfg_info: CfgInfo::default(), impl_cfg_info: FxHashMap::default() }
.fold_crate(cr)
} else {
cr
}
Expand All @@ -26,6 +28,10 @@ pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
struct CfgPropagator<'a, 'tcx> {
cx: &'a mut DocContext<'tcx>,
cfg_info: CfgInfo,

/// To ensure the `doc_cfg` feature works with how `rustdoc` handles impls, we need to store
/// the `cfg` info of `impl`s placeholder to use them later on the "real" impl item.
impl_cfg_info: FxHashMap<ItemId, CfgInfo>,
}

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

self.merge_with_parent_attributes(&mut item);
// If we have an impl, we check if it has an associated `cfg` "context", and if so we will
// use that context instead of the actual (wrong) one.
if let ItemKind::ImplItem(_) = item.kind
&& let Some(cfg_info) = self.impl_cfg_info.remove(&item.item_id)
{
self.cfg_info = cfg_info;
}

if let ItemKind::PlaceholderImplItem = item.kind {
// If we have a placeholder impl, we store the current `cfg` "context" to be used
// on the actual impl later on (the impls are generated after we go through the whole
// AST so they're stored in the `krate` object at the end).
self.impl_cfg_info.insert(item.item_id, self.cfg_info.clone());
} else {
self.merge_with_parent_attributes(&mut item);
}

let result = self.fold_item_recur(item);
self.cfg_info = old_cfg_info;
Expand Down
3 changes: 2 additions & 1 deletion src/librustdoc/passes/propagate_stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ impl DocFolder for StabilityPropagator<'_, '_> {
| ItemKind::AssocTypeItem(..)
| ItemKind::PrimitiveItem(..)
| ItemKind::KeywordItem
| ItemKind::AttributeItem => own_stability,
| ItemKind::AttributeItem
| ItemKind::PlaceholderImplItem => own_stability,

ItemKind::StrippedItem(..) => unreachable!(),
}
Expand Down
4 changes: 4 additions & 0 deletions src/librustdoc/passes/stripper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ impl DocFolder for Stripper<'_, '_> {

clean::ImplItem(..) => {}

// Since the `doc_cfg` propagation was handled before the current pass, we can (and
// should) remove all placeholder impl items.
clean::PlaceholderImplItem => return None,

// tymethods etc. have no control over privacy
clean::RequiredMethodItem(..)
| clean::RequiredAssocConstItem(..)
Expand Down
3 changes: 2 additions & 1 deletion src/librustdoc/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ pub(crate) trait DocVisitor<'a>: Sized {
| RequiredAssocTypeItem(..)
| AssocTypeItem(..)
| KeywordItem
| AttributeItem => {}
| AttributeItem
| PlaceholderImplItem => {}
}
}

Expand Down
27 changes: 18 additions & 9 deletions src/librustdoc/visit_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,19 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
&& !inherits_doc_hidden(tcx, item_def_id, None)
}

#[inline]
fn add_impl_to_current_mod(&mut self, item: &'tcx hir::Item<'_>, impl_: hir::Impl<'_>) {
self.add_to_current_mod(
item,
// The symbol here is used as a "sentinel" value and has no meaning in
// itself. It just tells that this is an inlined impl and that it should not
// be cleaned as a normal `ImplItem` but instead as a `PlaceholderImplItem`.
// It's to ensure that `doc_cfg` inheritance works as expected.
if impl_.of_trait.is_none() { None } else { Some(rustc_span::symbol::kw::Impl) },
None,
);
}

#[inline]
fn add_to_current_mod(
&mut self,
Expand Down Expand Up @@ -426,12 +439,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
// }
// Bar::bar();
// ```
if let hir::ItemKind::Impl(impl_) = item.kind &&
// Don't duplicate impls when inlining or if it's implementing a trait, we'll pick
// them up regardless of where they're located.
impl_.of_trait.is_none()
{
self.add_to_current_mod(item, None, None);
if let hir::ItemKind::Impl(impl_) = item.kind {
self.add_impl_to_current_mod(item, impl_);
}
return;
}
Expand Down Expand Up @@ -530,10 +539,10 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
}
}
hir::ItemKind::Impl(impl_) => {
// Don't duplicate impls when inlining or if it's implementing a trait, we'll pick
// Don't duplicate impls when inlining, we'll pick
// them up regardless of where they're located.
if !self.inlining && impl_.of_trait.is_none() {
self.add_to_current_mod(item, None, None);
if !self.inlining {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of copy-pasting this code, I'd like to see it refactored into a utility method add_impl_to_current_mod.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

self.add_impl_to_current_mod(item, impl_);
}
}
}
Expand Down
92 changes: 92 additions & 0 deletions tests/rustdoc-html/doc-cfg/trait-impls-manual.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// This test ensures that `doc_cfg` feature is working as expected on trait impls.
// Regression test for <https://github.com/rust-lang/rust/issues/153655>.

#![feature(doc_cfg)]
#![doc(auto_cfg(hide(
target_pointer_width = "64",
)))]

#![crate_name = "foo"]

pub trait Trait {
fn f(&self) {}
}

pub trait Bob {
fn bob(&self) {}
}

pub trait Foo {
fn foo(&self) {}
}

pub struct X;

//@has 'foo/struct.X.html'
//@count - '//*[@id="impl-Bob-for-X"]' 1
//@count - '//*[@id="impl-Bob-for-X"]/*[@class="item-info"]' 0
//@count - '//*[@id="impl-Trait-for-X"]' 1
//@count - '//*[@id="impl-Trait-for-X"]/*[@class="item-info"]' 0

// If you need to update this XPath, in particular `item-info`, update all
// the others in this file.
//@count - '//*[@id="impl-Foo-for-X"]/*[@class="item-info"]' 1

//@has 'foo/trait.Trait.html'
//@count - '//*[@id="impl-Trait-for-X"]' 1
//@count - '//*[@id="impl-Trait-for-X"]/*[@class="item-info"]' 0
#[doc(cfg(any(target_pointer_width = "64", target_arch = "wasm32")))]
#[doc(auto_cfg(hide(target_arch = "wasm32")))]
mod imp {
impl super::Trait for super::X { fn f(&self) {} }
}

//@has 'foo/trait.Bob.html'
//@count - '//*[@id="impl-Bob-for-X"]' 1
//@count - '//*[@id="impl-Bob-for-X"]/*[@class="item-info"]' 0
#[doc(cfg(any(target_pointer_width = "64", target_arch = "wasm32")))]
#[doc(auto_cfg = false)]
mod imp2 {
impl super::Bob for super::X { fn bob(&self) {} }
}

//@has 'foo/trait.Foo.html'
//@count - '//*[@id="impl-Foo-for-X"]/*[@class="item-info"]' 1
// We use this to force xpath tests to be updated if `item-info` class is changed.
#[doc(cfg(any(target_pointer_width = "64", target_arch = "wasm32")))]
mod imp3 {
impl super::Foo for super::X { fn foo(&self) {} }
}

pub struct Y;

//@has 'foo/struct.Y.html'
//@count - '//*[@id="implementations-list"]/*[@class="impl-items"]' 1
//@count - '//*[@id="implementations-list"]/*[@class="impl-items"]/*[@class="item-info"]' 0
#[doc(cfg(any(target_pointer_width = "64", target_arch = "wasm32")))]
#[doc(auto_cfg(hide(target_arch = "wasm32")))]
mod imp4 {
impl super::Y { pub fn plain_auto() {} }
}

pub struct Z;

//@has 'foo/struct.Z.html'
//@count - '//*[@id="implementations-list"]/*[@class="impl-items"]' 1
//@count - '//*[@id="implementations-list"]/*[@class="impl-items"]/*[@class="item-info"]' 0
#[doc(cfg(any(target_pointer_width = "64", target_arch = "wasm32")))]
#[doc(auto_cfg = false)]
mod imp5 {
impl super::Z { pub fn plain_auto() {} }
}

// The "witness" which has the item info.
pub struct W;

//@has 'foo/struct.W.html'
//@count - '//*[@id="implementations-list"]/*[@class="impl-items"]' 1
//@count - '//*[@id="implementations-list"]/*[@class="impl-items"]/*[@class="item-info"]' 1
#[doc(cfg(any(target_pointer_width = "64", target_arch = "wasm32")))]
mod imp6 {
impl super::W { pub fn plain_auto() {} }
}
Loading
Loading