Auto merge of #131076 - lukas-code:doc-stab2, r=notriddle

rustdoc: rewrite stability inheritance as a doc pass

Since doc inlining can almost arbitrarily change the module hierarchy, we can't just use the HIR ancestors of an item to compute its effective stability. This PR moves the stability inheritance that I implemented in https://github.com/rust-lang/rust/pull/130798 into a new doc pass `propagate-stability` that runs after doc inlining and uses the post-inlining ancestors of an item to correctly compute its effective stability.

fixes https://github.com/rust-lang/rust/issues/131020

r? `@notriddle`
This commit is contained in:
bors 2024-10-01 04:30:33 +00:00
commit 07f08ffb2d
10 changed files with 160 additions and 72 deletions

View File

@ -80,6 +80,10 @@ pub fn is_unstable(&self) -> bool {
pub fn is_stable(&self) -> bool {
self.level.is_stable()
}
pub fn stable_since(&self) -> Option<StableSince> {
self.level.stable_since()
}
}
/// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes.
@ -170,6 +174,12 @@ pub fn is_unstable(&self) -> bool {
pub fn is_stable(&self) -> bool {
matches!(self, StabilityLevel::Stable { .. })
}
pub fn stable_since(&self) -> Option<StableSince> {
match *self {
StabilityLevel::Stable { since, .. } => Some(since),
StabilityLevel::Unstable { .. } => None,
}
}
}
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]

View File

@ -117,6 +117,7 @@ fn synthesize_auto_trait_impl<'tcx>(
name: None,
inner: Box::new(clean::ItemInner {
attrs: Default::default(),
stability: None,
kind: clean::ImplItem(Box::new(clean::Impl {
safety: hir::Safety::Safe,
generics,

View File

@ -87,6 +87,7 @@ pub(crate) fn synthesize_blanket_impls(
item_id: clean::ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id },
inner: Box::new(clean::ItemInner {
attrs: Default::default(),
stability: None,
kind: clean::ImplItem(Box::new(clean::Impl {
safety: hir::Safety::Safe,
generics: clean_ty_generics(

View File

@ -672,6 +672,7 @@ fn build_module_items(
item_id: ItemId::DefId(did),
inner: Box::new(clean::ItemInner {
attrs: Default::default(),
stability: None,
kind: clean::ImportItem(clean::Import::new_simple(
item.ident.name,
clean::ImportSource {

View File

@ -6,7 +6,7 @@
use arrayvec::ArrayVec;
use rustc_ast_pretty::pprust;
use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel, StableSince};
use rustc_attr::{ConstStability, Deprecation, Stability, StableSince};
use rustc_const_eval::const_eval::is_unstable_const_fn;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def::{CtorKind, DefKind, Res};
@ -333,6 +333,8 @@ pub(crate) struct ItemInner {
/// E.g., struct vs enum vs function.
pub(crate) kind: ItemKind,
pub(crate) attrs: Attributes,
/// The effective stability, filled out by the `propagate-stability` pass.
pub(crate) stability: Option<Stability>,
}
impl std::ops::Deref for Item {
@ -381,46 +383,17 @@ fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
}
impl Item {
/// Returns the effective stability of the item.
///
/// This method should only be called after the `propagate-stability` pass has been run.
pub(crate) fn stability(&self, tcx: TyCtxt<'_>) -> Option<Stability> {
let (mut def_id, mut stability) = if let Some(inlined) = self.inline_stmt_id {
let inlined_def_id = inlined.to_def_id();
if let Some(stability) = tcx.lookup_stability(inlined_def_id) {
(inlined_def_id, stability)
} else {
// For re-exports into crates without `staged_api`, reuse the original stability.
// This is necessary, because we always want to mark unstable items.
let def_id = self.def_id()?;
return tcx.lookup_stability(def_id);
}
} else {
let def_id = self.def_id()?;
let stability = tcx.lookup_stability(def_id)?;
(def_id, stability)
};
let StabilityLevel::Stable { mut since, allowed_through_unstable_modules: false } =
stability.level
else {
return Some(stability);
};
// If any of the item's ancestors was stabilized later or is still unstable,
// then report the ancestor's stability instead.
while let Some(parent_def_id) = tcx.opt_parent(def_id) {
if let Some(parent_stability) = tcx.lookup_stability(parent_def_id) {
match parent_stability.level {
StabilityLevel::Unstable { .. } => return Some(parent_stability),
StabilityLevel::Stable { since: parent_since, .. } => {
if parent_since > since {
stability = parent_stability;
since = parent_since;
}
}
}
}
def_id = parent_def_id;
}
Some(stability)
let stability = self.inner.stability;
debug_assert!(
stability.is_some()
|| self.def_id().is_none_or(|did| tcx.lookup_stability(did).is_none()),
"missing stability for cleaned item: {self:?}",
);
stability
}
pub(crate) fn const_stability(&self, tcx: TyCtxt<'_>) -> Option<ConstStability> {
@ -502,7 +475,7 @@ pub(crate) fn from_def_id_and_attrs_and_parts(
Item {
item_id: def_id.into(),
inner: Box::new(ItemInner { kind, attrs }),
inner: Box::new(ItemInner { kind, attrs, stability: None }),
name,
cfg,
inline_stmt_id: None,
@ -638,10 +611,7 @@ pub(crate) fn stability_class(&self, tcx: TyCtxt<'_>) -> Option<String> {
}
pub(crate) fn stable_since(&self, tcx: TyCtxt<'_>) -> Option<StableSince> {
match self.stability(tcx)?.level {
StabilityLevel::Stable { since, .. } => Some(since),
StabilityLevel::Unstable { .. } => None,
}
self.stability(tcx).and_then(|stability| stability.stable_since())
}
pub(crate) fn is_non_exhaustive(&self) -> bool {

View File

@ -436,16 +436,9 @@ fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering {
}
clean::ImportItem(ref import) => {
let stab_tags = if let Some(import_def_id) = import.source.did {
// Just need an item with the correct def_id and attrs
let import_item =
clean::Item { item_id: import_def_id.into(), ..(*myitem).clone() };
let stab_tags = Some(extra_info_tags(&import_item, item, tcx).to_string());
stab_tags
} else {
None
};
let stab_tags = import.source.did.map_or_else(String::new, |import_def_id| {
extra_info_tags(tcx, myitem, item, Some(import_def_id)).to_string()
});
w.write_str(ITEM_TABLE_ROW_OPEN);
let id = match import.kind {
@ -454,7 +447,6 @@ fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering {
}
clean::ImportKind::Glob => String::new(),
};
let stab_tags = stab_tags.unwrap_or_default();
let (stab_tags_before, stab_tags_after) = if stab_tags.is_empty() {
("", "")
} else {
@ -521,7 +513,7 @@ fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering {
{docs_before}{docs}{docs_after}",
name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()),
visibility_and_hidden = visibility_and_hidden,
stab_tags = extra_info_tags(myitem, item, tcx),
stab_tags = extra_info_tags(tcx, myitem, item, None),
class = myitem.type_(),
unsafety_flag = unsafety_flag,
href = item_path(myitem.type_(), myitem.name.unwrap().as_str()),
@ -544,9 +536,10 @@ fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering {
/// Render the stability, deprecation and portability tags that are displayed in the item's summary
/// at the module level.
fn extra_info_tags<'a, 'tcx: 'a>(
tcx: TyCtxt<'tcx>,
item: &'a clean::Item,
parent: &'a clean::Item,
tcx: TyCtxt<'tcx>,
import_def_id: Option<DefId>,
) -> impl fmt::Display + 'a + Captures<'tcx> {
display_fn(move |f| {
fn tag_html<'a>(
@ -564,18 +557,18 @@ fn tag_html<'a>(
}
// The trailing space after each tag is to space it properly against the rest of the docs.
if let Some(depr) = &item.deprecation(tcx) {
let deprecation = import_def_id
.map_or_else(|| item.deprecation(tcx), |import_did| tcx.lookup_deprecation(import_did));
if let Some(depr) = deprecation {
let message = if depr.is_in_effect() { "Deprecated" } else { "Deprecation planned" };
write!(f, "{}", tag_html("deprecated", "", message))?;
}
// The "rustc_private" crates are permanently unstable so it makes no sense
// to render "unstable" everywhere.
if item
.stability(tcx)
.as_ref()
.is_some_and(|s| s.is_unstable() && s.feature != sym::rustc_private)
{
let stability = import_def_id
.map_or_else(|| item.stability(tcx), |import_did| tcx.lookup_stability(import_did));
if stability.is_some_and(|s| s.is_unstable() && s.feature != sym::rustc_private) {
write!(f, "{}", tag_html("unstable", "", "Experimental"))?;
}

View File

@ -23,6 +23,9 @@
mod propagate_doc_cfg;
pub(crate) use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
mod propagate_stability;
pub(crate) use self::propagate_stability::PROPAGATE_STABILITY;
pub(crate) mod collect_intra_doc_links;
pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
@ -75,6 +78,7 @@ pub(crate) enum Condition {
STRIP_PRIVATE,
STRIP_PRIV_IMPORTS,
PROPAGATE_DOC_CFG,
PROPAGATE_STABILITY,
COLLECT_INTRA_DOC_LINKS,
COLLECT_TRAIT_IMPLS,
CALCULATE_DOC_COVERAGE,
@ -91,6 +95,7 @@ pub(crate) enum Condition {
ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate),
ConditionalPass::always(COLLECT_INTRA_DOC_LINKS),
ConditionalPass::always(PROPAGATE_DOC_CFG),
ConditionalPass::always(PROPAGATE_STABILITY),
ConditionalPass::always(RUN_LINTS),
];

View File

@ -0,0 +1,72 @@
//! Propagates stability to child items.
//!
//! The purpose of this pass is to make items whose parents are "more unstable"
//! than the item itself inherit the parent's stability.
//! For example, [`core::error::Error`] is marked as stable since 1.0.0, but the
//! [`core::error`] module is marked as stable since 1.81.0, so we want to show
//! [`core::error::Error`] as stable since 1.81.0 as well.
use rustc_attr::{Stability, StabilityLevel};
use rustc_hir::def_id::CRATE_DEF_ID;
use crate::clean::{Crate, Item, ItemId};
use crate::core::DocContext;
use crate::fold::DocFolder;
use crate::passes::Pass;
pub(crate) const PROPAGATE_STABILITY: Pass = Pass {
name: "propagate-stability",
run: propagate_stability,
description: "propagates stability to child items",
};
pub(crate) fn propagate_stability(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
let crate_stability = cx.tcx.lookup_stability(CRATE_DEF_ID);
StabilityPropagator { parent_stability: crate_stability, cx }.fold_crate(cr)
}
struct StabilityPropagator<'a, 'tcx> {
parent_stability: Option<Stability>,
cx: &'a mut DocContext<'tcx>,
}
impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> {
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
let parent_stability = self.parent_stability;
let stability = match item.item_id {
ItemId::DefId(def_id) => {
let own_stability = self.cx.tcx.lookup_stability(def_id);
// If any of the item's parents was stabilized later or is still unstable,
// then use the parent's stability instead.
if let Some(own_stab) = own_stability
&& let StabilityLevel::Stable {
since: own_since,
allowed_through_unstable_modules: false,
} = own_stab.level
&& let Some(parent_stab) = parent_stability
&& (parent_stab.is_unstable()
|| parent_stab
.stable_since()
.is_some_and(|parent_since| parent_since > own_since))
{
parent_stability
} else {
own_stability
}
}
ItemId::Auto { .. } | ItemId::Blanket { .. } => {
// For now, we do now show stability for synthesized impls.
None
}
};
item.inner.stability = stability;
self.parent_stability = stability;
let item = self.fold_item_recur(item);
self.parent_stability = parent_stability;
Some(item)
}
}

View File

@ -5,6 +5,7 @@ strip-aliased-non-local - strips all non-local private aliased items from the ou
strip-private - strips all private items from a crate which cannot be seen externally, implies strip-priv-imports
strip-priv-imports - strips all private import statements (`use`, `extern crate`) from a crate
propagate-doc-cfg - propagates `#[doc(cfg(...))]` to child items
propagate-stability - propagates stability to child items
collect-intra-doc-links - resolves intra-doc links
collect-trait-impls - retrieves trait impls for items in the crate
calculate-doc-coverage - counts the number of items with and without documentation
@ -19,6 +20,7 @@ strip-aliased-non-local
strip-priv-imports (when --document-private-items)
collect-intra-doc-links
propagate-doc-cfg
propagate-stability
run-lints
Passes run with `--show-coverage`:

View File

@ -25,28 +25,61 @@ pub struct Unstable {
#[unstable(feature = "unstable", issue = "none")]
pub mod unstable {
//@ !hasraw stability/unstable/struct.Foo.html '//span[@class="since"]'
//@ !hasraw stability/unstable/struct.StableInUnstable.html \
// '//span[@class="since"]'
//@ has - '//div[@class="stab unstable"]' 'experimental'
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Foo;
pub struct StableInUnstable;
#[stable(feature = "rust1", since = "1.0.0")]
pub mod stable_in_unstable {
//@ !hasraw stability/unstable/stable_in_unstable/struct.Inner.html \
// '//span[@class="since"]'
//@ has - '//div[@class="stab unstable"]' 'experimental'
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Inner;
}
}
#[stable(feature = "rust2", since = "2.2.2")]
pub mod stable_later {
//@ has stability/stable_later/struct.Bar.html '//span[@class="since"]' '2.2.2'
//@ has stability/stable_later/struct.StableInLater.html \
// '//span[@class="since"]' '2.2.2'
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Bar;
pub struct StableInLater;
#[stable(feature = "rust1", since = "1.0.0")]
pub mod stable_in_later {
//@ has stability/stable_later/stable_in_later/struct.Inner.html \
// '//span[@class="since"]' '2.2.2'
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Inner;
}
}
#[stable(feature = "rust1", since = "1.0.0")]
pub mod stable_earlier {
//@ has stability/stable_earlier/struct.Foo.html '//span[@class="since"]' '1.0.0'
//@ has stability/stable_earlier/struct.StableInUnstable.html \
// '//span[@class="since"]' '1.0.0'
#[doc(inline)]
#[stable(feature = "rust1", since = "1.0.0")]
pub use crate::unstable::Foo;
pub use crate::unstable::StableInUnstable;
//@ has stability/stable_earlier/struct.Bar.html '//span[@class="since"]' '1.0.0'
//@ has stability/stable_earlier/stable_in_unstable/struct.Inner.html \
// '//span[@class="since"]' '1.0.0'
#[doc(inline)]
#[stable(feature = "rust1", since = "1.0.0")]
pub use crate::stable_later::Bar;
pub use crate::unstable::stable_in_unstable;
//@ has stability/stable_earlier/struct.StableInLater.html \
// '//span[@class="since"]' '1.0.0'
#[doc(inline)]
#[stable(feature = "rust1", since = "1.0.0")]
pub use crate::stable_later::StableInLater;
//@ has stability/stable_earlier/stable_in_later/struct.Inner.html \
// '//span[@class="since"]' '1.0.0'
#[doc(inline)]
#[stable(feature = "rust1", since = "1.0.0")]
pub use crate::stable_later::stable_in_later;
}