diff options
| author | Alona Enraght-Moony <code@alona.page> | 2025-06-20 02:48:15 +0000 |
|---|---|---|
| committer | Alona Enraght-Moony <code@alona.page> | 2025-07-15 16:52:41 +0000 |
| commit | 078332fdc8e11f7ff8253c019085098538ec3c2a (patch) | |
| tree | 25c35cd99077885e2ac7d26e2421f87972dab9db /src | |
| parent | cccf075eba88363269e8589ebb8d40874cc542d8 (diff) | |
| download | rust-078332fdc8e11f7ff8253c019085098538ec3c2a.tar.gz rust-078332fdc8e11f7ff8253c019085098538ec3c2a.zip | |
rustdoc-json: Structured attributes
Implements https://www.github.com/rust-lang/rust/issues/141358. This has 2 primary benefits: 1. For rustdoc-json consumers, they no longer need to parse strings of attributes, but it's there in a structured and normalized way. 2. For rustc contributors, the output of HIR pretty printing is no longer a versioned thing in the output. People can work on https://github.com/rust-lang/rust/issues/131229 without needing to bump `FORMAT_VERSION`. (Over time, as the attribute refractor continues, I expect we'll add new things to `rustdoc_json_types::Attribute`. But this can be done separately to the rustc changes).
Diffstat (limited to 'src')
| -rw-r--r-- | src/librustdoc/clean/types.rs | 80 | ||||
| -rw-r--r-- | src/librustdoc/html/render/mod.rs | 6 | ||||
| -rw-r--r-- | src/librustdoc/html/render/print_item.rs | 3 | ||||
| -rw-r--r-- | src/librustdoc/json/conversions.rs | 99 | ||||
| -rw-r--r-- | src/rustdoc-json-types/lib.rs | 89 |
5 files changed, 212 insertions, 65 deletions
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 3ecd41db2dd..20babc6168b 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -759,79 +759,48 @@ impl Item { Some(tcx.visibility(def_id)) } - fn attributes_without_repr(&self, tcx: TyCtxt<'_>, is_json: bool) -> Vec<String> { - const ALLOWED_ATTRIBUTES: &[Symbol] = - &[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive]; + /// Get a list of attributes excluding `#[repr]` to display. + /// + /// Only used by the HTML output-format. + fn attributes_without_repr(&self) -> Vec<String> { self.attrs .other_attrs .iter() - .filter_map(|attr| { - if let hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) = attr { + .filter_map(|attr| match attr { + hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => { Some(format!("#[link_section = \"{name}\"]")) } - // NoMangle is special cased, as it appears in HTML output, and we want to show it in source form, not HIR printing. - // It is also used by cargo-semver-checks. - else if let hir::Attribute::Parsed(AttributeKind::NoMangle(..)) = attr { + hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => { Some("#[no_mangle]".to_string()) - } else if let hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) = attr - { + } + hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => { Some(format!("#[export_name = \"{name}\"]")) - } else if let hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) = attr { + } + hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => { Some("#[non_exhaustive]".to_string()) - } else if is_json { - match attr { - // rustdoc-json stores this in `Item::deprecation`, so we - // don't want it it `Item::attrs`. - hir::Attribute::Parsed(AttributeKind::Deprecation { .. }) => None, - // We have separate pretty-printing logic for `#[repr(..)]` attributes. - hir::Attribute::Parsed(AttributeKind::Repr { .. }) => None, - // target_feature is special-cased because cargo-semver-checks uses it - hir::Attribute::Parsed(AttributeKind::TargetFeature(features, _)) => { - let mut output = String::new(); - for (i, (feature, _)) in features.iter().enumerate() { - if i != 0 { - output.push_str(", "); - } - output.push_str(&format!("enable=\"{}\"", feature.as_str())); - } - Some(format!("#[target_feature({output})]")) - } - hir::Attribute::Parsed(AttributeKind::AutomaticallyDerived(..)) => { - Some("#[automatically_derived]".to_string()) - } - _ => Some({ - let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr); - assert_eq!(s.pop(), Some('\n')); - s - }), - } - } else { - if !attr.has_any_name(ALLOWED_ATTRIBUTES) { - return None; - } - Some( - rustc_hir_pretty::attribute_to_string(&tcx, attr) - .replace("\\\n", "") - .replace('\n', "") - .replace(" ", " "), - ) } + _ => None, }) .collect() } - pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Vec<String> { - let mut attrs = self.attributes_without_repr(tcx, is_json); + /// Get a list of attributes to display on this item. + /// + /// Only used by the HTML output-format. + pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> { + let mut attrs = self.attributes_without_repr(); - if let Some(repr_attr) = self.repr(tcx, cache, is_json) { + if let Some(repr_attr) = self.repr(tcx, cache) { attrs.push(repr_attr); } attrs } /// Returns a stringified `#[repr(...)]` attribute. - pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> { - repr_attributes(tcx, cache, self.def_id()?, self.type_(), is_json) + /// + /// Only used by the HTML output-format. + pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> { + repr_attributes(tcx, cache, self.def_id()?, self.type_()) } pub fn is_doc_hidden(&self) -> bool { @@ -843,12 +812,14 @@ impl Item { } } +/// Return a string representing the `#[repr]` attribute if present. +/// +/// Only used by the HTML output-format. pub(crate) fn repr_attributes( tcx: TyCtxt<'_>, cache: &Cache, def_id: DefId, item_type: ItemType, - is_json: bool, ) -> Option<String> { use rustc_abi::IntegerType; @@ -865,7 +836,6 @@ pub(crate) fn repr_attributes( // Render `repr(transparent)` iff the non-1-ZST field is public or at least one // field is public in case all fields are 1-ZST fields. let render_transparent = cache.document_private - || is_json || adt .all_fields() .find(|field| { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 70f3f54e4c0..06de4944d97 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1191,7 +1191,7 @@ fn render_assoc_item( // a whitespace prefix and newline. fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display { fmt::from_fn(move |f| { - for a in it.attributes(cx.tcx(), cx.cache(), false) { + for a in it.attributes(cx.tcx(), cx.cache()) { writeln!(f, "{prefix}{a}")?; } Ok(()) @@ -1207,7 +1207,7 @@ fn render_code_attribute(code_attr: CodeAttribute, w: &mut impl fmt::Write) { // When an attribute is rendered inside a <code> tag, it is formatted using // a div to produce a newline after it. fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) { - for attr in it.attributes(cx.tcx(), cx.cache(), false) { + for attr in it.attributes(cx.tcx(), cx.cache()) { render_code_attribute(CodeAttribute(attr), w); } } @@ -1219,7 +1219,7 @@ fn render_repr_attributes_in_code( def_id: DefId, item_type: ItemType, ) { - if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type, false) { + if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) { render_code_attribute(CodeAttribute(repr), w); } } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index e33bdc0db32..667d39e9bc2 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1487,12 +1487,11 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { self.cx.cache(), self.def_id, ItemType::Union, - false, ) { writeln!(f, "{repr}")?; }; } else { - for a in self.it.attributes(self.cx.tcx(), self.cx.cache(), false) { + for a in self.it.attributes(self.cx.tcx(), self.cx.cache()) { writeln!(f, "{a}")?; } } diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index e7163bead92..0a84d8caa30 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -5,10 +5,12 @@ use rustc_abi::ExternAbi; use rustc_ast::ast; use rustc_attr_data_structures::{self as attrs, DeprecatedSince}; +use rustc_hir as hir; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; use rustc_hir::{HeaderSafety, Safety}; use rustc_metadata::rendered_const; +use rustc_middle::ty::TyCtxt; use rustc_middle::{bug, ty}; use rustc_span::{Pos, kw, sym}; use rustdoc_json_types::*; @@ -39,7 +41,12 @@ impl JsonRenderer<'_> { }) .collect(); let docs = item.opt_doc_value(); - let attrs = item.attributes(self.tcx, &self.cache, true); + let attrs = item + .attrs + .other_attrs + .iter() + .filter_map(|a| maybe_from_hir_attr(a, item.item_id, self.tcx)) + .collect(); let span = item.span(self.tcx); let visibility = item.visibility(self.tcx); let clean::ItemInner { name, item_id, .. } = *item.inner; @@ -886,3 +893,93 @@ impl FromClean<ItemType> for ItemKind { } } } + +/// Maybe convert a attribute from hir to json. +/// +/// Returns `None` if the attribute shouldn't be in the output. +fn maybe_from_hir_attr( + attr: &hir::Attribute, + item_id: ItemId, + tcx: TyCtxt<'_>, +) -> Option<Attribute> { + use attrs::AttributeKind as AK; + + let kind = match attr { + hir::Attribute::Parsed(kind) => kind, + + hir::Attribute::Unparsed(_) => { + // FIXME: We should handle `#[doc(hidden)]`. + return Some(other_attr(tcx, attr)); + } + }; + + Some(match kind { + AK::Deprecation { .. } => return None, // Handled separately into Item::deprecation. + AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"), + + AK::MustUse { reason, span: _ } => { + Attribute::MustUse { reason: reason.map(|s| s.to_string()) } + } + AK::Repr { .. } => repr_attr( + tcx, + item_id.as_def_id().expect("all items that could have #[repr] have a DefId"), + ), + AK::ExportName { name, span: _ } => Attribute::ExportName(name.to_string()), + AK::LinkSection { name, span: _ } => Attribute::LinkSection(name.to_string()), + AK::TargetFeature(features, _span) => Attribute::TargetFeature { + enable: features.iter().map(|(feat, _span)| feat.to_string()).collect(), + }, + + AK::NoMangle(_) => Attribute::NoMangle, + AK::NonExhaustive(_) => Attribute::NonExhaustive, + AK::AutomaticallyDerived(_) => Attribute::AutomaticallyDerived, + + _ => other_attr(tcx, attr), + }) +} + +fn other_attr(tcx: TyCtxt<'_>, attr: &hir::Attribute) -> Attribute { + let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr); + assert_eq!(s.pop(), Some('\n')); + Attribute::Other(s) +} + +fn repr_attr(tcx: TyCtxt<'_>, def_id: DefId) -> Attribute { + let repr = tcx.adt_def(def_id).repr(); + + let kind = if repr.c() { + ReprKind::C + } else if repr.transparent() { + ReprKind::Transparent + } else if repr.simd() { + ReprKind::Simd + } else { + ReprKind::Rust + }; + + let align = repr.align.map(|a| a.bytes()); + let packed = repr.pack.map(|p| p.bytes()); + let int = repr.int.map(format_integer_type); + + Attribute::Repr(AttributeRepr { kind, align, packed, int }) +} + +fn format_integer_type(it: rustc_abi::IntegerType) -> String { + use rustc_abi::Integer::*; + use rustc_abi::IntegerType::*; + match it { + Pointer(true) => "isize", + Pointer(false) => "usize", + Fixed(I8, true) => "i8", + Fixed(I8, false) => "u8", + Fixed(I16, true) => "i16", + Fixed(I16, false) => "u16", + Fixed(I32, true) => "i32", + Fixed(I32, false) => "u32", + Fixed(I64, true) => "i64", + Fixed(I64, false) => "u64", + Fixed(I128, true) => "i128", + Fixed(I128, false) => "u128", + } + .to_owned() +} diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 0e72ddd9db1..6235b0e8576 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -37,8 +37,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: Pretty printing of no_mangle attributes changed -pub const FORMAT_VERSION: u32 = 53; +// Latest feature: Structured Attributes +pub const FORMAT_VERSION: u32 = 54; /// The root of the emitted JSON blob. /// @@ -195,13 +195,94 @@ pub struct Item { /// - `#[repr(C)]` and other reprs also appear as themselves, /// though potentially with a different order: e.g. `repr(i8, C)` may become `repr(C, i8)`. /// Multiple repr attributes on the same item may be combined into an equivalent single attr. - pub attrs: Vec<String>, + pub attrs: Vec<Attribute>, /// Information about the item’s deprecation, if present. pub deprecation: Option<Deprecation>, /// The type-specific fields describing this item. pub inner: ItemEnum, } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +/// An attribute, e.g. `#[repr(C)]` +/// +/// This doesn't include: +/// - `#[doc = "Doc Comment"]` or `/// Doc comment`. These are in [`Item::docs`] instead. +/// - `#[deprecated]`. These are in [`Item::deprecation`] instead. +pub enum Attribute { + /// `#[non_exhaustive]` + NonExhaustive, + + /// `#[must_use]` + MustUse { reason: Option<String> }, + + /// `#[export_name = "name"]` + ExportName(String), + + /// `#[link_section = "name"]` + LinkSection(String), + + /// `#[automatically_derived]` + AutomaticallyDerived, + + /// `#[repr]` + Repr(AttributeRepr), + + /// `#[no_mangle]` + NoMangle, + + /// #[target_feature(enable = "feature1", enable = "feature2")] + TargetFeature { enable: Vec<String> }, + + /// Something else. + /// + /// Things here are explicitly *not* covered by the [`FORMAT_VERSION`] + /// constant, and may change without bumping the format version. + /// + /// As an implementation detail, this is currently either: + /// 1. A HIR debug printing, like `"#[attr = Optimize(Speed)]"` + /// 2. The attribute as it appears in source form, like + /// `"#[optimize(speed)]"`. + Other(String), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// The contents of a `#[repr(...)]` attribute. +/// +/// Used in [`Attribute::Repr`]. +pub struct AttributeRepr { + /// The representation, e.g. `#[repr(C)]`, `#[repr(transparent)]` + pub kind: ReprKind, + + /// Alignment in bytes, if explicitly specified by `#[repr(align(...)]`. + pub align: Option<u64>, + /// Alignment in bytes, if explicitly specified by `#[repr(packed(...)]]`. + pub packed: Option<u64>, + + /// The integer type for an enum descriminant, if explicitly specified. + /// + /// e.g. `"i32"`, for `#[repr(C, i32)]` + pub int: Option<String>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +/// The kind of `#[repr]`. +/// +/// See [AttributeRepr::kind]`. +pub enum ReprKind { + /// `#[repr(Rust)]` + /// + /// Also the default. + Rust, + /// `#[repr(C)]` + C, + /// `#[repr(transparent)] + Transparent, + /// `#[repr(simd)]` + Simd, +} + /// A range of source code. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Span { @@ -1343,7 +1424,7 @@ pub struct Static { /// Is the static `unsafe`? /// - /// This is only true if it's in an `extern` block, and not explicity marked + /// This is only true if it's in an `extern` block, and not explicitly marked /// as `safe`. /// /// ```rust |
