Skip to content

Commit fd4d595

Browse files
committed
rustdoc-json: Structured attributes
Implements #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 #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).
1 parent 2f8eeb2 commit fd4d595

27 files changed

+316
-115
lines changed

src/librustdoc/clean/types.rs

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -759,76 +759,48 @@ impl Item {
759759
Some(tcx.visibility(def_id))
760760
}
761761

762-
fn attributes_without_repr(&self, tcx: TyCtxt<'_>, is_json: bool) -> Vec<String> {
763-
const ALLOWED_ATTRIBUTES: &[Symbol] =
764-
&[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
762+
/// Get a list of attributes excluding `#[repr]` to display.
763+
///
764+
/// Only used by the HTML output-format.
765+
fn attributes_without_repr(&self) -> Vec<String> {
765766
self.attrs
766767
.other_attrs
767768
.iter()
768-
.filter_map(|attr| {
769-
if let hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) = attr {
769+
.filter_map(|attr| match attr {
770+
hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => {
770771
Some(format!("#[link_section = \"{name}\"]"))
771772
}
772-
// NoMangle is special cased, as it appears in HTML output, and we want to show it in source form, not HIR printing.
773-
// It is also used by cargo-semver-checks.
774-
else if let hir::Attribute::Parsed(AttributeKind::NoMangle(..)) = attr {
773+
hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
775774
Some("#[no_mangle]".to_string())
776-
} else if let hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) = attr
777-
{
775+
}
776+
hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
778777
Some(format!("#[export_name = \"{name}\"]"))
779-
} else if let hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) = attr {
778+
}
779+
hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => {
780780
Some("#[non_exhaustive]".to_string())
781-
} else if is_json {
782-
match attr {
783-
// rustdoc-json stores this in `Item::deprecation`, so we
784-
// don't want it it `Item::attrs`.
785-
hir::Attribute::Parsed(AttributeKind::Deprecation { .. }) => None,
786-
// We have separate pretty-printing logic for `#[repr(..)]` attributes.
787-
hir::Attribute::Parsed(AttributeKind::Repr { .. }) => None,
788-
// target_feature is special-cased because cargo-semver-checks uses it
789-
hir::Attribute::Parsed(AttributeKind::TargetFeature(features, _)) => {
790-
let mut output = String::new();
791-
for (i, (feature, _)) in features.iter().enumerate() {
792-
if i != 0 {
793-
output.push_str(", ");
794-
}
795-
output.push_str(&format!("enable=\"{}\"", feature.as_str()));
796-
}
797-
Some(format!("#[target_feature({output})]"))
798-
}
799-
_ => Some({
800-
let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr);
801-
assert_eq!(s.pop(), Some('\n'));
802-
s
803-
}),
804-
}
805-
} else {
806-
if !attr.has_any_name(ALLOWED_ATTRIBUTES) {
807-
return None;
808-
}
809-
Some(
810-
rustc_hir_pretty::attribute_to_string(&tcx, attr)
811-
.replace("\\\n", "")
812-
.replace('\n', "")
813-
.replace(" ", " "),
814-
)
815781
}
782+
_ => None,
816783
})
817784
.collect()
818785
}
819786

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

823-
if let Some(repr_attr) = self.repr(tcx, cache, is_json) {
793+
if let Some(repr_attr) = self.repr(tcx, cache) {
824794
attrs.push(repr_attr);
825795
}
826796
attrs
827797
}
828798

829799
/// Returns a stringified `#[repr(...)]` attribute.
830-
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> {
831-
repr_attributes(tcx, cache, self.def_id()?, self.type_(), is_json)
800+
///
801+
/// Only used by the HTML output-format.
802+
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> {
803+
repr_attributes(tcx, cache, self.def_id()?, self.type_())
832804
}
833805

834806
pub fn is_doc_hidden(&self) -> bool {
@@ -840,12 +812,14 @@ impl Item {
840812
}
841813
}
842814

815+
/// Return a string representing the `#[repr]` attribute if present.
816+
///
817+
/// Only used by the HTML output-format.
843818
pub(crate) fn repr_attributes(
844819
tcx: TyCtxt<'_>,
845820
cache: &Cache,
846821
def_id: DefId,
847822
item_type: ItemType,
848-
is_json: bool,
849823
) -> Option<String> {
850824
use rustc_abi::IntegerType;
851825

@@ -862,7 +836,6 @@ pub(crate) fn repr_attributes(
862836
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
863837
// field is public in case all fields are 1-ZST fields.
864838
let render_transparent = cache.document_private
865-
|| is_json
866839
|| adt
867840
.all_fields()
868841
.find(|field| {

src/librustdoc/html/render/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,7 +1191,7 @@ fn render_assoc_item(
11911191
// a whitespace prefix and newline.
11921192
fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display {
11931193
fmt::from_fn(move |f| {
1194-
for a in it.attributes(cx.tcx(), cx.cache(), false) {
1194+
for a in it.attributes(cx.tcx(), cx.cache()) {
11951195
writeln!(f, "{prefix}{a}")?;
11961196
}
11971197
Ok(())
@@ -1207,7 +1207,7 @@ fn render_code_attribute(code_attr: CodeAttribute, w: &mut impl fmt::Write) {
12071207
// When an attribute is rendered inside a <code> tag, it is formatted using
12081208
// a div to produce a newline after it.
12091209
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
1210-
for attr in it.attributes(cx.tcx(), cx.cache(), false) {
1210+
for attr in it.attributes(cx.tcx(), cx.cache()) {
12111211
render_code_attribute(CodeAttribute(attr), w);
12121212
}
12131213
}
@@ -1219,7 +1219,7 @@ fn render_repr_attributes_in_code(
12191219
def_id: DefId,
12201220
item_type: ItemType,
12211221
) {
1222-
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type, false) {
1222+
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) {
12231223
render_code_attribute(CodeAttribute(repr), w);
12241224
}
12251225
}

src/librustdoc/html/render/print_item.rs

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

src/librustdoc/json/conversions.rs

Lines changed: 105 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;
@@ -875,3 +882,100 @@ impl FromClean<ItemType> for ItemKind {
875882
}
876883
}
877884
}
885+
886+
/// Maybe convert a attribute from hir to json.
887+
///
888+
/// Returns `None` if the attribute shouldn't be in the output.
889+
fn maybe_from_hir_attr(
890+
attr: &hir::Attribute,
891+
item_id: ItemId,
892+
tcx: TyCtxt<'_>,
893+
) -> Option<Attribute> {
894+
use attrs::AttributeKind as AK;
895+
896+
let kind = match attr {
897+
hir::Attribute::Parsed(kind) => kind,
898+
899+
// There are some currently unstrucured attrs that we *do* care about.
900+
// As the attribute migration progresses (#131229), this is expected to shrink
901+
// and eventually be removed as all attributes gain a strutured representation in
902+
// HIR.
903+
hir::Attribute::Unparsed(_) => {
904+
return Some(if attr.has_name(sym::automatically_derived) {
905+
Attribute::AutomaticallyDerived
906+
} else {
907+
// FIXME: We should handle `#[doc(hidden)]` here.
908+
other_attr(tcx, attr)
909+
});
910+
}
911+
};
912+
913+
Some(match kind {
914+
AK::Deprecation { .. } => return None, // Handled separately into Item::deprecation.
915+
AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"),
916+
917+
AK::MustUse { reason, span: _ } => {
918+
Attribute::MustUse { reason: reason.map(|s| s.to_string()) }
919+
}
920+
AK::Repr { .. } => repr_attr(
921+
tcx,
922+
item_id.as_def_id().expect("all items that could have #[repr] have a DefId"),
923+
),
924+
AK::ExportName { name, span: _ } => Attribute::ExportName(name.to_string()),
925+
AK::LinkSection { name, span: _ } => Attribute::LinkSection(name.to_string()),
926+
927+
AK::NoMangle(_) => Attribute::NoMangle,
928+
AK::NonExhaustive(_) => Attribute::NonExhaustive,
929+
AK::TargetFeature(features, _span) => Attribute::TargetFeature {
930+
enable: features.iter().map(|(feat, _span)| feat.to_string()).collect(),
931+
},
932+
933+
_ => other_attr(tcx, attr),
934+
})
935+
}
936+
937+
fn other_attr(tcx: TyCtxt<'_>, attr: &hir::Attribute) -> Attribute {
938+
let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr);
939+
assert_eq!(s.pop(), Some('\n'));
940+
Attribute::Other(s)
941+
}
942+
943+
fn repr_attr(tcx: TyCtxt<'_>, def_id: DefId) -> Attribute {
944+
let repr = tcx.adt_def(def_id).repr();
945+
946+
let kind = if repr.c() {
947+
ReprKind::C
948+
} else if repr.transparent() {
949+
ReprKind::Transparent
950+
} else if repr.simd() {
951+
ReprKind::Simd
952+
} else {
953+
ReprKind::Rust
954+
};
955+
956+
let align = repr.align.map(|a| a.bytes());
957+
let packed = repr.pack.map(|p| p.bytes());
958+
let int = repr.int.map(format_integer_type);
959+
960+
Attribute::Repr(AttributeRepr { kind, align, packed, int })
961+
}
962+
963+
fn format_integer_type(it: rustc_abi::IntegerType) -> String {
964+
use rustc_abi::Integer::*;
965+
use rustc_abi::IntegerType::*;
966+
match it {
967+
Pointer(true) => "isize",
968+
Pointer(false) => "usize",
969+
Fixed(I8, true) => "i8",
970+
Fixed(I8, false) => "u8",
971+
Fixed(I16, true) => "i16",
972+
Fixed(I16, false) => "u16",
973+
Fixed(I32, true) => "i32",
974+
Fixed(I32, false) => "u32",
975+
Fixed(I64, true) => "i64",
976+
Fixed(I64, false) => "u64",
977+
Fixed(I128, true) => "i128",
978+
Fixed(I128, false) => "u128",
979+
}
980+
.to_owned()
981+
}

0 commit comments

Comments
 (0)