Skip to content

Commit

Permalink
Auto merge of rust-lang#131076 - lukas-code:doc-stab2, r=notriddle
Browse files Browse the repository at this point in the history
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 rust-lang#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 rust-lang#131020

r? `@notriddle`
  • Loading branch information
bors committed Oct 1, 2024
2 parents f79ef02 + cd31b3a commit 07f08ff
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 72 deletions.
10 changes: 10 additions & 0 deletions compiler/rustc_attr/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ impl Stability {
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.
Expand Down Expand Up @@ -170,6 +174,12 @@ impl StabilityLevel {
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)]
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/clean/auto_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/clean/blanket_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
60 changes: 15 additions & 45 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -638,10 +611,7 @@ impl Item {
}

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 {
Expand Down
31 changes: 12 additions & 19 deletions src/librustdoc/html/render/print_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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()),
Expand All @@ -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>(
Expand All @@ -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"))?;
}

Expand Down
5 changes: 5 additions & 0 deletions src/librustdoc/passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand All @@ -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),
];

Expand Down
72 changes: 72 additions & 0 deletions src/librustdoc/passes/propagate_stability.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 2 additions & 0 deletions tests/rustdoc-ui/issues/issue-91713.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`:
Expand Down
Loading

0 comments on commit 07f08ff

Please sign in to comment.