Skip to content

Commit cd31b3a

Browse files
author
Lukas Markeffsky
committed
rustdoc: rewrite stability inheritance as a pass
1 parent 19252bd commit cd31b3a

File tree

9 files changed

+149
-68
lines changed

9 files changed

+149
-68
lines changed

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

+14-41
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,

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`:

Diff for: tests/rustdoc/stability.rs

+41-8
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,61 @@ pub struct ZzStable;
2525

2626
#[unstable(feature = "unstable", issue = "none")]
2727
pub mod unstable {
28-
//@ !hasraw stability/unstable/struct.Foo.html '//span[@class="since"]'
28+
//@ !hasraw stability/unstable/struct.StableInUnstable.html \
29+
// '//span[@class="since"]'
2930
//@ has - '//div[@class="stab unstable"]' 'experimental'
3031
#[stable(feature = "rust1", since = "1.0.0")]
31-
pub struct Foo;
32+
pub struct StableInUnstable;
33+
34+
#[stable(feature = "rust1", since = "1.0.0")]
35+
pub mod stable_in_unstable {
36+
//@ !hasraw stability/unstable/stable_in_unstable/struct.Inner.html \
37+
// '//span[@class="since"]'
38+
//@ has - '//div[@class="stab unstable"]' 'experimental'
39+
#[stable(feature = "rust1", since = "1.0.0")]
40+
pub struct Inner;
41+
}
3242
}
3343

3444
#[stable(feature = "rust2", since = "2.2.2")]
3545
pub mod stable_later {
36-
//@ has stability/stable_later/struct.Bar.html '//span[@class="since"]' '2.2.2'
46+
//@ has stability/stable_later/struct.StableInLater.html \
47+
// '//span[@class="since"]' '2.2.2'
3748
#[stable(feature = "rust1", since = "1.0.0")]
38-
pub struct Bar;
49+
pub struct StableInLater;
50+
51+
#[stable(feature = "rust1", since = "1.0.0")]
52+
pub mod stable_in_later {
53+
//@ has stability/stable_later/stable_in_later/struct.Inner.html \
54+
// '//span[@class="since"]' '2.2.2'
55+
#[stable(feature = "rust1", since = "1.0.0")]
56+
pub struct Inner;
57+
}
3958
}
4059

4160
#[stable(feature = "rust1", since = "1.0.0")]
4261
pub mod stable_earlier {
43-
//@ has stability/stable_earlier/struct.Foo.html '//span[@class="since"]' '1.0.0'
62+
//@ has stability/stable_earlier/struct.StableInUnstable.html \
63+
// '//span[@class="since"]' '1.0.0'
64+
#[doc(inline)]
65+
#[stable(feature = "rust1", since = "1.0.0")]
66+
pub use crate::unstable::StableInUnstable;
67+
68+
//@ has stability/stable_earlier/stable_in_unstable/struct.Inner.html \
69+
// '//span[@class="since"]' '1.0.0'
70+
#[doc(inline)]
71+
#[stable(feature = "rust1", since = "1.0.0")]
72+
pub use crate::unstable::stable_in_unstable;
73+
74+
//@ has stability/stable_earlier/struct.StableInLater.html \
75+
// '//span[@class="since"]' '1.0.0'
4476
#[doc(inline)]
4577
#[stable(feature = "rust1", since = "1.0.0")]
46-
pub use crate::unstable::Foo;
78+
pub use crate::stable_later::StableInLater;
4779

48-
//@ has stability/stable_earlier/struct.Bar.html '//span[@class="since"]' '1.0.0'
80+
//@ has stability/stable_earlier/stable_in_later/struct.Inner.html \
81+
// '//span[@class="since"]' '1.0.0'
4982
#[doc(inline)]
5083
#[stable(feature = "rust1", since = "1.0.0")]
51-
pub use crate::stable_later::Bar;
84+
pub use crate::stable_later::stable_in_later;
5285
}

0 commit comments

Comments
 (0)