提交 6d768410 编写于 作者: B bors

Auto merge of #94009 - compiler-errors:gat-rustdoc, r=GuillaumeGomez

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)
...@@ -546,11 +546,11 @@ fn param_env_to_generics( ...@@ -546,11 +546,11 @@ fn param_env_to_generics(
} }
WherePredicate::EqPredicate { lhs, rhs } => { WherePredicate::EqPredicate { lhs, rhs } => {
match lhs { 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 ty = &*self_type;
let mut new_trait = trait_.clone(); 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 ty_to_fn
.entry(*ty.clone()) .entry(*ty.clone())
.and_modify(|e| { .and_modify(|e| {
...@@ -571,7 +571,7 @@ fn param_env_to_generics( ...@@ -571,7 +571,7 @@ fn param_env_to_generics(
// to 'T: Iterator<Item=u8>' // to 'T: Iterator<Item=u8>'
GenericArgs::AngleBracketed { ref mut bindings, .. } => { GenericArgs::AngleBracketed { ref mut bindings, .. } => {
bindings.push(TypeBinding { bindings.push(TypeBinding {
name: left_name, assoc: *assoc.clone(),
kind: TypeBindingKind::Equality { term: rhs }, kind: TypeBindingKind::Equality { term: rhs },
}); });
} }
......
...@@ -636,7 +636,7 @@ fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean: ...@@ -636,7 +636,7 @@ fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean:
g.where_predicates.retain(|pred| match pred { g.where_predicates.retain(|pred| match pred {
clean::WherePredicate::BoundPredicate { 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,
.. ..
} => !(bounds.is_empty() || *s == kw::SelfUpper && trait_.def_id() == trait_did), } => !(bounds.is_empty() || *s == kw::SelfUpper && trait_.def_id() == trait_did),
......
...@@ -388,7 +388,7 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Type { ...@@ -388,7 +388,7 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Type {
let trait_ = lifted.trait_ref(cx.tcx).clean(cx); let trait_ = lifted.trait_ref(cx.tcx).clean(cx);
let self_type = self.self_ty().clean(cx); let self_type = self.self_ty().clean(cx);
Type::QPath { 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_def_id: self_type.def_id(&cx.cache),
self_type: box self_type, self_type: box self_type,
trait_, trait_,
...@@ -396,6 +396,27 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Type { ...@@ -396,6 +396,27 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Type {
} }
} }
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 { impl Clean<GenericParamDef> for ty::GenericParamDef {
fn clean(&self, cx: &mut DocContext<'_>) -> GenericParamDef { fn clean(&self, cx: &mut DocContext<'_>) -> GenericParamDef {
let (name, kind) = match self.kind { let (name, kind) = match self.kind {
...@@ -601,8 +622,8 @@ fn clean_ty_generics( ...@@ -601,8 +622,8 @@ fn clean_ty_generics(
}) })
.collect::<Vec<GenericParamDef>>(); .collect::<Vec<GenericParamDef>>();
// param index -> [(DefId of trait, associated type name, type)] // param index -> [(DefId of trait, associated type name and generics, type)]
let mut impl_trait_proj = FxHashMap::<u32, Vec<(DefId, Symbol, Ty<'_>)>>::default(); let mut impl_trait_proj = FxHashMap::<u32, Vec<(DefId, PathSegment, Ty<'_>)>>::default();
let where_predicates = preds let where_predicates = preds
.predicates .predicates
...@@ -648,8 +669,9 @@ fn clean_ty_generics( ...@@ -648,8 +669,9 @@ fn clean_ty_generics(
let proj = projection let proj = projection
.map(|p| (p.skip_binder().projection_ty.clean(cx), p.skip_binder().term)); .map(|p| (p.skip_binder().projection_ty.clean(cx), p.skip_binder().term));
if let Some(((_, trait_did, name), rhs)) = if let Some(((_, trait_did, name), rhs)) = proj
proj.as_ref().and_then(|(lhs, rhs)| Some((lhs.projection()?, rhs))) .as_ref()
.and_then(|(lhs, rhs): &(Type, _)| Some((lhs.projection()?, rhs)))
{ {
// FIXME(...): Remove this unwrap() // FIXME(...): Remove this unwrap()
impl_trait_proj.entry(param_idx).or_default().push(( impl_trait_proj.entry(param_idx).or_default().push((
...@@ -992,9 +1014,10 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Item { ...@@ -992,9 +1014,10 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Item {
TyMethodItem(t) TyMethodItem(t)
} }
hir::TraitItemKind::Type(bounds, ref default) => { 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 bounds = bounds.iter().filter_map(|x| x.clean(cx)).collect();
let default = default.map(|t| t.clean(cx)); let default = default.map(|t| t.clean(cx));
AssocTypeItem(bounds, default) AssocTypeItem(Box::new(generics), bounds, default)
} }
}; };
let what_rustc_thinks = let what_rustc_thinks =
...@@ -1026,15 +1049,9 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Item { ...@@ -1026,15 +1049,9 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Item {
} }
hir::ImplItemKind::TyAlias(ref hir_ty) => { hir::ImplItemKind::TyAlias(ref hir_ty) => {
let type_ = hir_ty.clean(cx); 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); let item_type = hir_ty_to_ty(cx.tcx, hir_ty).clean(cx);
TypedefItem( TypedefItem(Typedef { type_, generics, item_type: Some(item_type) }, true)
Typedef {
type_,
generics: Generics::default(),
item_type: Some(item_type),
},
true,
)
} }
}; };
...@@ -1140,35 +1157,79 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Item { ...@@ -1140,35 +1157,79 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Item {
ty::AssocKind::Type => { ty::AssocKind::Type => {
let my_name = self.name; 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 { if let ty::TraitContainer(_) = self.container {
let bounds = tcx.explicit_item_bounds(self.def_id); let bounds = tcx.explicit_item_bounds(self.def_id);
let predicates = ty::GenericPredicates { parent: None, predicates: bounds }; 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 let mut bounds = generics
.where_predicates .where_predicates
.iter() .drain_filter(|pred| match *pred {
.filter_map(|pred| { WherePredicate::BoundPredicate {
let (name, self_type, trait_, bounds) = match *pred { ty: QPath { ref assoc, ref self_type, ref trait_, .. },
WherePredicate::BoundPredicate { ..
ty: QPath { ref name, ref self_type, ref trait_, .. }, } => {
ref bounds, if assoc.name != my_name {
.. return false;
} => (name, self_type, trait_, bounds), }
_ => return None, if trait_.def_id() != self.container.id() {
}; return false;
if *name != my_name { }
return None; match **self_type {
} Generic(ref s) if *s == kw::SelfUpper => {}
if trait_.def_id() != self.container.id() { _ => return false,
return None; }
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 { _ => false,
Generic(ref s) if *s == kw::SelfUpper => {} })
_ => return None, .flat_map(|pred| {
if let WherePredicate::BoundPredicate { bounds, .. } = pred {
bounds
} else {
unreachable!()
} }
Some(bounds)
}) })
.flat_map(|i| i.iter().cloned())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Our Sized/?Sized bound didn't get handled when creating the generics // 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 // because we didn't actually get our whole set of bounds until just now
...@@ -1188,7 +1249,7 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Item { ...@@ -1188,7 +1249,7 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Item {
None None
}; };
AssocTypeItem(bounds, ty.map(|t| t.clean(cx))) AssocTypeItem(Box::new(generics), bounds, ty.map(|t| t.clean(cx)))
} else { } else {
// FIXME: when could this happen? Associated items in inherent impls? // FIXME: when could this happen? Associated items in inherent impls?
let type_ = tcx.type_of(self.def_id).clean(cx); let type_ = tcx.type_of(self.def_id).clean(cx);
...@@ -1259,7 +1320,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type { ...@@ -1259,7 +1320,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type {
}; };
register_res(cx, trait_.res); register_res(cx, trait_.res);
Type::QPath { 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_def_id: Some(DefId::local(qself.hir_id.owner.local_def_index)),
self_type: box qself.clean(cx), self_type: box qself.clean(cx),
trait_, trait_,
...@@ -1276,7 +1337,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type { ...@@ -1276,7 +1337,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type {
let trait_ = hir::Path { span, res, segments: &[] }.clean(cx); let trait_ = hir::Path { span, res, segments: &[] }.clean(cx);
register_res(cx, trait_.res); register_res(cx, trait_.res);
Type::QPath { Type::QPath {
name: segment.ident.name, assoc: Box::new(segment.clean(cx)),
self_def_id: res.opt_def_id(), self_def_id: res.opt_def_id(),
self_type: box qself.clean(cx), self_type: box qself.clean(cx),
trait_, trait_,
...@@ -1548,7 +1609,16 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Type { ...@@ -1548,7 +1609,16 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Type {
let mut bindings = vec![]; let mut bindings = vec![];
for pb in obj.projection_bounds() { for pb in obj.projection_bounds() {
bindings.push(TypeBinding { 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) }, kind: TypeBindingKind::Equality { term: pb.skip_binder().term.clean(cx) },
}); });
} }
...@@ -1614,10 +1684,10 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Type { ...@@ -1614,10 +1684,10 @@ fn clean(&self, cx: &mut DocContext<'_>) -> Type {
== trait_ref.skip_binder() == trait_ref.skip_binder()
{ {
Some(TypeBinding { Some(TypeBinding {
name: cx assoc: projection_to_path_segment(
.tcx proj.projection_ty,
.associated_item(proj.projection_ty.item_def_id) cx,
.name, ),
kind: TypeBindingKind::Equality { kind: TypeBindingKind::Equality {
term: proj.term.clean(cx), term: proj.term.clean(cx),
}, },
...@@ -2160,7 +2230,10 @@ fn clean_maybe_renamed_foreign_item( ...@@ -2160,7 +2230,10 @@ fn clean_maybe_renamed_foreign_item(
impl Clean<TypeBinding> for hir::TypeBinding<'_> { impl Clean<TypeBinding> for hir::TypeBinding<'_> {
fn clean(&self, cx: &mut DocContext<'_>) -> 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),
}
} }
} }
......
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
cx: &clean::DocContext<'_>, cx: &clean::DocContext<'_>,
bounds: &mut Vec<clean::GenericBound>, bounds: &mut Vec<clean::GenericBound>,
trait_did: DefId, trait_did: DefId,
name: Symbol, assoc: clean::PathSegment,
rhs: &clean::Term, rhs: &clean::Term,
) -> bool { ) -> bool {
!bounds.iter_mut().any(|b| { !bounds.iter_mut().any(|b| {
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
match last.args { match last.args {
PP::AngleBracketed { ref mut bindings, .. } => { PP::AngleBracketed { ref mut bindings, .. } => {
bindings.push(clean::TypeBinding { bindings.push(clean::TypeBinding {
name, assoc: assoc.clone(),
kind: clean::TypeBindingKind::Equality { term: rhs.clone() }, kind: clean::TypeBindingKind::Equality { term: rhs.clone() },
}); });
} }
......
...@@ -683,7 +683,7 @@ impl Item { ...@@ -683,7 +683,7 @@ impl Item {
/// ///
/// The bounds may be non-empty if there is a `where` clause. /// 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; }`) /// 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 /// An item that has been stripped by a rustdoc pass
StrippedItem(Box<ItemKind>), StrippedItem(Box<ItemKind>),
KeywordItem(Symbol), KeywordItem(Symbol),
...@@ -721,7 +721,7 @@ impl ItemKind { ...@@ -721,7 +721,7 @@ impl ItemKind {
| ProcMacroItem(_) | ProcMacroItem(_)
| PrimitiveItem(_) | PrimitiveItem(_)
| AssocConstItem(_, _) | AssocConstItem(_, _)
| AssocTypeItem(_, _) | AssocTypeItem(..)
| StrippedItem(_) | StrippedItem(_)
| KeywordItem(_) => [].iter(), | KeywordItem(_) => [].iter(),
} }
...@@ -1397,7 +1397,7 @@ impl FnRetTy { ...@@ -1397,7 +1397,7 @@ impl FnRetTy {
/// A qualified path to an associated item: `<Type as Trait>::Name` /// A qualified path to an associated item: `<Type as Trait>::Name`
QPath { QPath {
name: Symbol, assoc: Box<PathSegment>,
self_type: Box<Type>, self_type: Box<Type>,
/// FIXME: This is a hack that should be removed; see [this discussion][1]. /// FIXME: This is a hack that should be removed; see [this discussion][1].
/// ///
...@@ -1415,7 +1415,7 @@ impl FnRetTy { ...@@ -1415,7 +1415,7 @@ impl FnRetTy {
// `Type` is used a lot. Make sure it doesn't unintentionally get bigger. // `Type` is used a lot. Make sure it doesn't unintentionally get bigger.
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] #[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 { impl Type {
/// When comparing types for equality, it can help to ignore `&` wrapping. /// When comparing types for equality, it can help to ignore `&` wrapping.
...@@ -1505,12 +1505,12 @@ impl Type { ...@@ -1505,12 +1505,12 @@ impl Type {
self.primitive_type().is_some() self.primitive_type().is_some()
} }
crate fn projection(&self) -> Option<(&Type, DefId, Symbol)> { crate fn projection(&self) -> Option<(&Type, DefId, PathSegment)> {
let (self_, trait_, name) = match self { let (self_, trait_, assoc) = match self {
QPath { self_type, trait_, name, .. } => (self_type, trait_, name), QPath { self_type, trait_, assoc, .. } => (self_type, trait_, assoc),
_ => return None, _ => 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> { fn inner_def_id(&self, cache: Option<&Cache>) -> Option<DefId> {
...@@ -2018,7 +2018,7 @@ impl Path { ...@@ -2018,7 +2018,7 @@ impl Path {
// `GenericArg` can occur many times in a single `Path`, so make sure it // `GenericArg` can occur many times in a single `Path`, so make sure it
// doesn't increase in size unexpectedly. // doesn't increase in size unexpectedly.
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] #[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)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]
crate enum GenericArgs { crate enum GenericArgs {
...@@ -2256,7 +2256,7 @@ impl Import { ...@@ -2256,7 +2256,7 @@ impl Import {
/// `A: Send + Sync` in `Foo<A: Send + Sync>`). /// `A: Send + Sync` in `Foo<A: Send + Sync>`).
#[derive(Clone, PartialEq, Eq, Debug, Hash)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]
crate struct TypeBinding { crate struct TypeBinding {
crate name: Symbol, crate assoc: PathSegment,
crate kind: TypeBindingKind, crate kind: TypeBindingKind,
} }
......
...@@ -86,7 +86,7 @@ fn fold_inner_recur(&mut self, kind: ItemKind) -> ItemKind { ...@@ -86,7 +86,7 @@ fn fold_inner_recur(&mut self, kind: ItemKind) -> ItemKind {
| ProcMacroItem(_) | ProcMacroItem(_)
| PrimitiveItem(_) | PrimitiveItem(_)
| AssocConstItem(_, _) | AssocConstItem(_, _)
| AssocTypeItem(_, _) | AssocTypeItem(..)
| KeywordItem(_) => kind, | KeywordItem(_) => kind,
} }
} }
......
...@@ -961,7 +961,7 @@ fn fmt_type<'cx>( ...@@ -961,7 +961,7 @@ fn fmt_type<'cx>(
write!(f, "impl {}", print_generic_bounds(bounds, cx)) write!(f, "impl {}", print_generic_bounds(bounds, cx))
} }
} }
clean::QPath { ref name, ref self_type, ref trait_, ref self_def_id } => { clean::QPath { ref assoc, ref self_type, ref trait_, ref self_def_id } => {
let should_show_cast = !trait_.segments.is_empty() let should_show_cast = !trait_.segments.is_empty()
&& self_def_id && self_def_id
.zip(Some(trait_.def_id())) .zip(Some(trait_.def_id()))
...@@ -994,14 +994,15 @@ fn fmt_type<'cx>( ...@@ -994,14 +994,15 @@ fn fmt_type<'cx>(
write!( write!(
f, f,
"<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \ "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
title=\"type {path}::{name}\">{name}</a>", title=\"type {path}::{name}\">{name}</a>{args}",
url = url, url = url,
shortty = ItemType::AssocType, shortty = ItemType::AssocType,
name = name, name = assoc.name,
path = join_with_double_colon(path), path = join_with_double_colon(path),
args = assoc.args.print(cx),
)?; )?;
} }
_ => write!(f, "{}", name)?, _ => write!(f, "{}{:#}", assoc.name, assoc.args.print(cx))?,
} }
Ok(()) Ok(())
} }
...@@ -1457,7 +1458,12 @@ impl clean::TypeBinding { ...@@ -1457,7 +1458,12 @@ impl clean::TypeBinding {
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl fmt::Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
f.write_str(self.name.as_str())?; f.write_str(self.assoc.name.as_str())?;
if f.alternate() {
write!(f, "{:#}", self.assoc.args.print(cx))?;
} else {
write!(f, "{}", self.assoc.args.print(cx))?;
}
match self.kind { match self.kind {
clean::TypeBindingKind::Equality { ref term } => { clean::TypeBindingKind::Equality { ref term } => {
if f.alternate() { if f.alternate() {
......
...@@ -773,27 +773,115 @@ fn assoc_const( ...@@ -773,27 +773,115 @@ fn assoc_const(
fn assoc_type( fn assoc_type(
w: &mut Buffer, w: &mut Buffer,
it: &clean::Item, it: &clean::Item,
generics: &clean::Generics,
bounds: &[clean::GenericBound], bounds: &[clean::GenericBound],
default: Option<&clean::Type>, default: Option<&clean::Type>,
link: AssocItemLink<'_>, link: AssocItemLink<'_>,
extra: &str, indent: usize,
cx: &Context<'_>, cx: &Context<'_>,
) { ) {
write!( write!(
w, w,
"{}type <a href=\"{}\" class=\"associatedtype\">{}</a>", "{indent}type <a href=\"{href}\" class=\"associatedtype\">{name}</a>{generics}",
extra, indent = " ".repeat(indent),
naive_assoc_href(it, link, cx), href = naive_assoc_href(it, link, cx),
it.name.as_ref().unwrap() name = it.name.as_ref().unwrap(),
generics = generics.print(cx),
); );
if !bounds.is_empty() { if !bounds.is_empty() {
write!(w, ": {}", print_generic_bounds(bounds, cx)) write!(w, ": {}", print_generic_bounds(bounds, cx))
} }
write!(w, "{}", print_where_clause(generics, cx, indent, false));
if let Some(default) = default { if let Some(default) = default {
write!(w, " = {}", default.print(cx)) write!(w, " = {}", default.print(cx))
} }
} }
fn assoc_method(
w: &mut Buffer,
meth: &clean::Item,
header: hir::FnHeader,
g: &clean::Generics,
d: &clean::FnDecl,
link: AssocItemLink<'_>,
parent: ItemType,
cx: &Context<'_>,
render_mode: RenderMode,
) {
let name = meth.name.as_ref().unwrap();
let href = match link {
AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
AssocItemLink::Anchor(None) => Some(format!("#{}.{}", meth.type_(), name)),
AssocItemLink::GotoSource(did, provided_methods) => {
// We're creating a link from an impl-item to the corresponding
// trait-item and need to map the anchored type accordingly.
let ty =
if provided_methods.contains(name) { ItemType::Method } else { ItemType::TyMethod };
match (href(did.expect_def_id(), cx), ty) {
(Ok(p), ty) => Some(format!("{}#{}.{}", p.0, ty, name)),
(Err(HrefError::DocumentationNotBuilt), ItemType::TyMethod) => None,
(Err(_), ty) => Some(format!("#{}.{}", ty, name)),
}
}
};
let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string();
// FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
// this condition.
let constness = match render_mode {
RenderMode::Normal => {
print_constness_with_space(&header.constness, meth.const_stability(cx.tcx()))
}
RenderMode::ForDeref { .. } => "",
};
let asyncness = header.asyncness.print_with_space();
let unsafety = header.unsafety.print_with_space();
let defaultness = print_default_space(meth.is_default());
let abi = print_abi_with_space(header.abi).to_string();
// NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
let generics_len = format!("{:#}", g.print(cx)).len();
let mut header_len = "fn ".len()
+ vis.len()
+ constness.len()
+ asyncness.len()
+ unsafety.len()
+ defaultness.len()
+ abi.len()
+ name.as_str().len()
+ generics_len;
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
header_len += 4;
let indent_str = " ";
render_attributes_in_pre(w, meth, indent_str);
(4, indent_str, false)
} else {
render_attributes_in_code(w, meth);
(0, "", true)
};
w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len());
write!(
w,
"{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a {href} class=\"fnname\">{name}</a>\
{generics}{decl}{notable_traits}{where_clause}",
indent = indent_str,
vis = vis,
constness = constness,
asyncness = asyncness,
unsafety = unsafety,
defaultness = defaultness,
abi = abi,
// links without a href are valid - https://www.w3schools.com/tags/att_a_href.asp
href = href.map(|href| format!("href=\"{}\"", href)).unwrap_or_else(|| "".to_string()),
name = name,
generics = g.print(cx),
decl = d.full_print(header_len, indent, header.asyncness, cx),
notable_traits = notable_traits_decl(d, cx),
where_clause = print_where_clause(g, cx, indent, end_newline),
)
}
/// Writes a span containing the versions at which an item became stable and/or const-stable. For /// Writes a span containing the versions at which an item became stable and/or const-stable. For
/// example, if the item became stable at 1.0.0, and const-stable at 1.45.0, this function would /// example, if the item became stable at 1.0.0, and const-stable at 1.45.0, this function would
/// write a span containing "1.0.0 (const: 1.45.0)". /// write a span containing "1.0.0 (const: 1.45.0)".
...@@ -875,111 +963,25 @@ fn render_assoc_item( ...@@ -875,111 +963,25 @@ fn render_assoc_item(
cx: &Context<'_>, cx: &Context<'_>,
render_mode: RenderMode, render_mode: RenderMode,
) { ) {
fn method(
w: &mut Buffer,
meth: &clean::Item,
header: hir::FnHeader,
g: &clean::Generics,
d: &clean::FnDecl,
link: AssocItemLink<'_>,
parent: ItemType,
cx: &Context<'_>,
render_mode: RenderMode,
) {
let name = meth.name.as_ref().unwrap();
let href = match link {
AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
AssocItemLink::Anchor(None) => Some(format!("#{}.{}", meth.type_(), name)),
AssocItemLink::GotoSource(did, provided_methods) => {
// We're creating a link from an impl-item to the corresponding
// trait-item and need to map the anchored type accordingly.
let ty = if provided_methods.contains(name) {
ItemType::Method
} else {
ItemType::TyMethod
};
match (href(did.expect_def_id(), cx), ty) {
(Ok(p), ty) => Some(format!("{}#{}.{}", p.0, ty, name)),
(Err(HrefError::DocumentationNotBuilt), ItemType::TyMethod) => None,
(Err(_), ty) => Some(format!("#{}.{}", ty, name)),
}
}
};
let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string();
// FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
// this condition.
let constness = match render_mode {
RenderMode::Normal => {
print_constness_with_space(&header.constness, meth.const_stability(cx.tcx()))
}
RenderMode::ForDeref { .. } => "",
};
let asyncness = header.asyncness.print_with_space();
let unsafety = header.unsafety.print_with_space();
let defaultness = print_default_space(meth.is_default());
let abi = print_abi_with_space(header.abi).to_string();
// NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
let generics_len = format!("{:#}", g.print(cx)).len();
let mut header_len = "fn ".len()
+ vis.len()
+ constness.len()
+ asyncness.len()
+ unsafety.len()
+ defaultness.len()
+ abi.len()
+ name.as_str().len()
+ generics_len;
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
header_len += 4;
let indent_str = " ";
render_attributes_in_pre(w, meth, indent_str);
(4, indent_str, false)
} else {
render_attributes_in_code(w, meth);
(0, "", true)
};
w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len());
write!(
w,
"{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a {href} class=\"fnname\">{name}</a>\
{generics}{decl}{notable_traits}{where_clause}",
indent = indent_str,
vis = vis,
constness = constness,
asyncness = asyncness,
unsafety = unsafety,
defaultness = defaultness,
abi = abi,
// links without a href are valid - https://www.w3schools.com/tags/att_a_href.asp
href = href.map(|href| format!("href=\"{}\"", href)).unwrap_or_else(|| "".to_string()),
name = name,
generics = g.print(cx),
decl = d.full_print(header_len, indent, header.asyncness, cx),
notable_traits = notable_traits_decl(d, cx),
where_clause = print_where_clause(g, cx, indent, end_newline),
)
}
match *item.kind { match *item.kind {
clean::StrippedItem(..) => {} clean::StrippedItem(..) => {}
clean::TyMethodItem(ref m) => { clean::TyMethodItem(ref m) => {
method(w, item, m.header, &m.generics, &m.decl, link, parent, cx, render_mode) assoc_method(w, item, m.header, &m.generics, &m.decl, link, parent, cx, render_mode)
} }
clean::MethodItem(ref m, _) => { clean::MethodItem(ref m, _) => {
method(w, item, m.header, &m.generics, &m.decl, link, parent, cx, render_mode) assoc_method(w, item, m.header, &m.generics, &m.decl, link, parent, cx, render_mode)
} }
clean::AssocConstItem(ref ty, _) => { clean::AssocConstItem(ref ty, _) => {
assoc_const(w, item, ty, link, if parent == ItemType::Trait { " " } else { "" }, cx) assoc_const(w, item, ty, link, if parent == ItemType::Trait { " " } else { "" }, cx)
} }
clean::AssocTypeItem(ref bounds, ref default) => assoc_type( clean::AssocTypeItem(ref generics, ref bounds, ref default) => assoc_type(
w, w,
item, item,
generics,
bounds, bounds,
default.as_ref(), default.as_ref(),
link, link,
if parent == ItemType::Trait { " " } else { "" }, if parent == ItemType::Trait { 4 } else { 0 },
cx, cx,
), ),
_ => panic!("render_assoc_item called on non-associated-item"), _ => panic!("render_assoc_item called on non-associated-item"),
...@@ -1283,7 +1285,16 @@ fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String { ...@@ -1283,7 +1285,16 @@ fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
let empty_set = FxHashSet::default(); let empty_set = FxHashSet::default();
let src_link = let src_link =
AssocItemLink::GotoSource(trait_did.into(), &empty_set); AssocItemLink::GotoSource(trait_did.into(), &empty_set);
assoc_type(&mut out, it, &[], Some(&tydef.type_), src_link, "", cx); assoc_type(
&mut out,
it,
&tydef.generics,
&[],
Some(&tydef.type_),
src_link,
0,
cx,
);
out.push_str(";</span>"); out.push_str(";</span>");
} }
} }
...@@ -1462,10 +1473,11 @@ fn doc_impl_item( ...@@ -1462,10 +1473,11 @@ fn doc_impl_item(
assoc_type( assoc_type(
w, w,
item, item,
&Vec::new(), &tydef.generics,
&[],
Some(&tydef.type_), Some(&tydef.type_),
link.anchor(if trait_.is_some() { &source_id } else { &id }), link.anchor(if trait_.is_some() { &source_id } else { &id }),
"", 0,
cx, cx,
); );
w.write_str("</h4>"); w.write_str("</h4>");
...@@ -1493,7 +1505,7 @@ fn doc_impl_item( ...@@ -1493,7 +1505,7 @@ fn doc_impl_item(
w.write_str("</h4>"); w.write_str("</h4>");
w.write_str("</section>"); w.write_str("</section>");
} }
clean::AssocTypeItem(ref bounds, ref default) => { clean::AssocTypeItem(ref generics, ref bounds, ref default) => {
let source_id = format!("{}.{}", item_type, name); let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone()); let id = cx.derive_id(source_id.clone());
write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class,); write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class,);
...@@ -1502,10 +1514,11 @@ fn doc_impl_item( ...@@ -1502,10 +1514,11 @@ fn doc_impl_item(
assoc_type( assoc_type(
w, w,
item, item,
generics,
bounds, bounds,
default.as_ref(), default.as_ref(),
link.anchor(if trait_.is_some() { &source_id } else { &id }), link.anchor(if trait_.is_some() { &source_id } else { &id }),
"", 0,
cx, cx,
); );
w.write_str("</h4>"); w.write_str("</h4>");
...@@ -1726,7 +1739,16 @@ pub(crate) fn render_impl_summary( ...@@ -1726,7 +1739,16 @@ pub(crate) fn render_impl_summary(
for it in &i.inner_impl().items { for it in &i.inner_impl().items {
if let clean::TypedefItem(ref tydef, _) = *it.kind { if let clean::TypedefItem(ref tydef, _) = *it.kind {
w.write_str("<span class=\"where fmt-newline\"> "); w.write_str("<span class=\"where fmt-newline\"> ");
assoc_type(w, it, &[], Some(&tydef.type_), AssocItemLink::Anchor(None), "", cx); assoc_type(
w,
it,
&tydef.generics,
&[],
Some(&tydef.type_),
AssocItemLink::Anchor(None),
0,
cx,
);
w.write_str(";</span>"); w.write_str(";</span>");
} }
} }
......
...@@ -154,7 +154,11 @@ fn from_tcx(constant: clean::Constant, tcx: TyCtxt<'_>) -> Self { ...@@ -154,7 +154,11 @@ fn from_tcx(constant: clean::Constant, tcx: TyCtxt<'_>) -> Self {
impl FromWithTcx<clean::TypeBinding> for TypeBinding { impl FromWithTcx<clean::TypeBinding> for TypeBinding {
fn from_tcx(binding: clean::TypeBinding, tcx: TyCtxt<'_>) -> Self { fn from_tcx(binding: clean::TypeBinding, tcx: TyCtxt<'_>) -> Self {
TypeBinding { name: binding.name.to_string(), binding: binding.kind.into_tcx(tcx) } TypeBinding {
name: binding.assoc.name.to_string(),
args: binding.assoc.args.into_tcx(tcx),
binding: binding.kind.into_tcx(tcx),
}
} }
} }
...@@ -222,8 +226,9 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { ...@@ -222,8 +226,9 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum {
AssocConstItem(ty, default) => { AssocConstItem(ty, default) => {
ItemEnum::AssocConst { type_: ty.into_tcx(tcx), default: default.map(|c| c.expr(tcx)) } ItemEnum::AssocConst { type_: ty.into_tcx(tcx), default: default.map(|c| c.expr(tcx)) }
} }
AssocTypeItem(g, t) => ItemEnum::AssocType { AssocTypeItem(g, b, t) => ItemEnum::AssocType {
bounds: g.into_iter().map(|x| x.into_tcx(tcx)).collect(), generics: (*g).into_tcx(tcx),
bounds: b.into_iter().map(|x| x.into_tcx(tcx)).collect(),
default: t.map(|x| x.into_tcx(tcx)), default: t.map(|x| x.into_tcx(tcx)),
}, },
// `convert_item` early returns `None` for striped items // `convert_item` early returns `None` for striped items
...@@ -444,11 +449,12 @@ fn from_tcx(ty: clean::Type, tcx: TyCtxt<'_>) -> Self { ...@@ -444,11 +449,12 @@ fn from_tcx(ty: clean::Type, tcx: TyCtxt<'_>) -> Self {
mutable: mutability == ast::Mutability::Mut, mutable: mutability == ast::Mutability::Mut,
type_: Box::new((*type_).into_tcx(tcx)), type_: Box::new((*type_).into_tcx(tcx)),
}, },
QPath { name, self_type, trait_, .. } => { QPath { assoc, self_type, trait_, .. } => {
// FIXME: should `trait_` be a clean::Path equivalent in JSON? // FIXME: should `trait_` be a clean::Path equivalent in JSON?
let trait_ = clean::Type::Path { path: trait_ }.into_tcx(tcx); let trait_ = clean::Type::Path { path: trait_ }.into_tcx(tcx);
Type::QualifiedPath { Type::QualifiedPath {
name: name.to_string(), name: assoc.name.to_string(),
args: Box::new(assoc.args.clone().into_tcx(tcx)),
self_type: Box::new((*self_type).into_tcx(tcx)), self_type: Box::new((*self_type).into_tcx(tcx)),
trait_: Box::new(trait_), trait_: Box::new(trait_),
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(control_flow_enum)] #![feature(control_flow_enum)]
#![feature(box_syntax)] #![feature(box_syntax)]
#![feature(drain_filter)]
#![feature(let_else)] #![feature(let_else)]
#![feature(nll)] #![feature(nll)]
#![feature(test)] #![feature(test)]
......
...@@ -62,7 +62,7 @@ fn add_test(&mut self, _: String, config: LangString, _: usize) { ...@@ -62,7 +62,7 @@ fn add_test(&mut self, _: String, config: LangString, _: usize) {
clean::StructFieldItem(_) clean::StructFieldItem(_)
| clean::VariantItem(_) | clean::VariantItem(_)
| clean::AssocConstItem(_, _) | clean::AssocConstItem(_, _)
| clean::AssocTypeItem(_, _) | clean::AssocTypeItem(..)
| clean::TypedefItem(_, _) | clean::TypedefItem(_, _)
| clean::StaticItem(_) | clean::StaticItem(_)
| clean::ConstantItem(_) | clean::ConstantItem(_)
......
...@@ -41,7 +41,7 @@ fn visit_inner_recur(&mut self, kind: &ItemKind) { ...@@ -41,7 +41,7 @@ fn visit_inner_recur(&mut self, kind: &ItemKind) {
| ProcMacroItem(_) | ProcMacroItem(_)
| PrimitiveItem(_) | PrimitiveItem(_)
| AssocConstItem(_, _) | AssocConstItem(_, _)
| AssocTypeItem(_, _) | AssocTypeItem(..)
| KeywordItem(_) => {} | KeywordItem(_) => {}
} }
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// rustdoc format-version. /// rustdoc format-version.
pub const FORMAT_VERSION: u32 = 11; pub const FORMAT_VERSION: u32 = 12;
/// A `Crate` is the root of the emitted JSON blob. It contains all type/documentation information /// A `Crate` is the root of the emitted JSON blob. It contains all type/documentation information
/// about the language items in the local crate, as well as info about external items to allow /// about the language items in the local crate, as well as info about external items to allow
...@@ -145,6 +145,7 @@ pub struct Constant { ...@@ -145,6 +145,7 @@ pub struct Constant {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TypeBinding { pub struct TypeBinding {
pub name: String, pub name: String,
pub args: GenericArgs,
pub binding: TypeBindingKind, pub binding: TypeBindingKind,
} }
...@@ -233,6 +234,7 @@ pub enum ItemEnum { ...@@ -233,6 +234,7 @@ pub enum ItemEnum {
default: Option<String>, default: Option<String>,
}, },
AssocType { AssocType {
generics: Generics,
bounds: Vec<GenericBound>, bounds: Vec<GenericBound>,
/// e.g. `type X = usize;` /// e.g. `type X = usize;`
default: Option<Type>, default: Option<Type>,
...@@ -432,6 +434,7 @@ pub enum Type { ...@@ -432,6 +434,7 @@ pub enum Type {
/// `<Type as Trait>::Name` or associated types like `T::Item` where `T: Iterator` /// `<Type as Trait>::Name` or associated types like `T::Item` where `T: Iterator`
QualifiedPath { QualifiedPath {
name: String, name: String,
args: Box<GenericArgs>,
self_type: Box<Type>, self_type: Box<Type>,
#[serde(rename = "trait")] #[serde(rename = "trait")]
trait_: Box<Type>, trait_: Box<Type>,
......
// ignore-tidy-linelength
#![no_core]
#![feature(generic_associated_types, lang_items, no_core)]
#[lang = "sized"]
pub trait Sized {}
pub trait Display {}
// @has gats.json
pub trait LendingIterator {
// @count - "$.index[*][?(@.name=='LendingItem')].inner.generics.params[*]" 1
// @is - "$.index[*][?(@.name=='LendingItem')].inner.generics.params[*].name" \"\'a\"
// @count - "$.index[*][?(@.name=='LendingItem')].inner.generics.where_predicates[*]" 1
// @is - "$.index[*][?(@.name=='LendingItem')].inner.generics.where_predicates[*].bound_predicate.ty.inner" \"Self\"
// @is - "$.index[*][?(@.name=='LendingItem')].inner.generics.where_predicates[*].bound_predicate.bounds[*].outlives" \"\'a\"
// @count - "$.index[*][?(@.name=='LendingItem')].inner.bounds[*]" 1
type LendingItem<'a>: Display where Self: 'a;
// @is - "$.index[*][?(@.name=='lending_next')].inner.decl.output.kind" \"qualified_path\"
// @count - "$.index[*][?(@.name=='lending_next')].inner.decl.output.inner.args.angle_bracketed.args[*]" 1
// @count - "$.index[*][?(@.name=='lending_next')].inner.decl.output.inner.args.angle_bracketed.bindings[*]" 0
// @is - "$.index[*][?(@.name=='lending_next')].inner.decl.output.inner.self_type.inner" \"Self\"
// @is - "$.index[*][?(@.name=='lending_next')].inner.decl.output.inner.name" \"LendingItem\"
fn lending_next<'a>(&'a self) -> Self::LendingItem<'a>;
}
// @has gats.json
pub trait Iterator {
// @count - "$.index[*][?(@.name=='Item')].inner.generics.params[*]" 0
// @count - "$.index[*][?(@.name=='Item')].inner.generics.where_predicates[*]" 0
// @count - "$.index[*][?(@.name=='Item')].inner.bounds[*]" 1
type Item: Display;
// @is - "$.index[*][?(@.name=='next')].inner.decl.output.kind" \"qualified_path\"
// @count - "$.index[*][?(@.name=='next')].inner.decl.output.inner.args.angle_bracketed.args[*]" 0
// @count - "$.index[*][?(@.name=='next')].inner.decl.output.inner.args.angle_bracketed.bindings[*]" 0
// @is - "$.index[*][?(@.name=='next')].inner.decl.output.inner.self_type.inner" \"Self\"
// @is - "$.index[*][?(@.name=='next')].inner.decl.output.inner.name" \"Item\"
fn next<'a>(&'a self) -> Self::Item;
}
#![crate_name = "foo"]
#![feature(generic_associated_types)]
// @has foo/trait.LendingIterator.html
pub trait LendingIterator {
// @has - '//*[@id="associatedtype.Item"]//h4[@class="code-header"]' "type Item<'a> where Self: 'a"
type Item<'a> where Self: 'a;
// @has - '//*[@id="tymethod.next"]//h4[@class="code-header"]' \
// "fn next<'a>(&'a self) -> Self::Item<'a>"
// @has - '//*[@id="tymethod.next"]//h4[@class="code-header"]//a[@href="trait.LendingIterator.html#associatedtype.Item"]' \
// "Item"
fn next<'a>(&'a self) -> Self::Item<'a>;
}
// @has foo/trait.LendingIterator.html
// @has - '//*[@id="associatedtype.Item-1"]//h4[@class="code-header"]' "type Item<'a> = ()"
impl LendingIterator for () {
type Item<'a> = ();
fn next<'a>(&self) -> () {}
}
pub struct Infinite<T>(T);
// @has foo/trait.LendingIterator.html
// @has - '//*[@id="associatedtype.Item-2"]//h4[@class="code-header"]' "type Item<'a> where Self: 'a = &'a T"
impl<T> LendingIterator for Infinite<T> {
type Item<'a> where Self: 'a = &'a T;
fn next<'a>(&'a self) -> Self::Item<'a> {
&self.0
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册