Skip to content

Commit 89a5a10

Browse files
committed
rustdoc-json: Structured attributes
Implements https://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 #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 cccf075 commit 89a5a10

29 files changed

+316
-122
lines changed

src/librustdoc/clean/types.rs

Lines changed: 25 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -759,79 +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-
hir::Attribute::Parsed(AttributeKind::AutomaticallyDerived(..)) => {
800-
Some("#[automatically_derived]".to_string())
801-
}
802-
_ => Some({
803-
let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr);
804-
assert_eq!(s.pop(), Some('\n'));
805-
s
806-
}),
807-
}
808-
} else {
809-
if !attr.has_any_name(ALLOWED_ATTRIBUTES) {
810-
return None;
811-
}
812-
Some(
813-
rustc_hir_pretty::attribute_to_string(&tcx, attr)
814-
.replace("\\\n", "")
815-
.replace('\n', "")
816-
.replace(" ", " "),
817-
)
818781
}
782+
_ => None,
819783
})
820784
.collect()
821785
}
822786

823-
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Vec<String> {
824-
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();
825792

826-
if let Some(repr_attr) = self.repr(tcx, cache, is_json) {
793+
if let Some(repr_attr) = self.repr(tcx, cache) {
827794
attrs.push(repr_attr);
828795
}
829796
attrs
830797
}
831798

832799
/// Returns a stringified `#[repr(...)]` attribute.
833-
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> {
834-
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_())
835804
}
836805

837806
pub fn is_doc_hidden(&self) -> bool {
@@ -843,12 +812,14 @@ impl Item {
843812
}
844813
}
845814

815+
/// Return a string representing the `#[repr]` attribute if present.
816+
///
817+
/// Only used by the HTML output-format.
846818
pub(crate) fn repr_attributes(
847819
tcx: TyCtxt<'_>,
848820
cache: &Cache,
849821
def_id: DefId,
850822
item_type: ItemType,
851-
is_json: bool,
852823
) -> Option<String> {
853824
use rustc_abi::IntegerType;
854825

@@ -865,7 +836,6 @@ pub(crate) fn repr_attributes(
865836
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
866837
// field is public in case all fields are 1-ZST fields.
867838
let render_transparent = cache.document_private
868-
|| is_json
869839
|| adt
870840
.all_fields()
871841
.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: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
use rustc_abi::ExternAbi;
66
use rustc_ast::ast;
77
use rustc_attr_data_structures::{self as attrs, DeprecatedSince};
8+
use rustc_hir as hir;
89
use rustc_hir::def::CtorKind;
910
use rustc_hir::def_id::DefId;
1011
use rustc_hir::{HeaderSafety, Safety};
1112
use rustc_metadata::rendered_const;
13+
use rustc_middle::ty::TyCtxt;
1214
use rustc_middle::{bug, ty};
1315
use rustc_span::{Pos, kw, sym};
1416
use rustdoc_json_types::*;
@@ -39,7 +41,12 @@ impl JsonRenderer<'_> {
3941
})
4042
.collect();
4143
let docs = item.opt_doc_value();
42-
let attrs = item.attributes(self.tcx, &self.cache, true);
44+
let attrs = item
45+
.attrs
46+
.other_attrs
47+
.iter()
48+
.filter_map(|a| maybe_from_hir_attr(a, item.item_id, self.tcx))
49+
.collect();
4350
let span = item.span(self.tcx);
4451
let visibility = item.visibility(self.tcx);
4552
let clean::ItemInner { name, item_id, .. } = *item.inner;
@@ -886,3 +893,93 @@ impl FromClean<ItemType> for ItemKind {
886893
}
887894
}
888895
}
896+
897+
/// Maybe convert a attribute from hir to json.
898+
///
899+
/// Returns `None` if the attribute shouldn't be in the output.
900+
fn maybe_from_hir_attr(
901+
attr: &hir::Attribute,
902+
item_id: ItemId,
903+
tcx: TyCtxt<'_>,
904+
) -> Option<Attribute> {
905+
use attrs::AttributeKind as AK;
906+
907+
let kind = match attr {
908+
hir::Attribute::Parsed(kind) => kind,
909+
910+
hir::Attribute::Unparsed(_) => {
911+
// FIXME: We should handle `#[doc(hidden)]`.
912+
return Some(other_attr(tcx, attr));
913+
}
914+
};
915+
916+
Some(match kind {
917+
AK::Deprecation { .. } => return None, // Handled separately into Item::deprecation.
918+
AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"),
919+
920+
AK::MustUse { reason, span: _ } => {
921+
Attribute::MustUse { reason: reason.map(|s| s.to_string()) }
922+
}
923+
AK::Repr { .. } => repr_attr(
924+
tcx,
925+
item_id.as_def_id().expect("all items that could have #[repr] have a DefId"),
926+
),
927+
AK::ExportName { name, span: _ } => Attribute::ExportName(name.to_string()),
928+
AK::LinkSection { name, span: _ } => Attribute::LinkSection(name.to_string()),
929+
AK::TargetFeature(features, _span) => Attribute::TargetFeature {
930+
enable: features.iter().map(|(feat, _span)| feat.to_string()).collect(),
931+
},
932+
933+
AK::NoMangle(_) => Attribute::NoMangle,
934+
AK::NonExhaustive(_) => Attribute::NonExhaustive,
935+
AK::AutomaticallyDerived(_) => Attribute::AutomaticallyDerived,
936+
937+
_ => other_attr(tcx, attr),
938+
})
939+
}
940+
941+
fn other_attr(tcx: TyCtxt<'_>, attr: &hir::Attribute) -> Attribute {
942+
let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr);
943+
assert_eq!(s.pop(), Some('\n'));
944+
Attribute::Other(s)
945+
}
946+
947+
fn repr_attr(tcx: TyCtxt<'_>, def_id: DefId) -> Attribute {
948+
let repr = tcx.adt_def(def_id).repr();
949+
950+
let kind = if repr.c() {
951+
ReprKind::C
952+
} else if repr.transparent() {
953+
ReprKind::Transparent
954+
} else if repr.simd() {
955+
ReprKind::Simd
956+
} else {
957+
ReprKind::Rust
958+
};
959+
960+
let align = repr.align.map(|a| a.bytes());
961+
let packed = repr.pack.map(|p| p.bytes());
962+
let int = repr.int.map(format_integer_type);
963+
964+
Attribute::Repr(AttributeRepr { kind, align, packed, int })
965+
}
966+
967+
fn format_integer_type(it: rustc_abi::IntegerType) -> String {
968+
use rustc_abi::Integer::*;
969+
use rustc_abi::IntegerType::*;
970+
match it {
971+
Pointer(true) => "isize",
972+
Pointer(false) => "usize",
973+
Fixed(I8, true) => "i8",
974+
Fixed(I8, false) => "u8",
975+
Fixed(I16, true) => "i16",
976+
Fixed(I16, false) => "u16",
977+
Fixed(I32, true) => "i32",
978+
Fixed(I32, false) => "u32",
979+
Fixed(I64, true) => "i64",
980+
Fixed(I64, false) => "u64",
981+
Fixed(I128, true) => "i128",
982+
Fixed(I128, false) => "u128",
983+
}
984+
.to_owned()
985+
}

src/librustdoc/json/conversions.rs:917:28

Whitespace-only changes.

0 commit comments

Comments
 (0)