Skip to content

Commit

Permalink
Auto merge of #94009 - compiler-errors:gat-rustdoc, r=GuillaumeGomez
Browse files Browse the repository at this point in the history
Support GATs in Rustdoc

Implements:
1. Rendering GATs in trait definitions and impl blocks
2. Rendering GATs in types (e.g. in the return type of a function)

Fixes #92341

This is my first rustdoc PR, so I have absolutely no idea how to produce tests for this. Advice from the rustdoc team would be wonderful!

I tested locally and things looked correct:
![image](https://user-images.githubusercontent.com/3674314/153988325-9732cbf3-0645-4e1a-9e64-ddfd93877b55.png)
  • Loading branch information
bors committed Mar 4, 2022
2 parents 40d3040 + 0e57a16 commit 6d76841
Show file tree
Hide file tree
Showing 15 changed files with 363 additions and 176 deletions.
6 changes: 3 additions & 3 deletions src/librustdoc/clean/auto_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,11 +546,11 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
}
WherePredicate::EqPredicate { lhs, rhs } => {
match lhs {
Type::QPath { name: left_name, ref self_type, ref trait_, .. } => {
Type::QPath { ref assoc, ref self_type, ref trait_, .. } => {
let ty = &*self_type;
let mut new_trait = trait_.clone();

if self.is_fn_trait(trait_) && left_name == sym::Output {
if self.is_fn_trait(trait_) && assoc.name == sym::Output {
ty_to_fn
.entry(*ty.clone())
.and_modify(|e| {
Expand All @@ -571,7 +571,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
// to 'T: Iterator<Item=u8>'
GenericArgs::AngleBracketed { ref mut bindings, .. } => {
bindings.push(TypeBinding {
name: left_name,
assoc: *assoc.clone(),
kind: TypeBindingKind::Equality { term: rhs },
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean:

g.where_predicates.retain(|pred| match pred {
clean::WherePredicate::BoundPredicate {
ty: clean::QPath { self_type: box clean::Generic(ref s), trait_, name: _, .. },
ty: clean::QPath { self_type: box clean::Generic(ref s), trait_, .. },
bounds,
..
} => !(bounds.is_empty() || *s == kw::SelfUpper && trait_.def_id() == trait_did),
Expand Down
161 changes: 117 additions & 44 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,14 +388,35 @@ impl<'tcx> Clean<Type> for ty::ProjectionTy<'tcx> {
let trait_ = lifted.trait_ref(cx.tcx).clean(cx);
let self_type = self.self_ty().clean(cx);
Type::QPath {
name: cx.tcx.associated_item(self.item_def_id).name,
assoc: Box::new(projection_to_path_segment(*self, cx)),
self_def_id: self_type.def_id(&cx.cache),
self_type: box self_type,
trait_,
}
}
}

fn projection_to_path_segment(ty: ty::ProjectionTy<'_>, cx: &mut DocContext<'_>) -> PathSegment {
let item = cx.tcx.associated_item(ty.item_def_id);
let generics = cx.tcx.generics_of(ty.item_def_id);
PathSegment {
name: item.name,
args: GenericArgs::AngleBracketed {
args: ty.substs[generics.parent_count..]
.iter()
.map(|ty| match ty.unpack() {
ty::subst::GenericArgKind::Lifetime(lt) => {
GenericArg::Lifetime(lt.clean(cx).unwrap())
}
ty::subst::GenericArgKind::Type(ty) => GenericArg::Type(ty.clean(cx)),
ty::subst::GenericArgKind::Const(c) => GenericArg::Const(Box::new(c.clean(cx))),
})
.collect(),
bindings: Default::default(),
},
}
}

impl Clean<GenericParamDef> for ty::GenericParamDef {
fn clean(&self, cx: &mut DocContext<'_>) -> GenericParamDef {
let (name, kind) = match self.kind {
Expand Down Expand Up @@ -601,8 +622,8 @@ fn clean_ty_generics(
})
.collect::<Vec<GenericParamDef>>();

// param index -> [(DefId of trait, associated type name, type)]
let mut impl_trait_proj = FxHashMap::<u32, Vec<(DefId, Symbol, Ty<'_>)>>::default();
// param index -> [(DefId of trait, associated type name and generics, type)]
let mut impl_trait_proj = FxHashMap::<u32, Vec<(DefId, PathSegment, Ty<'_>)>>::default();

let where_predicates = preds
.predicates
Expand Down Expand Up @@ -648,8 +669,9 @@ fn clean_ty_generics(

let proj = projection
.map(|p| (p.skip_binder().projection_ty.clean(cx), p.skip_binder().term));
if let Some(((_, trait_did, name), rhs)) =
proj.as_ref().and_then(|(lhs, rhs)| Some((lhs.projection()?, rhs)))
if let Some(((_, trait_did, name), rhs)) = proj
.as_ref()
.and_then(|(lhs, rhs): &(Type, _)| Some((lhs.projection()?, rhs)))
{
// FIXME(...): Remove this unwrap()
impl_trait_proj.entry(param_idx).or_default().push((
Expand Down Expand Up @@ -992,9 +1014,10 @@ impl Clean<Item> for hir::TraitItem<'_> {
TyMethodItem(t)
}
hir::TraitItemKind::Type(bounds, ref default) => {
let generics = enter_impl_trait(cx, |cx| self.generics.clean(cx));
let bounds = bounds.iter().filter_map(|x| x.clean(cx)).collect();
let default = default.map(|t| t.clean(cx));
AssocTypeItem(bounds, default)
AssocTypeItem(Box::new(generics), bounds, default)
}
};
let what_rustc_thinks =
Expand Down Expand Up @@ -1026,15 +1049,9 @@ impl Clean<Item> for hir::ImplItem<'_> {
}
hir::ImplItemKind::TyAlias(ref hir_ty) => {
let type_ = hir_ty.clean(cx);
let generics = self.generics.clean(cx);
let item_type = hir_ty_to_ty(cx.tcx, hir_ty).clean(cx);
TypedefItem(
Typedef {
type_,
generics: Generics::default(),
item_type: Some(item_type),
},
true,
)
TypedefItem(Typedef { type_, generics, item_type: Some(item_type) }, true)
}
};

Expand Down Expand Up @@ -1140,35 +1157,79 @@ impl Clean<Item> for ty::AssocItem {
ty::AssocKind::Type => {
let my_name = self.name;

fn param_eq_arg(param: &GenericParamDef, arg: &GenericArg) -> bool {
match (&param.kind, arg) {
(GenericParamDefKind::Type { .. }, GenericArg::Type(Type::Generic(ty)))
if *ty == param.name =>
{
true
}
(
GenericParamDefKind::Lifetime { .. },
GenericArg::Lifetime(Lifetime(lt)),
) if *lt == param.name => true,
(GenericParamDefKind::Const { .. }, GenericArg::Const(c)) => {
match &c.kind {
ConstantKind::TyConst { expr } => expr == param.name.as_str(),
_ => false,
}
}
_ => false,
}
}

if let ty::TraitContainer(_) = self.container {
let bounds = tcx.explicit_item_bounds(self.def_id);
let predicates = ty::GenericPredicates { parent: None, predicates: bounds };
let generics = clean_ty_generics(cx, tcx.generics_of(self.def_id), predicates);
let mut generics =
clean_ty_generics(cx, tcx.generics_of(self.def_id), predicates);
// Filter out the bounds that are (likely?) directly attached to the associated type,
// as opposed to being located in the where clause.
let mut bounds = generics
.where_predicates
.iter()
.filter_map(|pred| {
let (name, self_type, trait_, bounds) = match *pred {
WherePredicate::BoundPredicate {
ty: QPath { ref name, ref self_type, ref trait_, .. },
ref bounds,
..
} => (name, self_type, trait_, bounds),
_ => return None,
};
if *name != my_name {
return None;
}
if trait_.def_id() != self.container.id() {
return None;
.drain_filter(|pred| match *pred {
WherePredicate::BoundPredicate {
ty: QPath { ref assoc, ref self_type, ref trait_, .. },
..
} => {
if assoc.name != my_name {
return false;
}
if trait_.def_id() != self.container.id() {
return false;
}
match **self_type {
Generic(ref s) if *s == kw::SelfUpper => {}
_ => return false,
}
match &assoc.args {
GenericArgs::AngleBracketed { args, bindings } => {
if !bindings.is_empty()
|| generics
.params
.iter()
.zip(args)
.any(|(param, arg)| !param_eq_arg(param, arg))
{
return false;
}
}
GenericArgs::Parenthesized { .. } => {
// The only time this happens is if we're inside the rustdoc for Fn(),
// which only has one associated type, which is not a GAT, so whatever.
}
}
true
}
match **self_type {
Generic(ref s) if *s == kw::SelfUpper => {}
_ => return None,
_ => false,
})
.flat_map(|pred| {
if let WherePredicate::BoundPredicate { bounds, .. } = pred {
bounds
} else {
unreachable!()
}
Some(bounds)
})
.flat_map(|i| i.iter().cloned())
.collect::<Vec<_>>();
// Our Sized/?Sized bound didn't get handled when creating the generics
// because we didn't actually get our whole set of bounds until just now
Expand All @@ -1188,7 +1249,7 @@ impl Clean<Item> for ty::AssocItem {
None
};

AssocTypeItem(bounds, ty.map(|t| t.clean(cx)))
AssocTypeItem(Box::new(generics), bounds, ty.map(|t| t.clean(cx)))
} else {
// FIXME: when could this happen? Associated items in inherent impls?
let type_ = tcx.type_of(self.def_id).clean(cx);
Expand Down Expand Up @@ -1259,7 +1320,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type {
};
register_res(cx, trait_.res);
Type::QPath {
name: p.segments.last().expect("segments were empty").ident.name,
assoc: Box::new(p.segments.last().expect("segments were empty").clean(cx)),
self_def_id: Some(DefId::local(qself.hir_id.owner.local_def_index)),
self_type: box qself.clean(cx),
trait_,
Expand All @@ -1276,7 +1337,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type {
let trait_ = hir::Path { span, res, segments: &[] }.clean(cx);
register_res(cx, trait_.res);
Type::QPath {
name: segment.ident.name,
assoc: Box::new(segment.clean(cx)),
self_def_id: res.opt_def_id(),
self_type: box qself.clean(cx),
trait_,
Expand Down Expand Up @@ -1548,7 +1609,16 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
let mut bindings = vec![];
for pb in obj.projection_bounds() {
bindings.push(TypeBinding {
name: cx.tcx.associated_item(pb.item_def_id()).name,
assoc: projection_to_path_segment(
pb.skip_binder()
.lift_to_tcx(cx.tcx)
.unwrap()
// HACK(compiler-errors): Doesn't actually matter what self
// type we put here, because we're only using the GAT's substs.
.with_self_ty(cx.tcx, cx.tcx.types.self_param)
.projection_ty,
cx,
),
kind: TypeBindingKind::Equality { term: pb.skip_binder().term.clean(cx) },
});
}
Expand Down Expand Up @@ -1614,10 +1684,10 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
== trait_ref.skip_binder()
{
Some(TypeBinding {
name: cx
.tcx
.associated_item(proj.projection_ty.item_def_id)
.name,
assoc: projection_to_path_segment(
proj.projection_ty,
cx,
),
kind: TypeBindingKind::Equality {
term: proj.term.clean(cx),
},
Expand Down Expand Up @@ -2160,7 +2230,10 @@ fn clean_maybe_renamed_foreign_item(

impl Clean<TypeBinding> for hir::TypeBinding<'_> {
fn clean(&self, cx: &mut DocContext<'_>) -> TypeBinding {
TypeBinding { name: self.ident.name, kind: self.kind.clean(cx) }
TypeBinding {
assoc: PathSegment { name: self.ident.name, args: self.gen_args.clean(cx) },
kind: self.kind.clean(cx),
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/librustdoc/clean/simplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ crate fn merge_bounds(
cx: &clean::DocContext<'_>,
bounds: &mut Vec<clean::GenericBound>,
trait_did: DefId,
name: Symbol,
assoc: clean::PathSegment,
rhs: &clean::Term,
) -> bool {
!bounds.iter_mut().any(|b| {
Expand All @@ -107,7 +107,7 @@ crate fn merge_bounds(
match last.args {
PP::AngleBracketed { ref mut bindings, .. } => {
bindings.push(clean::TypeBinding {
name,
assoc: assoc.clone(),
kind: clean::TypeBindingKind::Equality { term: rhs.clone() },
});
}
Expand Down
20 changes: 10 additions & 10 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@ crate enum ItemKind {
///
/// The bounds may be non-empty if there is a `where` clause.
/// The `Option<Type>` is the default concrete type (e.g. `trait Trait { type Target = usize; }`)
AssocTypeItem(Vec<GenericBound>, Option<Type>),
AssocTypeItem(Box<Generics>, Vec<GenericBound>, Option<Type>),
/// An item that has been stripped by a rustdoc pass
StrippedItem(Box<ItemKind>),
KeywordItem(Symbol),
Expand Down Expand Up @@ -721,7 +721,7 @@ impl ItemKind {
| ProcMacroItem(_)
| PrimitiveItem(_)
| AssocConstItem(_, _)
| AssocTypeItem(_, _)
| AssocTypeItem(..)
| StrippedItem(_)
| KeywordItem(_) => [].iter(),
}
Expand Down Expand Up @@ -1397,7 +1397,7 @@ crate enum Type {

/// A qualified path to an associated item: `<Type as Trait>::Name`
QPath {
name: Symbol,
assoc: Box<PathSegment>,
self_type: Box<Type>,
/// FIXME: This is a hack that should be removed; see [this discussion][1].
///
Expand All @@ -1415,7 +1415,7 @@ crate enum Type {

// `Type` is used a lot. Make sure it doesn't unintentionally get bigger.
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
rustc_data_structures::static_assert_size!(Type, 72);
rustc_data_structures::static_assert_size!(Type, 80);

impl Type {
/// When comparing types for equality, it can help to ignore `&` wrapping.
Expand Down Expand Up @@ -1505,12 +1505,12 @@ impl Type {
self.primitive_type().is_some()
}

crate fn projection(&self) -> Option<(&Type, DefId, Symbol)> {
let (self_, trait_, name) = match self {
QPath { self_type, trait_, name, .. } => (self_type, trait_, name),
crate fn projection(&self) -> Option<(&Type, DefId, PathSegment)> {
let (self_, trait_, assoc) = match self {
QPath { self_type, trait_, assoc, .. } => (self_type, trait_, assoc),
_ => return None,
};
Some((&self_, trait_.def_id(), *name))
Some((&self_, trait_.def_id(), *assoc.clone()))
}

fn inner_def_id(&self, cache: Option<&Cache>) -> Option<DefId> {
Expand Down Expand Up @@ -2018,7 +2018,7 @@ crate enum GenericArg {
// `GenericArg` can occur many times in a single `Path`, so make sure it
// doesn't increase in size unexpectedly.
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
rustc_data_structures::static_assert_size!(GenericArg, 80);
rustc_data_structures::static_assert_size!(GenericArg, 88);

#[derive(Clone, PartialEq, Eq, Debug, Hash)]
crate enum GenericArgs {
Expand Down Expand Up @@ -2256,7 +2256,7 @@ crate struct ProcMacro {
/// `A: Send + Sync` in `Foo<A: Send + Sync>`).
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
crate struct TypeBinding {
crate name: Symbol,
crate assoc: PathSegment,
crate kind: TypeBindingKind,
}

Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ crate trait DocFolder: Sized {
| ProcMacroItem(_)
| PrimitiveItem(_)
| AssocConstItem(_, _)
| AssocTypeItem(_, _)
| AssocTypeItem(..)
| KeywordItem(_) => kind,
}
}
Expand Down
Loading

0 comments on commit 6d76841

Please sign in to comment.