Skip to content

Commit 79bb50a

Browse files
committed
rustdoc: hide #[repr(...)] if it isn't part of the public ABI
1 parent ee8c9d3 commit 79bb50a

File tree

6 files changed

+262
-75
lines changed

6 files changed

+262
-75
lines changed

src/doc/rustdoc/src/advanced-features.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,13 @@ https://doc.rust-lang.org/stable/std/?search=%s&go_to_first=true
111111
This URL adds the `go_to_first=true` query parameter which can be appended to any `rustdoc` search URL
112112
to automatically go to the first result.
113113

114-
## `#[repr(transparent)]`: Documenting the transparent representation
114+
## `#[repr(...)]`: Documenting the representation of a type
115+
116+
<!-- FIXME(fmease): Fill in this section once the semantics have been decided upon. -->
117+
118+
### `#[repr(transparent)]`
119+
120+
<!-- FIXME(fmease): Extract most parts into the section above. -->
115121

116122
You can read more about `#[repr(transparent)]` itself in the [Rust Reference][repr-trans-ref] and
117123
in the [Rustonomicon][repr-trans-nomicon].
@@ -124,7 +130,7 @@ fields are 1-ZST fields. The term *1-ZST* refers to types that are one-aligned a
124130
It would seem that one can manually hide the attribute with `#[cfg_attr(not(doc), repr(transparent))]`
125131
if one wishes to declare the representation as private even if the non-1-ZST field is public.
126132
However, due to [current limitations][cross-crate-cfg-doc], this method is not always guaranteed to work.
127-
Therefore, if you would like to do so, you should always write it down in prose independently of whether
133+
Therefore, if you would like to do so, you should always write that down in prose independently of whether
128134
you use `cfg_attr` or not.
129135

130136
[repr-trans-ref]: https://doc.rust-lang.org/reference/type-layout.html#the-transparent-representation

src/librustdoc/clean/types.rs

+114-61
Original file line numberDiff line numberDiff line change
@@ -713,17 +713,15 @@ impl Item {
713713
Some(tcx.visibility(def_id))
714714
}
715715

716-
pub(crate) fn attributes(
716+
pub(crate) fn attributes<'tcx>(
717717
&self,
718-
tcx: TyCtxt<'_>,
718+
tcx: TyCtxt<'tcx>,
719719
cache: &Cache,
720720
keep_as_is: bool,
721721
) -> Vec<String> {
722722
const ALLOWED_ATTRIBUTES: &[Symbol] =
723723
&[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
724724

725-
use rustc_abi::IntegerType;
726-
727725
let mut attrs: Vec<String> = self
728726
.attrs
729727
.other_attrs
@@ -743,67 +741,122 @@ impl Item {
743741
}
744742
})
745743
.collect();
746-
if !keep_as_is
747-
&& let Some(def_id) = self.def_id()
748-
&& let ItemType::Struct | ItemType::Enum | ItemType::Union = self.type_()
749-
{
750-
let adt = tcx.adt_def(def_id);
751-
let repr = adt.repr();
752-
let mut out = Vec::new();
753-
if repr.c() {
754-
out.push("C");
755-
}
756-
if repr.transparent() {
757-
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
758-
// field is public in case all fields are 1-ZST fields.
759-
let render_transparent = cache.document_private
760-
|| adt
761-
.all_fields()
762-
.find(|field| {
763-
let ty =
764-
field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
765-
tcx.layout_of(tcx.param_env(field.did).and(ty))
766-
.is_ok_and(|layout| !layout.is_1zst())
767-
})
768-
.map_or_else(
769-
|| adt.all_fields().any(|field| field.vis.is_public()),
770-
|field| field.vis.is_public(),
771-
);
772744

773-
if render_transparent {
774-
out.push("transparent");
775-
}
776-
}
777-
if repr.simd() {
778-
out.push("simd");
779-
}
780-
let pack_s;
781-
if let Some(pack) = repr.pack {
782-
pack_s = format!("packed({})", pack.bytes());
783-
out.push(&pack_s);
784-
}
785-
let align_s;
786-
if let Some(align) = repr.align {
787-
align_s = format!("align({})", align.bytes());
788-
out.push(&align_s);
789-
}
790-
let int_s;
791-
if let Some(int) = repr.int {
792-
int_s = match int {
793-
IntegerType::Pointer(is_signed) => {
794-
format!("{}size", if is_signed { 'i' } else { 'u' })
745+
if !keep_as_is && let Some(repr) = self.repr(tcx, cache) {
746+
attrs.push(repr);
747+
}
748+
749+
attrs
750+
}
751+
752+
/// Compute the *public* `#[repr]` of this item.
753+
///
754+
/// Read more about it here:
755+
/// https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type
756+
fn repr<'tcx>(&self, tcx: TyCtxt<'tcx>, cache: &Cache) -> Option<String> {
757+
let def_id = self.def_id()?;
758+
let (ItemType::Struct | ItemType::Enum | ItemType::Union) = self.type_() else {
759+
return None;
760+
};
761+
762+
let adt = tcx.adt_def(def_id);
763+
let repr = adt.repr();
764+
765+
let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id);
766+
let is_field_public =
767+
|field: &'tcx ty::FieldDef| field.vis.is_public() && is_visible(field.did);
768+
769+
if repr.transparent() {
770+
// `repr(transparent)` is public iff the non-1-ZST field is public or
771+
// at least one field is public in case all fields are 1-ZST fields.
772+
let is_public = cache.document_private
773+
|| adt.variants().iter().all(|variant| {
774+
if !is_visible(variant.def_id) {
775+
return false;
795776
}
796-
IntegerType::Fixed(size, is_signed) => {
797-
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
777+
778+
let field = variant.fields.iter().find(|field| {
779+
let args = ty::GenericArgs::identity_for_item(tcx, field.did);
780+
let ty = field.ty(tcx, args);
781+
tcx.layout_of(tcx.param_env(field.did).and(ty))
782+
.is_ok_and(|layout| !layout.is_1zst())
783+
});
784+
785+
if let Some(field) = field {
786+
return is_field_public(field);
798787
}
799-
};
800-
out.push(&int_s);
801-
}
802-
if !out.is_empty() {
803-
attrs.push(format!("#[repr({})]", out.join(", ")));
804-
}
788+
789+
adt.variants().iter().all(|variant| {
790+
variant.fields.is_empty() || variant.fields.iter().any(is_field_public)
791+
})
792+
});
793+
794+
// Since `repr(transparent)` can't have any other reprs or
795+
// repr modifiers beside it, we can safely return early here.
796+
return is_public.then(|| "#[repr(transparent)]".into());
805797
}
806-
attrs
798+
799+
// Fast path which avoids looking through the variants and fields in
800+
// the common case of no `#[repr]` or in the case of `#[repr(Rust)]`.
801+
if !repr.c()
802+
&& !repr.simd()
803+
&& repr.int.is_none()
804+
&& repr.pack.is_none()
805+
&& repr.align.is_none()
806+
{
807+
return None;
808+
}
809+
810+
let is_public = cache.document_private
811+
|| if adt.is_enum() {
812+
// FIXME(fmease): Should we take the visibility of fields of variants into account?
813+
// FIXME(fmease): `any` or `all`?
814+
adt.variants().is_empty()
815+
|| adt.variants().iter().any(|variant| is_visible(variant.def_id))
816+
} else {
817+
// FIXME(fmease): `all` or `any`?
818+
adt.all_fields().all(is_field_public)
819+
};
820+
if !is_public {
821+
return None;
822+
}
823+
824+
let mut result = Vec::new();
825+
826+
if repr.c() {
827+
result.push("C");
828+
}
829+
if repr.simd() {
830+
result.push("simd");
831+
}
832+
let int_s;
833+
if let Some(int) = repr.int {
834+
int_s = match int {
835+
rustc_abi::IntegerType::Pointer(is_signed) => {
836+
format!("{}size", if is_signed { 'i' } else { 'u' })
837+
}
838+
rustc_abi::IntegerType::Fixed(size, is_signed) => {
839+
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
840+
}
841+
};
842+
result.push(&int_s);
843+
}
844+
let pack_s;
845+
if let Some(pack) = repr.pack {
846+
pack_s = format!("packed({})", pack.bytes());
847+
result.push(&pack_s);
848+
}
849+
let align_s;
850+
if let Some(align) = repr.align {
851+
align_s = format!("align({})", align.bytes());
852+
result.push(&align_s);
853+
}
854+
855+
if result.is_empty() {
856+
return None;
857+
}
858+
859+
Some(format!("#[repr({})]", result.join(", ")))
807860
}
808861

809862
pub fn is_doc_hidden(&self) -> bool {

tests/rustdoc-gui/src/test_docs/lib.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,10 @@ pub fn safe_fn() {}
424424

425425
#[repr(C)]
426426
pub struct WithGenerics<T: TraitWithNoDocblocks, S = String, E = WhoLetTheDogOut, P = i8> {
427-
s: S,
428-
t: T,
429-
e: E,
430-
p: P,
427+
pub s: S,
428+
pub t: T,
429+
pub e: E,
430+
pub p: P,
431431
}
432432

433433
pub struct StructWithPublicUndocumentedFields {

tests/rustdoc/inline_cross/auxiliary/repr.rs

+42-8
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,60 @@
11
#![feature(repr_simd)]
22

3-
#[repr(C, align(8))]
3+
#[repr(Rust)]
4+
pub struct ReprRust;
5+
6+
#[repr(C, align(8))] // public
47
pub struct ReprC {
5-
field: u8,
8+
pub field: u8,
69
}
7-
#[repr(simd, packed(2))]
10+
11+
#[repr(C)] // private
12+
pub struct ReprCPrivField {
13+
private: u8,
14+
pub public: i8,
15+
}
16+
17+
#[repr(align(4))] // private
18+
pub struct ReprAlignHiddenField {
19+
#[doc(hidden)]
20+
pub hidden: i16,
21+
}
22+
23+
#[repr(simd, packed(2))] // public
824
pub struct ReprSimd {
9-
field: u8,
25+
pub field: u8,
1026
}
11-
#[repr(transparent)]
27+
28+
#[repr(transparent)] // public
1229
pub struct ReprTransparent {
13-
pub field: u8,
30+
pub field: u8, // non-1-ZST field
1431
}
15-
#[repr(isize)]
32+
33+
#[repr(isize)] // public
1634
pub enum ReprIsize {
1735
Bla,
1836
}
19-
#[repr(u8)]
37+
38+
#[repr(u8)] // public
2039
pub enum ReprU8 {
2140
Bla,
2241
}
2342

43+
#[repr(u32)] // public
44+
pub enum ReprU32 {
45+
#[doc(hidden)]
46+
Hidden,
47+
Public,
48+
}
49+
50+
#[repr(u64)] // private
51+
pub enum ReprU64HiddenVariants {
52+
#[doc(hidden)]
53+
A,
54+
#[doc(hidden)]
55+
B,
56+
}
57+
2458
#[repr(transparent)] // private
2559
pub struct ReprTransparentPrivField {
2660
field: u32, // non-1-ZST field

tests/rustdoc/inline_cross/repr.rs

+25
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,47 @@
77

88
extern crate repr;
99

10+
// Never display `repr(Rust)` since it's the default anyway.
11+
// @has 'foo/struct.ReprRust.html'
12+
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(Rust)]'
13+
pub use repr::ReprRust;
14+
1015
// @has 'foo/struct.ReprC.html'
1116
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C, align(8))]'
1217
pub use repr::ReprC;
18+
19+
// @has 'foo/struct.ReprCPrivField.html'
20+
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C)]'
21+
pub use repr::ReprCPrivField;
22+
23+
// @has 'foo/struct.ReprAlignHiddenField.html'
24+
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(align(4))]'
25+
pub use repr::ReprAlignHiddenField;
26+
1327
// @has 'foo/struct.ReprSimd.html'
1428
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(simd, packed(2))]'
1529
pub use repr::ReprSimd;
30+
1631
// @has 'foo/struct.ReprTransparent.html'
1732
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
1833
pub use repr::ReprTransparent;
34+
1935
// @has 'foo/enum.ReprIsize.html'
2036
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(isize)]'
2137
pub use repr::ReprIsize;
38+
2239
// @has 'foo/enum.ReprU8.html'
2340
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u8)]'
2441
pub use repr::ReprU8;
2542

43+
// @has 'foo/enum.ReprU32.html'
44+
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u32)]'
45+
pub use repr::ReprU32;
46+
47+
// @has 'foo/enum.ReprU64HiddenVariants.html'
48+
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u64)]'
49+
pub use repr::ReprU64HiddenVariants;
50+
2651
// Regression test for <https://github.com/rust-lang/rust/issues/90435>.
2752
// Check that we show `#[repr(transparent)]` iff the non-1-ZST field is public or at least one
2853
// field is public in case all fields are 1-ZST fields.

0 commit comments

Comments
 (0)