Skip to content

Commit 4dd4d9b

Browse files
authored
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
2 parents 0233608 + 64fa12a commit 4dd4d9b

File tree

10 files changed

+152
-42
lines changed

10 files changed

+152
-42
lines changed

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

+20
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,23 @@ https://doc.rust-lang.org/stable/std/?search=%s&go_to_first=true
110110

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

src/librustdoc/clean/types.rs

+36-15
Original file line numberDiff line numberDiff line change
@@ -713,12 +713,16 @@ impl Item {
713713
Some(tcx.visibility(def_id))
714714
}
715715

716-
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, keep_as_is: bool) -> Vec<String> {
716+
pub(crate) fn attributes(
717+
&self,
718+
tcx: TyCtxt<'_>,
719+
cache: &Cache,
720+
keep_as_is: bool,
721+
) -> Vec<String> {
717722
const ALLOWED_ATTRIBUTES: &[Symbol] =
718-
&[sym::export_name, sym::link_section, sym::no_mangle, sym::repr, sym::non_exhaustive];
723+
&[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
719724

720725
use rustc_abi::IntegerType;
721-
use rustc_middle::ty::ReprFlags;
722726

723727
let mut attrs: Vec<String> = self
724728
.attrs
@@ -739,20 +743,38 @@ impl Item {
739743
}
740744
})
741745
.collect();
742-
if let Some(def_id) = self.def_id() &&
743-
!def_id.is_local() &&
744-
// This check is needed because `adt_def` will panic if not a compatible type otherwise...
745-
matches!(self.type_(), ItemType::Struct | ItemType::Enum | ItemType::Union)
746+
if !keep_as_is
747+
&& let Some(def_id) = self.def_id()
748+
&& let ItemType::Struct | ItemType::Enum | ItemType::Union = self.type_()
746749
{
747-
let repr = tcx.adt_def(def_id).repr();
750+
let adt = tcx.adt_def(def_id);
751+
let repr = adt.repr();
748752
let mut out = Vec::new();
749-
if repr.flags.contains(ReprFlags::IS_C) {
753+
if repr.c() {
750754
out.push("C");
751755
}
752-
if repr.flags.contains(ReprFlags::IS_TRANSPARENT) {
753-
out.push("transparent");
756+
if repr.transparent() {
757+
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
758+
// field is public in case all fields are 1-ZST fields.
759+
let render_transparent = cache.document_private
760+
|| adt
761+
.all_fields()
762+
.find(|field| {
763+
let ty =
764+
field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
765+
tcx.layout_of(tcx.param_env(field.did).and(ty))
766+
.is_ok_and(|layout| !layout.is_1zst())
767+
})
768+
.map_or_else(
769+
|| adt.all_fields().any(|field| field.vis.is_public()),
770+
|field| field.vis.is_public(),
771+
);
772+
773+
if render_transparent {
774+
out.push("transparent");
775+
}
754776
}
755-
if repr.flags.contains(ReprFlags::IS_SIMD) {
777+
if repr.simd() {
756778
out.push("simd");
757779
}
758780
let pack_s;
@@ -777,10 +799,9 @@ impl Item {
777799
};
778800
out.push(&int_s);
779801
}
780-
if out.is_empty() {
781-
return Vec::new();
802+
if !out.is_empty() {
803+
attrs.push(format!("#[repr({})]", out.join(", ")));
782804
}
783-
attrs.push(format!("#[repr({})]", out.join(", ")));
784805
}
785806
attrs
786807
}

src/librustdoc/html/render/mod.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -868,10 +868,10 @@ fn assoc_method(
868868
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
869869
header_len += 4;
870870
let indent_str = " ";
871-
write!(w, "{}", render_attributes_in_pre(meth, indent_str, tcx));
871+
write!(w, "{}", render_attributes_in_pre(meth, indent_str, cx));
872872
(4, indent_str, Ending::NoNewline)
873873
} else {
874-
render_attributes_in_code(w, meth, tcx);
874+
render_attributes_in_code(w, meth, cx);
875875
(0, "", Ending::Newline)
876876
};
877877
w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
@@ -1047,13 +1047,13 @@ fn render_assoc_item(
10471047

10481048
// When an attribute is rendered inside a `<pre>` tag, it is formatted using
10491049
// a whitespace prefix and newline.
1050-
fn render_attributes_in_pre<'a, 'b: 'a>(
1050+
fn render_attributes_in_pre<'a, 'tcx: 'a>(
10511051
it: &'a clean::Item,
10521052
prefix: &'a str,
1053-
tcx: TyCtxt<'b>,
1054-
) -> impl fmt::Display + Captures<'a> + Captures<'b> {
1053+
cx: &'a Context<'tcx>,
1054+
) -> impl fmt::Display + Captures<'a> + Captures<'tcx> {
10551055
crate::html::format::display_fn(move |f| {
1056-
for a in it.attributes(tcx, false) {
1056+
for a in it.attributes(cx.tcx(), cx.cache(), false) {
10571057
writeln!(f, "{prefix}{a}")?;
10581058
}
10591059
Ok(())
@@ -1062,8 +1062,8 @@ fn render_attributes_in_pre<'a, 'b: 'a>(
10621062

10631063
// When an attribute is rendered inside a <code> tag, it is formatted using
10641064
// a div to produce a newline after it.
1065-
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, tcx: TyCtxt<'_>) {
1066-
for attr in it.attributes(tcx, false) {
1065+
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
1066+
for attr in it.attributes(cx.tcx(), cx.cache(), false) {
10671067
write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
10681068
}
10691069
}

src/librustdoc/html/render/print_item.rs

+11-12
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@ macro_rules! item_template_methods {
120120
fn render_attributes_in_pre<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
121121
display_fn(move |f| {
122122
let (item, cx) = self.item_and_mut_cx();
123-
let tcx = cx.tcx();
124-
let v = render_attributes_in_pre(item, "", tcx);
123+
let v = render_attributes_in_pre(item, "", &cx);
125124
write!(f, "{v}")
126125
})
127126
}
@@ -659,7 +658,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
659658
w,
660659
"{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \
661660
{name}{generics}{decl}{notable_traits}{where_clause}",
662-
attrs = render_attributes_in_pre(it, "", tcx),
661+
attrs = render_attributes_in_pre(it, "", cx),
663662
vis = visibility,
664663
constness = constness,
665664
asyncness = asyncness,
@@ -694,7 +693,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
694693
write!(
695694
w,
696695
"{attrs}{vis}{unsafety}{is_auto}trait {name}{generics}{bounds}",
697-
attrs = render_attributes_in_pre(it, "", tcx),
696+
attrs = render_attributes_in_pre(it, "", cx),
698697
vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
699698
unsafety = t.unsafety(tcx).print_with_space(),
700699
is_auto = if t.is_auto(tcx) { "auto " } else { "" },
@@ -1173,7 +1172,7 @@ fn item_trait_alias(
11731172
write!(
11741173
w,
11751174
"{attrs}trait {name}{generics}{where_b} = {bounds};",
1176-
attrs = render_attributes_in_pre(it, "", cx.tcx()),
1175+
attrs = render_attributes_in_pre(it, "", cx),
11771176
name = it.name.unwrap(),
11781177
generics = t.generics.print(cx),
11791178
where_b = print_where_clause(&t.generics, cx, 0, Ending::Newline),
@@ -1201,7 +1200,7 @@ fn item_opaque_ty(
12011200
write!(
12021201
w,
12031202
"{attrs}type {name}{generics}{where_clause} = impl {bounds};",
1204-
attrs = render_attributes_in_pre(it, "", cx.tcx()),
1203+
attrs = render_attributes_in_pre(it, "", cx),
12051204
name = it.name.unwrap(),
12061205
generics = t.generics.print(cx),
12071206
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
12261225
write!(
12271226
w,
12281227
"{attrs}{vis}type {name}{generics}{where_clause} = {type_};",
1229-
attrs = render_attributes_in_pre(it, "", cx.tcx()),
1228+
attrs = render_attributes_in_pre(it, "", cx),
12301229
vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx),
12311230
name = it.name.unwrap(),
12321231
generics = t.generics.print(cx),
@@ -1415,7 +1414,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
14151414
let tcx = cx.tcx();
14161415
let count_variants = e.variants().count();
14171416
wrap_item(w, |w| {
1418-
render_attributes_in_code(w, it, tcx);
1417+
render_attributes_in_code(w, it, cx);
14191418
write!(
14201419
w,
14211420
"{}enum {}{}",
@@ -1734,7 +1733,7 @@ fn item_primitive(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Ite
17341733
fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) {
17351734
wrap_item(w, |w| {
17361735
let tcx = cx.tcx();
1737-
render_attributes_in_code(w, it, tcx);
1736+
render_attributes_in_code(w, it, cx);
17381737

17391738
write!(
17401739
w,
@@ -1783,7 +1782,7 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle
17831782

17841783
fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) {
17851784
wrap_item(w, |w| {
1786-
render_attributes_in_code(w, it, cx.tcx());
1785+
render_attributes_in_code(w, it, cx);
17871786
render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx);
17881787
});
17891788

@@ -1843,7 +1842,7 @@ fn item_fields(
18431842

18441843
fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
18451844
wrap_item(w, |buffer| {
1846-
render_attributes_in_code(buffer, it, cx.tcx());
1845+
render_attributes_in_code(buffer, it, cx);
18471846
write!(
18481847
buffer,
18491848
"{vis}static {mutability}{name}: {typ}",
@@ -1861,7 +1860,7 @@ fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item,
18611860
fn item_foreign_type(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) {
18621861
wrap_item(w, |buffer| {
18631862
buffer.write_str("extern {\n").unwrap();
1864-
render_attributes_in_code(buffer, it, cx.tcx());
1863+
render_attributes_in_code(buffer, it, cx);
18651864
write!(
18661865
buffer,
18671866
" {}type {};\n}}",

src/librustdoc/json/conversions.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustdoc_json_types::*;
1818

1919
use crate::clean::{self, ItemId};
2020
use crate::formats::item_type::ItemType;
21+
use crate::formats::FormatRenderer;
2122
use crate::json::JsonRenderer;
2223
use crate::passes::collect_intra_doc_links::UrlFragment;
2324

@@ -41,7 +42,7 @@ impl JsonRenderer<'_> {
4142
})
4243
.collect();
4344
let docs = item.opt_doc_value();
44-
let attrs = item.attributes(self.tcx, true);
45+
let attrs = item.attributes(self.tcx, self.cache(), true);
4546
let span = item.span(self.tcx);
4647
let visibility = item.visibility(self.tcx);
4748
let clean::Item { name, item_id, .. } = item;
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// aux-crate:attributes=attributes.rs
2+
// edition:2021
3+
#![crate_name = "user"]
4+
5+
// @has 'user/struct.NonExhaustive.html'
6+
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[non_exhaustive]'
7+
pub use attributes::NonExhaustive;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#[non_exhaustive]
2+
pub struct NonExhaustive;

tests/rustdoc/inline_cross/auxiliary/repr.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub struct ReprSimd {
1010
}
1111
#[repr(transparent)]
1212
pub struct ReprTransparent {
13-
field: u8,
13+
pub field: u8,
1414
}
1515
#[repr(isize)]
1616
pub enum ReprIsize {
@@ -20,3 +20,23 @@ pub enum ReprIsize {
2020
pub enum ReprU8 {
2121
Bla,
2222
}
23+
24+
#[repr(transparent)] // private
25+
pub struct ReprTransparentPrivField {
26+
field: u32, // non-1-ZST field
27+
}
28+
29+
#[repr(transparent)] // public
30+
pub struct ReprTransparentPriv1ZstFields {
31+
marker0: Marker,
32+
pub main: u64, // non-1-ZST field
33+
marker1: Marker,
34+
}
35+
36+
#[repr(transparent)] // private
37+
pub struct ReprTransparentPrivFieldPub1ZstFields {
38+
main: [u16; 0], // non-1-ZST field
39+
pub marker: Marker,
40+
}
41+
42+
pub struct Marker; // 1-ZST

tests/rustdoc/inline_cross/repr.rs

+16-5
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,32 @@ extern crate repr;
99

1010
// @has 'foo/struct.ReprC.html'
1111
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(C, align(8))]'
12-
#[doc(inline)]
1312
pub use repr::ReprC;
1413
// @has 'foo/struct.ReprSimd.html'
1514
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(simd, packed(2))]'
16-
#[doc(inline)]
1715
pub use repr::ReprSimd;
1816
// @has 'foo/struct.ReprTransparent.html'
1917
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
20-
#[doc(inline)]
2118
pub use repr::ReprTransparent;
2219
// @has 'foo/enum.ReprIsize.html'
2320
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(isize)]'
24-
#[doc(inline)]
2521
pub use repr::ReprIsize;
2622
// @has 'foo/enum.ReprU8.html'
2723
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(u8)]'
28-
#[doc(inline)]
2924
pub use repr::ReprU8;
25+
26+
// Regression test for <https://github.com/rust-lang/rust/issues/90435>.
27+
// Check that we show `#[repr(transparent)]` iff the non-1-ZST field is public or at least one
28+
// field is public in case all fields are 1-ZST fields.
29+
30+
// @has 'foo/struct.ReprTransparentPrivField.html'
31+
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
32+
pub use repr::ReprTransparentPrivField;
33+
34+
// @has 'foo/struct.ReprTransparentPriv1ZstFields.html'
35+
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
36+
pub use repr::ReprTransparentPriv1ZstFields;
37+
38+
// @has 'foo/struct.ReprTransparentPrivFieldPub1ZstFields.html'
39+
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
40+
pub use repr::ReprTransparentPrivFieldPub1ZstFields;

tests/rustdoc/repr.rs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Regression test for <https://github.com/rust-lang/rust/issues/90435>.
2+
// Check that we show `#[repr(transparent)]` iff the non-1-ZST field is public or at least one
3+
// field is public in case all fields are 1-ZST fields.
4+
5+
// @has 'repr/struct.ReprTransparentPrivField.html'
6+
// @!has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
7+
#[repr(transparent)] // private
8+
pub struct ReprTransparentPrivField {
9+
field: u32, // non-1-ZST field
10+
}
11+
12+
// @has 'repr/struct.ReprTransparentPriv1ZstFields.html'
13+
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
14+
#[repr(transparent)] // public
15+
pub struct ReprTransparentPriv1ZstFields {
16+
marker0: Marker,
17+
pub main: u64, // non-1-ZST field
18+
marker1: Marker,
19+
}
20+
21+
// @has 'repr/struct.ReprTransparentPub1ZstField.html'
22+
// @has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[repr(transparent)]'
23+
#[repr(transparent)] // public
24+
pub struct ReprTransparentPub1ZstField {
25+
marker0: Marker,
26+
pub marker1: Marker,
27+
}
28+
29+
struct Marker; // 1-ZST

0 commit comments

Comments
 (0)