diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index e10c61901d05a..23bb020716dd5 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1410,6 +1410,12 @@ pub(crate) struct Generics { pub(crate) where_predicates: Vec, } +impl Generics { + pub(crate) fn is_empty(&self) -> bool { + self.params.is_empty() && self.where_predicates.is_empty() + } +} + #[derive(Clone, Debug)] pub(crate) struct Function { pub(crate) decl: FnDecl, diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 09aa042b1d02f..d7276a427c468 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -94,8 +94,7 @@ pub(crate) struct Cache { // Private fields only used when initially crawling a crate to build a cache stack: Vec, - parent_stack: Vec, - parent_is_trait_impl: bool, + parent_stack: Vec, stripped_mod: bool, pub(crate) search_index: Vec, @@ -105,7 +104,7 @@ pub(crate) struct Cache { // then the fully qualified name of the structure isn't presented in `paths` // yet when its implementation methods are being indexed. Caches such methods // and their parent id here and indexes them at the end of crate parsing. - pub(crate) orphan_impl_items: Vec<(DefId, clean::Item)>, + pub(crate) orphan_impl_items: Vec, // Similarly to `orphan_impl_items`, sometimes trait impls are picked up // even though the trait itself is not exported. This can happen if a trait @@ -261,7 +260,11 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { let (parent, is_inherent_impl_item) = match *item.kind { clean::StrippedItem(..) => ((None, None), false), clean::AssocConstItem(..) | clean::AssocTypeItem(..) - if self.cache.parent_is_trait_impl => + if self + .cache + .parent_stack + .last() + .map_or(false, |parent| parent.is_trait_impl()) => { // skip associated items in trait impls ((None, None), false) @@ -272,7 +275,14 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { | clean::StructFieldItem(..) | clean::VariantItem(..) => ( ( - Some(*self.cache.parent_stack.last().expect("parent_stack is empty")), + Some( + self.cache + .parent_stack + .last() + .expect("parent_stack is empty") + .item_id() + .expect_def_id(), + ), Some(&self.cache.stack[..self.cache.stack.len() - 1]), ), false, @@ -282,8 +292,11 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { ((None, None), false) } else { let last = self.cache.parent_stack.last().expect("parent_stack is empty 2"); - let did = *last; - let path = match self.cache.paths.get(&did) { + let did = match &*last { + ParentStackItem::Impl { for_, .. } => for_.def_id(&self.cache), + ParentStackItem::Type(item_id) => item_id.as_def_id(), + }; + let path = match did.and_then(|did| self.cache.paths.get(&did)) { // The current stack not necessarily has correlation // for where the type was defined. On the other // hand, `paths` always has the right @@ -291,7 +304,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { Some(&(ref fqp, _)) => Some(&fqp[..fqp.len() - 1]), None => None, }; - ((Some(*last), path), true) + ((did, path), true) } } _ => ((None, Some(&*self.cache.stack)), false), @@ -315,7 +328,12 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { desc, parent, parent_idx: None, - search_type: get_function_type_for_search(&item, self.tcx, self.cache), + search_type: get_function_type_for_search( + &item, + self.tcx, + clean_impl_generics(self.cache.parent_stack.last()).as_ref(), + self.cache, + ), aliases: item.attrs.get_doc_aliases(), }); } @@ -323,7 +341,12 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { (Some(parent), None) if is_inherent_impl_item => { // We have a parent, but we don't know where they're // defined yet. Wait for later to index this item. - self.cache.orphan_impl_items.push((parent, item.clone())); + let impl_generics = clean_impl_generics(self.cache.parent_stack.last()); + self.cache.orphan_impl_items.push(OrphanImplItem { + parent, + item: item.clone(), + impl_generics, + }); } _ => {} } @@ -398,51 +421,23 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { } } - // Maintain the parent stack - let orig_parent_is_trait_impl = self.cache.parent_is_trait_impl; - let parent_pushed = match *item.kind { + // Maintain the parent stack. + let (item, parent_pushed) = match *item.kind { clean::TraitItem(..) | clean::EnumItem(..) | clean::ForeignTypeItem | clean::StructItem(..) | clean::UnionItem(..) - | clean::VariantItem(..) => { - self.cache.parent_stack.push(item.item_id.expect_def_id()); - self.cache.parent_is_trait_impl = false; - true - } - clean::ImplItem(ref i) => { - self.cache.parent_is_trait_impl = i.trait_.is_some(); - match i.for_ { - clean::Type::Path { ref path } => { - self.cache.parent_stack.push(path.def_id()); - true - } - clean::DynTrait(ref bounds, _) - | clean::BorrowedRef { type_: box clean::DynTrait(ref bounds, _), .. } => { - self.cache.parent_stack.push(bounds[0].trait_.def_id()); - true - } - ref t => { - let prim_did = t - .primitive_type() - .and_then(|t| self.cache.primitive_locations.get(&t).cloned()); - match prim_did { - Some(did) => { - self.cache.parent_stack.push(did); - true - } - None => false, - } - } - } + | clean::VariantItem(..) + | clean::ImplItem(..) => { + self.cache.parent_stack.push(ParentStackItem::new(&item)); + (self.fold_item_recur(item), true) } - _ => false, + _ => (self.fold_item_recur(item), false), }; // Once we've recursively found all the generics, hoard off all the // implementations elsewhere. - let item = self.fold_item_recur(item); let ret = if let clean::Item { kind: box clean::ImplItem(ref i), .. } = item { // Figure out the id of this impl. This may map to a // primitive rather than always to a struct/enum. @@ -511,7 +506,64 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { self.cache.parent_stack.pop().expect("parent stack already empty"); } self.cache.stripped_mod = orig_stripped_mod; - self.cache.parent_is_trait_impl = orig_parent_is_trait_impl; ret } } + +pub(crate) struct OrphanImplItem { + pub(crate) parent: DefId, + pub(crate) item: clean::Item, + pub(crate) impl_generics: Option<(clean::Type, clean::Generics)>, +} + +/// Information about trait and type parents is tracked while traversing the item tree to build +/// the cache. +/// +/// We don't just store `Item` in there, because `Item` contains the list of children being +/// traversed and it would be wasteful to clone all that. We also need the item id, so just +/// storing `ItemKind` won't work, either. +enum ParentStackItem { + Impl { + for_: clean::Type, + trait_: Option, + generics: clean::Generics, + kind: clean::ImplKind, + item_id: ItemId, + }, + Type(ItemId), +} + +impl ParentStackItem { + fn new(item: &clean::Item) -> Self { + match &*item.kind { + clean::ItemKind::ImplItem(clean::Impl { for_, trait_, generics, kind, .. }) => { + ParentStackItem::Impl { + for_: for_.clone(), + trait_: trait_.clone(), + generics: generics.clone(), + kind: kind.clone(), + item_id: item.item_id, + } + } + _ => ParentStackItem::Type(item.item_id), + } + } + fn is_trait_impl(&self) -> bool { + matches!(self, ParentStackItem::Impl { trait_: Some(..), .. }) + } + fn item_id(&self) -> ItemId { + match self { + ParentStackItem::Impl { item_id, .. } => *item_id, + ParentStackItem::Type(item_id) => *item_id, + } + } +} + +fn clean_impl_generics(item: Option<&ParentStackItem>) -> Option<(clean::Type, clean::Generics)> { + if let Some(ParentStackItem::Impl { for_, generics, kind: clean::ImplKind::Normal, .. }) = item + { + Some((for_.clone(), generics.clone())) + } else { + None + } +} diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 3daef3dbb7947..25c70f0808c6d 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -8,7 +8,7 @@ use serde::ser::{Serialize, SerializeStruct, Serializer}; use crate::clean; use crate::clean::types::{FnRetTy, Function, GenericBound, Generics, Type, WherePredicate}; -use crate::formats::cache::Cache; +use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; use crate::html::markdown::short_markdown_summary; @@ -25,8 +25,8 @@ pub(crate) fn build_index<'tcx>( // Attach all orphan items to the type's definition if the type // has since been learned. - for &(did, ref item) in &cache.orphan_impl_items { - if let Some(&(ref fqp, _)) = cache.paths.get(&did) { + for &OrphanImplItem { parent, ref item, ref impl_generics } in &cache.orphan_impl_items { + if let Some(&(ref fqp, _)) = cache.paths.get(&parent) { let desc = item .doc_value() .map_or_else(String::new, |s| short_markdown_summary(&s, &item.link_names(cache))); @@ -35,9 +35,9 @@ pub(crate) fn build_index<'tcx>( name: item.name.unwrap().to_string(), path: join_with_double_colon(&fqp[..fqp.len() - 1]), desc, - parent: Some(did), + parent: Some(parent), parent_idx: None, - search_type: get_function_type_for_search(item, tcx, cache), + search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache), aliases: item.attrs.get_doc_aliases(), }); } @@ -192,12 +192,13 @@ pub(crate) fn build_index<'tcx>( pub(crate) fn get_function_type_for_search<'tcx>( item: &clean::Item, tcx: TyCtxt<'tcx>, + impl_generics: Option<&(clean::Type, clean::Generics)>, cache: &Cache, ) -> Option { let (mut inputs, mut output) = match *item.kind { - clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, cache), - clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, cache), - clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, cache), + clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache), + clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache), + clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache), _ => return None, }; @@ -247,9 +248,10 @@ fn get_index_type_name(clean_type: &clean::Type) -> Option { /// Important note: It goes through generics recursively. So if you have /// `T: Option>`, it'll go into `Option` and then into `Result`. #[instrument(level = "trace", skip(tcx, res, cache))] -fn add_generics_and_bounds_as_types<'tcx>( +fn add_generics_and_bounds_as_types<'tcx, 'a>( + self_: Option<&'a Type>, generics: &Generics, - arg: &Type, + arg: &'a Type, tcx: TyCtxt<'tcx>, recurse: usize, res: &mut Vec, @@ -334,6 +336,17 @@ fn add_generics_and_bounds_as_types<'tcx>( return; } + // First, check if it's "Self". + let arg = if let Some(self_) = self_ { + match &*arg { + Type::BorrowedRef { type_, .. } if type_.is_self_type() => self_, + type_ if type_.is_self_type() => self_, + arg => arg, + } + } else { + arg + }; + // If this argument is a type parameter and not a trait bound or a type, we need to look // for its bounds. if let Type::Generic(arg_s) = *arg { @@ -350,6 +363,7 @@ fn add_generics_and_bounds_as_types<'tcx>( match ¶m_def.kind { clean::GenericParamDefKind::Type { default: Some(ty), .. } => { add_generics_and_bounds_as_types( + self_, generics, ty, tcx, @@ -372,6 +386,7 @@ fn add_generics_and_bounds_as_types<'tcx>( if let Some(path) = bound.get_trait_path() { let ty = Type::Path { path }; add_generics_and_bounds_as_types( + self_, generics, &ty, tcx, @@ -393,6 +408,7 @@ fn add_generics_and_bounds_as_types<'tcx>( if let Some(arg_generics) = arg.generics() { for gen in arg_generics.iter() { add_generics_and_bounds_as_types( + self_, generics, gen, tcx, @@ -413,18 +429,33 @@ fn add_generics_and_bounds_as_types<'tcx>( fn get_fn_inputs_and_outputs<'tcx>( func: &Function, tcx: TyCtxt<'tcx>, + impl_generics: Option<&(clean::Type, clean::Generics)>, cache: &Cache, ) -> (Vec, Vec) { let decl = &func.decl; - let generics = &func.generics; + + let combined_generics; + let (self_, generics) = if let Some(&(ref impl_self, ref impl_generics)) = impl_generics { + match (impl_generics.is_empty(), func.generics.is_empty()) { + (true, _) => (Some(impl_self), &func.generics), + (_, true) => (Some(impl_self), impl_generics), + (false, false) => { + let mut params = func.generics.params.clone(); + params.extend(impl_generics.params.clone()); + let mut where_predicates = func.generics.where_predicates.clone(); + where_predicates.extend(impl_generics.where_predicates.clone()); + combined_generics = clean::Generics { params, where_predicates }; + (Some(impl_self), &combined_generics) + } + } + } else { + (None, &func.generics) + }; let mut all_types = Vec::new(); for arg in decl.inputs.values.iter() { - if arg.type_.is_self_type() { - continue; - } let mut args = Vec::new(); - add_generics_and_bounds_as_types(generics, &arg.type_, tcx, 0, &mut args, cache); + add_generics_and_bounds_as_types(self_, generics, &arg.type_, tcx, 0, &mut args, cache); if !args.is_empty() { all_types.extend(args); } else { @@ -437,7 +468,15 @@ fn get_fn_inputs_and_outputs<'tcx>( let mut ret_types = Vec::new(); match decl.output { FnRetTy::Return(ref return_type) => { - add_generics_and_bounds_as_types(generics, return_type, tcx, 0, &mut ret_types, cache); + add_generics_and_bounds_as_types( + self_, + generics, + return_type, + tcx, + 0, + &mut ret_types, + cache, + ); if ret_types.is_empty() { if let Some(kind) = return_type.def_id(cache).map(|did| tcx.def_kind(did).into()) { ret_types.push(TypeWithKind::from((get_index_type(return_type, vec![]), kind))); diff --git a/src/test/rustdoc-js/generics-impl.js b/src/test/rustdoc-js/generics-impl.js new file mode 100644 index 0000000000000..bb6e0041db5f2 --- /dev/null +++ b/src/test/rustdoc-js/generics-impl.js @@ -0,0 +1,57 @@ +// exact-check + +const QUERY = [ + 'Aaaaaaa -> u32', + 'Aaaaaaa -> bool', + 'Aaaaaaa -> usize', + 'Read -> u64', + 'bool -> u64', + 'Ddddddd -> u64', + '-> Ddddddd' +]; + +const EXPECTED = [ + { + // Aaaaaaa -> u32 + 'others': [ + { 'path': 'generics_impl::Aaaaaaa', 'name': 'bbbbbbb' }, + ], + }, + { + // Aaaaaaa -> bool + 'others': [ + { 'path': 'generics_impl::Aaaaaaa', 'name': 'ccccccc' }, + ], + }, + { + // Aaaaaaa -> usize + 'others': [ + { 'path': 'generics_impl::Aaaaaaa', 'name': 'read' }, + ], + }, + { + // Read -> u64 + 'others': [ + { 'path': 'generics_impl::Ddddddd', 'name': 'eeeeeee' }, + { 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' }, + ], + }, + { + // bool -> u64 + 'others': [ + { 'path': 'generics_impl::Ddddddd', 'name': 'fffffff' }, + ], + }, + { + // Ddddddd -> u64 + 'others': [ + { 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' }, + ], + }, + { + // -> Ddddddd + 'others': [ + { 'path': 'generics_impl::Ddddddd', 'name': 'hhhhhhh' }, + ], + }, +]; diff --git a/src/test/rustdoc-js/generics-impl.rs b/src/test/rustdoc-js/generics-impl.rs new file mode 100644 index 0000000000000..696218021d5af --- /dev/null +++ b/src/test/rustdoc-js/generics-impl.rs @@ -0,0 +1,35 @@ +use std::io::{Result as IoResult, Read}; + +pub struct Aaaaaaa; + +impl Aaaaaaa { + pub fn bbbbbbb(self) -> u32 { + 1 + } + pub fn ccccccc(&self) -> bool { + true + } +} + +impl Read for Aaaaaaa { + fn read(&mut self, out: &mut [u8]) -> IoResult { + Ok(out.len()) + } +} + +pub struct Ddddddd(T); + +impl Ddddddd { + pub fn eeeeeee(_: T) -> u64 { + 1 + } + pub fn fffffff(_: bool) -> u64 { + 1 + } + pub fn ggggggg(self) -> u64 { + 1 + } + pub fn hhhhhhh() -> Self where T: Default { + Ddddddd(T::default()) + } +}