Skip to content

Commit e614f5e

Browse files
committed
rustdoc: support multiple stability attributes
1 parent 67f9341 commit e614f5e

File tree

10 files changed

+69
-45
lines changed

10 files changed

+69
-45
lines changed

compiler/rustc_attr/src/builtin.rs

+4
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ impl Stability {
8989
pub fn unstable_features(&self) -> impl Iterator<Item = Symbol> + use<'_> {
9090
self.level.unstable_features()
9191
}
92+
93+
pub fn is_rustc_private(&self) -> bool {
94+
self.level.has_unstable_feature(sym::rustc_private)
95+
}
9296
}
9397

9498
/// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes.

src/librustdoc/clean/inline.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -432,10 +432,8 @@ pub(crate) fn build_impl(
432432
let associated_trait = tcx.impl_trait_ref(did).map(ty::EarlyBinder::skip_binder);
433433

434434
// Do not inline compiler-internal items unless we're a compiler-internal crate.
435-
let is_compiler_internal = |did| {
436-
tcx.lookup_stability(did)
437-
.is_some_and(|stab| stab.is_unstable() && stab.feature == sym::rustc_private)
438-
};
435+
let is_compiler_internal =
436+
|did| tcx.lookup_stability(did).is_some_and(|stab| stab.is_rustc_private());
439437
let document_compiler_internal = is_compiler_internal(LOCAL_CRATE.as_def_id());
440438
let is_directly_public = |cx: &mut DocContext<'_>, did| {
441439
cx.cache.effective_visibilities.is_directly_public(tcx, did)

src/librustdoc/clean/types.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,8 @@ impl Item {
385385
/// Returns the effective stability of the item.
386386
///
387387
/// This method should only be called after the `propagate-stability` pass has been run.
388-
pub(crate) fn stability(&self, tcx: TyCtxt<'_>) -> Option<Stability> {
389-
let stability = self.inner.stability;
388+
pub(crate) fn stability(&self, tcx: TyCtxt<'_>) -> Option<&Stability> {
389+
let stability = self.inner.stability.as_ref();
390390
debug_assert!(
391391
stability.is_some()
392392
|| self.def_id().is_none_or(|did| tcx.lookup_stability(did).is_none()),
@@ -395,7 +395,7 @@ impl Item {
395395
stability
396396
}
397397

398-
pub(crate) fn const_stability(&self, tcx: TyCtxt<'_>) -> Option<ConstStability> {
398+
pub(crate) fn const_stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option<&'tcx ConstStability> {
399399
self.def_id().and_then(|did| tcx.lookup_const_stability(did))
400400
}
401401

src/librustdoc/html/format.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1649,7 +1649,7 @@ impl PrintWithSpace for hir::Mutability {
16491649
pub(crate) fn print_constness_with_space(
16501650
c: &hir::Constness,
16511651
overall_stab: Option<StableSince>,
1652-
const_stab: Option<ConstStability>,
1652+
const_stab: Option<&ConstStability>,
16531653
) -> &'static str {
16541654
match c {
16551655
hir::Constness::Const => match (overall_stab, const_stab) {

src/librustdoc/html/render/mod.rs

+33-18
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ use rustc_hir::def_id::{DefId, DefIdSet};
5353
use rustc_middle::ty::print::PrintTraitRefExt;
5454
use rustc_middle::ty::{self, TyCtxt};
5555
use rustc_session::RustcVersion;
56-
use rustc_span::symbol::{Symbol, sym};
56+
use rustc_span::symbol::Symbol;
5757
use rustc_span::{BytePos, DUMMY_SP, FileName, RealFileName};
5858
use serde::ser::SerializeMap;
5959
use serde::{Serialize, Serializer};
@@ -675,17 +675,23 @@ enum ShortItemInfo {
675675
Deprecation {
676676
message: String,
677677
},
678-
/// The feature corresponding to an unstable item, and optionally
679-
/// a tracking issue URL and number.
678+
/// The features corresponding to an unstable item, and optionally
679+
/// a tracking issue URL and number for each.
680680
Unstable {
681-
feature: String,
682-
tracking: Option<(String, u32)>,
681+
features: Vec<UnstableFeature>,
683682
},
684683
Portability {
685684
message: String,
686685
},
687686
}
688687

688+
#[derive(Template)]
689+
#[template(path = "unstable_feature.html")]
690+
struct UnstableFeature {
691+
feature: String,
692+
tracking: Option<(String, u32)>,
693+
}
694+
689695
/// Render the stability, deprecation and portability information that is displayed at the top of
690696
/// the item's documentation.
691697
fn short_item_info(
@@ -724,19 +730,24 @@ fn short_item_info(
724730

725731
// Render unstable items. But don't render "rustc_private" crates (internal compiler crates).
726732
// Those crates are permanently unstable so it makes no sense to render "unstable" everywhere.
727-
if let Some((StabilityLevel::Unstable { reason: _, issue, .. }, feature)) = item
733+
if let Some(StabilityLevel::Unstable { unstables, .. }) = item
728734
.stability(cx.tcx())
729735
.as_ref()
730-
.filter(|stab| stab.feature != sym::rustc_private)
731-
.map(|stab| (stab.level, stab.feature))
736+
.filter(|stab| !stab.is_rustc_private())
737+
.map(|stab| &stab.level)
732738
{
733-
let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue)
734-
{
735-
Some((url.clone(), issue.get()))
736-
} else {
737-
None
739+
let track = |issue: Option<std::num::NonZero<u32>>| {
740+
if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) {
741+
Some((url.clone(), issue.get()))
742+
} else {
743+
None
744+
}
738745
};
739-
extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking });
746+
let features = unstables
747+
.iter()
748+
.map(|u| UnstableFeature { feature: u.feature.to_string(), tracking: track(u.issue) })
749+
.collect();
750+
extra_info.push(ShortItemInfo::Unstable { features });
740751
}
741752

742753
if let Some(message) = portability(item, parent) {
@@ -990,7 +1001,7 @@ fn assoc_method(
9901001
fn render_stability_since_raw_with_extra(
9911002
w: &mut Buffer,
9921003
stable_version: Option<StableSince>,
993-
const_stability: Option<ConstStability>,
1004+
const_stability: Option<&ConstStability>,
9941005
extra_class: &str,
9951006
) -> bool {
9961007
let mut title = String::new();
@@ -1006,12 +1017,16 @@ fn render_stability_since_raw_with_extra(
10061017
since_to_string(&since)
10071018
.map(|since| (format!("const since {since}"), format!("const: {since}")))
10081019
}
1009-
Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => {
1020+
Some(ConstStability { level: StabilityLevel::Unstable { unstables, .. }, .. }) => {
10101021
if stable_version.is_none() {
10111022
// don't display const unstable if entirely unstable
10121023
None
10131024
} else {
1014-
let unstable = if let Some(n) = issue {
1025+
// if constness depends on multiple unstable features, only link to the first
1026+
// tracking issue found, to save space. the issue description should link to issues
1027+
// for any features it can intersect with
1028+
let feature_issue = unstables.iter().find_map(|u| u.issue.map(|n| (u.feature, n)));
1029+
let unstable = if let Some((feature, n)) = feature_issue {
10151030
format!(
10161031
"<a \
10171032
href=\"https://github.com/rust-lang/rust/issues/{n}\" \
@@ -1061,7 +1076,7 @@ fn since_to_string(since: &StableSince) -> Option<String> {
10611076
fn render_stability_since_raw(
10621077
w: &mut Buffer,
10631078
ver: Option<StableSince>,
1064-
const_stability: Option<ConstStability>,
1079+
const_stability: Option<&ConstStability>,
10651080
) -> bool {
10661081
render_stability_since_raw_with_extra(w, ver, const_stability, "")
10671082
}

src/librustdoc/html/render/print_item.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_hir::def_id::DefId;
1414
use rustc_index::IndexVec;
1515
use rustc_middle::ty::{self, TyCtxt};
1616
use rustc_span::hygiene::MacroKind;
17-
use rustc_span::symbol::{Symbol, kw, sym};
17+
use rustc_span::symbol::{Symbol, kw};
1818
use tracing::{debug, info};
1919

2020
use super::type_layout::document_type_layout;
@@ -567,7 +567,7 @@ fn extra_info_tags<'a, 'tcx: 'a>(
567567
// to render "unstable" everywhere.
568568
let stability = import_def_id
569569
.map_or_else(|| item.stability(tcx), |import_did| tcx.lookup_stability(import_did));
570-
if stability.is_some_and(|s| s.is_unstable() && s.feature != sym::rustc_private) {
570+
if stability.is_some_and(|s| s.is_unstable() && !s.is_rustc_private()) {
571571
write!(f, "{}", tag_html("unstable", "", "Experimental"))?;
572572
}
573573

src/librustdoc/html/templates/short_item_info.html

+2-9
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,11 @@
44
<span class="emoji">👎</span> {# #}
55
<span>{{message|safe}}</span> {# #}
66
</div>
7-
{% when Self::Unstable with { feature, tracking } %}
7+
{% when Self::Unstable with { features } %}
88
<div class="stab unstable"> {# #}
99
<span class="emoji">🔬</span> {# #}
1010
<span> {# #}
11-
This is a nightly-only experimental API. ({# #}
12-
<code>{{feature}}</code>
13-
{% match tracking %}
14-
{% when Some with ((url, num)) %}
15-
&nbsp;<a href="{{url}}{{num}}">#{{num}}</a>
16-
{% when None %}
17-
{% endmatch %}
18-
) {# #}
11+
This is a nightly-only experimental API. ({{features|join(", ")|safe}}) {# #}
1912
</span> {# #}
2013
</div>
2114
{% when Self::Portability with { message } %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<code>{{feature}}</code>
2+
{% match tracking %}
3+
{% when Some with ((url, num)) %}
4+
&nbsp;<a href="{{url}}{{num}}">#{{num}}</a>
5+
{% when None %}
6+
{% endmatch %}

src/librustdoc/passes/propagate_stability.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub(crate) fn propagate_stability(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
2626
}
2727

2828
struct StabilityPropagator<'a, 'tcx> {
29-
parent_stability: Option<Stability>,
29+
parent_stability: Option<&'tcx Stability>,
3030
cx: &'a mut DocContext<'tcx>,
3131
}
3232

@@ -85,7 +85,7 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> {
8585
}
8686
};
8787

88-
item.inner.stability = stability;
88+
item.inner.stability = stability.cloned();
8989
self.parent_stability = stability;
9090
let item = self.fold_item_recur(item);
9191
self.parent_stability = parent_stability;
@@ -94,13 +94,13 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> {
9494
}
9595
}
9696

97-
fn merge_stability(
98-
own_stability: Option<Stability>,
99-
parent_stability: Option<Stability>,
100-
) -> Option<Stability> {
97+
fn merge_stability<'tcx>(
98+
own_stability: Option<&'tcx Stability>,
99+
parent_stability: Option<&'tcx Stability>,
100+
) -> Option<&'tcx Stability> {
101101
if let Some(own_stab) = own_stability
102-
&& let StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: false } =
103-
own_stab.level
102+
&& let &StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: false } =
103+
&own_stab.level
104104
&& let Some(parent_stab) = parent_stability
105105
&& (parent_stab.is_unstable()
106106
|| parent_stab.stable_since().is_some_and(|parent_since| parent_since > own_since))

tests/rustdoc/stability.rs

+8
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,11 @@ mod prim_i32 {}
169169
/// We currently don't document stability for keywords, but let's test it anyway.
170170
#[stable(feature = "rust1", since = "1.0.0")]
171171
mod if_keyword {}
172+
173+
#[unstable(feature = "test", issue = "none")]
174+
#[unstable(feature = "test2", issue = "none")]
175+
pub trait UnstableTraitWithMultipleFeatures {
176+
//@ has stability/trait.UnstableTraitWithMultipleFeatures.html \
177+
// '//span[@class="item-info"]//div[@class="stab unstable"]' \
178+
// 'This is a nightly-only experimental API. (test, test2)'
179+
}

0 commit comments

Comments
 (0)