Skip to content

Commit 527cc10

Browse files
authored
Unrolled build for #116882
Rollup merge of #116882 - fmease:rustdoc-generalized-priv-repr-heuristic, r=rustdoc rustdoc: hide `#[repr]` if it isn't part of the public ABI > [!IMPORTANT] > Temporarily stacked on top of PR #146527; only the last commit is relevant! Follow-up to #115439. Unblocks #116743, CC ``@dtolnay.`` Fixes #66401. Fixes #128364. Fixes #137440. Only display the representation `#[repr(REPR)]` (where `REPR` is not `Rust` or `transparent`) of a given type if none of its variants (incl. the synthetic variants of structs) are `#[doc(hidden)]` and 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. `--document-{private,hidden}-items` works as expected in this context, too. Moreover, we now also factor in the presence of `#[doc(hidden)]` when checking whether to show `repr(transparent)` or not.
2 parents eabf390 + 85c193a commit 527cc10

File tree

15 files changed

+350
-299
lines changed

15 files changed

+350
-299
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: 0 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -794,50 +794,6 @@ impl Item {
794794
Some(tcx.visibility(def_id))
795795
}
796796

797-
/// Get a list of attributes excluding `#[repr]` to display.
798-
///
799-
/// Only used by the HTML output-format.
800-
fn attributes_without_repr(&self) -> Vec<String> {
801-
self.attrs
802-
.other_attrs
803-
.iter()
804-
.filter_map(|attr| match attr {
805-
hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => {
806-
Some(format!("#[unsafe(link_section = \"{name}\")]"))
807-
}
808-
hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
809-
Some("#[unsafe(no_mangle)]".to_string())
810-
}
811-
hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
812-
Some(format!("#[unsafe(export_name = \"{name}\")]"))
813-
}
814-
hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => {
815-
Some("#[non_exhaustive]".to_string())
816-
}
817-
_ => None,
818-
})
819-
.collect()
820-
}
821-
822-
/// Get a list of attributes to display on this item.
823-
///
824-
/// Only used by the HTML output-format.
825-
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> {
826-
let mut attrs = self.attributes_without_repr();
827-
828-
if let Some(repr_attr) = self.repr(tcx, cache) {
829-
attrs.push(repr_attr);
830-
}
831-
attrs
832-
}
833-
834-
/// Returns a stringified `#[repr(...)]` attribute.
835-
///
836-
/// Only used by the HTML output-format.
837-
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> {
838-
repr_attributes(tcx, cache, self.def_id()?, self.type_())
839-
}
840-
841797
pub fn is_doc_hidden(&self) -> bool {
842798
self.attrs.is_doc_hidden()
843799
}
@@ -847,74 +803,6 @@ impl Item {
847803
}
848804
}
849805

850-
/// Return a string representing the `#[repr]` attribute if present.
851-
///
852-
/// Only used by the HTML output-format.
853-
pub(crate) fn repr_attributes(
854-
tcx: TyCtxt<'_>,
855-
cache: &Cache,
856-
def_id: DefId,
857-
item_type: ItemType,
858-
) -> Option<String> {
859-
use rustc_abi::IntegerType;
860-
861-
if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) {
862-
return None;
863-
}
864-
let adt = tcx.adt_def(def_id);
865-
let repr = adt.repr();
866-
let mut out = Vec::new();
867-
if repr.c() {
868-
out.push("C");
869-
}
870-
if repr.transparent() {
871-
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
872-
// field is public in case all fields are 1-ZST fields.
873-
let render_transparent = cache.document_private
874-
|| adt
875-
.all_fields()
876-
.find(|field| {
877-
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
878-
tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty))
879-
.is_ok_and(|layout| !layout.is_1zst())
880-
})
881-
.map_or_else(
882-
|| adt.all_fields().any(|field| field.vis.is_public()),
883-
|field| field.vis.is_public(),
884-
);
885-
886-
if render_transparent {
887-
out.push("transparent");
888-
}
889-
}
890-
if repr.simd() {
891-
out.push("simd");
892-
}
893-
let pack_s;
894-
if let Some(pack) = repr.pack {
895-
pack_s = format!("packed({})", pack.bytes());
896-
out.push(&pack_s);
897-
}
898-
let align_s;
899-
if let Some(align) = repr.align {
900-
align_s = format!("align({})", align.bytes());
901-
out.push(&align_s);
902-
}
903-
let int_s;
904-
if let Some(int) = repr.int {
905-
int_s = match int {
906-
IntegerType::Pointer(is_signed) => {
907-
format!("{}size", if is_signed { 'i' } else { 'u' })
908-
}
909-
IntegerType::Fixed(size, is_signed) => {
910-
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
911-
}
912-
};
913-
out.push(&int_s);
914-
}
915-
if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
916-
}
917-
918806
#[derive(Clone, Debug)]
919807
pub(crate) enum ItemKind {
920808
ExternCrateItem {

src/librustdoc/html/render/mod.rs

Lines changed: 142 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ use askama::Template;
5151
use itertools::Either;
5252
use rustc_ast::join_path_syms;
5353
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
54-
use rustc_hir::attrs::{DeprecatedSince, Deprecation};
54+
use rustc_hir as hir;
55+
use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation};
56+
use rustc_hir::def::DefKind;
5557
use rustc_hir::def_id::{DefId, DefIdSet};
5658
use rustc_hir::{ConstStability, Mutability, RustcVersion, StabilityLevel, StableSince};
5759
use rustc_middle::ty::print::PrintTraitRefExt;
@@ -1310,43 +1312,6 @@ fn render_assoc_item(
13101312
})
13111313
}
13121314

1313-
struct CodeAttribute(String);
1314-
1315-
fn render_code_attribute(prefix: &str, code_attr: CodeAttribute, w: &mut impl fmt::Write) {
1316-
write!(
1317-
w,
1318-
"<div class=\"code-attribute\">{prefix}{attr}</div>",
1319-
prefix = prefix,
1320-
attr = code_attr.0
1321-
)
1322-
.unwrap();
1323-
}
1324-
1325-
// When an attribute is rendered inside a <code> tag, it is formatted using
1326-
// a div to produce a newline after it.
1327-
fn render_attributes_in_code(
1328-
w: &mut impl fmt::Write,
1329-
it: &clean::Item,
1330-
prefix: &str,
1331-
cx: &Context<'_>,
1332-
) {
1333-
for attr in it.attributes(cx.tcx(), cx.cache()) {
1334-
render_code_attribute(prefix, CodeAttribute(attr), w);
1335-
}
1336-
}
1337-
1338-
/// 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) {
1346-
render_code_attribute("", CodeAttribute(repr), w);
1347-
}
1348-
}
1349-
13501315
#[derive(Copy, Clone)]
13511316
enum AssocItemLink<'a> {
13521317
Anchor(Option<&'a str>),
@@ -2959,3 +2924,142 @@ fn render_call_locations<W: fmt::Write>(
29592924

29602925
w.write_str("</div>")
29612926
}
2927+
2928+
fn render_attributes_in_code(
2929+
w: &mut impl fmt::Write,
2930+
item: &clean::Item,
2931+
prefix: &str,
2932+
cx: &Context<'_>,
2933+
) {
2934+
for attr in &item.attrs.other_attrs {
2935+
let hir::Attribute::Parsed(kind) = attr else { continue };
2936+
let attr = match kind {
2937+
AttributeKind::LinkSection { name, .. } => {
2938+
Cow::Owned(format!("#[unsafe(link_section = {})]", Escape(&format!("{name:?}"))))
2939+
}
2940+
AttributeKind::NoMangle(..) => Cow::Borrowed("#[unsafe(no_mangle)]"),
2941+
AttributeKind::ExportName { name, .. } => {
2942+
Cow::Owned(format!("#[unsafe(export_name = {})]", Escape(&format!("{name:?}"))))
2943+
}
2944+
AttributeKind::NonExhaustive(..) => Cow::Borrowed("#[non_exhaustive]"),
2945+
_ => continue,
2946+
};
2947+
render_code_attribute(prefix, attr.as_ref(), w);
2948+
}
2949+
2950+
if let Some(def_id) = item.def_id()
2951+
&& let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id)
2952+
{
2953+
render_code_attribute(prefix, &repr, w);
2954+
}
2955+
}
2956+
2957+
fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) {
2958+
if let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) {
2959+
render_code_attribute("", &repr, w);
2960+
}
2961+
}
2962+
2963+
fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) {
2964+
write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap();
2965+
}
2966+
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>.
2971+
fn repr_attribute<'tcx>(
2972+
tcx: TyCtxt<'tcx>,
2973+
cache: &Cache,
2974+
def_id: DefId,
2975+
) -> Option<Cow<'static, str>> {
2976+
let adt = match tcx.def_kind(def_id) {
2977+
DefKind::Struct | DefKind::Enum | DefKind::Union => tcx.adt_def(def_id),
2978+
_ => return None,
2979+
};
2980+
let repr = adt.repr();
2981+
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+
2987+
if repr.transparent() {
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+
};
3010+
3011+
// Since the transparent repr can't have any other reprs or
3012+
// repr modifiers beside it, we can safely return early here.
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;
3035+
}
3036+
3037+
let mut result = Vec::<Cow<'_, _>>::new();
3038+
3039+
if repr.c() {
3040+
result.push("C".into());
3041+
}
3042+
if repr.simd() {
3043+
result.push("simd".into());
3044+
}
3045+
if let Some(int) = repr.int {
3046+
let prefix = if int.is_signed() { 'i' } else { 'u' };
3047+
let int = match int {
3048+
rustc_abi::IntegerType::Pointer(_) => format!("{prefix}size"),
3049+
rustc_abi::IntegerType::Fixed(int, _) => {
3050+
format!("{prefix}{}", int.size().bytes() * 8)
3051+
}
3052+
};
3053+
result.push(int.into());
3054+
}
3055+
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+
3064+
(!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")).into())
3065+
}

src/librustdoc/html/render/print_item.rs

Lines changed: 4 additions & 4 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;
@@ -1555,7 +1555,7 @@ impl<'clean> DisplayEnum<'clean> {
15551555
wrap_item(w, |w| {
15561556
if is_type_alias {
15571557
// For now the only attributes we render for type aliases are `repr` attributes.
1558-
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum);
1558+
render_repr_attribute_in_code(w, cx, self.def_id);
15591559
} else {
15601560
render_attributes_in_code(w, it, "", cx);
15611561
}
@@ -2017,7 +2017,7 @@ impl<'a> DisplayStruct<'a> {
20172017
wrap_item(w, |w| {
20182018
if is_type_alias {
20192019
// For now the only attributes we render for type aliases are `repr` attributes.
2020-
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct);
2020+
render_repr_attribute_in_code(w, cx, self.def_id);
20212021
} else {
20222022
render_attributes_in_code(w, it, "", cx);
20232023
}
@@ -2371,7 +2371,7 @@ fn render_union(
23712371
fmt::from_fn(move |mut f| {
23722372
if is_type_alias {
23732373
// For now the only attributes we render for type aliases are `repr` attributes.
2374-
render_repr_attributes_in_code(f, cx, def_id, ItemType::Union);
2374+
render_repr_attribute_in_code(f, cx, def_id);
23752375
} else {
23762376
render_attributes_in_code(f, it, "", cx);
23772377
}

0 commit comments

Comments
 (0)