Auto merge of #114855 - Urgau:rustdoc-typedef-inner-variants, r=GuillaumeGomez

rustdoc: show inner enum and struct in type definition for concrete type

This PR implements the [Display enum variants for generic enum in type def page](https://rust-lang.zulipchat.com/#narrow/stream/266220-rustdoc/topic/Display.20enum.20variants.20for.20generic.20enum.20in.20type.20def.20page) #rustdoc/zulip proposal.

This proposal comes from looking at [`TyKind`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/sty/type.TyKind.html) typedef from the compiler. On that page, the documentation is able to show the layout for each variant, but not the variants themselves. This proposal suggests showing the fields and variants for those "concrete type". This would mean that instead of having many unresolved generics, like in `IrTyKind`:
```rust
    Array(I::Ty, I::Const),
    Slice(I::Ty),
    RawPtr(I::TypeAndMut),
    Ref(I::Region, I::Ty, I::Mutability),
    FnDef(I::DefId, I::GenericArgsRef),
```
those would be resolved with direct links to the proper types in the `TyKind` typedef page:
```rust
    Array(Ty<'tcx>, Const<'tcx>),
    Slice(Ty<'tcx>),
    RawPtr(TypeAndMut<'tcx>),
    Ref(Region<'tcx>, Ty<'tcx>, Mutability<'tcx>),
    FnDef(DefId<'tcx>, GenericArgsRef<'tcx>),
```
Saving both time and confusion.

-----

<details>

<summary>Old description</summary>

I've chosen to add the enums and structs under the "Show Aliased Type" details, as well as showing the variants and fields under the usual "Variants" and "Fields" sections. ~~*under new the `Inner Variants` and `Inner Fields` sections (except for their names, they are identical to the one found in the enum, struct and union pages). Those sections are complementary and do not replace anything else.*~~

This PR proposes the following condition for showing the aliased type (basically, has the aliased type some generics that are all of them resolved):
 - the typedef does NOT have any generics (modulo lifetimes)
 - AND the aliased type has some generics

</details>

### Examples

```rust
pub enum IrTyKind<'a, I: Interner> {
    /// Doc comment for AdtKind
    AdtKind(&'a I::Adt),
    /// and another one for TyKind
    TyKind(I::Adt, I::Ty),
    // no comment
    StructKind { a: I::Adt, },
}

pub type TyKind<'a> = IrTyKind<'a, TyCtxt>;
```
![TyKind](https://github.com/rust-lang/rust/assets/3616612/13307679-6d48-40d6-ad50-6db0b7f36ac7)

<details>
<summary>Old</summary>

![image](https://github.com/rust-lang/rust/assets/3616612/4147c049-d056-42d4-8a01-d43ebe747308)

![TyKind](https://user-images.githubusercontent.com/3616612/260988247-34831aa9-470d-4286-ad9f-3e8002153a92.png)

![TyKind](https://github.com/rust-lang/rust/assets/3616612/62381bb3-fa0f-4b05-926d-77759cf9115a)

</details>

```rust
pub struct One<T> {
    pub val: T,
    #[doc(hidden)]
    pub inner_tag: u64,
    __hidden: T,
}

/// `One` with `u64` as payload
pub type OneU64 = One<u64>;
```
![OneU64](https://github.com/rust-lang/rust/assets/3616612/d551b474-ce88-4f8c-bc94-5c88aba51424)

<details>
<summary>Old</summary>

![image](https://github.com/rust-lang/rust/assets/3616612/1a3f53c0-17bf-4aa7-894d-3fedc15b33da)

![OneU64](https://github.com/rust-lang/rust/assets/3616612/7b124a5b-e287-4efb-b9ca-fdcd1cdeeba8)

![OneU64](https://github.com/rust-lang/rust/assets/3616612/ddd962be-4f76-4ecd-81bd-531f3dd23832)

</details>

r? `@GuillaumeGomez`
This commit is contained in:
bors 2023-09-07 16:23:03 +00:00
commit 70c7e4d21c
12 changed files with 649 additions and 159 deletions

View File

@ -38,6 +38,22 @@ followed by a list of fields or variants for Rust types.
Finally, the page lists associated functions and trait implementations,
including automatic and blanket implementations that `rustdoc` knows about.
### Sections
<!-- FIXME: Implementations -->
<!-- FIXME: Trait Implementations -->
<!-- FIXME: Implementors -->
<!-- FIXME: Auto Trait Implementations -->
#### Aliased Type
A type alias is expanded at compile time to its
[aliased type](https://doc.rust-lang.org/reference/items/type-aliases.html).
That may involve substituting some or all of the type parameters in the target
type with types provided by the type alias definition. The Aliased Type section
shows the result of this expansion, including the types of public fields or
variants, which may depend on those substitutions.
### Navigation
Subheadings, variants, fields, and many other things in this documentation

View File

@ -20,7 +20,8 @@ use rustc_span::symbol::{kw, sym, Symbol};
use crate::clean::{
self, clean_fn_decl_from_did_and_sig, clean_generics, clean_impl_item, clean_middle_assoc_item,
clean_middle_field, clean_middle_ty, clean_trait_ref_with_bindings, clean_ty,
clean_ty_generics, clean_variant_def, utils, Attributes, AttributesExt, ImplKind, ItemId, Type,
clean_ty_alias_inner_type, clean_ty_generics, clean_variant_def, utils, Attributes,
AttributesExt, ImplKind, ItemId, Type,
};
use crate::core::DocContext;
use crate::formats::item_type::ItemType;
@ -289,16 +290,14 @@ fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union {
fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::TypeAlias> {
let predicates = cx.tcx.explicit_predicates_of(did);
let type_ = clean_middle_ty(
ty::Binder::dummy(cx.tcx.type_of(did).instantiate_identity()),
cx,
Some(did),
None,
);
let ty = cx.tcx.type_of(did).instantiate_identity();
let type_ = clean_middle_ty(ty::Binder::dummy(ty), cx, Some(did), None);
let inner_type = clean_ty_alias_inner_type(ty, cx);
Box::new(clean::TypeAlias {
type_,
generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates),
inner_type,
item_type: None,
})
}

View File

@ -24,6 +24,7 @@ use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData};
use rustc_middle::metadata::Reexport;
use rustc_middle::middle::resolve_bound_vars as rbv;
use rustc_middle::ty::fold::TypeFolder;
use rustc_middle::ty::GenericArgsRef;
use rustc_middle::ty::TypeVisitableExt;
use rustc_middle::ty::{self, AdtKind, EarlyBinder, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
@ -955,6 +956,43 @@ fn clean_ty_generics<'tcx>(
}
}
fn clean_ty_alias_inner_type<'tcx>(
ty: Ty<'tcx>,
cx: &mut DocContext<'tcx>,
) -> Option<TypeAliasInnerType> {
let ty::Adt(adt_def, args) = ty.kind() else {
return None;
};
Some(if adt_def.is_enum() {
let variants: rustc_index::IndexVec<_, _> = adt_def
.variants()
.iter()
.map(|variant| clean_variant_def_with_args(variant, args, cx))
.collect();
TypeAliasInnerType::Enum {
variants,
is_non_exhaustive: adt_def.is_variant_list_non_exhaustive(),
}
} else {
let variant = adt_def
.variants()
.iter()
.next()
.unwrap_or_else(|| bug!("a struct or union should always have one variant def"));
let fields: Vec<_> =
clean_variant_def_with_args(variant, args, cx).kind.inner_items().cloned().collect();
if adt_def.is_struct() {
TypeAliasInnerType::Struct { ctor_kind: variant.ctor_kind(), fields }
} else {
TypeAliasInnerType::Union { fields }
}
})
}
fn clean_proc_macro<'tcx>(
item: &hir::Item<'tcx>,
name: &mut Symbol,
@ -1222,6 +1260,7 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext
Box::new(TypeAlias {
type_: clean_ty(default, cx),
generics,
inner_type: None,
item_type: Some(item_type),
}),
bounds,
@ -1264,7 +1303,12 @@ pub(crate) fn clean_impl_item<'tcx>(
None,
);
AssocTypeItem(
Box::new(TypeAlias { type_, generics, item_type: Some(item_type) }),
Box::new(TypeAlias {
type_,
generics,
inner_type: None,
item_type: Some(item_type),
}),
Vec::new(),
)
}
@ -1471,6 +1515,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
None,
),
generics,
inner_type: None,
item_type: None,
}),
bounds,
@ -1490,6 +1535,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
None,
),
generics,
inner_type: None,
item_type: None,
}),
// Associated types inside trait or inherent impls are not allowed to have
@ -2363,6 +2409,83 @@ pub(crate) fn clean_variant_def<'tcx>(variant: &ty::VariantDef, cx: &mut DocCont
)
}
pub(crate) fn clean_variant_def_with_args<'tcx>(
variant: &ty::VariantDef,
args: &GenericArgsRef<'tcx>,
cx: &mut DocContext<'tcx>,
) -> Item {
let discriminant = match variant.discr {
ty::VariantDiscr::Explicit(def_id) => Some(Discriminant { expr: None, value: def_id }),
ty::VariantDiscr::Relative(_) => None,
};
use rustc_middle::traits::ObligationCause;
use rustc_trait_selection::infer::TyCtxtInferExt;
use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
let infcx = cx.tcx.infer_ctxt().build();
let kind = match variant.ctor_kind() {
Some(CtorKind::Const) => VariantKind::CLike,
Some(CtorKind::Fn) => VariantKind::Tuple(
variant
.fields
.iter()
.map(|field| {
let ty = cx.tcx.type_of(field.did).instantiate(cx.tcx, args);
// normalize the type to only show concrete types
// note: we do not use try_normalize_erasing_regions since we
// do care about showing the regions
let ty = infcx
.at(&ObligationCause::dummy(), cx.param_env)
.query_normalize(ty)
.map(|normalized| normalized.value)
.unwrap_or(ty);
clean_field_with_def_id(
field.did,
field.name,
clean_middle_ty(ty::Binder::dummy(ty), cx, Some(field.did), None),
cx,
)
})
.collect(),
),
None => VariantKind::Struct(VariantStruct {
fields: variant
.fields
.iter()
.map(|field| {
let ty = cx.tcx.type_of(field.did).instantiate(cx.tcx, args);
// normalize the type to only show concrete types
// note: we do not use try_normalize_erasing_regions since we
// do care about showing the regions
let ty = infcx
.at(&ObligationCause::dummy(), cx.param_env)
.query_normalize(ty)
.map(|normalized| normalized.value)
.unwrap_or(ty);
clean_field_with_def_id(
field.did,
field.name,
clean_middle_ty(ty::Binder::dummy(ty), cx, Some(field.did), None),
cx,
)
})
.collect(),
}),
};
Item::from_def_id_and_parts(
variant.def_id,
Some(variant.name),
VariantItem(Variant { kind, discriminant }),
cx,
)
}
fn clean_variant_data<'tcx>(
variant: &hir::VariantData<'tcx>,
disr_expr: &Option<hir::AnonConst>,
@ -2617,7 +2740,7 @@ fn clean_maybe_renamed_item<'tcx>(
ItemKind::TyAlias(hir_ty, generics) => {
*cx.current_type_aliases.entry(def_id).or_insert(0) += 1;
let rustdoc_ty = clean_ty(hir_ty, cx);
let ty = clean_middle_ty(
let type_ = clean_middle_ty(
ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)),
cx,
None,
@ -2630,10 +2753,15 @@ fn clean_maybe_renamed_item<'tcx>(
cx.current_type_aliases.remove(&def_id);
}
}
let ty = cx.tcx.type_of(def_id).instantiate_identity();
let inner_type = clean_ty_alias_inner_type(ty, cx);
TypeAliasItem(Box::new(TypeAlias {
type_: rustdoc_ty,
generics,
item_type: Some(ty),
inner_type,
type_: rustdoc_ty,
item_type: Some(type_),
}))
}
ItemKind::Enum(ref def, generics) => EnumItem(Enum {

View File

@ -2230,10 +2230,20 @@ pub(crate) struct PathSegment {
pub(crate) args: GenericArgs,
}
#[derive(Clone, Debug)]
pub(crate) enum TypeAliasInnerType {
Enum { variants: IndexVec<VariantIdx, Item>, is_non_exhaustive: bool },
Union { fields: Vec<Item> },
Struct { ctor_kind: Option<CtorKind>, fields: Vec<Item> },
}
#[derive(Clone, Debug)]
pub(crate) struct TypeAlias {
pub(crate) type_: Type,
pub(crate) generics: Generics,
/// Inner `AdtDef` type, ie `type TyKind = IrTyKind<Adt, Ty>`,
/// to be shown directly on the typedef page.
pub(crate) inner_type: Option<TypeAliasInnerType>,
/// `type_` can come from either the HIR or from metadata. If it comes from HIR, it may be a type
/// alias instead of the final type. This will always have the final type, regardless of whether
/// `type_` came from HIR or from metadata.

View File

@ -52,10 +52,31 @@ pub(crate) trait DocFolder: Sized {
VariantItem(Variant { kind, discriminant })
}
TypeAliasItem(mut typealias) => {
typealias.inner_type = typealias.inner_type.map(|inner_type| match inner_type {
TypeAliasInnerType::Enum { variants, is_non_exhaustive } => {
let variants = variants
.into_iter_enumerated()
.filter_map(|(_, x)| self.fold_item(x))
.collect();
TypeAliasInnerType::Enum { variants, is_non_exhaustive }
}
TypeAliasInnerType::Union { fields } => {
let fields = fields.into_iter().filter_map(|x| self.fold_item(x)).collect();
TypeAliasInnerType::Union { fields }
}
TypeAliasInnerType::Struct { ctor_kind, fields } => {
let fields = fields.into_iter().filter_map(|x| self.fold_item(x)).collect();
TypeAliasInnerType::Struct { ctor_kind, fields }
}
});
TypeAliasItem(typealias)
}
ExternCrateItem { src: _ }
| ImportItem(_)
| FunctionItem(_)
| TypeAliasItem(_)
| OpaqueTyItem(_)
| StaticItem(_)
| ConstantItem(_)

View File

@ -457,6 +457,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
| clean::StructItem(..)
| clean::UnionItem(..)
| clean::VariantItem(..)
| clean::TypeAliasItem(..)
| clean::ImplItem(..) => {
self.cache.parent_stack.push(ParentStackItem::new(&item));
(self.fold_item_recur(item), true)

View File

@ -1237,6 +1237,75 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
if let Some(inner_type) = &t.inner_type {
write!(
w,
"<h2 id=\"aliased-type\" class=\"small-section-header\">\
Aliased Type<a href=\"#aliased-type\" class=\"anchor\">§</a></h2>"
);
match inner_type {
clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive } => {
let variants_iter = || variants.iter().filter(|i| !i.is_stripped());
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(
w,
cx,
Some(&t.generics),
variants_iter(),
variants_count,
has_stripped_entries,
*is_non_exhaustive,
)
});
item_variants(w, cx, it, variants_iter());
}
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;
write!(w, "union {}{}", it.name.unwrap(), t.generics.print(cx));
render_struct_fields(
w,
Some(&t.generics),
None,
fields,
"",
true,
has_stripped_fields,
cx,
);
});
item_fields(w, cx, it, fields, None);
}
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;
write!(w, "struct {}{}", it.name.unwrap(), t.generics.print(cx));
render_struct_fields(
w,
Some(&t.generics),
*ctor_kind,
fields,
"",
true,
has_stripped_fields,
cx,
);
});
item_fields(w, cx, it, fields, None);
}
}
}
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
@ -1332,7 +1401,7 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>(
fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) {
let tcx = cx.tcx();
let count_variants = e.variants().count();
wrap_item(w, |mut w| {
wrap_item(w, |w| {
render_attributes_in_code(w, it, tcx);
write!(
w,
@ -1341,150 +1410,181 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
it.name.unwrap(),
e.generics.print(cx),
);
if !print_where_clause_and_check(w, &e.generics, cx) {
// If there wasn't a `where` clause, we add a whitespace.
w.write_str(" ");
}
let variants_stripped = e.has_stripped_entries();
if count_variants == 0 && !variants_stripped {
w.write_str("{}");
} else {
w.write_str("{\n");
let toggle = should_hide_fields(count_variants);
if toggle {
toggle_open(&mut w, format_args!("{count_variants} variants"));
}
for v in e.variants() {
w.write_str(" ");
let name = v.name.unwrap();
match *v.kind {
// FIXME(#101337): Show discriminant
clean::VariantItem(ref var) => match var.kind {
clean::VariantKind::CLike => w.write_str(name.as_str()),
clean::VariantKind::Tuple(ref s) => {
write!(w, "{name}({})", print_tuple_struct_fields(cx, s),);
}
clean::VariantKind::Struct(ref s) => {
render_struct(w, v, None, None, &s.fields, " ", false, cx);
}
},
_ => unreachable!(),
}
w.write_str(",\n");
}
if variants_stripped && !it.is_non_exhaustive() {
w.write_str(" // some variants omitted\n");
}
if toggle {
toggle_close(&mut w);
}
w.write_str("}");
}
render_enum_fields(
w,
cx,
Some(&e.generics),
e.variants(),
count_variants,
e.has_stripped_entries(),
it.is_non_exhaustive(),
);
});
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
if count_variants != 0 {
write!(
w,
"<h2 id=\"variants\" class=\"variants small-section-header\">\
Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
</h2>\
{}\
<div class=\"variants\">",
document_non_exhaustive_header(it),
document_non_exhaustive(it)
);
for variant in e.variants() {
let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap()));
write!(
w,
"<section id=\"{id}\" class=\"variant\">\
<a href=\"#{id}\" class=\"anchor\">§</a>",
);
render_stability_since_raw_with_extra(
w,
variant.stable_since(tcx),
variant.const_stability(tcx),
it.stable_since(tcx),
it.const_stable_since(tcx),
" rightside",
);
write!(w, "<h3 class=\"code-header\">{name}", name = variant.name.unwrap());
let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() };
if let clean::VariantKind::Tuple(ref s) = variant_data.kind {
write!(w, "({})", print_tuple_struct_fields(cx, s));
}
w.write_str("</h3></section>");
let heading_and_fields = match &variant_data.kind {
clean::VariantKind::Struct(s) => Some(("Fields", &s.fields)),
clean::VariantKind::Tuple(fields) => {
// Documentation on tuple variant fields is rare, so to reduce noise we only emit
// the section if at least one field is documented.
if fields.iter().any(|f| !f.doc_value().is_empty()) {
Some(("Tuple Fields", fields))
} else {
None
}
}
clean::VariantKind::CLike => None,
};
if let Some((heading, fields)) = heading_and_fields {
let variant_id =
cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap()));
write!(
w,
"<div class=\"sub-variant\" id=\"{variant_id}\">\
<h4>{heading}</h4>\
{}",
document_non_exhaustive(variant)
);
for field in fields {
match *field.kind {
clean::StrippedItem(box clean::StructFieldItem(_)) => {}
clean::StructFieldItem(ref ty) => {
let id = cx.derive_id(format!(
"variant.{}.field.{}",
variant.name.unwrap(),
field.name.unwrap()
));
write!(
w,
"<div class=\"sub-variant-field\">\
<span id=\"{id}\" class=\"small-section-header\">\
<a href=\"#{id}\" class=\"anchor field\">§</a>\
<code>{f}: {t}</code>\
</span>",
f = field.name.unwrap(),
t = ty.print(cx),
);
write!(
w,
"{}</div>",
document(cx, field, Some(variant), HeadingOffset::H5)
);
}
_ => unreachable!(),
}
}
w.write_str("</div>");
}
write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4));
}
write!(w, "</div>");
item_variants(w, cx, it, e.variants());
}
let def_id = it.item_id.expect_def_id();
write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
write!(w, "{}", document_type_layout(cx, def_id));
}
fn render_enum_fields<'a>(
mut w: &mut Buffer,
cx: &mut Context<'_>,
g: Option<&clean::Generics>,
variants: impl Iterator<Item = &'a clean::Item>,
count_variants: usize,
has_stripped_entries: bool,
is_non_exhaustive: bool,
) {
if !g.is_some_and(|g| print_where_clause_and_check(w, g, cx)) {
// If there wasn't a `where` clause, we add a whitespace.
w.write_str(" ");
}
let variants_stripped = has_stripped_entries;
if count_variants == 0 && !variants_stripped {
w.write_str("{}");
} else {
w.write_str("{\n");
let toggle = should_hide_fields(count_variants);
if toggle {
toggle_open(&mut w, format_args!("{count_variants} variants"));
}
const TAB: &str = " ";
for v in variants {
w.write_str(TAB);
let name = v.name.unwrap();
match *v.kind {
// FIXME(#101337): Show discriminant
clean::VariantItem(ref var) => match var.kind {
clean::VariantKind::CLike => w.write_str(name.as_str()),
clean::VariantKind::Tuple(ref s) => {
write!(w, "{name}({})", print_tuple_struct_fields(cx, s),);
}
clean::VariantKind::Struct(ref s) => {
render_struct(w, v, None, None, &s.fields, TAB, false, cx);
}
},
_ => unreachable!(),
}
w.write_str(",\n");
}
if variants_stripped && !is_non_exhaustive {
w.write_str(" // some variants omitted\n");
}
if toggle {
toggle_close(&mut w);
}
w.write_str("}");
}
}
fn item_variants<'a>(
w: &mut Buffer,
cx: &mut Context<'_>,
it: &clean::Item,
variants: impl Iterator<Item = &'a clean::Item>,
) {
let tcx = cx.tcx();
write!(
w,
"<h2 id=\"variants\" class=\"variants small-section-header\">\
Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
</h2>\
{}\
<div class=\"variants\">",
document_non_exhaustive_header(it),
document_non_exhaustive(it)
);
for variant in variants {
let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap()));
write!(
w,
"<section id=\"{id}\" class=\"variant\">\
<a href=\"#{id}\" class=\"anchor\">§</a>",
);
render_stability_since_raw_with_extra(
w,
variant.stable_since(tcx),
variant.const_stability(tcx),
it.stable_since(tcx),
it.const_stable_since(tcx),
" rightside",
);
write!(w, "<h3 class=\"code-header\">{name}", name = variant.name.unwrap());
let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() };
if let clean::VariantKind::Tuple(ref s) = variant_data.kind {
write!(w, "({})", print_tuple_struct_fields(cx, s));
}
w.write_str("</h3></section>");
let heading_and_fields = match &variant_data.kind {
clean::VariantKind::Struct(s) => Some(("Fields", &s.fields)),
clean::VariantKind::Tuple(fields) => {
// Documentation on tuple variant fields is rare, so to reduce noise we only emit
// the section if at least one field is documented.
if fields.iter().any(|f| !f.doc_value().is_empty()) {
Some(("Tuple Fields", fields))
} else {
None
}
}
clean::VariantKind::CLike => None,
};
if let Some((heading, fields)) = heading_and_fields {
let variant_id =
cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap()));
write!(
w,
"<div class=\"sub-variant\" id=\"{variant_id}\">\
<h4>{heading}</h4>\
{}",
document_non_exhaustive(variant)
);
for field in fields {
match *field.kind {
clean::StrippedItem(box clean::StructFieldItem(_)) => {}
clean::StructFieldItem(ref ty) => {
let id = cx.derive_id(format!(
"variant.{}.field.{}",
variant.name.unwrap(),
field.name.unwrap()
));
write!(
w,
"<div class=\"sub-variant-field\">\
<span id=\"{id}\" class=\"small-section-header\">\
<a href=\"#{id}\" class=\"anchor field\">§</a>\
<code>{f}: {t}</code>\
</span>",
f = field.name.unwrap(),
t = ty.print(cx),
);
write!(
w,
"{}</div>",
document(cx, field, Some(variant), HeadingOffset::H5)
);
}
_ => unreachable!(),
}
}
w.write_str("</div>");
}
write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4));
}
write!(w, "</div>");
}
fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Macro) {
highlight::render_item_decl_with_highlighting(&t.source, w);
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
@ -1593,15 +1693,28 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
let mut fields = s
.fields
item_fields(w, cx, it, &s.fields, s.ctor_kind);
let def_id = it.item_id.expect_def_id();
write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
write!(w, "{}", document_type_layout(cx, def_id));
}
fn item_fields(
w: &mut Buffer,
cx: &mut Context<'_>,
it: &clean::Item,
fields: &Vec<clean::Item>,
ctor_kind: Option<CtorKind>,
) {
let mut fields = fields
.iter()
.filter_map(|f| match *f.kind {
clean::StructFieldItem(ref ty) => Some((f, ty)),
_ => None,
})
.peekable();
if let None | Some(CtorKind::Fn) = s.ctor_kind {
if let None | Some(CtorKind::Fn) = ctor_kind {
if fields.peek().is_some() {
write!(
w,
@ -1609,7 +1722,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
{}{}<a href=\"#fields\" class=\"anchor\">§</a>\
</h2>\
{}",
if s.ctor_kind.is_none() { "Fields" } else { "Tuple Fields" },
if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" },
document_non_exhaustive_header(it),
document_non_exhaustive(it)
);
@ -1630,9 +1743,6 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
}
}
}
let def_id = it.item_id.expect_def_id();
write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
write!(w, "{}", document_type_layout(cx, def_id));
}
fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
@ -1871,7 +1981,7 @@ fn render_union<'a, 'cx: 'a>(
}
fn render_struct(
mut w: &mut Buffer,
w: &mut Buffer,
it: &clean::Item,
g: Option<&clean::Generics>,
ty: Option<CtorKind>,
@ -1891,6 +2001,29 @@ fn render_struct(
if let Some(g) = g {
write!(w, "{}", g.print(cx))
}
render_struct_fields(
w,
g,
ty,
fields,
tab,
structhead,
it.has_stripped_entries().unwrap_or(false),
cx,
)
}
fn render_struct_fields(
mut w: &mut Buffer,
g: Option<&clean::Generics>,
ty: Option<CtorKind>,
fields: &[clean::Item],
tab: &str,
structhead: bool,
has_stripped_entries: bool,
cx: &Context<'_>,
) {
let tcx = cx.tcx();
match ty {
None => {
let where_displayed =
@ -1922,11 +2055,11 @@ fn render_struct(
}
if has_visible_fields {
if it.has_stripped_entries().unwrap() {
if has_stripped_entries {
write!(w, "\n{tab} /* private fields */");
}
write!(w, "\n{tab}");
} else if it.has_stripped_entries().unwrap() {
} else if has_stripped_entries {
write!(w, " /* private fields */ ");
}
if toggle {

View File

@ -82,7 +82,7 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buf
clean::PrimitiveItem(_) => sidebar_primitive(cx, it),
clean::UnionItem(ref u) => sidebar_union(cx, it, u),
clean::EnumItem(ref e) => sidebar_enum(cx, it, e),
clean::TypeAliasItem(_) => sidebar_type_alias(cx, it),
clean::TypeAliasItem(ref t) => sidebar_type_alias(cx, it, t),
clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)],
clean::ForeignTypeItem => sidebar_foreign_type(cx, it),
_ => vec![],
@ -230,8 +230,32 @@ fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBl
}
}
fn sidebar_type_alias<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
fn sidebar_type_alias<'a>(
cx: &'a Context<'_>,
it: &'a clean::Item,
t: &'a clean::TypeAlias,
) -> Vec<LinkBlock<'a>> {
let mut items = vec![];
if let Some(inner_type) = &t.inner_type {
match inner_type {
clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive: _ } => {
let mut variants = variants
.iter()
.filter(|i| !i.is_stripped())
.filter_map(|v| v.name)
.map(|name| Link::new(format!("variant.{name}"), name.to_string()))
.collect::<Vec<_>>();
variants.sort_unstable();
items.push(LinkBlock::new(Link::new("variants", "Variants"), variants));
}
clean::TypeAliasInnerType::Union { fields }
| clean::TypeAliasInnerType::Struct { ctor_kind: _, fields } => {
let fields = get_struct_fields_name(fields);
items.push(LinkBlock::new(Link::new("fields", "Fields"), fields));
}
}
}
sidebar_assoc_items(cx, it, &mut items);
items
}

View File

@ -789,7 +789,7 @@ pub(crate) fn from_macro_kind(kind: rustc_span::hygiene::MacroKind) -> MacroKind
impl FromWithTcx<Box<clean::TypeAlias>> for TypeAlias {
fn from_tcx(type_alias: Box<clean::TypeAlias>, tcx: TyCtxt<'_>) -> Self {
let clean::TypeAlias { type_, generics, item_type: _ } = *type_alias;
let clean::TypeAlias { type_, generics, item_type: _, inner_type: _ } = *type_alias;
TypeAlias { type_: type_.into_tcx(tcx), generics: generics.into_tcx(tcx) }
}
}

View File

@ -0,0 +1,5 @@
pub struct InlineOne<A> {
pub inline: A
}
pub type InlineU64 = InlineOne<u64>;

View File

@ -0,0 +1,34 @@
#![crate_name = "inner_types_lazy"]
#![feature(lazy_type_alias)]
#![allow(incomplete_features)]
// @has 'inner_types_lazy/struct.Pair.html'
pub struct Pair<A, B> {
pub first: A,
pub second: B,
}
// @has 'inner_types_lazy/type.ReversedTypesPair.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 1
// @count - '//span[@class="where fmt-newline"]' 0
pub type ReversedTypesPair<Q, R> = Pair<R, Q>;
// @has 'inner_types_lazy/type.ReadWrite.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 1
// @count - '//span[@class="where fmt-newline"]' 2
pub type ReadWrite<R, W> = Pair<R, W>
where
R: std::io::Read,
W: std::io::Write;
// @has 'inner_types_lazy/type.VecPair.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 1
// @count - '//span[@class="where fmt-newline"]' 0
pub type VecPair<U, V> = Pair<Vec<U>, Vec<V>>;

View File

@ -0,0 +1,119 @@
// This test checks different combinations of structs, enums, and unions
// for the "Show Aliased Type" feature on type definition.
#![crate_name = "inner_variants"]
// aux-build:cross_crate_generic_typedef.rs
extern crate cross_crate_generic_typedef;
pub struct Adt;
pub struct Ty;
pub struct TyCtxt;
pub trait Interner {
type Adt;
type Ty;
}
impl Interner for TyCtxt {
type Adt = Adt;
type Ty = Ty;
}
// @has 'inner_variants/type.AliasTy.html'
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 0
pub type AliasTy = Ty;
// @has 'inner_variants/enum.IrTyKind.html'
pub enum IrTyKind<A, I: Interner> {
/// Doc comment for AdtKind
AdtKind(I::Adt),
/// and another one for TyKind
TyKind(I::Adt, <I as Interner>::Ty),
// no comment
StructKind { a: A, },
#[doc(hidden)]
Unspecified,
}
// @has 'inner_variants/type.NearlyTyKind.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 1
// @count - '//*[@id="fields"]' 0
pub type NearlyTyKind<A> = IrTyKind<A, TyCtxt>;
// @has 'inner_variants/type.TyKind.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 1
// @count - '//*[@id="fields"]' 0
// @count - '//*[@class="variant"]' 3
// @matches - '//pre[@class="rust item-decl"]//code' "enum TyKind"
// @has - '//pre[@class="rust item-decl"]//code/a[1]' "Adt"
// @has - '//pre[@class="rust item-decl"]//code/a[2]' "Adt"
// @has - '//pre[@class="rust item-decl"]//code/a[3]' "Ty"
// @has - '//pre[@class="rust item-decl"]//code/a[4]' "i64"
pub type TyKind = IrTyKind<i64, TyCtxt>;
// @has 'inner_variants/union.OneOr.html'
pub union OneOr<A: Copy> {
pub one: i64,
pub or: A,
}
// @has 'inner_variants/type.OneOrF64.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 1
// @count - '//*[@class="structfield small-section-header"]' 2
// @matches - '//pre[@class="rust item-decl"]//code' "union OneOrF64"
pub type OneOrF64 = OneOr<f64>;
// @has 'inner_variants/struct.One.html'
pub struct One<T> {
pub val: T,
#[doc(hidden)]
pub __hidden: T,
__private: T,
}
// @has 'inner_variants/type.OneU64.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 1
// @count - '//*[@class="structfield small-section-header"]' 1
// @matches - '//pre[@class="rust item-decl"]//code' "struct OneU64"
// @matches - '//pre[@class="rust item-decl"]//code' "pub val"
pub type OneU64 = One<u64>;
// @has 'inner_variants/struct.OnceA.html'
pub struct OnceA<'a, A> {
pub a: &'a A,
}
// @has 'inner_variants/type.Once.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 1
// @matches - '//pre[@class="rust item-decl"]//code' "struct Once<'a>"
// @matches - '//pre[@class="rust item-decl"]//code' "&'a"
pub type Once<'a> = OnceA<'a, i64>;
// @has 'inner_variants/struct.HighlyGenericStruct.html'
pub struct HighlyGenericStruct<A, B, C, D> {
pub z: (A, B, C, D)
}
// @has 'inner_variants/type.HighlyGenericAABB.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 1
// @matches - '//pre[@class="rust item-decl"]//code' "struct HighlyGenericAABB<A, B>"
// @matches - '//pre[@class="rust item-decl"]//code' "pub z"
pub type HighlyGenericAABB<A, B> = HighlyGenericStruct<A, A, B, B>;
// @has 'inner_variants/type.InlineU64.html'
// @count - '//*[@id="aliased-type"]' 1
// @count - '//*[@id="variants"]' 0
// @count - '//*[@id="fields"]' 1
pub use cross_crate_generic_typedef::InlineU64;