Skip to content

Commit 2385b33

Browse files
committed
rustdoc: support multiple stability attributes
1 parent 5950df6 commit 2385b33

File tree

10 files changed

+89
-55
lines changed

10 files changed

+89
-55
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
@@ -436,10 +436,8 @@ pub(crate) fn build_impl(
436436
let associated_trait = tcx.impl_trait_ref(did).map(ty::EarlyBinder::skip_binder);
437437

438438
// Do not inline compiler-internal items unless we're a compiler-internal crate.
439-
let is_compiler_internal = |did| {
440-
tcx.lookup_stability(did)
441-
.is_some_and(|stab| stab.is_unstable() && stab.feature == sym::rustc_private)
442-
};
439+
let is_compiler_internal =
440+
|did| tcx.lookup_stability(did).is_some_and(|stab| stab.is_rustc_private());
443441
let document_compiler_internal = is_compiler_internal(LOCAL_CRATE.as_def_id());
444442
let is_directly_public = |cx: &mut DocContext<'_>, did| {
445443
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

+9-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::iter::{self, once};
1414

1515
use itertools::Itertools;
1616
use rustc_abi::ExternAbi;
17-
use rustc_attr::{ConstStability, StabilityLevel, StableSince};
17+
use rustc_attr::{ConstStability, ConstStabilityLevel, StableSince};
1818
use rustc_data_structures::captures::Captures;
1919
use rustc_data_structures::fx::FxHashSet;
2020
use rustc_hir as hir;
@@ -1690,20 +1690,22 @@ impl PrintWithSpace for hir::Mutability {
16901690
pub(crate) fn print_constness_with_space(
16911691
c: &hir::Constness,
16921692
overall_stab: Option<StableSince>,
1693-
const_stab: Option<ConstStability>,
1693+
const_stab: Option<&ConstStability>,
16941694
) -> &'static str {
16951695
match c {
16961696
hir::Constness::Const => match (overall_stab, const_stab) {
16971697
// const stable...
1698-
(_, Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }))
1698+
(_, Some(ConstStability { level: ConstStabilityLevel::Stable { .. }, .. }))
16991699
// ...or when feature(staged_api) is not set...
17001700
| (_, None)
1701-
// ...or when const unstable, but overall unstable too
1702-
| (None, Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => {
1701+
// ...or when const unstable, but overall unstable too...
1702+
| (None, Some(ConstStability { level: ConstStabilityLevel::Unstable { .. }, .. }))
1703+
| (None, Some(ConstStability { level: ConstStabilityLevel::ImplicitUnstable, .. })) => {
17031704
"const "
17041705
}
1705-
// const unstable (and overall stable)
1706-
(Some(_), Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => "",
1706+
// const unstable (and overall stable)...
1707+
(Some(_), Some(ConstStability { level: ConstStabilityLevel::Unstable { .. }, .. }))
1708+
| (Some(_), Some(ConstStability { level: ConstStabilityLevel::ImplicitUnstable, .. })) => "",
17071709
},
17081710
// not const
17091711
hir::Constness::NotConst => "",

src/librustdoc/html/render/mod.rs

+45-22
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,17 @@ use std::rc::Rc;
4545
use std::{fs, str};
4646

4747
use rinja::Template;
48-
use rustc_attr::{ConstStability, DeprecatedSince, Deprecation, StabilityLevel, StableSince};
48+
use rustc_attr::{
49+
ConstStability, ConstStabilityLevel, DeprecatedSince, Deprecation, StabilityLevel, StableSince,
50+
};
4951
use rustc_data_structures::captures::Captures;
5052
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
5153
use rustc_hir::Mutability;
5254
use rustc_hir::def_id::{DefId, DefIdSet};
5355
use rustc_middle::ty::print::PrintTraitRefExt;
5456
use rustc_middle::ty::{self, TyCtxt};
5557
use rustc_session::RustcVersion;
56-
use rustc_span::symbol::{Symbol, sym};
58+
use rustc_span::symbol::Symbol;
5759
use rustc_span::{BytePos, DUMMY_SP, FileName, RealFileName};
5860
use serde::ser::SerializeMap;
5961
use serde::{Serialize, Serializer};
@@ -674,17 +676,23 @@ enum ShortItemInfo {
674676
Deprecation {
675677
message: String,
676678
},
677-
/// The feature corresponding to an unstable item, and optionally
678-
/// a tracking issue URL and number.
679+
/// The features corresponding to an unstable item, and optionally
680+
/// a tracking issue URL and number for each.
679681
Unstable {
680-
feature: String,
681-
tracking: Option<(String, u32)>,
682+
features: Vec<UnstableFeature>,
682683
},
683684
Portability {
684685
message: String,
685686
},
686687
}
687688

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

724732
// Render unstable items. But don't render "rustc_private" crates (internal compiler crates).
725733
// Those crates are permanently unstable so it makes no sense to render "unstable" everywhere.
726-
if let Some((StabilityLevel::Unstable { reason: _, issue, .. }, feature)) = item
734+
if let Some(StabilityLevel::Unstable { unstables, .. }) = item
727735
.stability(cx.tcx())
728736
.as_ref()
729-
.filter(|stab| stab.feature != sym::rustc_private)
730-
.map(|stab| (stab.level, stab.feature))
737+
.filter(|stab| !stab.is_rustc_private())
738+
.map(|stab| &stab.level)
731739
{
732-
let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue)
733-
{
734-
Some((url.clone(), issue.get()))
735-
} else {
736-
None
740+
let track = |issue: Option<std::num::NonZero<u32>>| {
741+
if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) {
742+
Some((url.clone(), issue.get()))
743+
} else {
744+
None
745+
}
737746
};
738-
extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking });
747+
let features = unstables
748+
.iter()
749+
.map(|u| UnstableFeature { feature: u.feature.to_string(), tracking: track(u.issue) })
750+
.collect();
751+
extra_info.push(ShortItemInfo::Unstable { features });
739752
}
740753

741754
if let Some(message) = portability(item, parent) {
@@ -989,7 +1002,7 @@ fn assoc_method(
9891002
fn render_stability_since_raw_with_extra(
9901003
w: &mut Buffer,
9911004
stable_version: Option<StableSince>,
992-
const_stability: Option<ConstStability>,
1005+
const_stability: Option<&ConstStability>,
9931006
extra_class: &str,
9941007
) -> bool {
9951008
let mut title = String::new();
@@ -1001,18 +1014,20 @@ fn render_stability_since_raw_with_extra(
10011014
}
10021015

10031016
let const_title_and_stability = match const_stability {
1004-
Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. }) => {
1017+
Some(ConstStability { level: ConstStabilityLevel::Stable { since, .. }, .. }) => {
10051018
since_to_string(&since)
10061019
.map(|since| (format!("const since {since}"), format!("const: {since}")))
10071020
}
1008-
Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => {
1021+
Some(ConstStability { level: ConstStabilityLevel::Unstable { unstables, .. }, .. }) => {
10091022
if stable_version.is_none() {
10101023
// don't display const unstable if entirely unstable
10111024
None
10121025
} else {
1013-
let unstable = if let Some(n) = issue
1014-
&& let Some(feature) = feature
1015-
{
1026+
// if constness depends on multiple unstable features, only link to the first
1027+
// tracking issue found, to save space. the issue description should link to issues
1028+
// for any features it can intersect with
1029+
let feature_issue = unstables.iter().find_map(|u| u.issue.map(|n| (u.feature, n)));
1030+
let unstable = if let Some((feature, n)) = feature_issue {
10161031
format!(
10171032
"<a \
10181033
href=\"https://github.com/rust-lang/rust/issues/{n}\" \
@@ -1026,6 +1041,14 @@ fn render_stability_since_raw_with_extra(
10261041
Some((String::from("const unstable"), format!("const: {unstable}")))
10271042
}
10281043
}
1044+
Some(ConstStability { level: ConstStabilityLevel::ImplicitUnstable, .. }) => {
1045+
// No explicit const-stability annotation was provided. Treat it as const-unstable.
1046+
if stable_version.is_none() {
1047+
None
1048+
} else {
1049+
Some((String::from("const unstable"), format!("const: unstable")))
1050+
}
1051+
}
10291052
_ => None,
10301053
};
10311054

@@ -1062,7 +1085,7 @@ fn since_to_string(since: &StableSince) -> Option<String> {
10621085
fn render_stability_since_raw(
10631086
w: &mut Buffer,
10641087
ver: Option<StableSince>,
1065-
const_stability: Option<ConstStability>,
1088+
const_stability: Option<&ConstStability>,
10661089
) -> bool {
10671090
render_stability_since_raw_with_extra(w, ver, const_stability, "")
10681091
}

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;
@@ -568,7 +568,7 @@ fn extra_info_tags<'a, 'tcx: 'a>(
568568
// to render "unstable" everywhere.
569569
let stability = import_def_id
570570
.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) {
571+
if stability.is_some_and(|s| s.is_unstable() && !s.is_rustc_private()) {
572572
write!(f, "{}", tag_html("unstable", "", "Experimental"))?;
573573
}
574574

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)