Skip to content

Commit 385d3be

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

File tree

9 files changed

+267
-100
lines changed

9 files changed

+267
-100
lines changed

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

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

92-
## `#[repr(transparent)]`: Documenting the transparent representation
92+
## `#[repr(...)]`: Documenting the representation of a type
93+
94+
Generally, rustdoc only displays the representation of a given type if none of its variants are
95+
`#[doc(hidden)]` and if all of its fields are public and not `#[doc(hidden)]` since it's likely not
96+
meant to be considered part of the public ABI otherwise.
97+
98+
### `#[repr(transparent)]`
9399

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

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

src/librustdoc/clean/types.rs

Lines changed: 95 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::hash::Hash;
23
use std::path::PathBuf;
34
use std::sync::{Arc, OnceLock as OnceCell};
@@ -763,9 +764,7 @@ impl Item {
763764
const ALLOWED_ATTRIBUTES: &[Symbol] =
764765
&[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
765766

766-
use rustc_abi::IntegerType;
767-
768-
let mut attrs: Vec<String> = self
767+
let mut attrs: Vec<_> = self
769768
.attrs
770769
.other_attrs
771770
.iter()
@@ -805,69 +804,103 @@ impl Item {
805804
})
806805
.collect();
807806

808-
// Add #[repr(...)]
809-
if let Some(def_id) = self.def_id()
810-
&& let ItemType::Struct | ItemType::Enum | ItemType::Union = self.type_()
807+
if let Some(repr) = self.repr(tcx, cache) {
808+
attrs.push(repr);
809+
}
810+
811+
attrs
812+
}
813+
814+
/// Compute the *public* `#[repr]` of this item.
815+
///
816+
/// Read more about it here:
817+
/// https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type
818+
fn repr<'tcx>(&self, tcx: TyCtxt<'tcx>, cache: &Cache) -> Option<String> {
819+
let def_id = self.def_id()?;
820+
let (ItemType::Struct | ItemType::Enum | ItemType::Union) = self.type_() else {
821+
return None;
822+
};
823+
let adt = tcx.adt_def(def_id);
824+
let repr = adt.repr();
825+
826+
let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id);
827+
let is_field_public = |field: &'tcx ty::FieldDef| {
828+
(cache.document_private || field.vis.is_public()) && is_visible(field.did)
829+
};
830+
831+
if repr.transparent() {
832+
// `repr(transparent)` can only be applied to structs and one-variant enums.
833+
let var = adt.variant(rustc_abi::FIRST_VARIANT);
834+
// `repr(transparent)` is public iff the non-1-ZST field is public or
835+
// at least one field is public in case all fields are 1-ZST fields.
836+
let is_public = is_visible(var.def_id)
837+
&& var
838+
.fields
839+
.iter()
840+
.find(|field| {
841+
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
842+
tcx.layout_of(
843+
ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty),
844+
)
845+
.is_ok_and(|layout| !layout.is_1zst())
846+
})
847+
.map_or_else(
848+
|| var.fields.is_empty() || var.fields.iter().any(is_field_public),
849+
is_field_public,
850+
);
851+
852+
// Since `repr(transparent)` can't have any other reprs or
853+
// repr modifiers beside it, we can safely return early here.
854+
return is_public.then(|| "#[repr(transparent)]".into());
855+
}
856+
857+
// Fast path which avoids looking through the variants and fields in
858+
// the common case of no `#[repr]` or in the case of `#[repr(Rust)]`.
859+
// FIXME: This check is not forward compatible!
860+
if !repr.c()
861+
&& !repr.simd()
862+
&& repr.int.is_none()
863+
&& repr.pack.is_none()
864+
&& repr.align.is_none()
811865
{
812-
let adt = tcx.adt_def(def_id);
813-
let repr = adt.repr();
814-
let mut out = Vec::new();
815-
if repr.c() {
816-
out.push("C");
817-
}
818-
if repr.transparent() {
819-
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
820-
// field is public in case all fields are 1-ZST fields.
821-
let render_transparent = cache.document_private
822-
|| adt
823-
.all_fields()
824-
.find(|field| {
825-
let ty =
826-
field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
827-
tcx.layout_of(
828-
ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty),
829-
)
830-
.is_ok_and(|layout| !layout.is_1zst())
831-
})
832-
.map_or_else(
833-
|| adt.all_fields().any(|field| field.vis.is_public()),
834-
|field| field.vis.is_public(),
835-
);
866+
return None;
867+
}
836868

837-
if render_transparent {
838-
out.push("transparent");
869+
let is_public = adt.variants().iter().all(|variant| {
870+
is_visible(variant.def_id) && variant.fields.iter().all(is_field_public)
871+
});
872+
if !is_public {
873+
return None;
874+
}
875+
876+
let mut result = Vec::<Cow<'_, _>>::new();
877+
878+
if repr.c() {
879+
result.push("C".into());
880+
}
881+
if repr.simd() {
882+
result.push("simd".into());
883+
}
884+
if let Some(int) = repr.int {
885+
let prefix = if int.is_signed() { 'i' } else { 'u' };
886+
let int = match int {
887+
rustc_abi::IntegerType::Pointer(_) => format!("{prefix}size"),
888+
rustc_abi::IntegerType::Fixed(int, _) => {
889+
format!("{prefix}{}", int.size().bytes() * 8)
839890
}
840-
}
841-
if repr.simd() {
842-
out.push("simd");
843-
}
844-
let pack_s;
845-
if let Some(pack) = repr.pack {
846-
pack_s = format!("packed({})", pack.bytes());
847-
out.push(&pack_s);
848-
}
849-
let align_s;
850-
if let Some(align) = repr.align {
851-
align_s = format!("align({})", align.bytes());
852-
out.push(&align_s);
853-
}
854-
let int_s;
855-
if let Some(int) = repr.int {
856-
int_s = match int {
857-
IntegerType::Pointer(is_signed) => {
858-
format!("{}size", if is_signed { 'i' } else { 'u' })
859-
}
860-
IntegerType::Fixed(size, is_signed) => {
861-
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
862-
}
863-
};
864-
out.push(&int_s);
865-
}
866-
if !out.is_empty() {
867-
attrs.push(format!("#[repr({})]", out.join(", ")));
868-
}
891+
};
892+
result.push(int.into());
869893
}
870-
attrs
894+
895+
// Render modifiers last.
896+
if let Some(pack) = repr.pack {
897+
result.push(format!("packed({})", pack.bytes()).into());
898+
}
899+
if let Some(align) = repr.align {
900+
result.push(format!("align({})", align.bytes()).into());
901+
}
902+
903+
(!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")))
871904
}
872905

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

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,10 @@ pub fn safe_fn() {}
454454

455455
#[repr(C)]
456456
pub struct WithGenerics<T: TraitWithNoDocblocks, S = String, E = WhoLetTheDogOut, P = i8> {
457-
s: S,
458-
t: T,
459-
e: E,
460-
p: P,
457+
pub s: S,
458+
pub t: T,
459+
pub e: E,
460+
pub p: P,
461461
}
462462

463463
pub struct StructWithPublicUndocumentedFields {

tests/rustdoc-json/attrs/repr_align.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
//@ is "$.index[?(@.name=='Aligned')].attrs" '["#[repr(align(4))]"]'
44
#[repr(align(4))]
55
pub struct Aligned {
6-
a: i8,
7-
b: i64,
6+
pub a: i8,
7+
pub b: i64,
88
}

tests/rustdoc-json/attrs/repr_combination.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,54 +25,54 @@ pub enum ReversedReprCUsize {
2525
//@ is "$.index[?(@.name=='ReprCPacked')].attrs" '["#[repr(C, packed(1))]"]'
2626
#[repr(C, packed)]
2727
pub struct ReprCPacked {
28-
a: i8,
29-
b: i64,
28+
pub a: i8,
29+
pub b: i64,
3030
}
3131

3232
//@ is "$.index[?(@.name=='SeparateReprCPacked')].attrs" '["#[repr(C, packed(2))]"]'
3333
#[repr(C)]
3434
#[repr(packed(2))]
3535
pub struct SeparateReprCPacked {
36-
a: i8,
37-
b: i64,
36+
pub a: i8,
37+
pub b: i64,
3838
}
3939

4040
//@ is "$.index[?(@.name=='ReversedReprCPacked')].attrs" '["#[repr(C, packed(2))]"]'
4141
#[repr(packed(2), C)]
4242
pub struct ReversedReprCPacked {
43-
a: i8,
44-
b: i64,
43+
pub a: i8,
44+
pub b: i64,
4545
}
4646

4747
//@ is "$.index[?(@.name=='ReprCAlign')].attrs" '["#[repr(C, align(16))]"]'
4848
#[repr(C, align(16))]
4949
pub struct ReprCAlign {
50-
a: i8,
51-
b: i64,
50+
pub a: i8,
51+
pub b: i64,
5252
}
5353

5454
//@ is "$.index[?(@.name=='SeparateReprCAlign')].attrs" '["#[repr(C, align(2))]"]'
5555
#[repr(C)]
5656
#[repr(align(2))]
5757
pub struct SeparateReprCAlign {
58-
a: i8,
59-
b: i64,
58+
pub a: i8,
59+
pub b: i64,
6060
}
6161

6262
//@ is "$.index[?(@.name=='ReversedReprCAlign')].attrs" '["#[repr(C, align(2))]"]'
6363
#[repr(align(2), C)]
6464
pub struct ReversedReprCAlign {
65-
a: i8,
66-
b: i64,
65+
pub a: i8,
66+
pub b: i64,
6767
}
6868

69-
//@ is "$.index[?(@.name=='AlignedExplicitRepr')].attrs" '["#[repr(C, align(16), isize)]"]'
69+
//@ is "$.index[?(@.name=='AlignedExplicitRepr')].attrs" '["#[repr(C, isize, align(16))]"]'
7070
#[repr(C, align(16), isize)]
7171
pub enum AlignedExplicitRepr {
7272
First,
7373
}
7474

75-
//@ is "$.index[?(@.name=='ReorderedAlignedExplicitRepr')].attrs" '["#[repr(C, align(16), isize)]"]'
75+
//@ is "$.index[?(@.name=='ReorderedAlignedExplicitRepr')].attrs" '["#[repr(C, isize, align(16))]"]'
7676
#[repr(isize, C, align(16))]
7777
pub enum ReorderedAlignedExplicitRepr {
7878
First,

tests/rustdoc-json/attrs/repr_packed.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
//@ is "$.index[?(@.name=='Packed')].attrs" '["#[repr(packed(1))]"]'
77
#[repr(packed)]
88
pub struct Packed {
9-
a: i8,
10-
b: i64,
9+
pub a: i8,
10+
pub b: i64,
1111
}
1212

1313
//@ is "$.index[?(@.name=='PackedAligned')].attrs" '["#[repr(packed(4))]"]'
1414
#[repr(packed(4))]
1515
pub struct PackedAligned {
16-
a: i8,
17-
b: i64,
16+
pub a: i8,
17+
pub b: i64,
1818
}

tests/rustdoc/inline_cross/auxiliary/repr.rs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,66 @@
1-
#![feature(repr_simd)]
1+
#![feature(repr_simd)] // only used for the `ReprSimd` test case
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,
9+
}
10+
11+
#[repr(C)] // private
12+
pub struct ReprCPrivField {
13+
private: u8,
14+
pub public: i8,
615
}
7-
#[repr(simd, packed(2))]
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; 1],
25+
pub field: [u8; 1],
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)] // private
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
2761
}
2862

29-
#[repr(transparent)] // public
63+
#[repr(transparent)] // **public**
3064
pub struct ReprTransparentPriv1ZstFields {
3165
marker0: Marker,
3266
pub main: u64, // non-1-ZST field

0 commit comments

Comments
 (0)