From 2c35abe37c1e8fb51fa15892f59aaa248a768960 Mon Sep 17 00:00:00 2001 From: Urgau Date: Tue, 15 Aug 2023 14:13:17 +0200 Subject: [PATCH] rustdoc: show inner enum and struct in type definition for concrete type --- src/librustdoc/clean/inline.rs | 3 + src/librustdoc/clean/mod.rs | 116 ++++++++++++++++++++++- src/librustdoc/clean/types.rs | 21 ++++ src/librustdoc/html/render/print_item.rs | 94 ++++++++++++++++++ src/librustdoc/json/conversions.rs | 2 +- tests/rustdoc/typedef-inner-variants.rs | 80 ++++++++++++++++ 6 files changed, 311 insertions(+), 5 deletions(-) create mode 100644 tests/rustdoc/typedef-inner-variants.rs diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index cc86a3d74757..25f303d8a52e 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( } } +fn clean_ty_alias_inner_type<'tcx>( + ty: Ty<'tcx>, + cx: &mut DocContext<'tcx>, +) -> Option { + 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, @@ -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 9cf3c068b602..6efe41797071 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -2229,10 +2229,31 @@ pub(crate) struct PathSegment { pub(crate) args: GenericArgs, } +#[derive(Clone, Debug)] +pub(crate) enum TypeAliasInnerType { + Enum { + variants: IndexVec, + has_stripped_variants: bool, + is_non_exhaustive: bool, + }, + Union { + fields: Vec, + has_stripped_fields: bool, + }, + Struct { + ctor_kind: Option, + fields: Vec, + 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`, + /// to be shown directly on the typedef page. + pub(crate) inner_type: Option, /// `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 007c1cf1ef91..09be756f26d2 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: &mut W, f: F) + where + W: fmt::Write, + F: FnOnce(&mut W), + { + write!( + w, + "
\ + \ + Show Aliased Type\ + ", + ) + .unwrap(); + f(w); + write!(w, "
").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 66b5798797f6..406c7218f98c 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> for TypeAlias { fn from_tcx(type_alias: Box, 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 000000000000..e0c5b7dd98ff --- /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 { + /// 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 = IrTyKind; + +// @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; + +// @has 'inner_variants/union.OneOr.html' +pub union OneOr { + 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; + +// @has 'inner_variants/struct.One.html' +pub struct One { + 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; + +// @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 { + 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 = HighlyGenericStruct;