Skip to content
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
76 changes: 74 additions & 2 deletions src/librustdoc/json/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::thin_vec::ThinVec;
use rustc_hir as hir;
use rustc_hir::attrs::{self, DeprecatedSince, DocAttribute, DocInline, HideOrShow};
use rustc_hir::def::CtorKind;
use rustc_hir::def::{CtorKind, DefKind};
use rustc_hir::def_id::DefId;
use rustc_hir::{HeaderSafety, Safety};
use rustc_hir::{HeaderSafety, Safety, find_attr};
use rustc_metadata::rendered_const;
use rustc_middle::ty::TyCtxt;
use rustc_middle::{bug, ty};
Expand Down Expand Up @@ -92,6 +92,9 @@ impl JsonRenderer<'_> {
item.item_id.as_def_id()
};
let stability = stability_def_id.and_then(|def_id| self.tcx.lookup_stability(def_id));
let const_stability = item.item_id.as_def_id().and_then(|def_id| {
const_stability_for_def_id(self.tcx, def_id).map(|s| Box::new(s.into_json(self)))
});

Some(Item {
id,
Expand All @@ -100,6 +103,7 @@ impl JsonRenderer<'_> {
span: span.and_then(|span| span.into_json(self)),
visibility: visibility.into_json(self),
stability: stability.map(|s| Box::new(s.into_json(self))),
const_stability,
docs,
attrs,
deprecation: deprecation.into_json(self),
Expand Down Expand Up @@ -246,6 +250,24 @@ impl FromClean<hir::Stability> for Stability {
}
}

impl FromClean<hir::ConstStability> for Stability {
fn from_clean(stab: &hir::ConstStability, _renderer: &JsonRenderer<'_>) -> Self {
let feature = stab.feature.to_string();
let level = match stab.level {
hir::StabilityLevel::Stable { since, .. } => StabilityLevel::Stable {
since: match since {
hir::StableSince::Version(since) => Some(since.to_string()),
hir::StableSince::Current => Some(hir::RustcVersion::CURRENT.to_string()),
// Match rustdoc HTML: malformed stable-since values are omitted.
hir::StableSince::Err(_) => None,
},
},
hir::StabilityLevel::Unstable { .. } => StabilityLevel::Unstable,
};
Stability { feature, level }
}
}

impl FromClean<clean::GenericArgs> for Option<Box<GenericArgs>> {
fn from_clean(generic_args: &clean::GenericArgs, renderer: &JsonRenderer<'_>) -> Self {
use clean::GenericArgs::*;
Expand Down Expand Up @@ -948,6 +970,55 @@ impl FromClean<ItemType> for ItemKind {
}
}

fn const_stability_for_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<hir::ConstStability> {
if !tcx.is_conditionally_const(def_id) {
// The item cannot be conditionally-const. No const stability here.
//
// This includes associated consts, which are an interesting exception
// to the general rule that items inside `const impl` and `const trait` carry
// the const-stability of that block. Associated consts are already const, always.
return None;
}

let const_stability = tcx.lookup_const_stability(def_id)?;
if find_attr!(tcx, def_id, RustcConstStability { .. }) {
// Direct const-stability attribute on the item itself. Return it directly.
return Some(const_stability);
}

if const_stability.is_const_stable() {
// Items that are const-stable without an explicit attribute on their own item
// must be associated items inside `const trait` or `const impl`.
// We don't want to duplicate their parent item's const-stability attribute.
return None;
}

// We're dealing with an item that is const-unstable,
// but doesn't have an explicit const-stability attribute on it.
//
// Today, this means one of two cases:
// - The item is enclosed within a `#[rustc_const_unstable]` block,
// like a `const trait` or `const impl`, in which case our query propagated the parent's
// const-instability info. This const-instability is desirable to place into JSON
// because *only some* associated items inside such a block are const-unstable.
// Associated consts are the exception, and were handled earlier.
// - The item is `#[unstable]` which implies it's const-unstable under the same feature,
// in which case we don't want to duplicate the existing stability attribute
// which would already appear in an adjacent field in the JSON anyway.
if let Some(parent_def_id) = tcx.opt_parent(def_id)
&& matches!(tcx.def_kind(parent_def_id), DefKind::Trait | DefKind::Impl { .. })
&& tcx.lookup_const_stability(parent_def_id) == Some(const_stability)
{
Some(const_stability)
} else {
std::debug_assert_matches!(
tcx.lookup_stability(def_id).map(|s| s.level),
Some(hir::StabilityLevel::Unstable { .. })
);
None
}
}

/// Maybe convert a attribute from hir to json.
///
/// Returns `None` if the attribute shouldn't be in the output.
Expand All @@ -966,6 +1037,7 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>)
vec![match kind {
AK::Deprecated { .. } => return Vec::new(), // Handled separately into Item::deprecation.
AK::Stability { .. } => return Vec::new(), // Handled separately into Item::stability
AK::RustcConstStability { .. } => return Vec::new(), // Handled separately into Item::const_stability.

AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"),

Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,6 @@ mod size_asserts {
// tidy-alphabetical-end

// These contains a `PathBuf`, which is different sizes on different OSes.
static_assert_size!(Item, 536 + size_of::<std::path::PathBuf>());
static_assert_size!(Item, 544 + size_of::<std::path::PathBuf>());
static_assert_size!(ExternalCrate, 48 + size_of::<std::path::PathBuf>());
}
28 changes: 21 additions & 7 deletions src/rustdoc-json-types/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
// will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line
// are deliberately not in a doc comment, because they need not be in public docs.)
//
// Latest feature: Add `Item::stability`.
pub const FORMAT_VERSION: u32 = 58;
// Latest feature: Add `Item::const_stability`.
pub const FORMAT_VERSION: u32 = 59;

/// The root of the emitted JSON blob.
///
Expand Down Expand Up @@ -286,6 +286,8 @@ pub struct Item {
/// - `#[doc = "Doc Comment"]` or `/// Doc comment`: see [`Self::docs`] instead.
/// - `#[deprecated]` attributes: see the [`Self::deprecation`] field instead.
/// - `#[stable]` and `#[unstable]` attributes: see the [`Self::stability`] field instead.
/// - `#[rustc_const_stable]` and `#[rustc_const_unstable]` attributes:
/// see the [`Self::const_stability`] field instead.
///
/// Attributes appear in pretty-printed Rust form, regardless of their formatting
/// in the original source code. For example:
Expand Down Expand Up @@ -319,20 +321,31 @@ pub struct Item {
/// most ordinary third-party crates usually have no data here.
pub stability: Option<Box<Stability>>,

/// Stability information for using this item in const contexts, if any.
///
/// This is separate from [`Self::stability`]. An item can be stable as regular API while its
/// const use is unstable. An unstable item may have no separate const-stability value here.
///
/// This field is only populated for item kinds whose const behavior can have separate
/// stability information, such as const functions, const traits, const trait impls,
/// and associated items whose const behavior is controlled by a const trait or const impl.
pub const_stability: Option<Box<Stability>>,

/// The type-specific fields describing this item.
pub inner: ItemEnum,
}

/// Stability information for an item.
///
/// This only refers to regular item stability: whether the item is stable or unstable
/// as represented by the `#[stable]` or `#[unstable]` attributes.
/// Const stability and default-body stability are different things and not captured here.
/// In [`Item::stability`], this refers to regular item stability: whether the item is
/// stable or unstable as represented by the `#[stable]` or `#[unstable]` attributes.
/// In [`Item::const_stability`], this refers to using the item in const contexts,
/// as represented by `#[rustc_const_stable]` or `#[rustc_const_unstable]`.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "rkyv_0_8", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
#[cfg_attr(feature = "rkyv_0_8", rkyv(derive(Debug)))]
pub struct Stability {
/// The stability feature associated with this item.
/// The feature associated with this stability record.
///
/// For unstable items, this is the feature gate associated with the item.
/// For stable items, this is the historical label recorded when the item was stabilized.
Expand All @@ -342,7 +355,6 @@ pub struct Stability {
pub level: StabilityLevel,
}

/// Whether an item is stable or unstable as regular public API.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "rkyv_0_8", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
#[cfg_attr(feature = "rkyv_0_8", rkyv(derive(Debug)))]
Expand All @@ -365,6 +377,8 @@ pub enum StabilityLevel {
/// - `#[doc = "Doc Comment"]` or `/// Doc comment`. These are in [`Item::docs`] instead.
/// - `#[deprecated]`. These are in [`Item::deprecation`] instead.
/// - `#[stable]` and `#[unstable]`. These are in [`Item::stability`] instead.
/// - `#[rustc_const_stable]` and `#[rustc_const_unstable]`. These are in
/// [`Item::const_stability`] instead.
pub enum Attribute {
/// `#[non_exhaustive]`
NonExhaustive,
Expand Down
7 changes: 7 additions & 0 deletions src/tools/jsondoclint/src/validator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fn errors_on_missing_links() {
attrs: vec![],
deprecation: None,
stability: None,
const_stability: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![],
Expand Down Expand Up @@ -83,6 +84,7 @@ fn errors_on_local_in_paths_and_not_index() {
attrs: Vec::new(),
deprecation: None,
stability: None,
const_stability: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![Id(1)],
Expand All @@ -103,6 +105,7 @@ fn errors_on_local_in_paths_and_not_index() {
attrs: Vec::new(),
deprecation: None,
stability: None,
const_stability: None,
inner: ItemEnum::Primitive(Primitive { name: "i32".to_owned(), impls: vec![] }),
},
),
Expand Down Expand Up @@ -157,6 +160,7 @@ fn errors_on_missing_path() {
attrs: Vec::new(),
deprecation: None,
stability: None,
const_stability: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![Id(1), Id(2)],
Expand All @@ -177,6 +181,7 @@ fn errors_on_missing_path() {
attrs: Vec::new(),
deprecation: None,
stability: None,
const_stability: None,
inner: ItemEnum::Struct(Struct {
kind: StructKind::Unit,
generics: generics.clone(),
Expand All @@ -197,6 +202,7 @@ fn errors_on_missing_path() {
attrs: Vec::new(),
deprecation: None,
stability: None,
const_stability: None,
inner: ItemEnum::Function(Function {
sig: FunctionSignature {
inputs: vec![],
Expand Down Expand Up @@ -260,6 +266,7 @@ fn checks_local_crate_id_is_correct() {
attrs: Vec::new(),
deprecation: None,
stability: None,
const_stability: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![],
Expand Down
55 changes: 55 additions & 0 deletions tests/rustdoc-json/attrs/stability/const.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#![feature(staged_api)]

//@ is "$.index[?(@.name=='non_const_function')].const_stability" null
#[stable(feature = "non_const_function_feature", since = "0.9.0")]
pub fn non_const_function() {}

//@ set stable_const_fn = "$.index[?(@.name=='stable_const_fn')].id"
//@ is "$.index[?(@.name=='stable_const_fn')].stability.level" '"stable"'
//@ is "$.index[?(@.name=='stable_const_fn')].stability.feature" '"stable_const_fn_feature"'
//@ is "$.index[?(@.name=='stable_const_fn')].stability.since" '"1.0.0"'
//@ is "$.index[?(@.name=='stable_const_fn')].const_stability.level" '"stable"'
//@ is "$.index[?(@.name=='stable_const_fn')].const_stability.feature" '"stable_const_fn_const_feature"'
//@ is "$.index[?(@.name=='stable_const_fn')].const_stability.since" '"1.1.0"'
//@ is "$.index[?(@.name=='stable_const_fn')].attrs" []
#[stable(feature = "stable_const_fn_feature", since = "1.0.0")]
#[rustc_const_stable(feature = "stable_const_fn_const_feature", since = "1.1.0")]
pub const fn stable_const_fn() {}

//@ is "$.index[?(@.name=='const_unstable_fn')].stability.level" '"stable"'
//@ is "$.index[?(@.name=='const_unstable_fn')].stability.feature" '"const_unstable_fn_feature"'
//@ is "$.index[?(@.name=='const_unstable_fn')].stability.since" '"2.0.0"'
//@ is "$.index[?(@.name=='const_unstable_fn')].const_stability.level" '"unstable"'
//@ is "$.index[?(@.name=='const_unstable_fn')].const_stability.feature" '"const_unstable_fn_const_feature"'
//@ !has "$.index[?(@.name=='const_unstable_fn')].const_stability.since"
//@ is "$.index[?(@.name=='const_unstable_fn')].attrs" []
#[stable(feature = "const_unstable_fn_feature", since = "2.0.0")]
#[rustc_const_unstable(feature = "const_unstable_fn_const_feature", issue = "none")]
pub const fn const_unstable_fn() {}

// Even when the item itself is unstable, if a separate const-stability attribute is present,
// that's a distinct fact possibly associated with a different feature gate.
// It should therefore be exposed on its own, instead of being collapsed into regular stability.
//@ is "$.index[?(@.name=='unstable_fn_with_explicit_const_gate')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='unstable_fn_with_explicit_const_gate')].stability.feature" '"unstable_fn_with_explicit_const_gate_feature"'
//@ !has "$.index[?(@.name=='unstable_fn_with_explicit_const_gate')].stability.since"
//@ is "$.index[?(@.name=='unstable_fn_with_explicit_const_gate')].const_stability.level" '"unstable"'
//@ is "$.index[?(@.name=='unstable_fn_with_explicit_const_gate')].const_stability.feature" '"explicit_const_gate_on_unstable_fn"'
//@ !has "$.index[?(@.name=='unstable_fn_with_explicit_const_gate')].const_stability.since"
//@ is "$.index[?(@.name=='unstable_fn_with_explicit_const_gate')].attrs" []
#[unstable(feature = "unstable_fn_with_explicit_const_gate_feature", issue = "none")]
#[rustc_const_unstable(feature = "explicit_const_gate_on_unstable_fn", issue = "none")]
pub const fn unstable_fn_with_explicit_const_gate() {}

// `lookup_const_stability` synthesizes a const-unstable record for this item from its regular
// instability. Rustdoc JSON filters that out because there is no separate const feature gate.
//@ is "$.index[?(@.name=='unstable_const_fn_without_const_gate')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='unstable_const_fn_without_const_gate')].stability.feature" '"unstable_const_fn_without_const_gate_feature"'
//@ is "$.index[?(@.name=='unstable_const_fn_without_const_gate')].const_stability" null
#[unstable(feature = "unstable_const_fn_without_const_gate_feature", issue = "none")]
pub const fn unstable_const_fn_without_const_gate() {}

// The `Use` item describes the re-export. It doesn't have `const_stability` of its own.
//@ is "$.index[?(@.inner.use.name=='stable_const_fn_reexport')].const_stability" null
//@ is "$.index[?(@.inner.use.name=='stable_const_fn_reexport')].inner.use.id" $stable_const_fn
pub use stable_const_fn as stable_const_fn_reexport;
Loading
Loading