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
45 changes: 45 additions & 0 deletions src/librustdoc/json/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,37 @@ impl JsonRenderer<'_> {
}
_ => from_clean_item(item, self),
};

// Rustdoc JSON keeps re-exports as `Use` items, so their stability describes
// the local `pub use` declaration. The imported target item's stability
// remains available through `inner.use.id`.
//
// We use raw stability attributes here instead of `clean::Item::stability()`,
// which is the effective stability rustdoc uses for rendering paths.
// For example, a stable `pub use` inside an unstable module is effectively unstable
// through that module path, but the `Use` declaration itself is still stable.
// In that example, `clean::Item::stability()` would return "unstable" as
// the effective stability, which is appropriate for HTML but makes JSON uses harder.
//
// JSON consumers already have to do path-based reasoning to reconstruct item reachability,
// names, and stability. Keeping component-wise stability allows them to easily reconstruct
// stability from the module, use item, and target item records.
let stability_def_id = if matches!(&item.kind, clean::ImportItem(_)) {
item.inline_stmt_id
.map(|def_id| def_id.to_def_id())
.or_else(|| item.item_id.as_def_id())
} else {
item.item_id.as_def_id()
};
let stability = stability_def_id.and_then(|def_id| self.tcx.lookup_stability(def_id));

Some(Item {
id,
crate_id: item_id.krate().as_u32(),
name: name.map(|sym| sym.to_string()),
span: span.and_then(|span| span.into_json(self)),
visibility: visibility.into_json(self),
stability: stability.map(|s| Box::new(s.into_json(self))),
docs,
attrs,
deprecation: deprecation.into_json(self),
Expand Down Expand Up @@ -203,6 +228,24 @@ impl FromClean<attrs::Deprecation> for Deprecation {
}
}

impl FromClean<hir::Stability> for Stability {
fn from_clean(stab: &hir::Stability, _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 @@ -922,6 +965,8 @@ 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::DocComment { .. } => unreachable!("doc comments stripped out earlier"),

AK::MacroExport { .. } => Attribute::MacroExport,
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, 528 + size_of::<std::path::PathBuf>());
static_assert_size!(Item, 536 + size_of::<std::path::PathBuf>());
static_assert_size!(ExternalCrate, 48 + size_of::<std::path::PathBuf>());
}
64 changes: 61 additions & 3 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 `ExternCrate::path`.
pub const FORMAT_VERSION: u32 = 57;
// Latest feature: Add `Item::stability`.
pub const FORMAT_VERSION: u32 = 58;

/// The root of the emitted JSON blob.
///
Expand Down Expand Up @@ -282,7 +282,10 @@ pub struct Item {
pub links: HashMap<String, Id>,
/// Attributes on this item.
///
/// Does not include `#[deprecated]` attributes: see the [`Self::deprecation`] field instead.
/// Does not include:
/// - `#[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.
///
/// Attributes appear in pretty-printed Rust form, regardless of their formatting
/// in the original source code. For example:
Expand All @@ -294,10 +297,64 @@ pub struct Item {
pub attrs: Vec<Attribute>,
/// Information about the item’s deprecation, if present.
pub deprecation: Option<Deprecation>,

/// Stability information for this item, if any.
///
/// This describes whether the item itself is stable or unstable, as noted by a `#[stable]` or
/// `#[unstable]` attribute. It does not capture const stability, default-body stability, etc.
///
/// Whether a path to an item is stable depends on the stability of containing modules
/// or re-exports along that path. For example, a stable item can be reachable through both an
/// unstable module and a stable re-export.
///
/// For items whose inner kind is [`ItemEnum::Use`], this is the stability of the import itself,
/// not the item being imported. This allows users to determine the stability of paths
/// that involve re-exports.
///
/// Associated items can inherit instability from their enclosing unstable trait or impl.
/// Unannotated associated items in stable traits or impls may have no separate stability value.
///
/// Currently, Rust's `#[stable]` and `#[unstable]` attributes are themselves not stable.
/// As a result, this field is primarily populated for standard-library items;
/// most ordinary third-party crates usually have no data here.
pub 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.
#[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.
///
/// 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.
pub feature: String,

#[serde(flatten)]
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)))]
#[serde(tag = "level", rename_all = "snake_case")]
pub enum StabilityLevel {
Stable {
/// The Rust version in which this item became stable, if available.
since: Option<String>,
},
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)))]
Expand All @@ -307,6 +364,7 @@ pub struct Item {
/// This doesn't include:
/// - `#[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.
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 @@ -33,6 +33,7 @@ fn errors_on_missing_links() {
links: FxHashMap::from_iter([("Not Found".to_owned(), Id(1))]),
attrs: vec![],
deprecation: None,
stability: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![],
Expand Down Expand Up @@ -81,6 +82,7 @@ fn errors_on_local_in_paths_and_not_index() {
links: FxHashMap::from_iter([("prim@i32".to_owned(), Id(2))]),
attrs: Vec::new(),
deprecation: None,
stability: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![Id(1)],
Expand All @@ -100,6 +102,7 @@ fn errors_on_local_in_paths_and_not_index() {
links: FxHashMap::default(),
attrs: Vec::new(),
deprecation: None,
stability: None,
inner: ItemEnum::Primitive(Primitive { name: "i32".to_owned(), impls: vec![] }),
},
),
Expand Down Expand Up @@ -153,6 +156,7 @@ fn errors_on_missing_path() {
links: FxHashMap::default(),
attrs: Vec::new(),
deprecation: None,
stability: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![Id(1), Id(2)],
Expand All @@ -172,6 +176,7 @@ fn errors_on_missing_path() {
links: FxHashMap::default(),
attrs: Vec::new(),
deprecation: None,
stability: None,
inner: ItemEnum::Struct(Struct {
kind: StructKind::Unit,
generics: generics.clone(),
Expand All @@ -191,6 +196,7 @@ fn errors_on_missing_path() {
links: FxHashMap::default(),
attrs: Vec::new(),
deprecation: None,
stability: None,
inner: ItemEnum::Function(Function {
sig: FunctionSignature {
inputs: vec![],
Expand Down Expand Up @@ -253,6 +259,7 @@ fn checks_local_crate_id_is_correct() {
links: FxHashMap::default(),
attrs: Vec::new(),
deprecation: None,
stability: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![],
Expand Down
66 changes: 66 additions & 0 deletions tests/rustdoc-json/attrs/stability/associated_items.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#![feature(staged_api)]

// Trait-associated item declarations only. Items defined inside impl blocks are tested in
// `impls.rs` because they have different parent-item behavior.

//@ is "$.index[?(@.name=='StableTraitWithAssociatedItems')].stability.level" '"stable"'
//@ is "$.index[?(@.name=='StableTraitWithAssociatedItems')].stability.feature" '"stable_trait_with_associated_items"'
//@ is "$.index[?(@.name=='StableTraitWithAssociatedItems')].stability.since" '"1.0.0"'
#[stable(feature = "stable_trait_with_associated_items", since = "1.0.0")]
pub trait StableTraitWithAssociatedItems {
// Stable trait-associated items need their own stability attributes in staged API crates.
//@ is "$.index[?(@.name=='StableAssocType')].stability.level" '"stable"'
//@ is "$.index[?(@.name=='StableAssocType')].stability.feature" '"stable_assoc_type_feature"'
//@ is "$.index[?(@.name=='StableAssocType')].stability.since" '"1.1.0"'
//@ is "$.index[?(@.name=='StableAssocType')].attrs" []
#[stable(feature = "stable_assoc_type_feature", since = "1.1.0")]
type StableAssocType;

//@ is "$.index[?(@.name=='UNSTABLE_ASSOC_CONST_IN_STABLE_TRAIT')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='UNSTABLE_ASSOC_CONST_IN_STABLE_TRAIT')].stability.feature" '"unstable_assoc_const_in_stable_trait"'
//@ !has "$.index[?(@.name=='UNSTABLE_ASSOC_CONST_IN_STABLE_TRAIT')].stability.since"
#[unstable(feature = "unstable_assoc_const_in_stable_trait", issue = "none")]
const UNSTABLE_ASSOC_CONST_IN_STABLE_TRAIT: usize = 0;

//@ is "$.index[?(@.name=='unstable_provided_method')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='unstable_provided_method')].stability.feature" '"unstable_provided_method_feature"'
//@ !has "$.index[?(@.name=='unstable_provided_method')].stability.since"
#[unstable(feature = "unstable_provided_method_feature", issue = "none")]
fn unstable_provided_method(&self) {}
}

//@ is "$.index[?(@.name=='UnstableTraitWithUnannotatedAssociatedItems')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='UnstableTraitWithUnannotatedAssociatedItems')].stability.feature" '"unstable_trait_with_unannotated_associated_items"'
//@ !has "$.index[?(@.name=='UnstableTraitWithUnannotatedAssociatedItems')].stability.since"
#[unstable(feature = "unstable_trait_with_unannotated_associated_items", issue = "none")]
pub trait UnstableTraitWithUnannotatedAssociatedItems {
// Unannotated associated items in unstable traits inherit the trait's unstable stability.
//@ is "$.index[?(@.name=='UnannotatedAssocTypeInUnstableTrait')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='UnannotatedAssocTypeInUnstableTrait')].stability.feature" '"unstable_trait_with_unannotated_associated_items"'
//@ !has "$.index[?(@.name=='UnannotatedAssocTypeInUnstableTrait')].stability.since"
type UnannotatedAssocTypeInUnstableTrait;

//@ is "$.index[?(@.name=='UNANNOTATED_ASSOC_CONST_IN_UNSTABLE_TRAIT')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='UNANNOTATED_ASSOC_CONST_IN_UNSTABLE_TRAIT')].stability.feature" '"unstable_trait_with_unannotated_associated_items"'
//@ !has "$.index[?(@.name=='UNANNOTATED_ASSOC_CONST_IN_UNSTABLE_TRAIT')].stability.since"
const UNANNOTATED_ASSOC_CONST_IN_UNSTABLE_TRAIT: usize;

//@ is "$.index[?(@.name=='unannotated_required_method_in_unstable_trait')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='unannotated_required_method_in_unstable_trait')].stability.feature" '"unstable_trait_with_unannotated_associated_items"'
//@ !has "$.index[?(@.name=='unannotated_required_method_in_unstable_trait')].stability.since"
fn unannotated_required_method_in_unstable_trait(&self);
}

//@ is "$.index[?(@.name=='UnstableTraitWithExplicitAssociatedItem')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='UnstableTraitWithExplicitAssociatedItem')].stability.feature" '"unstable_trait_with_explicit_associated_item"'
//@ !has "$.index[?(@.name=='UnstableTraitWithExplicitAssociatedItem')].stability.since"
#[unstable(feature = "unstable_trait_with_explicit_associated_item", issue = "none")]
pub trait UnstableTraitWithExplicitAssociatedItem {
// It's possble to override the parent's instability with another `#[unstable]` attribute,
// for example to specify a different feature gate for that item.
//@ is "$.index[?(@.name=='UnstableAssocTypeInUnstableTrait')].stability.level" '"unstable"'
//@ is "$.index[?(@.name=='UnstableAssocTypeInUnstableTrait')].stability.feature" '"unstable_assoc_type_in_unstable_trait"'
//@ !has "$.index[?(@.name=='UnstableAssocTypeInUnstableTrait')].stability.since"
#[unstable(feature = "unstable_assoc_type_in_unstable_trait", issue = "none")]
type UnstableAssocTypeInUnstableTrait;
}
Loading
Loading