Skip to content

Commit 85c193a

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

File tree

7 files changed

+229
-122
lines changed

7 files changed

+229
-122
lines changed

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,30 @@ 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
96+
not meant to be considered part of the public ABI otherwise.
97+
98+
Note that there's no way to overwrite that heuristic and force rustdoc to show the representation
99+
regardless.
100+
101+
### `#[repr(transparent)]`
93102

94103
You can read more about `#[repr(transparent)]` itself in the [Rust Reference][repr-trans-ref] and
95104
in the [Rustonomicon][repr-trans-nomicon].
96105

97106
Since this representation is only considered part of the public ABI if the single field with non-trivial
98-
size or alignment is public and if the documentation does not state otherwise, Rustdoc helpfully displays
99-
the attribute if and only if the non-1-ZST field is public or at least one field is public in case all
100-
fields are 1-ZST fields. The term *1-ZST* refers to types that are one-aligned and zero-sized.
107+
size or alignment is public and if the documentation does not state otherwise, rustdoc helpfully displays
108+
the attribute if and only if the non-1-ZST field is public and not `#[doc(hidden)]` or
109+
– in case all fields are 1-ZST fields — at least one field is public and not `#[doc(hidden)]`.
110+
The term *1-ZST* refers to types that are one-aligned and zero-sized.
101111

102112
It would seem that one can manually hide the attribute with `#[cfg_attr(not(doc), repr(transparent))]`
103113
if one wishes to declare the representation as private even if the non-1-ZST field is public.
104114
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
115+
Therefore, if you would like to do so, you should always write that down in prose independently of whether
106116
you use `cfg_attr` or not.
107117

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

src/librustdoc/html/render/mod.rs

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2964,6 +2964,10 @@ fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) {
29642964
write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap();
29652965
}
29662966

2967+
/// Compute the *public* `#[repr]` of the item given by `DefId`.
2968+
///
2969+
/// Read more about it here:
2970+
/// <https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type>.
29672971
fn repr_attribute<'tcx>(
29682972
tcx: TyCtxt<'tcx>,
29692973
cache: &Cache,
@@ -2975,25 +2979,59 @@ fn repr_attribute<'tcx>(
29752979
};
29762980
let repr = adt.repr();
29772981

2982+
let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id);
2983+
let is_public_field = |field: &ty::FieldDef| {
2984+
(cache.document_private || field.vis.is_public()) && is_visible(field.did)
2985+
};
2986+
29782987
if repr.transparent() {
2979-
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
2980-
// field is public in case all fields are 1-ZST fields.
2981-
let render_transparent = cache.document_private
2982-
|| adt
2983-
.all_fields()
2984-
.find(|field| {
2985-
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
2986-
tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty))
2987-
.is_ok_and(|layout| !layout.is_1zst())
2988-
})
2989-
.map_or_else(
2990-
|| adt.all_fields().any(|field| field.vis.is_public()),
2991-
|field| field.vis.is_public(),
2992-
);
2988+
// The transparent repr is public iff the non-1-ZST field is public and visible or
2989+
// – in case all fields are 1-ZST fields — at least one field is public and visible.
2990+
let is_public = 'is_public: {
2991+
// `#[repr(transparent)]` can only be applied to structs and single-variant enums.
2992+
let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant
2993+
2994+
if !is_visible(var.def_id) {
2995+
break 'is_public false;
2996+
}
2997+
2998+
// Side note: There can only ever be one or zero non-1-ZST fields.
2999+
let non_1zst_field = var.fields.iter().find(|field| {
3000+
let ty = ty::TypingEnv::post_analysis(tcx, field.did)
3001+
.as_query_input(tcx.type_of(field.did).instantiate_identity());
3002+
tcx.layout_of(ty).is_ok_and(|layout| !layout.is_1zst())
3003+
});
3004+
3005+
match non_1zst_field {
3006+
Some(field) => is_public_field(field),
3007+
None => var.fields.is_empty() || var.fields.iter().any(is_public_field),
3008+
}
3009+
};
29933010

29943011
// Since the transparent repr can't have any other reprs or
29953012
// repr modifiers beside it, we can safely return early here.
2996-
return render_transparent.then(|| "#[repr(transparent)]".into());
3013+
return is_public.then(|| "#[repr(transparent)]".into());
3014+
}
3015+
3016+
// Fast path which avoids looking through the variants and fields in
3017+
// the common case of no `#[repr]` or in the case of `#[repr(Rust)]`.
3018+
// FIXME: This check is not very robust / forward compatible!
3019+
if !repr.c()
3020+
&& !repr.simd()
3021+
&& repr.int.is_none()
3022+
&& repr.pack.is_none()
3023+
&& repr.align.is_none()
3024+
{
3025+
return None;
3026+
}
3027+
3028+
// The repr is public iff all components are public and visible.
3029+
let is_public = adt
3030+
.variants()
3031+
.iter()
3032+
.all(|variant| is_visible(variant.def_id) && variant.fields.iter().all(is_public_field));
3033+
if !is_public {
3034+
return None;
29973035
}
29983036

29993037
let mut result = Vec::<Cow<'_, _>>::new();
@@ -3004,12 +3042,6 @@ fn repr_attribute<'tcx>(
30043042
if repr.simd() {
30053043
result.push("simd".into());
30063044
}
3007-
if let Some(pack) = repr.pack {
3008-
result.push(format!("packed({})", pack.bytes()).into());
3009-
}
3010-
if let Some(align) = repr.align {
3011-
result.push(format!("align({})", align.bytes()).into());
3012-
}
30133045
if let Some(int) = repr.int {
30143046
let prefix = if int.is_signed() { 'i' } else { 'u' };
30153047
let int = match int {
@@ -3021,5 +3053,13 @@ fn repr_attribute<'tcx>(
30213053
result.push(int.into());
30223054
}
30233055

3056+
// Render modifiers last.
3057+
if let Some(pack) = repr.pack {
3058+
result.push(format!("packed({})", pack.bytes()).into());
3059+
}
3060+
if let Some(align) = repr.align {
3061+
result.push(format!("align({})", align.bytes()).into());
3062+
}
3063+
30243064
(!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")).into())
30253065
}

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

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

460460
#[repr(C)]
461461
pub struct WithGenerics<T: TraitWithNoDocblocks, S = String, E = WhoLetTheDogOut, P = i8> {
462-
s: S,
463-
t: T,
464-
e: E,
465-
p: P,
462+
pub s: S,
463+
pub t: T,
464+
pub e: E,
465+
pub p: P,
466466
}
467467

468468
pub struct StructWithPublicUndocumentedFields {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[repr(i8)]
2+
pub enum ReprI8 {
3+
Var0,
4+
Var1,
5+
}

tests/rustdoc/inline_cross/auxiliary/repr.rs

Lines changed: 0 additions & 42 deletions
This file was deleted.

tests/rustdoc/inline_cross/repr.rs

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)