Skip to content

Commit

Permalink
Auto merge of #114855 - Urgau:rustdoc-typedef-inner-variants, r=Guill…
Browse files Browse the repository at this point in the history
…aumeGomez

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`
  • Loading branch information
bors committed Sep 7, 2023
2 parents f06b7c5 + 85e4b89 commit 70c7e4d
Show file tree
Hide file tree
Showing 12 changed files with 636 additions and 146 deletions.
16 changes: 16 additions & 0 deletions src/doc/rustdoc/src/how-to-read-rustdoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 6 additions & 7 deletions src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
})
}
Expand Down
136 changes: 132 additions & 4 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
)
}
Expand Down Expand Up @@ -1471,6 +1515,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
None,
),
generics,
inner_type: None,
item_type: None,
}),
bounds,
Expand All @@ -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
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 22 additions & 1 deletion src/librustdoc/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_)
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/formats/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 70c7e4d

Please sign in to comment.