about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Markeffsky <@>2024-09-30 01:20:17 +0000
committerLukas Markeffsky <@>2024-09-30 21:58:18 +0000
commitcd31b3acb3c963aff8d226030ac02379d741f3bd (patch)
tree06ce09bb825e85edd62b211340e02597f72badea
parent19252bde65c8587bcf43ae464d49c56791696afa (diff)
downloadrust-cd31b3acb3c963aff8d226030ac02379d741f3bd.tar.gz
rust-cd31b3acb3c963aff8d226030ac02379d741f3bd.zip
rustdoc: rewrite stability inheritance as a pass
-rw-r--r--src/librustdoc/clean/auto_trait.rs1
-rw-r--r--src/librustdoc/clean/blanket_impl.rs1
-rw-r--r--src/librustdoc/clean/inline.rs1
-rw-r--r--src/librustdoc/clean/types.rs55
-rw-r--r--src/librustdoc/html/render/print_item.rs31
-rw-r--r--src/librustdoc/passes/mod.rs5
-rw-r--r--src/librustdoc/passes/propagate_stability.rs72
-rw-r--r--tests/rustdoc-ui/issues/issue-91713.stdout2
-rw-r--r--tests/rustdoc/stability.rs49
9 files changed, 149 insertions, 68 deletions
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 std::{fmt, iter};
 
 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 @@ impl Item {
 
         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 item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
             }
 
             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 item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
                     }
                     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 item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
                      {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 item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
 /// 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 extra_info_tags<'a, 'tcx: '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 @@ pub(crate) use self::strip_priv_imports::STRIP_PRIV_IMPORTS;
 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) const PASSES: &[Pass] = &[
     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) const DEFAULT_PASSES: &[ConditionalPass] = &[
     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<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)
+    }
+}
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 ZzStable;
 
 #[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::StableInUnstable;
+
+    //@ 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::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::unstable::Foo;
+    pub use crate::stable_later::StableInLater;
 
-    //@ has stability/stable_earlier/struct.Bar.html '//span[@class="since"]' '1.0.0'
+    //@ 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::Bar;
+    pub use crate::stable_later::stable_in_later;
 }