diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index a3154d8f03bf8..62f3527525e0e 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -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| { @@ -571,7 +571,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // to 'T: Iterator' GenericArgs::AngleBracketed { ref mut bindings, .. } => { bindings.push(TypeBinding { - name: left_name, + assoc: *assoc.clone(), kind: TypeBindingKind::Equality { term: rhs }, }); } diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index f0ae01f3803f4..2a32432d4e607 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -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), diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index e0e641c2f9b5e..4194091806249 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -388,7 +388,7 @@ impl<'tcx> Clean 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_, @@ -396,6 +396,27 @@ impl<'tcx> Clean for ty::ProjectionTy<'tcx> { } } +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 for ty::GenericParamDef { fn clean(&self, cx: &mut DocContext<'_>) -> GenericParamDef { let (name, kind) = match self.kind { @@ -601,8 +622,8 @@ fn clean_ty_generics( }) .collect::>(); - // param index -> [(DefId of trait, associated type name, type)] - let mut impl_trait_proj = FxHashMap::)>>::default(); + // param index -> [(DefId of trait, associated type name and generics, type)] + let mut impl_trait_proj = FxHashMap::)>>::default(); let where_predicates = preds .predicates @@ -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(( @@ -985,9 +1007,10 @@ impl Clean 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 = @@ -1019,15 +1042,9 @@ impl Clean 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) } }; @@ -1135,35 +1152,79 @@ impl Clean for ty::AssocItem { ty::AssocKind::Type => { let my_name = self.name; + fn param_eq_arg(param: &GenericParamDef, arg: &GenericArg) -> bool { + match (¶m.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::>(); // 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 @@ -1183,7 +1244,7 @@ impl Clean 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); @@ -1243,7 +1304,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_, @@ -1260,7 +1321,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_, @@ -1533,7 +1594,16 @@ impl<'tcx> Clean 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) }, }); } @@ -1599,10 +1669,10 @@ impl<'tcx> Clean 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), }, @@ -2145,7 +2215,10 @@ fn clean_maybe_renamed_foreign_item( impl Clean 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), + } } } diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index ea18d915deb7a..dd8e1132572fc 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -89,7 +89,7 @@ crate fn merge_bounds( cx: &clean::DocContext<'_>, bounds: &mut Vec, trait_did: DefId, - name: Symbol, + assoc: clean::PathSegment, rhs: &clean::Term, ) -> bool { !bounds.iter_mut().any(|b| { @@ -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() }, }); } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 467a6940628e7..78928fb4059ba 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -683,7 +683,7 @@ crate enum ItemKind { /// /// The bounds may be non-empty if there is a `where` clause. /// The `Option` is the default concrete type (e.g. `trait Trait { type Target = usize; }`) - AssocTypeItem(Vec, Option), + AssocTypeItem(Box, Vec, Option), /// An item that has been stripped by a rustdoc pass StrippedItem(Box), KeywordItem(Symbol), @@ -721,7 +721,7 @@ impl ItemKind { | ProcMacroItem(_) | PrimitiveItem(_) | AssocConstItem(_, _) - | AssocTypeItem(_, _) + | AssocTypeItem(..) | StrippedItem(_) | KeywordItem(_) => [].iter(), } @@ -1397,7 +1397,7 @@ crate enum Type { /// A qualified path to an associated item: `::Name` QPath { - name: Symbol, + assoc: Box, self_type: Box, /// FIXME: This is a hack that should be removed; see [this discussion][1]. /// @@ -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. @@ -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 { @@ -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 { @@ -2256,7 +2256,7 @@ crate struct ProcMacro { /// `A: Send + Sync` in `Foo`). #[derive(Clone, PartialEq, Eq, Debug, Hash)] crate struct TypeBinding { - crate name: Symbol, + crate assoc: PathSegment, crate kind: TypeBindingKind, } diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index cd0f44e5696fe..fbb8b572ea430 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -86,7 +86,7 @@ crate trait DocFolder: Sized { | ProcMacroItem(_) | PrimitiveItem(_) | AssocConstItem(_, _) - | AssocTypeItem(_, _) + | AssocTypeItem(..) | KeywordItem(_) => kind, } } diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index c0115bfc6d4fa..78965712dfa18 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -961,7 +961,7 @@ fn fmt_type<'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() && self_def_id .zip(Some(trait_.def_id())) @@ -994,14 +994,15 @@ fn fmt_type<'cx>( write!( f, "{name}", + title=\"type {path}::{name}\">{name}{args}", url = url, shortty = ItemType::AssocType, - name = name, + name = assoc.name, path = join_with_double_colon(path), + args = assoc.args.print(cx), )?; } - _ => write!(f, "{}", name)?, + _ => write!(f, "{}{:#}", assoc.name, assoc.args.print(cx))?, } Ok(()) } @@ -1457,7 +1458,12 @@ impl clean::TypeBinding { cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx> { 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 { clean::TypeBindingKind::Equality { ref term } => { if f.alternate() { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 3c1737c5e3f5f..558dbb3b3965a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -773,27 +773,115 @@ fn assoc_const( fn assoc_type( w: &mut Buffer, it: &clean::Item, + generics: &clean::Generics, bounds: &[clean::GenericBound], default: Option<&clean::Type>, link: AssocItemLink<'_>, - extra: &str, + indent: usize, cx: &Context<'_>, ) { write!( w, - "{}type {}", - extra, - naive_assoc_href(it, link, cx), - it.name.as_ref().unwrap() + "{indent}type {name}{generics}", + indent = " ".repeat(indent), + href = naive_assoc_href(it, link, cx), + name = it.name.as_ref().unwrap(), + generics = generics.print(cx), ); if !bounds.is_empty() { write!(w, ": {}", print_generic_bounds(bounds, cx)) } + write!(w, "{}", print_where_clause(generics, cx, indent, false)); if let Some(default) = default { 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 + "{".len() + "".len()); + write!( + w, + "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn {name}\ + {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 /// 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)". @@ -875,111 +963,25 @@ fn render_assoc_item( cx: &Context<'_>, 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 + "{".len() + "".len()); - write!( - w, - "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn {name}\ - {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 { clean::StrippedItem(..) => {} 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, _) => { - 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, _) => { 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, item, + generics, bounds, default.as_ref(), link, - if parent == ItemType::Trait { " " } else { "" }, + if parent == ItemType::Trait { 4 } else { 0 }, cx, ), _ => panic!("render_assoc_item called on non-associated-item"), @@ -1283,7 +1285,16 @@ fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String { let empty_set = FxHashSet::default(); let src_link = 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(";"); } } @@ -1462,10 +1473,11 @@ fn render_impl( assoc_type( w, item, - &Vec::new(), + &tydef.generics, + &[], Some(&tydef.type_), link.anchor(if trait_.is_some() { &source_id } else { &id }), - "", + 0, cx, ); w.write_str(""); @@ -1493,7 +1505,7 @@ fn render_impl( w.write_str(""); w.write_str(""); } - clean::AssocTypeItem(ref bounds, ref default) => { + clean::AssocTypeItem(ref generics, ref bounds, ref default) => { let source_id = format!("{}.{}", item_type, name); let id = cx.derive_id(source_id.clone()); write!(w, "
", id, item_type, in_trait_class,); @@ -1502,10 +1514,11 @@ fn render_impl( assoc_type( w, item, + generics, bounds, default.as_ref(), link.anchor(if trait_.is_some() { &source_id } else { &id }), - "", + 0, cx, ); w.write_str(""); @@ -1726,7 +1739,16 @@ pub(crate) fn render_impl_summary( for it in &i.inner_impl().items { if let clean::TypedefItem(ref tydef, _) = *it.kind { w.write_str(" "); - 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(";"); } } diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index d4aedb41ddb18..4358dc8980f2f 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -154,7 +154,11 @@ impl FromWithTcx for Constant { impl FromWithTcx for TypeBinding { 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 { AssocConstItem(ty, default) => { ItemEnum::AssocConst { type_: ty.into_tcx(tcx), default: default.map(|c| c.expr(tcx)) } } - AssocTypeItem(g, t) => ItemEnum::AssocType { - bounds: g.into_iter().map(|x| x.into_tcx(tcx)).collect(), + AssocTypeItem(g, b, t) => ItemEnum::AssocType { + generics: (*g).into_tcx(tcx), + bounds: b.into_iter().map(|x| x.into_tcx(tcx)).collect(), default: t.map(|x| x.into_tcx(tcx)), }, // `convert_item` early returns `None` for striped items @@ -444,11 +449,12 @@ impl FromWithTcx for Type { mutable: mutability == ast::Mutability::Mut, 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? let trait_ = clean::Type::Path { path: trait_ }.into_tcx(tcx); 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)), trait_: Box::new(trait_), } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 5c8a8fb80f1f4..3c2a08463d8ed 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -9,6 +9,7 @@ #![feature(box_patterns)] #![feature(control_flow_enum)] #![feature(box_syntax)] +#![feature(drain_filter)] #![feature(let_else)] #![feature(nll)] #![feature(test)] diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index e8f8ff988c1f0..6cffb52bb875f 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -62,7 +62,7 @@ crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> boo clean::StructFieldItem(_) | clean::VariantItem(_) | clean::AssocConstItem(_, _) - | clean::AssocTypeItem(_, _) + | clean::AssocTypeItem(..) | clean::TypedefItem(_, _) | clean::StaticItem(_) | clean::ConstantItem(_) diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index df4d1558ebdf4..b16cab1c646f1 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -41,7 +41,7 @@ crate trait DocVisitor: Sized { | ProcMacroItem(_) | PrimitiveItem(_) | AssocConstItem(_, _) - | AssocTypeItem(_, _) + | AssocTypeItem(..) | KeywordItem(_) => {} } } diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index be9bbc7391d75..40b0de448293a 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; /// 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 /// about the language items in the local crate, as well as info about external items to allow @@ -145,6 +145,7 @@ pub struct Constant { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TypeBinding { pub name: String, + pub args: GenericArgs, pub binding: TypeBindingKind, } @@ -233,6 +234,7 @@ pub enum ItemEnum { default: Option, }, AssocType { + generics: Generics, bounds: Vec, /// e.g. `type X = usize;` default: Option, @@ -432,6 +434,7 @@ pub enum Type { /// `::Name` or associated types like `T::Item` where `T: Iterator` QualifiedPath { name: String, + args: Box, self_type: Box, #[serde(rename = "trait")] trait_: Box, diff --git a/src/test/rustdoc-json/generic-associated-types/gats.rs b/src/test/rustdoc-json/generic-associated-types/gats.rs new file mode 100644 index 0000000000000..7adcd712ba6ab --- /dev/null +++ b/src/test/rustdoc-json/generic-associated-types/gats.rs @@ -0,0 +1,42 @@ +// 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; +} diff --git a/src/test/rustdoc/generic-associated-types/gats.rs b/src/test/rustdoc/generic-associated-types/gats.rs new file mode 100644 index 0000000000000..ae981b9499a67 --- /dev/null +++ b/src/test/rustdoc/generic-associated-types/gats.rs @@ -0,0 +1,34 @@ +#![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); + +// @has foo/trait.LendingIterator.html +// @has - '//*[@id="associatedtype.Item-2"]//h4[@class="code-header"]' "type Item<'a> where Self: 'a = &'a T" +impl LendingIterator for Infinite { + type Item<'a> where Self: 'a = &'a T; + + fn next<'a>(&'a self) -> Self::Item<'a> { + &self.0 + } +}