From cd31b3acb3c963aff8d226030ac02379d741f3bd Mon Sep 17 00:00:00 2001 From: Lukas Markeffsky <@> Date: Mon, 30 Sep 2024 01:20:17 +0000 Subject: [PATCH] rustdoc: rewrite stability inheritance as a pass --- src/librustdoc/clean/auto_trait.rs | 1 + src/librustdoc/clean/blanket_impl.rs | 1 + src/librustdoc/clean/inline.rs | 1 + src/librustdoc/clean/types.rs | 55 ++++----------- src/librustdoc/html/render/print_item.rs | 31 ++++----- src/librustdoc/passes/mod.rs | 5 ++ src/librustdoc/passes/propagate_stability.rs | 72 ++++++++++++++++++++ tests/rustdoc-ui/issues/issue-91713.stdout | 2 + tests/rustdoc/stability.rs | 49 ++++++++++--- 9 files changed, 149 insertions(+), 68 deletions(-) create mode 100644 src/librustdoc/passes/propagate_stability.rs diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 08c88fc950d..d966f993104 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -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, diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs index 36821294885..1f3cb4a61b8 100644 --- a/src/librustdoc/clean/blanket_impl.rs +++ b/src/librustdoc/clean/blanket_impl.rs @@ -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( diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index d3c4ef4dc90..e7f921eef7f 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -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 { diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 2fccdb923d4..a3277e8ca92 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -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, } 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 { - 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 { @@ -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, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index d120e7f36eb..38276e4d20c 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -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, ) -> 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"))?; } diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index b1dc766049d..f5b78023721 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -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), ]; diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs new file mode 100644 index 00000000000..f51e993bfa5 --- /dev/null +++ b/src/librustdoc/passes/propagate_stability.rs @@ -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, + cx: &'a mut DocContext<'tcx>, +} + +impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { + fn fold_item(&mut self, mut item: Item) -> Option { + 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) + } +} diff --git a/tests/rustdoc-ui/issues/issue-91713.stdout b/tests/rustdoc-ui/issues/issue-91713.stdout index 1ea3dbfb59f..790e58b0df9 100644 --- a/tests/rustdoc-ui/issues/issue-91713.stdout +++ b/tests/rustdoc-ui/issues/issue-91713.stdout @@ -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`: diff --git a/tests/rustdoc/stability.rs b/tests/rustdoc/stability.rs index de855b43ba5..fc72154cad8 100644 --- a/tests/rustdoc/stability.rs +++ b/tests/rustdoc/stability.rs @@ -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; }