Rollup merge of #140863 - GuillaumeGomez:cleanup-tyalias-render, r=lolbinarycat

[rustdoc] Unify type aliases rendering with other ADT

Fixes #140739.

Better reviewed one commit at a time.

Just one thing I'm wondering: should we also render non-`repr` attributes? If so, I wonder if we shouldn't simply change `clean::TypeAlias` to contain the other ADT directly (`Struct`, `Enum` and `Union`) and remove the `TypeAlias::generics` field.

Can be done in a follow-up too.

cc ``@camelid``
r? ``@notriddle``
This commit is contained in:
Jacob Pratt 2025-05-26 03:38:16 +02:00 committed by GitHub
commit 6257d2fb1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 381 additions and 219 deletions

View file

@ -610,6 +610,9 @@ impl Item {
UnionItem(ref union_) => Some(union_.has_stripped_entries()),
EnumItem(ref enum_) => Some(enum_.has_stripped_entries()),
VariantItem(ref v) => v.has_stripped_entries(),
TypeAliasItem(ref type_alias) => {
type_alias.inner_type.as_ref().and_then(|t| t.has_stripped_entries())
}
_ => None,
}
}
@ -761,14 +764,11 @@ impl Item {
Some(tcx.visibility(def_id))
}
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Vec<String> {
pub(crate) fn attributes_without_repr(&self, tcx: TyCtxt<'_>, is_json: bool) -> Vec<String> {
const ALLOWED_ATTRIBUTES: &[Symbol] =
&[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
use rustc_abi::IntegerType;
let mut attrs: Vec<String> = self
.attrs
self.attrs
.other_attrs
.iter()
.filter_map(|attr| {
@ -796,74 +796,28 @@ impl Item {
None
}
})
.collect();
.collect()
}
// Add #[repr(...)]
if let Some(def_id) = self.def_id()
&& let ItemType::Struct | ItemType::Enum | ItemType::Union = self.type_()
{
let adt = tcx.adt_def(def_id);
let repr = adt.repr();
let mut out = Vec::new();
if repr.c() {
out.push("C");
}
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 = is_json
|| cache.document_private
|| adt
.all_fields()
.find(|field| {
let ty =
field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
tcx.layout_of(
ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(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(),
);
pub(crate) fn attributes_and_repr(
&self,
tcx: TyCtxt<'_>,
cache: &Cache,
is_json: bool,
) -> Vec<String> {
let mut attrs = self.attributes_without_repr(tcx, is_json);
if render_transparent {
out.push("transparent");
}
}
if repr.simd() {
out.push("simd");
}
let pack_s;
if let Some(pack) = repr.pack {
pack_s = format!("packed({})", pack.bytes());
out.push(&pack_s);
}
let align_s;
if let Some(align) = repr.align {
align_s = format!("align({})", align.bytes());
out.push(&align_s);
}
let int_s;
if let Some(int) = repr.int {
int_s = match int {
IntegerType::Pointer(is_signed) => {
format!("{}size", if is_signed { 'i' } else { 'u' })
}
IntegerType::Fixed(size, is_signed) => {
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
}
};
out.push(&int_s);
}
if !out.is_empty() {
attrs.push(format!("#[repr({})]", out.join(", ")));
}
if let Some(repr_attr) = self.repr(tcx, cache, is_json) {
attrs.push(repr_attr);
}
attrs
}
/// Returns a stringified `#[repr(...)]` attribute.
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> {
repr_attributes(tcx, cache, self.def_id()?, self.type_(), is_json)
}
pub fn is_doc_hidden(&self) -> bool {
self.attrs.is_doc_hidden()
}
@ -873,6 +827,73 @@ impl Item {
}
}
pub(crate) fn repr_attributes(
tcx: TyCtxt<'_>,
cache: &Cache,
def_id: DefId,
item_type: ItemType,
is_json: bool,
) -> Option<String> {
use rustc_abi::IntegerType;
if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) {
return None;
}
let adt = tcx.adt_def(def_id);
let repr = adt.repr();
let mut out = Vec::new();
if repr.c() {
out.push("C");
}
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
|| is_json
|| adt
.all_fields()
.find(|field| {
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(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");
}
}
if repr.simd() {
out.push("simd");
}
let pack_s;
if let Some(pack) = repr.pack {
pack_s = format!("packed({})", pack.bytes());
out.push(&pack_s);
}
let align_s;
if let Some(align) = repr.align {
align_s = format!("align({})", align.bytes());
out.push(&align_s);
}
let int_s;
if let Some(int) = repr.int {
int_s = match int {
IntegerType::Pointer(is_signed) => {
format!("{}size", if is_signed { 'i' } else { 'u' })
}
IntegerType::Fixed(size, is_signed) => {
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
}
};
out.push(&int_s);
}
if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
}
#[derive(Clone, Debug)]
pub(crate) enum ItemKind {
ExternCrateItem {
@ -2107,7 +2128,7 @@ impl Enum {
self.variants.iter().any(|f| f.is_stripped())
}
pub(crate) fn variants(&self) -> impl Iterator<Item = &Item> {
pub(crate) fn non_stripped_variants(&self) -> impl Iterator<Item = &Item> {
self.variants.iter().filter(|v| !v.is_stripped())
}
}
@ -2345,6 +2366,17 @@ pub(crate) enum TypeAliasInnerType {
Struct { ctor_kind: Option<CtorKind>, fields: Vec<Item> },
}
impl TypeAliasInnerType {
fn has_stripped_entries(&self) -> Option<bool> {
Some(match self {
Self::Enum { variants, .. } => variants.iter().any(|v| v.is_stripped()),
Self::Union { fields } | Self::Struct { fields, .. } => {
fields.iter().any(|f| f.is_stripped())
}
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct TypeAlias {
pub(crate) type_: Type,

View file

@ -1194,18 +1194,36 @@ fn render_assoc_item(
// a whitespace prefix and newline.
fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display {
fmt::from_fn(move |f| {
for a in it.attributes(cx.tcx(), cx.cache(), false) {
for a in it.attributes_and_repr(cx.tcx(), cx.cache(), false) {
writeln!(f, "{prefix}{a}")?;
}
Ok(())
})
}
struct CodeAttribute(String);
fn render_code_attribute(code_attr: CodeAttribute, w: &mut impl fmt::Write) {
write!(w, "<div class=\"code-attribute\">{}</div>", code_attr.0).unwrap();
}
// When an attribute is rendered inside a <code> tag, it is formatted using
// a div to produce a newline after it.
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
for attr in it.attributes(cx.tcx(), cx.cache(), false) {
write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
for attr in it.attributes_and_repr(cx.tcx(), cx.cache(), false) {
render_code_attribute(CodeAttribute(attr), w);
}
}
/// used for type aliases to only render their `repr` attribute.
fn render_repr_attributes_in_code(
w: &mut impl fmt::Write,
cx: &Context<'_>,
def_id: DefId,
item_type: ItemType,
) {
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type, false) {
render_code_attribute(CodeAttribute(repr), w);
}
}

View file

@ -20,7 +20,7 @@ use super::{
collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
render_impl, render_rightside, render_stability_since_raw,
render_impl, render_repr_attributes_in_code, render_rightside, render_stability_since_raw,
render_stability_since_raw_with_extra, write_section_heading,
};
use crate::clean;
@ -1278,94 +1278,58 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) ->
match inner_type {
clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive } => {
let variants_iter = || variants.iter().filter(|i| !i.is_stripped());
let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
let enum_def_id = ty.ty_adt_def().unwrap().did();
wrap_item(w, |w| {
let variants_len = variants.len();
let variants_count = variants_iter().count();
let has_stripped_entries = variants_len != variants_count;
write!(
w,
"enum {}{}{}",
it.name.unwrap(),
t.generics.print(cx),
render_enum_fields(
cx,
Some(&t.generics),
variants,
variants_count,
has_stripped_entries,
*is_non_exhaustive,
enum_def_id,
)
)
})?;
write!(w, "{}", item_variants(cx, it, variants, enum_def_id))?;
DisplayEnum {
variants,
generics: &t.generics,
is_non_exhaustive: *is_non_exhaustive,
def_id: enum_def_id,
}
.render_into(cx, it, true, w)?;
}
clean::TypeAliasInnerType::Union { fields } => {
wrap_item(w, |w| {
let fields_count = fields.iter().filter(|i| !i.is_stripped()).count();
let has_stripped_fields = fields.len() != fields_count;
let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
let union_def_id = ty.ty_adt_def().unwrap().did();
write!(
w,
"union {}{}{}",
it.name.unwrap(),
t.generics.print(cx),
render_struct_fields(
Some(&t.generics),
None,
fields,
"",
true,
has_stripped_fields,
cx,
),
)
})?;
write!(w, "{}", item_fields(cx, it, fields, None))?;
ItemUnion {
cx,
it,
fields,
generics: &t.generics,
is_type_alias: true,
def_id: union_def_id,
}
.render_into(w)?;
}
clean::TypeAliasInnerType::Struct { ctor_kind, fields } => {
wrap_item(w, |w| {
let fields_count = fields.iter().filter(|i| !i.is_stripped()).count();
let has_stripped_fields = fields.len() != fields_count;
let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
let struct_def_id = ty.ty_adt_def().unwrap().did();
write!(
w,
"struct {}{}{}",
it.name.unwrap(),
t.generics.print(cx),
render_struct_fields(
Some(&t.generics),
*ctor_kind,
fields,
"",
true,
has_stripped_fields,
cx,
),
)
})?;
write!(w, "{}", item_fields(cx, it, fields, None))?;
DisplayStruct {
ctor_kind: *ctor_kind,
generics: &t.generics,
fields,
def_id: struct_def_id,
}
.render_into(cx, it, true, w)?;
}
}
} else {
let def_id = it.item_id.expect_def_id();
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
write!(
w,
"{}{}",
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id)
)?;
}
let def_id = it.item_id.expect_def_id();
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
write!(
w,
"{}{}",
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id)
)?;
// [RUSTDOCIMPL] type.impl
//
// Include type definitions from the alias target type.
@ -1463,50 +1427,83 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) ->
})
}
fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt::Display {
item_template!(
#[template(path = "item_union.html")]
struct ItemUnion<'a, 'cx> {
cx: &'a Context<'cx>,
it: &'a clean::Item,
s: &'a clean::Union,
},
methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items]
);
item_template!(
#[template(path = "item_union.html")]
struct ItemUnion<'a, 'cx> {
cx: &'a Context<'cx>,
it: &'a clean::Item,
fields: &'a [clean::Item],
generics: &'a clean::Generics,
is_type_alias: bool,
def_id: DefId,
},
methods = [document, document_type_layout, render_assoc_items]
);
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
fn render_union(&self) -> impl Display {
render_union(self.it, Some(&self.s.generics), &self.s.fields, self.cx)
}
fn document_field(&self, field: &'a clean::Item) -> impl Display {
document(self.cx, field, Some(self.it), HeadingOffset::H3)
}
fn stability_field(&self, field: &clean::Item) -> Option<String> {
field.stability_class(self.cx.tcx())
}
fn print_ty(&self, ty: &'a clean::Type) -> impl Display {
ty.print(self.cx)
}
fn fields_iter(
&self,
) -> iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>> {
self.s
.fields
.iter()
.filter_map(|f| match f.kind {
clean::StructFieldItem(ref ty) => Some((f, ty)),
_ => None,
})
.peekable()
}
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
fn render_union(&self) -> impl Display {
render_union(self.it, Some(&self.generics), &self.fields, self.cx)
}
fn document_field(&self, field: &'a clean::Item) -> impl Display {
document(self.cx, field, Some(self.it), HeadingOffset::H3)
}
fn stability_field(&self, field: &clean::Item) -> Option<String> {
field.stability_class(self.cx.tcx())
}
fn print_ty(&self, ty: &'a clean::Type) -> impl Display {
ty.print(self.cx)
}
// FIXME (GuillaumeGomez): When <https://github.com/askama-rs/askama/issues/452> is implemented,
// we can replace the returned value with:
//
// `iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>>`
//
// And update `item_union.html`.
fn fields_iter(&self) -> impl Iterator<Item = (&'a clean::Item, &'a clean::Type)> {
self.fields.iter().filter_map(|f| match f.kind {
clean::StructFieldItem(ref ty) => Some((f, ty)),
_ => None,
})
}
fn render_attributes_in_pre(&self) -> impl fmt::Display {
fmt::from_fn(move |f| {
if self.is_type_alias {
// For now the only attributes we render for type aliases are `repr` attributes.
if let Some(repr) = clean::repr_attributes(
self.cx.tcx(),
self.cx.cache(),
self.def_id,
ItemType::Union,
false,
) {
writeln!(f, "{repr}")?;
};
} else {
for a in self.it.attributes_and_repr(self.cx.tcx(), self.cx.cache(), false) {
writeln!(f, "{a}")?;
}
}
Ok(())
})
}
}
fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt::Display {
fmt::from_fn(|w| {
ItemUnion { cx, it, s }.render_into(w).unwrap();
ItemUnion {
cx,
it,
fields: &s.fields,
generics: &s.generics,
is_type_alias: false,
def_id: it.def_id().unwrap(),
}
.render_into(w)?;
Ok(())
})
}
@ -1533,41 +1530,81 @@ fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Displa
})
}
fn item_enum(cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) -> impl fmt::Display {
fmt::from_fn(|w| {
let count_variants = e.variants().count();
struct DisplayEnum<'clean> {
variants: &'clean IndexVec<VariantIdx, clean::Item>,
generics: &'clean clean::Generics,
is_non_exhaustive: bool,
def_id: DefId,
}
impl<'clean> DisplayEnum<'clean> {
fn render_into<W: fmt::Write>(
self,
cx: &Context<'_>,
it: &clean::Item,
is_type_alias: bool,
w: &mut W,
) -> fmt::Result {
let non_stripped_variant_count = self.variants.iter().filter(|i| !i.is_stripped()).count();
let variants_len = self.variants.len();
let has_stripped_entries = variants_len != non_stripped_variant_count;
wrap_item(w, |w| {
render_attributes_in_code(w, it, cx);
if is_type_alias {
// For now the only attributes we render for type aliases are `repr` attributes.
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum);
} else {
render_attributes_in_code(w, it, cx);
}
write!(
w,
"{}enum {}{}{}",
visibility_print_with_space(it, cx),
it.name.unwrap(),
e.generics.print(cx),
self.generics.print(cx),
render_enum_fields(
cx,
Some(&e.generics),
&e.variants,
count_variants,
e.has_stripped_entries(),
it.is_non_exhaustive(),
it.def_id().unwrap(),
Some(self.generics),
self.variants,
non_stripped_variant_count,
has_stripped_entries,
self.is_non_exhaustive,
self.def_id,
),
)
})?;
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
if count_variants != 0 {
write!(w, "{}", item_variants(cx, it, &e.variants, it.def_id().unwrap()))?;
}
let def_id = it.item_id.expect_def_id();
let layout_def_id = if is_type_alias {
self.def_id
} else {
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
// We don't return the same `DefId` since the layout size of the type alias might be
// different since we might have more information on the generics.
def_id
};
if non_stripped_variant_count != 0 {
write!(w, "{}", item_variants(cx, it, self.variants, self.def_id))?;
}
write!(
w,
"{}{}",
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id)
document_type_layout(cx, layout_def_id)
)
}
}
fn item_enum(cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) -> impl fmt::Display {
fmt::from_fn(|w| {
DisplayEnum {
variants: &e.variants,
generics: &e.generics,
is_non_exhaustive: it.is_non_exhaustive(),
def_id: it.def_id().unwrap(),
}
.render_into(cx, it, false, w)
})
}
@ -1955,27 +1992,59 @@ fn item_constant(
})
}
fn item_struct(cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) -> impl fmt::Display {
fmt::from_fn(|w| {
struct DisplayStruct<'a> {
ctor_kind: Option<CtorKind>,
generics: &'a clean::Generics,
fields: &'a [clean::Item],
def_id: DefId,
}
impl<'a> DisplayStruct<'a> {
fn render_into<W: fmt::Write>(
self,
cx: &Context<'_>,
it: &clean::Item,
is_type_alias: bool,
w: &mut W,
) -> fmt::Result {
wrap_item(w, |w| {
render_attributes_in_code(w, it, cx);
if is_type_alias {
// For now the only attributes we render for type aliases are `repr` attributes.
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct);
} else {
render_attributes_in_code(w, it, cx);
}
write!(
w,
"{}",
render_struct(it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx)
render_struct(it, Some(self.generics), self.ctor_kind, self.fields, "", true, cx)
)
})?;
let def_id = it.item_id.expect_def_id();
if !is_type_alias {
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
}
let def_id = it.item_id.expect_def_id();
write!(
w,
"{}{}{}{}",
document(cx, it, None, HeadingOffset::H2),
item_fields(cx, it, &s.fields, s.ctor_kind),
"{}{}{}",
item_fields(cx, it, self.fields, self.ctor_kind),
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id),
)
}
}
fn item_struct(cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) -> impl fmt::Display {
fmt::from_fn(|w| {
DisplayStruct {
ctor_kind: s.ctor_kind,
generics: &s.generics,
fields: s.fields.as_slice(),
def_id: it.def_id().unwrap(),
}
.render_into(cx, it, false, w)
})
}

View file

@ -599,7 +599,7 @@ fn sidebar_enum<'a>(
deref_id_map: &'a DefIdMap<String>,
) {
let mut variants = e
.variants()
.non_stripped_variants()
.filter_map(|v| v.name)
.map(|name| Link::new(format!("variant.{name}"), name.to_string()))
.collect::<Vec<_>>();

View file

@ -2,15 +2,16 @@
{{ self.render_attributes_in_pre()|safe }}
{{ self.render_union()|safe }}
</code></pre>
{{ self.document()|safe }}
{% if self.fields_iter().peek().is_some() %}
{% if !self.is_type_alias %}
{{ self.document()|safe }}
{% endif %}
{% if self.fields_iter().next().is_some() %}
<h2 id="fields" class="fields section-header"> {# #}
Fields<a href="#fields" class="anchor">§</a> {# #}
</h2>
{% for (field, ty) in self.fields_iter() %}
{% let name = field.name.expect("union field name") %}
<span id="structfield.{{ name }}" {#+ #}
class="{{ ItemType::StructField +}} section-header"> {# #}
<span id="structfield.{{ name }}" class="{{ ItemType::StructField +}} section-header"> {# #}
<a href="#structfield.{{ name }}" class="anchor field">§</a> {# #}
<code>{{ name }}: {{+ self.print_ty(ty)|safe }}</code> {# #}
</span>

View file

@ -40,7 +40,7 @@ impl JsonRenderer<'_> {
})
.collect();
let docs = item.opt_doc_value();
let attrs = item.attributes(self.tcx, self.cache(), true);
let attrs = item.attributes_and_repr(self.tcx, self.cache(), true);
let span = item.span(self.tcx);
let visibility = item.visibility(self.tcx);
let clean::ItemInner { name, item_id, .. } = *item.inner;

View file

@ -0,0 +1,42 @@
// This test ensures that the `repr` attribute is displayed in type aliases.
//
// Regression test for <https://github.com/rust-lang/rust/issues/140739>.
#![crate_name = "foo"]
/// bla
#[repr(C)]
pub struct Foo1;
//@ has 'foo/type.Bar1.html'
//@ has - '//*[@class="rust item-decl"]/code' '#[repr(C)]pub struct Bar1;'
// Ensures that we see the doc comment of the type alias and not of the aliased type.
//@ has - '//*[@class="toggle top-doc"]/*[@class="docblock"]' 'bar'
/// bar
pub type Bar1 = Foo1;
/// bla
#[repr(C)]
pub union Foo2 {
pub a: u8,
}
//@ has 'foo/type.Bar2.html'
//@ matches - '//*[@class="rust item-decl"]' '#\[repr\(C\)\]\npub union Bar2 \{*'
// Ensures that we see the doc comment of the type alias and not of the aliased type.
//@ has - '//*[@class="toggle top-doc"]/*[@class="docblock"]' 'bar'
/// bar
pub type Bar2 = Foo2;
/// bla
#[repr(C)]
pub enum Foo3 {
A,
}
//@ has 'foo/type.Bar3.html'
//@ matches - '//*[@class="rust item-decl"]' '#\[repr\(C\)\]pub enum Bar3 \{*'
// Ensures that we see the doc comment of the type alias and not of the aliased type.
//@ has - '//*[@class="toggle top-doc"]/*[@class="docblock"]' 'bar'
/// bar
pub type Bar3 = Foo3;

View file

@ -61,7 +61,7 @@ pub type TypeAlias = X;
pub type GenericTypeAlias = (Generic<(u32, ())>, Generic<u32>);
// Regression test for the rustdoc equivalent of #85103.
//@ hasraw type_layout/type.Edges.html 'Encountered an error during type layout; the type failed to be normalized.'
//@ hasraw type_layout/type.Edges.html 'Unable to compute type layout, possibly due to this type having generic parameters. Layout can only be computed for concrete, fully-instantiated types.'
pub type Edges<'a, E> = std::borrow::Cow<'a, [E]>;
//@ !hasraw type_layout/trait.MyTrait.html 'Size: '