diff options
| author | León Orell Valerian Liehr <me@fmease.dev> | 2025-05-17 14:05:07 +0200 |
|---|---|---|
| committer | León Orell Valerian Liehr <me@fmease.dev> | 2025-09-25 11:49:27 +0200 |
| commit | 85c193a4ed9cf54a70d6d1edaf411b082d15fd13 (patch) | |
| tree | 9376eb28698837e7c2954bd8b53cc5a1f8bbd521 | |
| parent | d7d7725b8cbeea8db490c3b033773776ce8794f5 (diff) | |
| download | rust-85c193a4ed9cf54a70d6d1edaf411b082d15fd13.tar.gz rust-85c193a4ed9cf54a70d6d1edaf411b082d15fd13.zip | |
rustdoc: hide `#[repr(...)]` if it isn't part of the public ABI
| -rw-r--r-- | src/doc/rustdoc/src/advanced-features.md | 20 | ||||
| -rw-r--r-- | src/librustdoc/html/render/mod.rs | 82 | ||||
| -rw-r--r-- | tests/rustdoc-gui/src/test_docs/lib.rs | 8 | ||||
| -rw-r--r-- | tests/rustdoc/auxiliary/ext-repr.rs | 5 | ||||
| -rw-r--r-- | tests/rustdoc/inline_cross/auxiliary/repr.rs | 42 | ||||
| -rw-r--r-- | tests/rustdoc/inline_cross/repr.rs | 40 | ||||
| -rw-r--r-- | tests/rustdoc/repr.rs | 154 |
7 files changed, 229 insertions, 122 deletions
diff --git a/src/doc/rustdoc/src/advanced-features.md b/src/doc/rustdoc/src/advanced-features.md index c02c9aebe7e..f49edb2ac78 100644 --- a/src/doc/rustdoc/src/advanced-features.md +++ b/src/doc/rustdoc/src/advanced-features.md @@ -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 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 in the [Rustonomicon][repr-trans-nomicon]. 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 -the attribute if and only if the non-1-ZST field is public or at least one field is public in case all -fields are 1-ZST fields. The term *1-ZST* refers to types that are one-aligned and zero-sized. +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 and not `#[doc(hidden)]` or +– 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))]` 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. -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. [repr-trans-ref]: https://doc.rust-lang.org/reference/type-layout.html#the-transparent-representation diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 9280701ea3f..143911cb104 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -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(); } +/// 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>( tcx: TyCtxt<'tcx>, cache: &Cache, @@ -2975,25 +2979,59 @@ fn repr_attribute<'tcx>( }; 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() { - // 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 - || 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(), - ); + // The transparent repr is public iff the non-1-ZST field is public and visible or + // – in case all fields are 1-ZST fields — at least one field is public and visible. + let is_public = 'is_public: { + // `#[repr(transparent)]` can only be applied to structs and single-variant enums. + let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant + + if !is_visible(var.def_id) { + break 'is_public false; + } + + // Side note: There can only ever be one or zero non-1-ZST fields. + let non_1zst_field = var.fields.iter().find(|field| { + 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 // 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(); @@ -3004,12 +3042,6 @@ fn repr_attribute<'tcx>( if repr.simd() { 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 { let prefix = if int.is_signed() { 'i' } else { 'u' }; let int = match int { @@ -3021,5 +3053,13 @@ fn repr_attribute<'tcx>( 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()) } diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index 42f2fbd93b1..de7c89a9fa3 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -459,10 +459,10 @@ pub fn safe_fn() {} #[repr(C)] pub struct WithGenerics<T: TraitWithNoDocblocks, S = String, E = WhoLetTheDogOut, P = i8> { - s: S, - t: T, - e: E, - p: P, + pub s: S, + pub t: T, + pub e: E, + pub p: P, } pub struct StructWithPublicUndocumentedFields { diff --git a/tests/rustdoc/auxiliary/ext-repr.rs b/tests/rustdoc/auxiliary/ext-repr.rs new file mode 100644 index 00000000000..25acaa49449 --- /dev/null +++ b/tests/rustdoc/auxiliary/ext-repr.rs @@ -0,0 +1,5 @@ +#[repr(i8)] +pub enum ReprI8 { + Var0, + Var1, +} diff --git a/tests/rustdoc/inline_cross/auxiliary/repr.rs b/tests/rustdoc/inline_cross/auxiliary/repr.rs deleted file mode 100644 index 0211e1a8658..00000000000 --- a/tests/rustdoc/inline_cross/auxiliary/repr.rs +++ /dev/null @@ -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 diff --git a/tests/rustdoc/inline_cross/repr.rs b/tests/rustdoc/inline_cross/repr.rs deleted file mode 100644 index d13e560b8d7..00000000000 --- a/tests/rustdoc/inline_cross/repr.rs +++ /dev/null @@ -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; diff --git a/tests/rustdoc/repr.rs b/tests/rustdoc/repr.rs index f4f683b3d81..1e8fad6ec0a 100644 --- a/tests/rustdoc/repr.rs +++ b/tests/rustdoc/repr.rs @@ -1,29 +1,163 @@ -// 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. +// Test the rendering of `#[repr]` on ADTs. +#![feature(repr_simd)] // only used for the `ReprSimd` test case + +// 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 - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]' -#[repr(transparent)] // private +#[repr(transparent)] // private... 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 - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]' -#[repr(transparent)] // public +#[repr(transparent)] // public... pub struct ReprTransparentPriv1ZstFields { marker0: Marker, - pub main: u64, // non-1-ZST field + pub main: u64, // ...since the non-1-ZST field is public and visible 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 - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]' -#[repr(transparent)] // public +#[repr(transparent)] // public... pub struct ReprTransparentPub1ZstField { - marker0: Marker, - pub marker1: Marker, + marker0: Marker, // ...since we don't have a non-1-ZST field... + 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 + +// 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; |
