about summary refs log tree commit diff
diff options
context:
space:
mode:
authorUrgau <urgau@numericable.fr>2023-08-15 14:13:17 +0200
committerUrgau <urgau@numericable.fr>2023-08-26 00:14:49 +0200
commit2c35abe37c1e8fb51fa15892f59aaa248a768960 (patch)
tree4f648f16650a78357bb3baec52b99db1ff751ea6
parent08cdb40219cc8ac2c60e24f1242ac5157c1436ee (diff)
downloadrust-2c35abe37c1e8fb51fa15892f59aaa248a768960.tar.gz
rust-2c35abe37c1e8fb51fa15892f59aaa248a768960.zip
rustdoc: show inner enum and struct in type definition for concrete type
-rw-r--r--src/librustdoc/clean/inline.rs3
-rw-r--r--src/librustdoc/clean/mod.rs116
-rw-r--r--src/librustdoc/clean/types.rs21
-rw-r--r--src/librustdoc/html/render/print_item.rs94
-rw-r--r--src/librustdoc/json/conversions.rs2
-rw-r--r--tests/rustdoc/typedef-inner-variants.rs80
6 files changed, 311 insertions, 5 deletions
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index cc86a3d7475..25f303d8a52 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -299,6 +299,9 @@ fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::TypeAlias
     Box::new(clean::TypeAlias {
         type_,
         generics: clean_ty_generics(cx, cx.tcx.generics_of(did), predicates),
+        // FIXME: Figure-out how to handle inner_type with
+        // clean_ty_typedef_inner_type
+        inner_type: None,
         item_type: None,
     })
 }
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 1e85284371d..d0d92a1116f 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -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,46 @@ 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();
+
+        let has_stripped_variants = adt_def.variants().len() != variants.len();
+        TypeAliasInnerType::Enum {
+            variants,
+            has_stripped_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();
+
+        let has_stripped_fields = variant.fields.len() != fields.len();
+        if adt_def.is_struct() {
+            TypeAliasInnerType::Struct { ctor_kind: variant.ctor_kind(), fields, has_stripped_fields }
+        } else {
+            TypeAliasInnerType::Union { fields, has_stripped_fields }
+        }
+    })
+}
+
 fn clean_proc_macro<'tcx>(
     item: &hir::Item<'tcx>,
     name: &mut Symbol,
@@ -1222,6 +1263,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 +1306,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 +1518,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
                                 None,
                             ),
                             generics,
+                            inner_type: None,
                             item_type: None,
                         }),
                         bounds,
@@ -1490,6 +1538,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
@@ -2350,6 +2399,60 @@ 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,
+    };
+
+    let kind = match variant.ctor_kind() {
+        Some(CtorKind::Const) => VariantKind::CLike,
+        Some(CtorKind::Fn) => VariantKind::Tuple(
+            variant
+                .fields
+                .iter()
+                .filter(|field| field.vis.is_public())
+                .map(|field| {
+                    let ty = cx.tcx.type_of(field.did).instantiate(cx.tcx, args);
+                    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()
+                .filter(|field| field.vis.is_public())
+                .map(|field| {
+                    let ty = cx.tcx.type_of(field.did).instantiate(cx.tcx, args);
+                    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>,
@@ -2604,7 +2707,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,
@@ -2617,10 +2720,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 {
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 9cf3c068b60..6efe4179707 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -2230,9 +2230,30 @@ pub(crate) struct PathSegment {
 }
 
 #[derive(Clone, Debug)]
+pub(crate) enum TypeAliasInnerType {
+    Enum {
+        variants: IndexVec<VariantIdx, Item>,
+        has_stripped_variants: bool,
+        is_non_exhaustive: bool,
+    },
+    Union {
+        fields: Vec<Item>,
+        has_stripped_fields: bool,
+    },
+    Struct {
+        ctor_kind: Option<CtorKind>,
+        fields: Vec<Item>,
+        has_stripped_fields: bool,
+    },
+}
+
+#[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.
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 007c1cf1ef9..09be756f26d 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1237,6 +1237,100 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c
 
     write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
 
+    // Only show inner variants if:
+    //  - the typealias does NOT have any generics (modulo lifetimes)
+    //  - AND the aliased type has some generics
+    //
+    // Otherwise, showing a non-generic type is rendurant with its own page, or
+    // if it still has some generics, it's not as useful.
+    let should_print_inner_type = t
+        .generics
+        .params
+        .iter()
+        .all(|param| matches!(param.kind, clean::GenericParamDefKind::Lifetime { .. }))
+        && t.generics.where_predicates.is_empty()
+        && t.type_.generics().is_some_and(|generics| !generics.is_empty());
+
+    if should_print_inner_type {
+        fn toggle<W, F>(w: &mut W, f: F)
+        where
+            W: fmt::Write,
+            F: FnOnce(&mut W),
+        {
+            write!(
+                w,
+                "<details class=\"toggle\">\
+                    <summary>\
+                        <span>Show Aliased Type</span>\
+                    </summary>",
+            )
+            .unwrap();
+            f(w);
+            write!(w, "</details>").unwrap();
+        }
+
+        match &t.inner_type {
+            Some(clean::TypeAliasInnerType::Enum {
+                variants,
+                has_stripped_variants: has_stripped_entries,
+                is_non_exhaustive,
+            }) => {
+                toggle(w, |w| {
+                    wrap_item(w, |w| {
+                        write!(w, "enum {}{}", it.name.unwrap(), t.generics.print(cx));
+                        render_enum_fields(
+                            w,
+                            cx,
+                            None,
+                            variants.iter(),
+                            variants.len(),
+                            *has_stripped_entries,
+                            *is_non_exhaustive,
+                        )
+                    });
+                    item_variants(w, cx, it, variants.iter());
+                });
+            }
+            Some(clean::TypeAliasInnerType::Union { fields, has_stripped_fields }) => {
+                toggle(w, |w| {
+                    wrap_item(w, |w| {
+                        write!(w, "union {}{}", it.name.unwrap(), t.generics.print(cx));
+                        render_struct_fields(
+                            w,
+                            None,
+                            None,
+                            fields,
+                            "",
+                            true,
+                            *has_stripped_fields,
+                            cx,
+                        );
+                    });
+                    item_fields(w, cx, it, fields, None);
+                });
+            }
+            Some(clean::TypeAliasInnerType::Struct { ctor_kind, fields, has_stripped_fields }) => {
+                toggle(w, |w| {
+                    wrap_item(w, |w| {
+                        write!(w, "struct {}{}", it.name.unwrap(), t.generics.print(cx));
+                        render_struct_fields(
+                            w,
+                            None,
+                            *ctor_kind,
+                            fields,
+                            "",
+                            true,
+                            *has_stripped_fields,
+                            cx,
+                        );
+                    });
+                    item_fields(w, cx, it, fields, None);
+                });
+            }
+            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
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 66b5798797f..406c7218f98 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -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) }
     }
 }
diff --git a/tests/rustdoc/typedef-inner-variants.rs b/tests/rustdoc/typedef-inner-variants.rs
new file mode 100644
index 00000000000..e0c5b7dd98f
--- /dev/null
+++ b/tests/rustdoc/typedef-inner-variants.rs
@@ -0,0 +1,80 @@
+#![crate_name = "inner_variants"]
+
+pub struct Adt;
+pub struct 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, B> {
+    /// Doc comment for AdtKind
+    AdtKind(A),
+    /// and another one for TyKind
+    TyKind(A, B),
+    // no comment
+    StructKind { a: A, },
+}
+
+// @has 'inner_variants/type.NearlyTyKind.html'
+// @count - '//*[@id="variants"]' 0
+// @count - '//*[@id="fields"]' 0
+pub type NearlyTyKind<B> = IrTyKind<Adt, B>;
+
+// @has 'inner_variants/type.TyKind.html'
+// @count - '//*[@id="variants"]' 1
+// @count - '//*[@id="fields"]' 0
+// @count - '//*[@class="variant"]' 3
+// @matches - '//details[@class="toggle"]//pre[@class="rust item-decl"]//code' "enum TyKind"
+pub type TyKind = IrTyKind<Adt, Ty>;
+
+// @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="variants"]' 0
+// @count - '//*[@id="fields"]' 1
+// @count - '//*[@class="structfield small-section-header"]' 2
+// @matches - '//details[@class="toggle"]//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,
+    __hidden: T,
+}
+
+// @has 'inner_variants/type.OneU64.html'
+// @count - '//*[@id="variants"]' 0
+// @count - '//*[@id="fields"]' 1
+// @count - '//*[@class="structfield small-section-header"]' 1
+// @matches - '//details[@class="toggle"]//pre[@class="rust item-decl"]//code' "struct OneU64"
+pub type OneU64 = One<u64>;
+
+// @has 'inner_variants/struct.OnceA.html'
+pub struct OnceA<'a, A> {
+    a: &'a A, // private
+}
+
+// @has 'inner_variants/type.Once.html'
+// @count - '//*[@id="variants"]' 0
+// @count - '//*[@id="fields"]' 0
+// @matches - '//details[@class="toggle"]//pre[@class="rust item-decl"]//code' "struct Once<'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)
+}
+
+// VERIFY that we NOT show the Aliased Type
+// @has 'inner_variants/type.HighlyGenericAABB.html'
+// @count - '//details[@class="toggle"]' 0
+// @count - '//*[@id="variants"]' 0
+// @count - '//*[@id="fields"]' 0
+pub type HighlyGenericAABB<A, B> = HighlyGenericStruct<A, A, B, B>;