Skip to content

Commit 157ec24

Browse files
committed
WIP: rustdoc-json: Structured attributes
1 parent 42245d3 commit 157ec24

File tree

5 files changed

+144
-36
lines changed

5 files changed

+144
-36
lines changed

src/librustdoc/clean/types.rs

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -746,33 +746,21 @@ impl Item {
746746
Some(tcx.visibility(def_id))
747747
}
748748

749-
fn attributes_without_repr(&self, tcx: TyCtxt<'_>, is_json: bool) -> Vec<String> {
749+
/// Get a list of attributes excluding `#[repr]`.
750+
///
751+
/// Only used by the HTML output-format.
752+
fn attributes_without_repr(&self, tcx: TyCtxt<'_>) -> Vec<String> {
750753
const ALLOWED_ATTRIBUTES: &[Symbol] =
751-
&[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
754+
&[sym::export_name, sym::link_section, sym::non_exhaustive];
752755
self.attrs
753756
.other_attrs
754757
.iter()
755758
.filter_map(|attr| {
756-
// NoMangle is special-cased because cargo-semver-checks uses it
757-
if matches!(attr, hir::Attribute::Parsed(AttributeKind::NoMangle(..))) {
759+
if matches!(attr, hir::Attribute::Parsed(AttributeKind::NoMangle(_))) {
758760
Some("#[no_mangle]".to_string())
759-
} else if is_json {
760-
match attr {
761-
// rustdoc-json stores this in `Item::deprecation`, so we
762-
// don't want it it `Item::attrs`.
763-
hir::Attribute::Parsed(AttributeKind::Deprecation { .. }) => None,
764-
// We have separate pretty-printing logic for `#[repr(..)]` attributes.
765-
hir::Attribute::Parsed(AttributeKind::Repr(..)) => None,
766-
_ => Some({
767-
let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr);
768-
assert_eq!(s.pop(), Some('\n'));
769-
s
770-
}),
771-
}
761+
} else if !attr.has_any_name(ALLOWED_ATTRIBUTES) {
762+
None
772763
} else {
773-
if !attr.has_any_name(ALLOWED_ATTRIBUTES) {
774-
return None;
775-
}
776764
Some(
777765
rustc_hir_pretty::attribute_to_string(&tcx, attr)
778766
.replace("\\\n", "")
@@ -784,18 +772,23 @@ impl Item {
784772
.collect()
785773
}
786774

787-
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Vec<String> {
788-
let mut attrs = self.attributes_without_repr(tcx, is_json);
775+
/// Get a list of attributes to display on this item.
776+
///
777+
/// Only used by the HTML output-format.
778+
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> {
779+
let mut attrs = self.attributes_without_repr(tcx);
789780

790-
if let Some(repr_attr) = self.repr(tcx, cache, is_json) {
781+
if let Some(repr_attr) = self.repr(tcx, cache) {
791782
attrs.push(repr_attr);
792783
}
793784
attrs
794785
}
795786

796787
/// Returns a stringified `#[repr(...)]` attribute.
797-
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> {
798-
repr_attributes(tcx, cache, self.def_id()?, self.type_(), is_json)
788+
///
789+
/// Only used by the HTML output-format.
790+
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> {
791+
repr_attributes(tcx, cache, self.def_id()?, self.type_())
799792
}
800793

801794
pub fn is_doc_hidden(&self) -> bool {
@@ -807,12 +800,14 @@ impl Item {
807800
}
808801
}
809802

803+
/// Return a string representing the `#[repr]` attribute if present.
804+
///
805+
/// Only used by the HTML output-format.
810806
pub(crate) fn repr_attributes(
811807
tcx: TyCtxt<'_>,
812808
cache: &Cache,
813809
def_id: DefId,
814810
item_type: ItemType,
815-
is_json: bool,
816811
) -> Option<String> {
817812
use rustc_abi::IntegerType;
818813

@@ -829,7 +824,6 @@ pub(crate) fn repr_attributes(
829824
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
830825
// field is public in case all fields are 1-ZST fields.
831826
let render_transparent = cache.document_private
832-
|| is_json
833827
|| adt
834828
.all_fields()
835829
.find(|field| {

src/librustdoc/html/render/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,7 @@ fn render_assoc_item(
11941194
// a whitespace prefix and newline.
11951195
fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display {
11961196
fmt::from_fn(move |f| {
1197-
for a in it.attributes(cx.tcx(), cx.cache(), false) {
1197+
for a in it.attributes(cx.tcx(), cx.cache()) {
11981198
writeln!(f, "{prefix}{a}")?;
11991199
}
12001200
Ok(())
@@ -1210,7 +1210,7 @@ fn render_code_attribute(code_attr: CodeAttribute, w: &mut impl fmt::Write) {
12101210
// When an attribute is rendered inside a <code> tag, it is formatted using
12111211
// a div to produce a newline after it.
12121212
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
1213-
for attr in it.attributes(cx.tcx(), cx.cache(), false) {
1213+
for attr in it.attributes(cx.tcx(), cx.cache()) {
12141214
render_code_attribute(CodeAttribute(attr), w);
12151215
}
12161216
}
@@ -1222,7 +1222,7 @@ fn render_repr_attributes_in_code(
12221222
def_id: DefId,
12231223
item_type: ItemType,
12241224
) {
1225-
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type, false) {
1225+
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) {
12261226
render_code_attribute(CodeAttribute(repr), w);
12271227
}
12281228
}

src/librustdoc/html/render/print_item.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,12 +1486,11 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
14861486
self.cx.cache(),
14871487
self.def_id,
14881488
ItemType::Union,
1489-
false,
14901489
) {
14911490
writeln!(f, "{repr}")?;
14921491
};
14931492
} else {
1494-
for a in self.it.attributes(self.cx.tcx(), self.cx.cache(), false) {
1493+
for a in self.it.attributes(self.cx.tcx(), self.cx.cache()) {
14951494
writeln!(f, "{a}")?;
14961495
}
14971496
}

src/librustdoc/json/conversions.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
use rustc_abi::ExternAbi;
88
use rustc_ast::ast;
99
use rustc_attr_data_structures::{self as attrs, DeprecatedSince};
10+
use rustc_hir as hir;
1011
use rustc_hir::def::CtorKind;
1112
use rustc_hir::def_id::DefId;
1213
use rustc_metadata::rendered_const;
14+
use rustc_middle::ty::TyCtxt;
1315
use rustc_middle::{bug, ty};
1416
use rustc_span::{Pos, kw, sym};
1517
use rustdoc_json_types::*;
@@ -40,7 +42,12 @@ impl JsonRenderer<'_> {
4042
})
4143
.collect();
4244
let docs = item.opt_doc_value();
43-
let attrs = item.attributes(self.tcx, &self.cache, true);
45+
let attrs = item
46+
.attrs
47+
.other_attrs
48+
.iter()
49+
.filter_map(|a| maybe_from_hir_attr(a, item.item_id, self.tcx))
50+
.collect();
4451
let span = item.span(self.tcx);
4552
let visibility = item.visibility(self.tcx);
4653
let clean::ItemInner { name, item_id, .. } = *item.inner;
@@ -872,3 +879,68 @@ impl FromClean<ItemType> for ItemKind {
872879
}
873880
}
874881
}
882+
883+
/// Maybe convert a attribue from hir to json.
884+
///
885+
/// Returns `None` if the attribute shouldn't be in the output.
886+
fn maybe_from_hir_attr(
887+
attr: &hir::Attribute,
888+
item_id: ItemId,
889+
tcx: TyCtxt<'_>,
890+
) -> Option<Attribute> {
891+
use attrs::AttributeKind as AK;
892+
893+
let kind = match attr {
894+
hir::Attribute::Parsed(kind) => kind,
895+
896+
// There are some currently unstrucured attrs that we *do* care about.
897+
// As the attribute migration progresses (#131229), this is expected to shrink
898+
// and eventually be removed as all attributes gain a strutured representation in
899+
// HIR.
900+
hir::Attribute::Unparsed(_) => {
901+
return Some(if attr.has_name(sym::non_exhaustive) {
902+
Attribute::NonExhaustive
903+
} else if attr.has_name(sym::automatically_derived) {
904+
Attribute::AutomaticallyDerived
905+
} else if attr.has_name(sym::export_name) {
906+
Attribute::ExportName(
907+
attr.value_str().expect("checked by attr validation").to_string(),
908+
)
909+
} else if attr.has_name(sym::doc)
910+
&& attr
911+
.meta_item_list()
912+
.is_some_and(|metas| metas.iter().any(|item| item.has_name(sym::hidden)))
913+
{
914+
Attribute::DocHidden
915+
} else {
916+
Attribute::Other(rustc_hir_pretty::attribute_to_string(&tcx, attr))
917+
});
918+
}
919+
};
920+
921+
Some(match kind {
922+
AK::Deprecation { .. } => return None, // Handled seperatly into Item::deprecation.
923+
AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"),
924+
925+
AK::MustUse { reason, span: _ } => {
926+
Attribute::MustUse { reason: reason.map(|s| s.to_string()) }
927+
}
928+
AK::Repr { .. } => Attribute::Repr(repr(item_id.as_def_id().expect("reason"), tcx)),
929+
AK::NoMangle(_) => Attribute::NoMangle,
930+
931+
_ => Attribute::Other(rustc_hir_pretty::attribute_to_string(&tcx, attr)),
932+
})
933+
}
934+
935+
fn repr(def_id: DefId, tcx: TyCtxt<'_>) -> AttributeRepr {
936+
let repr = tcx.adt_def(def_id).repr();
937+
938+
// FIXME: This is wildly insufficient
939+
if repr.c() {
940+
AttributeRepr::C
941+
} else if repr.transparent() {
942+
AttributeRepr::Transparent
943+
} else {
944+
AttributeRepr::Rust
945+
}
946+
}

src/rustdoc-json-types/lib.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
3737
// will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line
3838
// are deliberately not in a doc comment, because they need not be in public docs.)
3939
//
40-
// Latest feature: Pretty printing of no_mangle attributes changed
41-
pub const FORMAT_VERSION: u32 = 53;
40+
// Latest feature: Structed Attributes
41+
pub const FORMAT_VERSION: u32 = 54;
4242

4343
/// The root of the emitted JSON blob.
4444
///
@@ -195,13 +195,56 @@ pub struct Item {
195195
/// - `#[repr(C)]` and other reprs also appear as themselves,
196196
/// though potentially with a different order: e.g. `repr(i8, C)` may become `repr(C, i8)`.
197197
/// Multiple repr attributes on the same item may be combined into an equivalent single attr.
198-
pub attrs: Vec<String>,
198+
pub attrs: Vec<Attribute>,
199199
/// Information about the item’s deprecation, if present.
200200
pub deprecation: Option<Deprecation>,
201201
/// The type-specific fields describing this item.
202202
pub inner: ItemEnum,
203203
}
204204

205+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
206+
/// An attribute, eg `#[repr(C)]`
207+
///
208+
/// This doesn't include:
209+
/// - `#[doc = "Doc Comment"]` or `/// Doc comment`. These are in [`Item::docs`] instead.
210+
/// - `#[depricate]`. These are in [`Item::deprecation`] instead.
211+
pub enum Attribute {
212+
/// `#[non_exaustive]`
213+
NonExhaustive,
214+
215+
/// `#[must_use]`
216+
MustUse {
217+
reason: Option<String>,
218+
},
219+
220+
/// `#[automatically_derived]`
221+
AutomaticallyDerived,
222+
223+
/// `#[repr]`
224+
Repr(AttributeRepr),
225+
226+
ExportName(String),
227+
/// `#[doc(hidden)]`
228+
DocHidden,
229+
/// `#[no_mangle]`
230+
NoMangle,
231+
232+
/// Something else.
233+
///
234+
/// Things here are explicitly *not* covered by the [`FORMAT_VERSION`]
235+
/// constant, and may change without bumping the format version. If you rely
236+
/// on an attibute here, please open an issue about adding a new variant for
237+
/// that attr.
238+
Other(String),
239+
}
240+
241+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
242+
pub enum AttributeRepr {
243+
C,
244+
Transparent,
245+
Rust,
246+
}
247+
205248
/// A range of source code.
206249
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
207250
pub struct Span {

0 commit comments

Comments
 (0)