Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustdoc: rewrite stability inheritance as a doc pass #131076

Merged
merged 2 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading