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:
commit
07f08ffb2d
@ -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)]
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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"))?;
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
];
|
||||
|
||||
|
72
src/librustdoc/passes/propagate_stability.rs
Normal file
72
src/librustdoc/passes/propagate_stability.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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`:
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user