Rollup merge of #115439 - fmease:rustdoc-priv-repr-transparent-heuristic, r=GuillaumeGomez

rustdoc: hide `#[repr(transparent)]` if it isn't part of the public ABI

Fixes #90435.

This hides `#[repr(transparent)]` when the non-1-ZST field the struct is "transparent" over is private.

CC `@RalfJung`

Tentatively nominating it for the release notes, feel free to remove the nomination.
`@rustbot` label needs-fcp relnotes A-rustdoc-ui
This commit is contained in:
Matthias Krüger 2023-10-14 19:22:16 +02:00 committed by GitHub
commit 4dd4d9b489
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 152 additions and 42 deletions

View File

@ -110,3 +110,23 @@ https://doc.rust-lang.org/stable/std/?search=%s&go_to_first=true
This URL adds the `go_to_first=true` query parameter which can be appended to any `rustdoc` search URL This URL adds the `go_to_first=true` query parameter which can be appended to any `rustdoc` search URL
to automatically go to the first result. to automatically go to the first result.
## `#[repr(transparent)]`: Documenting the transparent representation
You can read more about `#[repr(transparent)]` itself in the [Rust Reference][repr-trans-ref] and
in the [Rustonomicon][repr-trans-nomicon].
Since this representation is only considered part of the public ABI if the single field with non-trivial
size or alignment is public and if the documentation does not state otherwise, Rustdoc helpfully displays
the attribute if and only if the non-1-ZST field is public or at least one field is public in case all
fields are 1-ZST fields. The term *1-ZST* refers to types that are one-aligned and zero-sized.
It would seem that one can manually hide the attribute with `#[cfg_attr(not(doc), repr(transparent))]`
if one wishes to declare the representation as private even if the non-1-ZST field is public.
However, due to [current limitations][cross-crate-cfg-doc], this method is not always guaranteed to work.
Therefore, if you would like to do so, you should always write it down in prose independently of whether
you use `cfg_attr` or not.
[repr-trans-ref]: https://doc.rust-lang.org/reference/type-layout.html#the-transparent-representation
[repr-trans-nomicon]: https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent
[cross-crate-cfg-doc]: https://github.com/rust-lang/rust/issues/114952

View File

@ -713,12 +713,16 @@ impl Item {
Some(tcx.visibility(def_id)) Some(tcx.visibility(def_id))
} }
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, keep_as_is: bool) -> Vec<String> { pub(crate) fn attributes(
&self,
tcx: TyCtxt<'_>,
cache: &Cache,
keep_as_is: bool,
) -> Vec<String> {
const ALLOWED_ATTRIBUTES: &[Symbol] = const ALLOWED_ATTRIBUTES: &[Symbol] =
&[sym::export_name, sym::link_section, sym::no_mangle, sym::repr, sym::non_exhaustive]; &[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
use rustc_abi::IntegerType; use rustc_abi::IntegerType;
use rustc_middle::ty::ReprFlags;
let mut attrs: Vec<String> = self let mut attrs: Vec<String> = self
.attrs .attrs
@ -739,20 +743,38 @@ impl Item {
} }
}) })
.collect(); .collect();
if let Some(def_id) = self.def_id() && if !keep_as_is
!def_id.is_local() && && let Some(def_id) = self.def_id()
// This check is needed because `adt_def` will panic if not a compatible type otherwise... && let ItemType::Struct | ItemType::Enum | ItemType::Union = self.type_()
matches!(self.type_(), ItemType::Struct | ItemType::Enum | ItemType::Union)
{ {
let repr = tcx.adt_def(def_id).repr(); let adt = tcx.adt_def(def_id);
let repr = adt.repr();
let mut out = Vec::new(); let mut out = Vec::new();
if repr.flags.contains(ReprFlags::IS_C) { if repr.c() {
out.push("C"); out.push("C");
} }
if repr.flags.contains(ReprFlags::IS_TRANSPARENT) { if repr.transparent() {
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
// field is public in case all fields are 1-ZST fields.
let render_transparent = cache.document_private
|| adt
.all_fields()
.find(|field| {
let ty =
field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
tcx.layout_of(tcx.param_env(field.did).and(ty))
.is_ok_and(|layout| !layout.is_1zst())
})
.map_or_else(
|| adt.all_fields().any(|field| field.vis.is_public()),
|field| field.vis.is_public(),
);
if render_transparent {
out.push("transparent"); out.push("transparent");
} }
if repr.flags.contains(ReprFlags::IS_SIMD) { }
if repr.simd() {
out.push("simd"); out.push("simd");
} }
let pack_s; let pack_s;
@ -777,11 +799,10 @@ impl Item {
}; };
out.push(&int_s); out.push(&int_s);
} }
if out.is_empty() { if !out.is_empty() {
return Vec::new();
}
attrs.push(format!("#[repr({})]", out.join(", "))); attrs.push(format!("#[repr({})]", out.join(", ")));
} }
}
attrs attrs
} }

View File

@ -868,10 +868,10 @@ fn assoc_method(
let (indent, indent_str, end_newline) = if parent == ItemType::Trait { let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
header_len += 4; header_len += 4;
let indent_str = " "; let indent_str = " ";
write!(w, "{}", render_attributes_in_pre(meth, indent_str, tcx)); write!(w, "{}", render_attributes_in_pre(meth, indent_str, cx));
(4, indent_str, Ending::NoNewline) (4, indent_str, Ending::NoNewline)
} else { } else {
render_attributes_in_code(w, meth, tcx); render_attributes_in_code(w, meth, cx);
(0, "", Ending::Newline) (0, "", Ending::Newline)
}; };
w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len()); w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
@ -1047,13 +1047,13 @@ fn render_assoc_item(
// When an attribute is rendered inside a `<pre>` tag, it is formatted using // When an attribute is rendered inside a `<pre>` tag, it is formatted using
// a whitespace prefix and newline. // a whitespace prefix and newline.
fn render_attributes_in_pre<'a, 'b: 'a>( fn render_attributes_in_pre<'a, 'tcx: 'a>(
it: &'a clean::Item, it: &'a clean::Item,
prefix: &'a str, prefix: &'a str,
tcx: TyCtxt<'b>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + Captures<'a> + Captures<'b> { ) -> impl fmt::Display + Captures<'a> + Captures<'tcx> {
crate::html::format::display_fn(move |f| { crate::html::format::display_fn(move |f| {
for a in it.attributes(tcx, false) { for a in it.attributes(cx.tcx(), cx.cache(), false) {
writeln!(f, "{prefix}{a}")?; writeln!(f, "{prefix}{a}")?;
} }
Ok(()) Ok(())
@ -1062,8 +1062,8 @@ fn render_attributes_in_pre<'a, 'b: 'a>(
// When an attribute is rendered inside a <code> tag, it is formatted using // When an attribute is rendered inside a <code> tag, it is formatted using
// a div to produce a newline after it. // a div to produce a newline after it.
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, tcx: TyCtxt<'_>) { fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
for attr in it.attributes(tcx, false) { for attr in it.attributes(cx.tcx(), cx.cache(), false) {
write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap(); write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
} }
} }

View File

@ -120,8 +120,7 @@ macro_rules! item_template_methods {
fn render_attributes_in_pre<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { fn render_attributes_in_pre<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
display_fn(move |f| { display_fn(move |f| {
let (item, cx) = self.item_and_mut_cx(); let (item, cx) = self.item_and_mut_cx();
let tcx = cx.tcx(); let v = render_attributes_in_pre(item, "", &cx);
let v = render_attributes_in_pre(item, "", tcx);
write!(f, "{v}") write!(f, "{v}")
}) })
} }
@ -659,7 +658,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
w, w,
"{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \ "{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \
{name}{generics}{decl}{notable_traits}{where_clause}", {name}{generics}{decl}{notable_traits}{where_clause}",
attrs = render_attributes_in_pre(it, "", tcx), attrs = render_attributes_in_pre(it, "", cx),
vis = visibility, vis = visibility,
constness = constness, constness = constness,
asyncness = asyncness, asyncness = asyncness,
@ -694,7 +693,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
write!( write!(
w, w,
"{attrs}{vis}{unsafety}{is_auto}trait {name}{generics}{bounds}", "{attrs}{vis}{unsafety}{is_auto}trait {name}{generics}{bounds}",
attrs = render_attributes_in_pre(it, "", tcx), attrs = render_attributes_in_pre(it, "", cx),
vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx), vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
unsafety = t.unsafety(tcx).print_with_space(), unsafety = t.unsafety(tcx).print_with_space(),
is_auto = if t.is_auto(tcx) { "auto " } else { "" }, is_auto = if t.is_auto(tcx) { "auto " } else { "" },
@ -1173,7 +1172,7 @@ fn item_trait_alias(
write!( write!(
w, w,
"{attrs}trait {name}{generics}{where_b} = {bounds};", "{attrs}trait {name}{generics}{where_b} = {bounds};",
attrs = render_attributes_in_pre(it, "", cx.tcx()), attrs = render_attributes_in_pre(it, "", cx),
name = it.name.unwrap(), name = it.name.unwrap(),
generics = t.generics.print(cx), generics = t.generics.print(cx),
where_b = print_where_clause(&t.generics, cx, 0, Ending::Newline), where_b = print_where_clause(&t.generics, cx, 0, Ending::Newline),
@ -1201,7 +1200,7 @@ fn item_opaque_ty(
write!( write!(
w, w,
"{attrs}type {name}{generics}{where_clause} = impl {bounds};", "{attrs}type {name}{generics}{where_clause} = impl {bounds};",
attrs = render_attributes_in_pre(it, "", cx.tcx()), attrs = render_attributes_in_pre(it, "", cx),
name = it.name.unwrap(), name = it.name.unwrap(),
generics = t.generics.print(cx), generics = t.generics.print(cx),
where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
@ -1226,7 +1225,7 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c
write!( write!(
w, w,
"{attrs}{vis}type {name}{generics}{where_clause} = {type_};", "{attrs}{vis}type {name}{generics}{where_clause} = {type_};",
attrs = render_attributes_in_pre(it, "", cx.tcx()), attrs = render_attributes_in_pre(it, "", cx),
vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx),
name = it.name.unwrap(), name = it.name.unwrap(),
generics = t.generics.print(cx), generics = t.generics.print(cx),
@ -1415,7 +1414,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
let tcx = cx.tcx(); let tcx = cx.tcx();
let count_variants = e.variants().count(); let count_variants = e.variants().count();
wrap_item(w, |w| { wrap_item(w, |w| {
render_attributes_in_code(w, it, tcx); render_attributes_in_code(w, it, cx);
write!( write!(
w, w,
"{}enum {}{}", "{}enum {}{}",
@ -1734,7 +1733,7 @@ fn item_primitive(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Ite
fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) { fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) {
wrap_item(w, |w| { wrap_item(w, |w| {
let tcx = cx.tcx(); let tcx = cx.tcx();
render_attributes_in_code(w, it, tcx); render_attributes_in_code(w, it, cx);
write!( write!(
w, w,
@ -1783,7 +1782,7 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle
fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) { fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) {
wrap_item(w, |w| { wrap_item(w, |w| {
render_attributes_in_code(w, it, cx.tcx()); render_attributes_in_code(w, it, cx);
render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx); render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx);
}); });
@ -1843,7 +1842,7 @@ fn item_fields(
fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
wrap_item(w, |buffer| { wrap_item(w, |buffer| {
render_attributes_in_code(buffer, it, cx.tcx()); render_attributes_in_code(buffer, it, cx);
write!( write!(
buffer, buffer,
"{vis}static {mutability}{name}: {typ}", "{vis}static {mutability}{name}: {typ}",
@ -1861,7 +1860,7 @@ fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item,
fn item_foreign_type(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) { fn item_foreign_type(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) {
wrap_item(w, |buffer| { wrap_item(w, |buffer| {
buffer.write_str("extern {\n").unwrap(); buffer.write_str("extern {\n").unwrap();
render_attributes_in_code(buffer, it, cx.tcx()); render_attributes_in_code(buffer, it, cx);
write!( write!(
buffer, buffer,
" {}type {};\n}}", " {}type {};\n}}",

View File

@ -18,6 +18,7 @@ use rustdoc_json_types::*;
use crate::clean::{self, ItemId}; use crate::clean::{self, ItemId};
use crate::formats::item_type::ItemType; use crate::formats::item_type::ItemType;
use crate::formats::FormatRenderer;
use crate::json::JsonRenderer; use crate::json::JsonRenderer;
use crate::passes::collect_intra_doc_links::UrlFragment; use crate::passes::collect_intra_doc_links::UrlFragment;
@ -41,7 +42,7 @@ impl JsonRenderer<'_> {
}) })
.collect(); .collect();
let docs = item.opt_doc_value(); let docs = item.opt_doc_value();
let attrs = item.attributes(self.tcx, true); let attrs = item.attributes(self.tcx, self.cache(), true);
let span = item.span(self.tcx); let span = item.span(self.tcx);
let visibility = item.visibility(self.tcx); let visibility = item.visibility(self.tcx);
let clean::Item { name, item_id, .. } = item; let clean::Item { name, item_id, .. } = item;

View File

@ -0,0 +1,7 @@
// aux-crate:attributes=attributes.rs
// edition:2021
#![crate_name = "user"]
// @has 'user/struct.NonExhaustive.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[non_exhaustive]'
pub use attributes::NonExhaustive;

View File

@ -0,0 +1,2 @@
#[non_exhaustive]
pub struct NonExhaustive;

View File

@ -10,7 +10,7 @@ pub struct ReprSimd {
} }
#[repr(transparent)] #[repr(transparent)]
pub struct ReprTransparent { pub struct ReprTransparent {
field: u8, pub field: u8,
} }
#[repr(isize)] #[repr(isize)]
pub enum ReprIsize { pub enum ReprIsize {
@ -20,3 +20,23 @@ pub enum ReprIsize {
pub enum ReprU8 { pub enum ReprU8 {
Bla, Bla,
} }
#[repr(transparent)] // private
pub struct ReprTransparentPrivField {
field: u32, // non-1-ZST field
}
#[repr(transparent)] // public
pub struct ReprTransparentPriv1ZstFields {
marker0: Marker,
pub main: u64, // non-1-ZST field
marker1: Marker,
}
#[repr(transparent)] // private
pub struct ReprTransparentPrivFieldPub1ZstFields {
main: [u16; 0], // non-1-ZST field
pub marker: Marker,
}
pub struct Marker; // 1-ZST

View File

@ -9,21 +9,32 @@ extern crate repr;
// @has 'foo/struct.ReprC.html' // @has 'foo/struct.ReprC.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C, align(8))]' // @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C, align(8))]'
#[doc(inline)]
pub use repr::ReprC; pub use repr::ReprC;
// @has 'foo/struct.ReprSimd.html' // @has 'foo/struct.ReprSimd.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(simd, packed(2))]' // @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(simd, packed(2))]'
#[doc(inline)]
pub use repr::ReprSimd; pub use repr::ReprSimd;
// @has 'foo/struct.ReprTransparent.html' // @has 'foo/struct.ReprTransparent.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]' // @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[doc(inline)]
pub use repr::ReprTransparent; pub use repr::ReprTransparent;
// @has 'foo/enum.ReprIsize.html' // @has 'foo/enum.ReprIsize.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(isize)]' // @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(isize)]'
#[doc(inline)]
pub use repr::ReprIsize; pub use repr::ReprIsize;
// @has 'foo/enum.ReprU8.html' // @has 'foo/enum.ReprU8.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u8)]' // @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u8)]'
#[doc(inline)]
pub use repr::ReprU8; pub use repr::ReprU8;
// Regression test for <https://github.com/rust-lang/rust/issues/90435>.
// Check that we show `#[repr(transparent)]` iff the non-1-ZST field is public or at least one
// field is public in case all fields are 1-ZST fields.
// @has 'foo/struct.ReprTransparentPrivField.html'
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
pub use repr::ReprTransparentPrivField;
// @has 'foo/struct.ReprTransparentPriv1ZstFields.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
pub use repr::ReprTransparentPriv1ZstFields;
// @has 'foo/struct.ReprTransparentPrivFieldPub1ZstFields.html'
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
pub use repr::ReprTransparentPrivFieldPub1ZstFields;

29
tests/rustdoc/repr.rs Normal file
View File

@ -0,0 +1,29 @@
// Regression test for <https://github.com/rust-lang/rust/issues/90435>.
// Check that we show `#[repr(transparent)]` iff the non-1-ZST field is public or at least one
// field is public in case all fields are 1-ZST fields.
// @has 'repr/struct.ReprTransparentPrivField.html'
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // private
pub struct ReprTransparentPrivField {
field: u32, // non-1-ZST field
}
// @has 'repr/struct.ReprTransparentPriv1ZstFields.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // public
pub struct ReprTransparentPriv1ZstFields {
marker0: Marker,
pub main: u64, // non-1-ZST field
marker1: Marker,
}
// @has 'repr/struct.ReprTransparentPub1ZstField.html'
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
#[repr(transparent)] // public
pub struct ReprTransparentPub1ZstField {
marker0: Marker,
pub marker1: Marker,
}
struct Marker; // 1-ZST