Tweak attribute rendering depending on wether or not it is a type alias

This commit is contained in:
Guillaume Gomez
2025-05-09 18:47:25 +02:00
parent eb9f05481b
commit 4f3dd7b018
4 changed files with 173 additions and 80 deletions

View File

@@ -815,67 +815,7 @@ impl Item {
/// Returns a `#[repr(...)]` representation.
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> {
use rustc_abi::IntegerType;
let def_id = self.def_id()?;
if !matches!(self.type_(), ItemType::Struct | ItemType::Enum | ItemType::Union) {
return None;
}
let adt = tcx.adt_def(def_id);
let repr = adt.repr();
let mut out = Vec::new();
if repr.c() {
out.push("C");
}
if repr.transparent() {
// 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 = is_json
|| cache.document_private
|| adt
.all_fields()
.find(|field| {
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
tcx.layout_of(
ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty),
)
.is_ok_and(|layout| !layout.is_1zst())
})
.map_or_else(
|| adt.all_fields().any(|field| field.vis.is_public()),
|field| field.vis.is_public(),
);
if render_transparent {
out.push("transparent");
}
}
if repr.simd() {
out.push("simd");
}
let pack_s;
if let Some(pack) = repr.pack {
pack_s = format!("packed({})", pack.bytes());
out.push(&pack_s);
}
let align_s;
if let Some(align) = repr.align {
align_s = format!("align({})", align.bytes());
out.push(&align_s);
}
let int_s;
if let Some(int) = repr.int {
int_s = match int {
IntegerType::Pointer(is_signed) => {
format!("{}size", if is_signed { 'i' } else { 'u' })
}
IntegerType::Fixed(size, is_signed) => {
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
}
};
out.push(&int_s);
}
if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
repr_attributes(tcx, cache, self.def_id()?, self.type_(), is_json)
}
pub fn is_doc_hidden(&self) -> bool {
@@ -887,6 +827,73 @@ impl Item {
}
}
pub(crate) fn repr_attributes(
tcx: TyCtxt<'_>,
cache: &Cache,
def_id: DefId,
item_type: ItemType,
is_json: bool,
) -> Option<String> {
use rustc_abi::IntegerType;
if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) {
return None;
}
let adt = tcx.adt_def(def_id);
let repr = adt.repr();
let mut out = Vec::new();
if repr.c() {
out.push("C");
}
if repr.transparent() {
// 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| {
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty))
.is_ok_and(|layout| !layout.is_1zst())
})
.map_or_else(
|| adt.all_fields().any(|field| field.vis.is_public()),
|field| field.vis.is_public(),
);
if render_transparent {
out.push("transparent");
}
}
if repr.simd() {
out.push("simd");
}
let pack_s;
if let Some(pack) = repr.pack {
pack_s = format!("packed({})", pack.bytes());
out.push(&pack_s);
}
let align_s;
if let Some(align) = repr.align {
align_s = format!("align({})", align.bytes());
out.push(&align_s);
}
let int_s;
if let Some(int) = repr.int {
int_s = match int {
IntegerType::Pointer(is_signed) => {
format!("{}size", if is_signed { 'i' } else { 'u' })
}
IntegerType::Fixed(size, is_signed) => {
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
}
};
out.push(&int_s);
}
if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
}
#[derive(Clone, Debug)]
pub(crate) enum ItemKind {
ExternCrateItem {

View File

@@ -1201,11 +1201,31 @@ fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) ->
})
}
struct CodeAttribute(String);
impl CodeAttribute {
fn render_into(self, w: &mut impl fmt::Write) {
write!(w, "<div class=\"code-attribute\">{}</div>", self.0).unwrap();
}
}
// 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_and_repr(cx.tcx(), cx.cache(), false) {
write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
CodeAttribute(attr).render_into(w);
}
}
/// used for type aliases to only render their `repr` attribute.
fn render_repr_attributes_in_code(
w: &mut impl fmt::Write,
cx: &Context<'_>,
def_id: DefId,
item_type: ItemType,
) {
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) {
CodeAttribute(repr).render_into(w);
}
}

View File

@@ -20,7 +20,7 @@ use super::{
collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
render_impl, render_rightside, render_stability_since_raw,
render_impl, render_repr_attributes_in_code, render_rightside, render_stability_since_raw,
render_stability_since_raw_with_extra, write_section_heading,
};
use crate::clean;
@@ -1290,12 +1290,30 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) ->
.render_into(cx, it, true, w)?;
}
clean::TypeAliasInnerType::Union { fields } => {
ItemUnion { cx, it, fields, generics: &t.generics, is_type_alias: true }
.render_into(w)?;
let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
let union_def_id = ty.ty_adt_def().unwrap().did();
ItemUnion {
cx,
it,
fields,
generics: &t.generics,
is_type_alias: true,
def_id: union_def_id,
}
.render_into(w)?;
}
clean::TypeAliasInnerType::Struct { ctor_kind, fields } => {
DisplayStruct { ctor_kind: *ctor_kind, generics: &t.generics, fields }
.render_into(cx, it, true, w)?;
let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
let struct_def_id = ty.ty_adt_def().unwrap().did();
DisplayStruct {
ctor_kind: *ctor_kind,
generics: &t.generics,
fields,
def_id: struct_def_id,
}
.render_into(cx, it, true, w)?;
}
}
} else {
@@ -1417,8 +1435,9 @@ item_template!(
fields: &'a [clean::Item],
generics: &'a clean::Generics,
is_type_alias: bool,
def_id: DefId,
},
methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items]
methods = [document, document_type_layout, render_assoc_items]
);
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
@@ -1449,13 +1468,41 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
})
.peekable()
}
fn render_attributes_in_pre(&self) -> impl fmt::Display {
fmt::from_fn(move |f| {
if !self.is_type_alias {
for a in self.it.attributes_and_repr(self.cx.tcx(), self.cx.cache(), false) {
writeln!(f, "{a}")?;
}
} else {
// For now we only render `repr` attributes for type aliases.
if let Some(repr) = clean::repr_attributes(
self.cx.tcx(),
self.cx.cache(),
self.def_id,
ItemType::Union,
) {
writeln!(f, "{repr}")?;
};
}
Ok(())
})
}
}
fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt::Display {
fmt::from_fn(|w| {
ItemUnion { cx, it, fields: &s.fields, generics: &s.generics, is_type_alias: false }
.render_into(w)
.unwrap();
ItemUnion {
cx,
it,
fields: &s.fields,
generics: &s.generics,
is_type_alias: false,
def_id: it.def_id().unwrap(),
}
.render_into(w)
.unwrap();
Ok(())
})
}
@@ -1502,7 +1549,12 @@ impl<'a> DisplayEnum<'a> {
let has_stripped_entries = variants_len != variants_count;
wrap_item(w, |w| {
render_attributes_in_code(w, it, cx);
if !is_type_alias {
render_attributes_in_code(w, it, cx);
} else {
// For now we only render `repr` attributes for type aliases.
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum);
}
write!(
w,
"{}enum {}{}{}",
@@ -1521,19 +1573,22 @@ impl<'a> DisplayEnum<'a> {
)
})?;
if !is_type_alias {
let def_id = it.item_id.expect_def_id();
let layout_def_id = if !is_type_alias {
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
}
def_id
} else {
self.def_id
};
if variants_count != 0 {
write!(w, "{}", item_variants(cx, it, self.variants, self.def_id))?;
}
let def_id = it.item_id.expect_def_id();
write!(
w,
"{}{}",
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id)
document_type_layout(cx, layout_def_id)
)
}
}
@@ -1938,6 +1993,7 @@ struct DisplayStruct<'a> {
ctor_kind: Option<CtorKind>,
generics: &'a clean::Generics,
fields: &'a [clean::Item],
def_id: DefId,
}
impl<'a> DisplayStruct<'a> {
@@ -1949,7 +2005,12 @@ impl<'a> DisplayStruct<'a> {
w: &mut W,
) -> fmt::Result {
wrap_item(w, |w| {
render_attributes_in_code(w, it, cx);
if !is_type_alias {
render_attributes_in_code(w, it, cx);
} else {
// For now we only render `repr` attributes for type aliases.
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct);
}
write!(
w,
"{}",
@@ -1974,8 +2035,13 @@ impl<'a> DisplayStruct<'a> {
fn item_struct(cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) -> impl fmt::Display {
fmt::from_fn(|w| {
DisplayStruct { ctor_kind: s.ctor_kind, generics: &s.generics, fields: s.fields.as_slice() }
.render_into(cx, it, false, w)
DisplayStruct {
ctor_kind: s.ctor_kind,
generics: &s.generics,
fields: s.fields.as_slice(),
def_id: it.def_id().unwrap(),
}
.render_into(cx, it, false, w)
})
}

View File

@@ -61,7 +61,7 @@ pub type TypeAlias = X;
pub type GenericTypeAlias = (Generic<(u32, ())>, Generic<u32>);
// Regression test for the rustdoc equivalent of #85103.
//@ hasraw type_layout/type.Edges.html 'Encountered an error during type layout; the type failed to be normalized.'
//@ hasraw type_layout/type.Edges.html 'Unable to compute type layout, possibly due to this type having generic parameters. Layout can only be computed for concrete, fully-instantiated types.'
pub type Edges<'a, E> = std::borrow::Cow<'a, [E]>;
//@ !hasraw type_layout/trait.MyTrait.html 'Size: '