rustdoc: hide #[repr(...)] if it isn't part of the public ABI

This commit is contained in:
León Orell Valerian Liehr
2025-05-17 14:05:07 +02:00
parent d7d7725b8c
commit 85c193a4ed
7 changed files with 229 additions and 122 deletions

View File

@@ -89,20 +89,30 @@ https://doc.rust-lang.org/stable/std/?search=%s&go_to_first=true
This URL adds the `go_to_first=true` query parameter which can be appended to any `rustdoc` search URL This URL adds the `go_to_first=true` query parameter which can be appended to any `rustdoc` search URL
to automatically go to the first result. to automatically go to the first result.
## `#[repr(transparent)]`: Documenting the transparent representation ## `#[repr(...)]`: Documenting the representation of a type
Generally, rustdoc only displays the representation of a given type if none of its variants are
`#[doc(hidden)]` and if all of its fields are public and not `#[doc(hidden)]` since it's likely
not meant to be considered part of the public ABI otherwise.
Note that there's no way to overwrite that heuristic and force rustdoc to show the representation
regardless.
### `#[repr(transparent)]`
You can read more about `#[repr(transparent)]` itself in the [Rust Reference][repr-trans-ref] and You can read more about `#[repr(transparent)]` itself in the [Rust Reference][repr-trans-ref] and
in the [Rustonomicon][repr-trans-nomicon]. in the [Rustonomicon][repr-trans-nomicon].
Since this representation is only considered part of the public ABI if the single field with non-trivial Since this representation is only considered part of the public ABI if the single field with non-trivial
size or alignment is public and if the documentation does not state otherwise, Rustdoc helpfully displays size or alignment is public and if the documentation does not state otherwise, rustdoc helpfully displays
the attribute if and only if the non-1-ZST field is public or at least one field is public in case all the attribute if and only if the non-1-ZST field is public and not `#[doc(hidden)]` or
fields are 1-ZST fields. The term *1-ZST* refers to types that are one-aligned and zero-sized. in case all fields are 1-ZST fields — at least one field is public and not `#[doc(hidden)]`.
The term *1-ZST* refers to types that are one-aligned and zero-sized.
It would seem that one can manually hide the attribute with `#[cfg_attr(not(doc), repr(transparent))]` It would seem that one can manually hide the attribute with `#[cfg_attr(not(doc), repr(transparent))]`
if one wishes to declare the representation as private even if the non-1-ZST field is public. if one wishes to declare the representation as private even if the non-1-ZST field is public.
However, due to [current limitations][cross-crate-cfg-doc], this method is not always guaranteed to work. However, due to [current limitations][cross-crate-cfg-doc], this method is not always guaranteed to work.
Therefore, if you would like to do so, you should always write it down in prose independently of whether Therefore, if you would like to do so, you should always write that down in prose independently of whether
you use `cfg_attr` or not. you use `cfg_attr` or not.
[repr-trans-ref]: https://doc.rust-lang.org/reference/type-layout.html#the-transparent-representation [repr-trans-ref]: https://doc.rust-lang.org/reference/type-layout.html#the-transparent-representation

View File

@@ -2964,6 +2964,10 @@ fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) {
write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap(); write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap();
} }
/// Compute the *public* `#[repr]` of the item given by `DefId`.
///
/// Read more about it here:
/// <https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type>.
fn repr_attribute<'tcx>( fn repr_attribute<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
cache: &Cache, cache: &Cache,
@@ -2975,25 +2979,59 @@ fn repr_attribute<'tcx>(
}; };
let repr = adt.repr(); let repr = adt.repr();
let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id);
let is_public_field = |field: &ty::FieldDef| {
(cache.document_private || field.vis.is_public()) && is_visible(field.did)
};
if repr.transparent() { if repr.transparent() {
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one // The transparent repr is public iff the non-1-ZST field is public and visible or
// field is public in case all fields are 1-ZST fields. // in case all fields are 1-ZST fields — at least one field is public and visible.
let render_transparent = cache.document_private let is_public = 'is_public: {
|| adt // `#[repr(transparent)]` can only be applied to structs and single-variant enums.
.all_fields() let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant
.find(|field| {
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did)); if !is_visible(var.def_id) {
tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty)) break 'is_public false;
.is_ok_and(|layout| !layout.is_1zst()) }
})
.map_or_else( // Side note: There can only ever be one or zero non-1-ZST fields.
|| adt.all_fields().any(|field| field.vis.is_public()), let non_1zst_field = var.fields.iter().find(|field| {
|field| field.vis.is_public(), let ty = ty::TypingEnv::post_analysis(tcx, field.did)
); .as_query_input(tcx.type_of(field.did).instantiate_identity());
tcx.layout_of(ty).is_ok_and(|layout| !layout.is_1zst())
});
match non_1zst_field {
Some(field) => is_public_field(field),
None => var.fields.is_empty() || var.fields.iter().any(is_public_field),
}
};
// Since the transparent repr can't have any other reprs or // Since the transparent repr can't have any other reprs or
// repr modifiers beside it, we can safely return early here. // repr modifiers beside it, we can safely return early here.
return render_transparent.then(|| "#[repr(transparent)]".into()); return is_public.then(|| "#[repr(transparent)]".into());
}
// Fast path which avoids looking through the variants and fields in
// the common case of no `#[repr]` or in the case of `#[repr(Rust)]`.
// FIXME: This check is not very robust / forward compatible!
if !repr.c()
&& !repr.simd()
&& repr.int.is_none()
&& repr.pack.is_none()
&& repr.align.is_none()
{
return None;
}
// The repr is public iff all components are public and visible.
let is_public = adt
.variants()
.iter()
.all(|variant| is_visible(variant.def_id) && variant.fields.iter().all(is_public_field));
if !is_public {
return None;
} }
let mut result = Vec::<Cow<'_, _>>::new(); let mut result = Vec::<Cow<'_, _>>::new();
@@ -3004,12 +3042,6 @@ fn repr_attribute<'tcx>(
if repr.simd() { if repr.simd() {
result.push("simd".into()); result.push("simd".into());
} }
if let Some(pack) = repr.pack {
result.push(format!("packed({})", pack.bytes()).into());
}
if let Some(align) = repr.align {
result.push(format!("align({})", align.bytes()).into());
}
if let Some(int) = repr.int { if let Some(int) = repr.int {
let prefix = if int.is_signed() { 'i' } else { 'u' }; let prefix = if int.is_signed() { 'i' } else { 'u' };
let int = match int { let int = match int {
@@ -3021,5 +3053,13 @@ fn repr_attribute<'tcx>(
result.push(int.into()); result.push(int.into());
} }
// Render modifiers last.
if let Some(pack) = repr.pack {
result.push(format!("packed({})", pack.bytes()).into());
}
if let Some(align) = repr.align {
result.push(format!("align({})", align.bytes()).into());
}
(!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")).into()) (!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")).into())
} }

View File

@@ -459,10 +459,10 @@ pub fn safe_fn() {}
#[repr(C)] #[repr(C)]
pub struct WithGenerics<T: TraitWithNoDocblocks, S = String, E = WhoLetTheDogOut, P = i8> { pub struct WithGenerics<T: TraitWithNoDocblocks, S = String, E = WhoLetTheDogOut, P = i8> {
s: S, pub s: S,
t: T, pub t: T,
e: E, pub e: E,
p: P, pub p: P,
} }
pub struct StructWithPublicUndocumentedFields { pub struct StructWithPublicUndocumentedFields {

View File

@@ -0,0 +1,5 @@
#[repr(i8)]
pub enum ReprI8 {
Var0,
Var1,
}

View File

@@ -1,42 +0,0 @@
#![feature(repr_simd)]
#[repr(C, align(8))]
pub struct ReprC {
field: u8,
}
#[repr(simd, packed(2))]
pub struct ReprSimd {
field: [u8; 1],
}
#[repr(transparent)]
pub struct ReprTransparent {
pub field: u8,
}
#[repr(isize)]
pub enum ReprIsize {
Bla,
}
#[repr(u8)]
pub enum ReprU8 {
Bla,
}
#[repr(transparent)] // private
pub struct ReprTransparentPrivField {
field: u32, // non-1-ZST field
}
#[repr(transparent)] // public
pub struct ReprTransparentPriv1ZstFields {
marker0: Marker,
pub main: u64, // non-1-ZST field
marker1: Marker,
}
#[repr(transparent)] // private
pub struct ReprTransparentPrivFieldPub1ZstFields {
main: [u16; 0], // non-1-ZST field
pub marker: Marker,
}
pub struct Marker; // 1-ZST

View File

@@ -1,40 +0,0 @@
// Regression test for <https://github.com/rust-lang/rust/issues/110698>.
// This test ensures that the re-exported items still have the `#[repr(...)]` attribute.
//@ aux-build:repr.rs
#![crate_name = "foo"]
extern crate repr;
//@ has 'foo/struct.ReprC.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C, align(8))]'
pub use repr::ReprC;
//@ has 'foo/struct.ReprSimd.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(simd, packed(2))]'
pub use repr::ReprSimd;
//@ has 'foo/struct.ReprTransparent.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
pub use repr::ReprTransparent;
//@ has 'foo/enum.ReprIsize.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(isize)]'
pub use repr::ReprIsize;
//@ has 'foo/enum.ReprU8.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u8)]'
pub use repr::ReprU8;
// Regression test for <https://github.com/rust-lang/rust/issues/90435>.
// Check that we show `#[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.
//@ has 'foo/struct.ReprTransparentPrivField.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
pub use repr::ReprTransparentPrivField;
//@ has 'foo/struct.ReprTransparentPriv1ZstFields.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
pub use repr::ReprTransparentPriv1ZstFields;
//@ has 'foo/struct.ReprTransparentPrivFieldPub1ZstFields.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
pub use repr::ReprTransparentPrivFieldPub1ZstFields;

View File

@@ -1,29 +1,163 @@
// Regression test for <https://github.com/rust-lang/rust/issues/90435>. // Test the rendering of `#[repr]` on ADTs.
// Check that we show `#[repr(transparent)]` iff the non-1-ZST field is public or at least one #![feature(repr_simd)] // only used for the `ReprSimd` test case
// field is public in case all fields are 1-ZST fields.
// Check the "local case" (HIR cleaning) //
// Don't render the default repr which is `Rust`.
//@ has 'repr/struct.ReprDefault.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(Rust)]'
pub struct ReprDefault;
// Don't render the `Rust` repr — even if given explicitly — since it's the default.
//@ has 'repr/struct.ReprRust.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(Rust)]'
#[repr(Rust)] // omitted
pub struct ReprRust;
//@ has 'repr/struct.ReprCPubFields.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C)]'
#[repr(C)] // public
pub struct ReprCPubFields {
pub a: u32,
pub b: u32,
}
//@ has 'repr/struct.ReprCPrivField.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C)]'
#[repr(C)] // private...
pub struct ReprCPrivField {
a: u32, // ...since this is private
pub b: u32,
}
//@ has 'repr/enum.ReprIsize.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(isize)]'
#[repr(isize)] // public
pub enum ReprIsize {
Bla,
}
//@ has 'repr/enum.ReprU32HiddenVariant.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u32)]'
#[repr(u32)] // private...
pub enum ReprU32HiddenVariant {
#[doc(hidden)]
Hidden, // ...since this is hidden
Public,
}
//@ has 'repr/struct.ReprAlignHiddenField.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(align(4))]'
#[repr(align(4))] // private...
pub struct ReprAlignHiddenField {
#[doc(hidden)]
pub hidden: i16, // ...since this field is hidden
}
//@ has 'repr/struct.ReprSimd.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(simd, packed(2))]'
#[repr(simd, packed(2))] // public
pub struct ReprSimd {
pub field: [u8; 1],
}
//@ has 'repr/enum.ReprU32Align.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u32, align(8))]'
#[repr(u32, align(8))] // public
pub enum ReprU32Align {
Variant(u16),
}
//@ has 'repr/enum.ReprCHiddenVariantField.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C)]'
#[repr(C)] // private...
pub enum ReprCHiddenVariantField {
Variant { #[doc(hidden)] field: () }, //...since this field is hidden
}
//@ has 'repr/struct.ReprTransparentPrivField.html' //@ has 'repr/struct.ReprTransparentPrivField.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]' //@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // private #[repr(transparent)] // private...
pub struct ReprTransparentPrivField { pub struct ReprTransparentPrivField {
field: u32, // non-1-ZST field field: u32, // ...since the non-1-ZST field is private
} }
//@ has 'repr/struct.ReprTransparentPriv1ZstFields.html' //@ has 'repr/struct.ReprTransparentPriv1ZstFields.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]' //@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // public #[repr(transparent)] // public...
pub struct ReprTransparentPriv1ZstFields { pub struct ReprTransparentPriv1ZstFields {
marker0: Marker, marker0: Marker,
pub main: u64, // non-1-ZST field pub main: u64, // ...since the non-1-ZST field is public and visible
marker1: Marker, marker1: Marker,
} // the two private 1-ZST fields don't matter
//@ has 'repr/struct.ReprTransparentPrivFieldPub1ZstField.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // private...
pub struct ReprTransparentPrivFieldPub1ZstField {
main: [u16; 0], // ...since the non-1-ZST field is private
pub marker: Marker, // this public 1-ZST field doesn't matter
} }
//@ has 'repr/struct.ReprTransparentPub1ZstField.html' //@ has 'repr/struct.ReprTransparentPub1ZstField.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]' //@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // public #[repr(transparent)] // public...
pub struct ReprTransparentPub1ZstField { pub struct ReprTransparentPub1ZstField {
marker0: Marker, marker0: Marker, // ...since we don't have a non-1-ZST field...
pub marker1: Marker, pub marker1: Marker, // ...and this field is public and visible
}
//@ has 'repr/struct.ReprTransparentUnitStruct.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // public
pub struct ReprTransparentUnitStruct;
//@ has 'repr/enum.ReprTransparentEnumUnitVariant.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // public
pub enum ReprTransparentEnumUnitVariant {
Variant,
}
//@ has 'repr/enum.ReprTransparentEnumHiddenUnitVariant.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // private
pub enum ReprTransparentEnumHiddenUnitVariant {
#[doc(hidden)] Variant(u32),
}
//@ has 'repr/enum.ReprTransparentEnumPub1ZstField.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // public...
pub enum ReprTransparentEnumPub1ZstField {
Variant {
field: u64, // ...since the non-1-ZST field is public
#[doc(hidden)]
marker: Marker, // this hidden 1-ZST field doesn't matter
},
}
//@ has 'repr/enum.ReprTransparentEnumHidden1ZstField.html'
//@ !has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // private...
pub enum ReprTransparentEnumHidden1ZstField {
Variant {
#[doc(hidden)]
field: u64, // ...since the non-1-ZST field is public
},
} }
struct Marker; // 1-ZST struct Marker; // 1-ZST
// Check the "extern case" (middle cleaning) //
// Internally, HIR and middle cleaning share `#[repr]` rendering.
// Thus we'll only test the very basics in this section.
//@ aux-build: ext-repr.rs
extern crate ext_repr as ext;
// Regression test for <https://github.com/rust-lang/rust/issues/110698>.
//@ has 'repr/enum.ReprI8.html'
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(i8)]'
pub use ext::ReprI8;