Skip to content

Commit 7d22152

Browse files
committed
rustdoc: hide #[repr(...)] if it isn't part of the public ABI
1 parent 93edf9f commit 7d22152

File tree

9 files changed

+272
-186
lines changed

9 files changed

+272
-186
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/clean/types.rs

Lines changed: 96 additions & 72 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};
@@ -772,11 +773,10 @@ impl Item {
772773
Some(tcx.visibility(def_id))
773774
}
774775

775-
/// Get a list of attributes excluding `#[repr]` to display.
776-
///
777-
/// Only used by the HTML output-format.
778-
fn attributes_without_repr(&self) -> Vec<String> {
779-
self.attrs
776+
// FIXME(fmease): Move into html/ mod, this is for HTML output only.
777+
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> {
778+
let mut attrs: Vec<_> = self
779+
.attrs
780780
.other_attrs
781781
.iter()
782782
.filter_map(|attr| match attr {
@@ -794,26 +794,15 @@ impl Item {
794794
}
795795
_ => None,
796796
})
797-
.collect()
798-
}
797+
.collect();
799798

800-
/// Get a list of attributes to display on this item.
801-
///
802-
/// Only used by the HTML output-format.
803-
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> {
804-
let mut attrs = self.attributes_without_repr();
805-
806-
if let Some(repr_attr) = self.repr(tcx, cache) {
807-
attrs.push(repr_attr);
799+
if let Some(def_id) = self.def_id()
800+
&& let Some(attr) = repr_attribute(tcx, cache, def_id)
801+
{
802+
attrs.push(attr);
808803
}
809-
attrs
810-
}
811804

812-
/// Returns a stringified `#[repr(...)]` attribute.
813-
///
814-
/// Only used by the HTML output-format.
815-
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> {
816-
repr_attributes(tcx, cache, self.def_id()?, self.type_())
805+
attrs
817806
}
818807

819808
pub fn is_doc_hidden(&self) -> bool {
@@ -825,72 +814,107 @@ impl Item {
825814
}
826815
}
827816

828-
/// Return a string representing the `#[repr]` attribute if present.
817+
/// Compute the *public* `#[repr]` of the item given by `DefId`.
829818
///
830-
/// Only used by the HTML output-format.
831-
pub(crate) fn repr_attributes(
832-
tcx: TyCtxt<'_>,
819+
/// Read more about it here:
820+
/// <https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type>.
821+
///
822+
/// For use in the HTML output only.
823+
// FIXME(fmease): Move into html/ mod
824+
pub(crate) fn repr_attribute<'tcx>(
825+
tcx: TyCtxt<'tcx>,
833826
cache: &Cache,
834827
def_id: DefId,
835-
item_type: ItemType,
836828
) -> Option<String> {
837-
use rustc_abi::IntegerType;
829+
let adt = match tcx.def_kind(def_id) {
830+
DefKind::Struct | DefKind::Enum | DefKind::Union => tcx.adt_def(def_id),
831+
_ => return None,
832+
};
833+
let repr = adt.repr();
834+
835+
let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id);
836+
let is_public_field = |field: &ty::FieldDef| {
837+
(cache.document_private || field.vis.is_public()) && is_visible(field.did)
838+
};
838839

839-
if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) {
840+
if repr.transparent() {
841+
// The transparent repr is public iff the non-1-ZST field is public and visible or
842+
// – in case all fields are 1-ZST fields — at least one field is public and visible.
843+
let is_public = 'is_public: {
844+
// `#[repr(transparent)]` can only be applied to structs and single-variant enums.
845+
let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant
846+
847+
if !is_visible(var.def_id) {
848+
break 'is_public false;
849+
}
850+
851+
// Side note: There can only ever be one or zero non-1-ZST fields.
852+
let non_1zst_field = var.fields.iter().find(|field| {
853+
let ty = ty::TypingEnv::post_analysis(tcx, field.did)
854+
.as_query_input(tcx.type_of(field.did).instantiate_identity());
855+
tcx.layout_of(ty).is_ok_and(|layout| !layout.is_1zst())
856+
});
857+
858+
match non_1zst_field {
859+
Some(field) => is_public_field(field),
860+
None => var.fields.is_empty() || var.fields.iter().any(is_public_field),
861+
}
862+
};
863+
864+
// Since the transparent repr can't have any other reprs or
865+
// repr modifiers beside it, we can safely return early here.
866+
return is_public.then(|| "#[repr(transparent)]".into());
867+
}
868+
869+
// Fast path which avoids looking through the variants and fields in
870+
// the common case of no `#[repr]` or in the case of `#[repr(Rust)]`.
871+
// FIXME: This check is not very robust / forward compatible!
872+
if !repr.c()
873+
&& !repr.simd()
874+
&& repr.int.is_none()
875+
&& repr.pack.is_none()
876+
&& repr.align.is_none()
877+
{
840878
return None;
841879
}
842-
let adt = tcx.adt_def(def_id);
843-
let repr = adt.repr();
844-
let mut out = Vec::new();
845-
if repr.c() {
846-
out.push("C");
880+
881+
// The repr is public iff all components are public and visible.
882+
let is_public = adt
883+
.variants()
884+
.iter()
885+
.all(|variant| is_visible(variant.def_id) && variant.fields.iter().all(is_public_field));
886+
if !is_public {
887+
return None;
847888
}
848-
if repr.transparent() {
849-
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
850-
// field is public in case all fields are 1-ZST fields.
851-
let render_transparent = cache.document_private
852-
|| adt
853-
.all_fields()
854-
.find(|field| {
855-
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
856-
tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty))
857-
.is_ok_and(|layout| !layout.is_1zst())
858-
})
859-
.map_or_else(
860-
|| adt.all_fields().any(|field| field.vis.is_public()),
861-
|field| field.vis.is_public(),
862-
);
863889

864-
if render_transparent {
865-
out.push("transparent");
866-
}
890+
let mut result = Vec::<Cow<'_, _>>::new();
891+
892+
if repr.c() {
893+
result.push("C".into());
867894
}
868895
if repr.simd() {
869-
out.push("simd");
870-
}
871-
let pack_s;
872-
if let Some(pack) = repr.pack {
873-
pack_s = format!("packed({})", pack.bytes());
874-
out.push(&pack_s);
896+
result.push("simd".into());
875897
}
876-
let align_s;
877-
if let Some(align) = repr.align {
878-
align_s = format!("align({})", align.bytes());
879-
out.push(&align_s);
880-
}
881-
let int_s;
882898
if let Some(int) = repr.int {
883-
int_s = match int {
884-
IntegerType::Pointer(is_signed) => {
885-
format!("{}size", if is_signed { 'i' } else { 'u' })
886-
}
887-
IntegerType::Fixed(size, is_signed) => {
888-
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
899+
let prefix = if int.is_signed() { 'i' } else { 'u' };
900+
let int = match int {
901+
rustc_abi::IntegerType::Pointer(_) => format!("{prefix}size"),
902+
rustc_abi::IntegerType::Fixed(int, _) => {
903+
format!("{prefix}{}", int.size().bytes() * 8)
889904
}
890905
};
891-
out.push(&int_s);
906+
result.push(int.into());
892907
}
893-
if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
908+
909+
// Render modifiers last.
910+
if let Some(pack) = repr.pack {
911+
result.push(format!("packed({})", pack.bytes()).into());
912+
}
913+
if let Some(align) = repr.align {
914+
result.push(format!("align({})", align.bytes()).into());
915+
}
916+
917+
(!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")))
894918
}
895919

896920
#[derive(Clone, Debug)]

src/librustdoc/html/render/mod.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,13 +1336,8 @@ fn render_attributes_in_code(
13361336
}
13371337

13381338
/// used for type aliases to only render their `repr` attribute.
1339-
fn render_repr_attributes_in_code(
1340-
w: &mut impl fmt::Write,
1341-
cx: &Context<'_>,
1342-
def_id: DefId,
1343-
item_type: ItemType,
1344-
) {
1345-
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) {
1339+
fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) {
1340+
if let Some(repr) = clean::repr_attribute(cx.tcx(), cx.cache(), def_id) {
13461341
render_code_attribute("", CodeAttribute(repr), w);
13471342
}
13481343
}

src/librustdoc/html/render/print_item.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use super::{
2121
collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
2222
item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
2323
render_assoc_item, render_assoc_items, render_attributes_in_code, render_impl,
24-
render_repr_attributes_in_code, render_rightside, render_stability_since_raw,
24+
render_repr_attribute_in_code, render_rightside, render_stability_since_raw,
2525
render_stability_since_raw_with_extra, write_section_heading,
2626
};
2727
use crate::clean;
@@ -1544,8 +1544,8 @@ impl<'clean> DisplayEnum<'clean> {
15441544

15451545
wrap_item(w, |w| {
15461546
if is_type_alias {
1547-
// For now the only attributes we render for type aliases are `repr` attributes.
1548-
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum);
1547+
// For now the only attribute we render for type aliases is the `repr` attribute.
1548+
render_repr_attribute_in_code(w, cx, self.def_id);
15491549
} else {
15501550
render_attributes_in_code(w, it, "", cx);
15511551
}
@@ -2000,8 +2000,8 @@ impl<'a> DisplayStruct<'a> {
20002000
) -> fmt::Result {
20012001
wrap_item(w, |w| {
20022002
if is_type_alias {
2003-
// For now the only attributes we render for type aliases are `repr` attributes.
2004-
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct);
2003+
// For now the only attribute we render for type aliases is the `repr` attribute.
2004+
render_repr_attribute_in_code(w, cx, self.def_id);
20052005
} else {
20062006
render_attributes_in_code(w, it, "", cx);
20072007
}
@@ -2350,7 +2350,7 @@ fn render_union(
23502350
fmt::from_fn(move |mut f| {
23512351
if is_type_alias {
23522352
// For now the only attributes we render for type aliases are `repr` attributes.
2353-
render_repr_attributes_in_code(f, cx, def_id, ItemType::Union);
2353+
render_repr_attribute_in_code(f, cx, def_id);
23542354
} else {
23552355
render_attributes_in_code(f, it, "", cx);
23562356
}

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

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

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

464464
pub struct StructWithPublicUndocumentedFields {

tests/rustdoc/auxiliary/ext-repr.rs

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.

0 commit comments

Comments
 (0)