Skip to content

Commit 07f08ff

Browse files
committed
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 #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 #131020 r? `@notriddle`
2 parents f79ef02 + cd31b3a commit 07f08ff

File tree

10 files changed

+160
-72
lines changed

10 files changed

+160
-72
lines changed

Diff for: compiler/rustc_attr/src/builtin.rs

+10
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ impl Stability {
8080
pub fn is_stable(&self) -> bool {
8181
self.level.is_stable()
8282
}
83+
84+
pub fn stable_since(&self) -> Option<StableSince> {
85+
self.level.stable_since()
86+
}
8387
}
8488

8589
/// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes.
@@ -170,6 +174,12 @@ impl StabilityLevel {
170174
pub fn is_stable(&self) -> bool {
171175
matches!(self, StabilityLevel::Stable { .. })
172176
}
177+
pub fn stable_since(&self) -> Option<StableSince> {
178+
match *self {
179+
StabilityLevel::Stable { since, .. } => Some(since),
180+
StabilityLevel::Unstable { .. } => None,
181+
}
182+
}
173183
}
174184

175185
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]

Diff for: src/librustdoc/clean/auto_trait.rs

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ fn synthesize_auto_trait_impl<'tcx>(
117117
name: None,
118118
inner: Box::new(clean::ItemInner {
119119
attrs: Default::default(),
120+
stability: None,
120121
kind: clean::ImplItem(Box::new(clean::Impl {
121122
safety: hir::Safety::Safe,
122123
generics,

Diff for: src/librustdoc/clean/blanket_impl.rs

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub(crate) fn synthesize_blanket_impls(
8787
item_id: clean::ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id },
8888
inner: Box::new(clean::ItemInner {
8989
attrs: Default::default(),
90+
stability: None,
9091
kind: clean::ImplItem(Box::new(clean::Impl {
9192
safety: hir::Safety::Safe,
9293
generics: clean_ty_generics(

Diff for: src/librustdoc/clean/inline.rs

+1
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ fn build_module_items(
672672
item_id: ItemId::DefId(did),
673673
inner: Box::new(clean::ItemInner {
674674
attrs: Default::default(),
675+
stability: None,
675676
kind: clean::ImportItem(clean::Import::new_simple(
676677
item.ident.name,
677678
clean::ImportSource {

Diff for: src/librustdoc/clean/types.rs

+15-45
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{fmt, iter};
66

77
use arrayvec::ArrayVec;
88
use rustc_ast_pretty::pprust;
9-
use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel, StableSince};
9+
use rustc_attr::{ConstStability, Deprecation, Stability, StableSince};
1010
use rustc_const_eval::const_eval::is_unstable_const_fn;
1111
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1212
use rustc_hir::def::{CtorKind, DefKind, Res};
@@ -333,6 +333,8 @@ pub(crate) struct ItemInner {
333333
/// E.g., struct vs enum vs function.
334334
pub(crate) kind: ItemKind,
335335
pub(crate) attrs: Attributes,
336+
/// The effective stability, filled out by the `propagate-stability` pass.
337+
pub(crate) stability: Option<Stability>,
336338
}
337339

338340
impl std::ops::Deref for Item {
@@ -381,46 +383,17 @@ fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
381383
}
382384

383385
impl Item {
386+
/// Returns the effective stability of the item.
387+
///
388+
/// This method should only be called after the `propagate-stability` pass has been run.
384389
pub(crate) fn stability(&self, tcx: TyCtxt<'_>) -> Option<Stability> {
385-
let (mut def_id, mut stability) = if let Some(inlined) = self.inline_stmt_id {
386-
let inlined_def_id = inlined.to_def_id();
387-
if let Some(stability) = tcx.lookup_stability(inlined_def_id) {
388-
(inlined_def_id, stability)
389-
} else {
390-
// For re-exports into crates without `staged_api`, reuse the original stability.
391-
// This is necessary, because we always want to mark unstable items.
392-
let def_id = self.def_id()?;
393-
return tcx.lookup_stability(def_id);
394-
}
395-
} else {
396-
let def_id = self.def_id()?;
397-
let stability = tcx.lookup_stability(def_id)?;
398-
(def_id, stability)
399-
};
400-
401-
let StabilityLevel::Stable { mut since, allowed_through_unstable_modules: false } =
402-
stability.level
403-
else {
404-
return Some(stability);
405-
};
406-
407-
// If any of the item's ancestors was stabilized later or is still unstable,
408-
// then report the ancestor's stability instead.
409-
while let Some(parent_def_id) = tcx.opt_parent(def_id) {
410-
if let Some(parent_stability) = tcx.lookup_stability(parent_def_id) {
411-
match parent_stability.level {
412-
StabilityLevel::Unstable { .. } => return Some(parent_stability),
413-
StabilityLevel::Stable { since: parent_since, .. } => {
414-
if parent_since > since {
415-
stability = parent_stability;
416-
since = parent_since;
417-
}
418-
}
419-
}
420-
}
421-
def_id = parent_def_id;
422-
}
423-
Some(stability)
390+
let stability = self.inner.stability;
391+
debug_assert!(
392+
stability.is_some()
393+
|| self.def_id().is_none_or(|did| tcx.lookup_stability(did).is_none()),
394+
"missing stability for cleaned item: {self:?}",
395+
);
396+
stability
424397
}
425398

426399
pub(crate) fn const_stability(&self, tcx: TyCtxt<'_>) -> Option<ConstStability> {
@@ -502,7 +475,7 @@ impl Item {
502475

503476
Item {
504477
item_id: def_id.into(),
505-
inner: Box::new(ItemInner { kind, attrs }),
478+
inner: Box::new(ItemInner { kind, attrs, stability: None }),
506479
name,
507480
cfg,
508481
inline_stmt_id: None,
@@ -638,10 +611,7 @@ impl Item {
638611
}
639612

640613
pub(crate) fn stable_since(&self, tcx: TyCtxt<'_>) -> Option<StableSince> {
641-
match self.stability(tcx)?.level {
642-
StabilityLevel::Stable { since, .. } => Some(since),
643-
StabilityLevel::Unstable { .. } => None,
644-
}
614+
self.stability(tcx).and_then(|stability| stability.stable_since())
645615
}
646616

647617
pub(crate) fn is_non_exhaustive(&self) -> bool {

Diff for: src/librustdoc/html/render/print_item.rs

+12-19
Original file line numberDiff line numberDiff line change
@@ -436,16 +436,9 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
436436
}
437437

438438
clean::ImportItem(ref import) => {
439-
let stab_tags = if let Some(import_def_id) = import.source.did {
440-
// Just need an item with the correct def_id and attrs
441-
let import_item =
442-
clean::Item { item_id: import_def_id.into(), ..(*myitem).clone() };
443-
444-
let stab_tags = Some(extra_info_tags(&import_item, item, tcx).to_string());
445-
stab_tags
446-
} else {
447-
None
448-
};
439+
let stab_tags = import.source.did.map_or_else(String::new, |import_def_id| {
440+
extra_info_tags(tcx, myitem, item, Some(import_def_id)).to_string()
441+
});
449442

450443
w.write_str(ITEM_TABLE_ROW_OPEN);
451444
let id = match import.kind {
@@ -454,7 +447,6 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
454447
}
455448
clean::ImportKind::Glob => String::new(),
456449
};
457-
let stab_tags = stab_tags.unwrap_or_default();
458450
let (stab_tags_before, stab_tags_after) = if stab_tags.is_empty() {
459451
("", "")
460452
} else {
@@ -521,7 +513,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
521513
{docs_before}{docs}{docs_after}",
522514
name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()),
523515
visibility_and_hidden = visibility_and_hidden,
524-
stab_tags = extra_info_tags(myitem, item, tcx),
516+
stab_tags = extra_info_tags(tcx, myitem, item, None),
525517
class = myitem.type_(),
526518
unsafety_flag = unsafety_flag,
527519
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:
544536
/// Render the stability, deprecation and portability tags that are displayed in the item's summary
545537
/// at the module level.
546538
fn extra_info_tags<'a, 'tcx: 'a>(
539+
tcx: TyCtxt<'tcx>,
547540
item: &'a clean::Item,
548541
parent: &'a clean::Item,
549-
tcx: TyCtxt<'tcx>,
542+
import_def_id: Option<DefId>,
550543
) -> impl fmt::Display + 'a + Captures<'tcx> {
551544
display_fn(move |f| {
552545
fn tag_html<'a>(
@@ -564,18 +557,18 @@ fn extra_info_tags<'a, 'tcx: 'a>(
564557
}
565558

566559
// The trailing space after each tag is to space it properly against the rest of the docs.
567-
if let Some(depr) = &item.deprecation(tcx) {
560+
let deprecation = import_def_id
561+
.map_or_else(|| item.deprecation(tcx), |import_did| tcx.lookup_deprecation(import_did));
562+
if let Some(depr) = deprecation {
568563
let message = if depr.is_in_effect() { "Deprecated" } else { "Deprecation planned" };
569564
write!(f, "{}", tag_html("deprecated", "", message))?;
570565
}
571566

572567
// The "rustc_private" crates are permanently unstable so it makes no sense
573568
// to render "unstable" everywhere.
574-
if item
575-
.stability(tcx)
576-
.as_ref()
577-
.is_some_and(|s| s.is_unstable() && s.feature != sym::rustc_private)
578-
{
569+
let stability = import_def_id
570+
.map_or_else(|| item.stability(tcx), |import_did| tcx.lookup_stability(import_did));
571+
if stability.is_some_and(|s| s.is_unstable() && s.feature != sym::rustc_private) {
579572
write!(f, "{}", tag_html("unstable", "", "Experimental"))?;
580573
}
581574

Diff for: src/librustdoc/passes/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ pub(crate) use self::strip_priv_imports::STRIP_PRIV_IMPORTS;
2323
mod propagate_doc_cfg;
2424
pub(crate) use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
2525

26+
mod propagate_stability;
27+
pub(crate) use self::propagate_stability::PROPAGATE_STABILITY;
28+
2629
pub(crate) mod collect_intra_doc_links;
2730
pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
2831

@@ -75,6 +78,7 @@ pub(crate) const PASSES: &[Pass] = &[
7578
STRIP_PRIVATE,
7679
STRIP_PRIV_IMPORTS,
7780
PROPAGATE_DOC_CFG,
81+
PROPAGATE_STABILITY,
7882
COLLECT_INTRA_DOC_LINKS,
7983
COLLECT_TRAIT_IMPLS,
8084
CALCULATE_DOC_COVERAGE,
@@ -91,6 +95,7 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[
9195
ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate),
9296
ConditionalPass::always(COLLECT_INTRA_DOC_LINKS),
9397
ConditionalPass::always(PROPAGATE_DOC_CFG),
98+
ConditionalPass::always(PROPAGATE_STABILITY),
9499
ConditionalPass::always(RUN_LINTS),
95100
];
96101

Diff for: src/librustdoc/passes/propagate_stability.rs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! Propagates stability to child items.
2+
//!
3+
//! The purpose of this pass is to make items whose parents are "more unstable"
4+
//! than the item itself inherit the parent's stability.
5+
//! For example, [`core::error::Error`] is marked as stable since 1.0.0, but the
6+
//! [`core::error`] module is marked as stable since 1.81.0, so we want to show
7+
//! [`core::error::Error`] as stable since 1.81.0 as well.
8+
9+
use rustc_attr::{Stability, StabilityLevel};
10+
use rustc_hir::def_id::CRATE_DEF_ID;
11+
12+
use crate::clean::{Crate, Item, ItemId};
13+
use crate::core::DocContext;
14+
use crate::fold::DocFolder;
15+
use crate::passes::Pass;
16+
17+
pub(crate) const PROPAGATE_STABILITY: Pass = Pass {
18+
name: "propagate-stability",
19+
run: propagate_stability,
20+
description: "propagates stability to child items",
21+
};
22+
23+
pub(crate) fn propagate_stability(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
24+
let crate_stability = cx.tcx.lookup_stability(CRATE_DEF_ID);
25+
StabilityPropagator { parent_stability: crate_stability, cx }.fold_crate(cr)
26+
}
27+
28+
struct StabilityPropagator<'a, 'tcx> {
29+
parent_stability: Option<Stability>,
30+
cx: &'a mut DocContext<'tcx>,
31+
}
32+
33+
impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> {
34+
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
35+
let parent_stability = self.parent_stability;
36+
37+
let stability = match item.item_id {
38+
ItemId::DefId(def_id) => {
39+
let own_stability = self.cx.tcx.lookup_stability(def_id);
40+
41+
// If any of the item's parents was stabilized later or is still unstable,
42+
// then use the parent's stability instead.
43+
if let Some(own_stab) = own_stability
44+
&& let StabilityLevel::Stable {
45+
since: own_since,
46+
allowed_through_unstable_modules: false,
47+
} = own_stab.level
48+
&& let Some(parent_stab) = parent_stability
49+
&& (parent_stab.is_unstable()
50+
|| parent_stab
51+
.stable_since()
52+
.is_some_and(|parent_since| parent_since > own_since))
53+
{
54+
parent_stability
55+
} else {
56+
own_stability
57+
}
58+
}
59+
ItemId::Auto { .. } | ItemId::Blanket { .. } => {
60+
// For now, we do now show stability for synthesized impls.
61+
None
62+
}
63+
};
64+
65+
item.inner.stability = stability;
66+
self.parent_stability = stability;
67+
let item = self.fold_item_recur(item);
68+
self.parent_stability = parent_stability;
69+
70+
Some(item)
71+
}
72+
}

Diff for: tests/rustdoc-ui/issues/issue-91713.stdout

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ strip-aliased-non-local - strips all non-local private aliased items from the ou
55
strip-private - strips all private items from a crate which cannot be seen externally, implies strip-priv-imports
66
strip-priv-imports - strips all private import statements (`use`, `extern crate`) from a crate
77
propagate-doc-cfg - propagates `#[doc(cfg(...))]` to child items
8+
propagate-stability - propagates stability to child items
89
collect-intra-doc-links - resolves intra-doc links
910
collect-trait-impls - retrieves trait impls for items in the crate
1011
calculate-doc-coverage - counts the number of items with and without documentation
@@ -19,6 +20,7 @@ strip-aliased-non-local
1920
strip-priv-imports (when --document-private-items)
2021
collect-intra-doc-links
2122
propagate-doc-cfg
23+
propagate-stability
2224
run-lints
2325

2426
Passes run with `--show-coverage`:

0 commit comments

Comments
 (0)