Skip to content

Fix doc cfg on reexports #101006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 26, 2022
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
2 changes: 1 addition & 1 deletion src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ pub(crate) fn build_impls(
}

/// `parent_module` refers to the parent of the re-export, not the original item
fn merge_attrs(
pub(crate) fn merge_attrs(
cx: &mut DocContext<'_>,
parent_module: Option<DefId>,
old_attrs: Attrs<'_>,
Expand Down
27 changes: 26 additions & 1 deletion src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ impl Item {
cx: &mut DocContext<'_>,
cfg: Option<Arc<Cfg>>,
) -> Item {
trace!("name={:?}, def_id={:?}", name, def_id);
trace!("name={:?}, def_id={:?} cfg={:?}", name, def_id, cfg);

// Primitives and Keywords are written in the source code as private modules.
// The modules need to be private so that nobody actually uses them, but the
Expand Down Expand Up @@ -801,6 +801,31 @@ impl ItemKind {
| KeywordItem => [].iter(),
}
}

/// Returns `true` if this item does not appear inside an impl block.
pub(crate) fn is_non_assoc(&self) -> bool {
matches!(
self,
StructItem(_)
| UnionItem(_)
| EnumItem(_)
| TraitItem(_)
| ModuleItem(_)
| ExternCrateItem { .. }
| FunctionItem(_)
| TypedefItem(_)
| OpaqueTyItem(_)
| StaticItem(_)
| ConstantItem(_)
| TraitAliasItem(_)
| ForeignFunctionItem(_)
| ForeignStaticItem(_)
| ForeignTypeItem
| MacroItem(_)
| ProcMacroItem(_)
| PrimitiveItem(_)
)
}
}

#[derive(Clone, Debug)]
Expand Down
9 changes: 8 additions & 1 deletion src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,14 @@ fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<Strin
(cfg, _) => cfg.as_deref().cloned(),
};

debug!("Portability {:?} - {:?} = {:?}", item.cfg, parent.and_then(|p| p.cfg.as_ref()), cfg);
debug!(
"Portability {:?} {:?} (parent: {:?}) - {:?} = {:?}",
item.name,
item.cfg,
parent,
parent.and_then(|p| p.cfg.as_ref()),
cfg
);

Some(format!("<div class=\"stab portability\">{}</div>", cfg?.render_long_html()))
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/html/render/print_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) ->
(cfg, _) => cfg.as_deref().cloned(),
};

debug!("Portability {:?} - {:?} = {:?}", item.cfg, parent.cfg, cfg);
debug!("Portability name={:?} {:?} - {:?} = {:?}", item.name, item.cfg, parent.cfg, cfg);
if let Some(ref cfg) = cfg {
tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html());
}
Expand Down
39 changes: 35 additions & 4 deletions src/librustdoc/passes/propagate_doc_cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,53 @@
use std::sync::Arc;

use crate::clean::cfg::Cfg;
use crate::clean::inline::{load_attrs, merge_attrs};
use crate::clean::{Crate, Item};
use crate::core::DocContext;
use crate::fold::DocFolder;
use crate::passes::Pass;

use rustc_hir::def_id::LocalDefId;

pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass {
name: "propagate-doc-cfg",
run: propagate_doc_cfg,
description: "propagates `#[doc(cfg(...))]` to child items",
};

pub(crate) fn propagate_doc_cfg(cr: Crate, _: &mut DocContext<'_>) -> Crate {
CfgPropagator { parent_cfg: None }.fold_crate(cr)
pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
CfgPropagator { parent_cfg: None, parent: None, cx }.fold_crate(cr)
}

struct CfgPropagator {
struct CfgPropagator<'a, 'tcx> {
parent_cfg: Option<Arc<Cfg>>,
parent: Option<LocalDefId>,
cx: &'a mut DocContext<'tcx>,
}

impl DocFolder for CfgPropagator {
impl<'a, 'tcx> DocFolder for CfgPropagator<'a, 'tcx> {
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
let old_parent_cfg = self.parent_cfg.clone();

if item.kind.is_non_assoc() &&
let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) {
let hir = self.cx.tcx.hir();
let hir_id = hir.local_def_id_to_hir_id(def_id);
let expected_parent = hir.get_parent_item(hir_id);

// If parents are different, it means that `item` is a reexport and we need to compute
// the actual `cfg` by iterating through its "real" parents.
if self.parent != Some(expected_parent) {
let mut attrs = Vec::new();
for (parent_hir_id, _) in hir.parent_iter(hir_id) {
let def_id = hir.local_def_id(parent_hir_id).to_def_id();
attrs.extend_from_slice(load_attrs(self.cx, def_id));
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this is going to wind up copying real cfg, and not just doc(cfg). Do we want that? The way libc uses re-exports, this seems like it would make their lives harder, not easier.

Copy link
Member Author

@GuillaumeGomez GuillaumeGomez Aug 25, 2022

Choose a reason for hiding this comment

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

We need to keep both until we call merge_attrs because it internally calls AttributesExt::cfg which makes the "filtering" depending on the doc_cfg and the doc_auto_cfg features (which is why it returns a tuple of two and not just a ast::Attributes).

Copy link
Contributor

Choose a reason for hiding this comment

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

But here's a screenshot of what the libc looks like, when it's built from nightly and #![feature(doc_auto_cfg)] is added:

image

And here's a screenshot of what libc looks like, when it's built from your branch and #![feature(doc_auto_cfg)] is added:

image

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry I'm a bit lost: isn't it how it should be? The reexports should display the cfg features of the reexported item, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

The docs are claiming things that aren't true:

image

This thing claims that c_short is present on Unix and absent on Windows. But c_short is definitely present on Windows; it's just implemented separately.

Basically, if this PR is going to be merged, then doc_auto_cfg should probably be off by default. Otherwise, a lot of crates with platform-specific implementations of common interfaces (libc is just a very high-profile example) are going to have wrong documentation.

Copy link
Member Author

@GuillaumeGomez GuillaumeGomez Aug 25, 2022

Choose a reason for hiding this comment

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

I don't agree on that but it confirms that allowing to disable/enable doc_auto_cfg is a must have.

Copy link
Contributor

Choose a reason for hiding this comment

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

As long as we agree that doc_auto_cfg needs to be optional, I’m fine with merging this.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this PR just highlighted it. The discussion now will be either it's enabled by default or not and what the attribute to enable/disable should look like.

}
let (_, cfg) =
merge_attrs(self.cx, None, item.attrs.other_attrs.as_slice(), Some(&attrs));
item.cfg = cfg;
}
}
let new_cfg = match (self.parent_cfg.take(), item.cfg.take()) {
(None, None) => None,
(Some(rc), None) | (None, Some(rc)) => Some(rc),
Expand All @@ -37,8 +61,15 @@ impl DocFolder for CfgPropagator {
self.parent_cfg = new_cfg.clone();
item.cfg = new_cfg;

let old_parent =
if let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) {
self.parent.replace(def_id)
} else {
self.parent.take()
};
let result = self.fold_item_recur(item);
self.parent_cfg = old_parent_cfg;
self.parent = old_parent;

Some(result)
}
Expand Down
33 changes: 33 additions & 0 deletions src/test/rustdoc/cfg_doc_reexport.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#![feature(doc_cfg)]
#![feature(no_core)]

#![crate_name = "foo"]
#![no_core]

// @has 'foo/index.html'
// @has - '//*[@class="item-left module-item"]/*[@class="stab portability"]' 'foobar'
// @has - '//*[@class="item-left module-item"]/*[@class="stab portability"]' 'bar'

#[doc(cfg(feature = "foobar"))]
mod imp_priv {
// @has 'foo/struct.BarPriv.html'
// @has - '//*[@id="main-content"]/*[@class="item-info"]/*[@class="stab portability"]' \
// 'Available on crate feature foobar only.'
pub struct BarPriv {}
impl BarPriv {
pub fn test() {}
}
}
#[doc(cfg(feature = "foobar"))]
pub use crate::imp_priv::*;

pub mod bar {
// @has 'foo/bar/struct.Bar.html'
// @has - '//*[@id="main-content"]/*[@class="item-info"]/*[@class="stab portability"]' \
// 'Available on crate feature bar only.'
#[doc(cfg(feature = "bar"))]
pub struct Bar;
}

#[doc(cfg(feature = "bar"))]
pub use bar::Bar;