diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 7f81e29180baf..86d4c8e6599a8 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -598,8 +598,9 @@ pub(crate) fn build_impl( }; let trait_ = associated_trait .map(|t| clean_trait_ref_with_constraints(cx, ty::Binder::dummy(t), ThinVec::new())); - if trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() - && polarity != ty::ImplPolarity::Negative + if trait_ + .as_ref() + .is_some_and(|t| polarity != ty::ImplPolarity::Negative && t.is_deref_trait(tcx)) { super::build_deref_target_impls(cx, &trait_items, ret); } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 1b4fb883cbde8..b3b762bb2b771 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -3104,7 +3104,7 @@ fn clean_impl<'tcx>( // If this impl block is a positive implementation of the Deref trait, then we // need to try inlining the target's inherent impl blocks as well. - if trait_.as_ref().is_some_and(|t| tcx.lang_items().deref_trait() == Some(t.def_id())) + if trait_.as_ref().is_some_and(|t| t.is_deref_trait(tcx)) && tcx.impl_polarity(def_id) != ty::ImplPolarity::Negative { build_deref_target_impls(cx, &items, &mut ret); diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index c34b33d9c92f5..032749b05c064 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -21,7 +21,7 @@ use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::{BodyId, Mutability}; use rustc_index::IndexVec; use rustc_metadata::rendered_const; -use rustc_middle::ty::fast_reject::SimplifiedType; +use rustc_middle::ty::fast_reject::{DeepRejectCtxt, SimplifiedType}; use rustc_middle::ty::{self, Ty, TyCtxt, Visibility}; use rustc_resolve::rustdoc::{ DocFragment, add_doc_fragment, attrs_to_doc_fragments, span_of_fragments, @@ -1380,6 +1380,22 @@ pub(crate) struct PolyTrait { pub(crate) generic_params: Vec, } +// Also used in `write_shared.rs` when generating the JS `impl` file for aliased types. +// +// FIXME(checked_type_alias): Once the feature is complete or stable, rewrite this +// to use type unification. +// Be aware of `tests/rustdoc-html/type-alias/deeply-nested-112515.rs` which might +// regress. +pub(crate) fn impl_may_apply_to_type_alias( + tcx: TyCtxt<'_>, + alias_def_id: DefId, + impl_def_id: DefId, +) -> bool { + let alias_ty = tcx.type_of(alias_def_id).skip_binder(); + let for_ty = tcx.type_of(impl_def_id).skip_binder(); + DeepRejectCtxt::relate_infer_infer(tcx).types_may_unify(alias_ty, for_ty) +} + /// Rustdoc's representation of types, mostly based on the [`hir::Ty`]. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) enum Type { @@ -1469,7 +1485,7 @@ impl Type { /// /// An owned type is also the same as its borrowed variants (this is commutative), /// but `&T` is not the same as `&mut T`. - pub(crate) fn is_doc_subtype_of(&self, other: &Self, cache: &Cache) -> bool { + pub(crate) fn is_doc_subtype_of(&self, other: &Self, cx: &Context<'_>) -> bool { // Strip the references so that it can compare the actual types, unless both are references. // If both are references, leave them alone and compare the mutabilities later. let (self_cleared, other_cleared) = if !self.is_borrowed_ref() || !other.is_borrowed_ref() { @@ -1478,29 +1494,33 @@ impl Type { (self, other) }; - // FIXME: `Cache` does not have the data required to unwrap type aliases, - // so we just assume they are equal. - // This is only remotely acceptable because we were previously - // assuming all types were equal when used - // as a generic parameter of a type in `Deref::Target`. + // FIXME(checked_type_alias): Once the feature is complete or stable, rewrite this + // to use type unification. + // Be aware of `tests/rustdoc-html/type-alias/deeply-nested-112515.rs` which might + // regress. if self_cleared.is_type_alias() || other_cleared.is_type_alias() { - return true; + let cache = cx.cache(); + if let Some(self_def_id) = self_cleared.def_id(&cache) + && let Some(other_def_id) = other_cleared.def_id(&cache) + { + return impl_may_apply_to_type_alias(cx.tcx(), self_def_id, other_def_id); + } } match (self_cleared, other_cleared) { // Recursive cases. (Type::Tuple(a), Type::Tuple(b)) => { - a.iter().eq_by(b, |a, b| a.is_doc_subtype_of(b, cache)) + a.iter().eq_by(b, |a, b| a.is_doc_subtype_of(b, cx)) } - (Type::Slice(a), Type::Slice(b)) => a.is_doc_subtype_of(b, cache), - (Type::Array(a, al), Type::Array(b, bl)) => al == bl && a.is_doc_subtype_of(b, cache), + (Type::Slice(a), Type::Slice(b)) => a.is_doc_subtype_of(b, cx), + (Type::Array(a, al), Type::Array(b, bl)) => al == bl && a.is_doc_subtype_of(b, cx), (Type::RawPointer(mutability, type_), Type::RawPointer(b_mutability, b_type_)) => { - mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cache) + mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cx) } ( Type::BorrowedRef { mutability, type_, .. }, Type::BorrowedRef { mutability: b_mutability, type_: b_type_, .. }, - ) => mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cache), + ) => mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cx), // Placeholders are equal to all other types. (Type::Infer, _) | (_, Type::Infer) => true, // Generics match everything on the right, but not on the left. @@ -1514,13 +1534,13 @@ impl Type { a.def_id() == b.def_id() && a.generics() .zip(b.generics()) - .map(|(ag, bg)| ag.zip(bg).all(|(at, bt)| at.is_doc_subtype_of(bt, cache))) + .map(|(ag, bg)| ag.zip(bg).all(|(at, bt)| at.is_doc_subtype_of(bt, cx))) .unwrap_or(true) } // Other cases, such as primitives, just use recursion. (a, b) => a - .def_id(cache) - .and_then(|a| Some((a, b.def_id(cache)?))) + .def_id(cx.cache()) + .and_then(|a| Some((a, b.def_id(cx.cache())?))) .map(|(a, b)| a == b) .unwrap_or(false), } @@ -2149,6 +2169,10 @@ impl Path { } }) } + + pub(crate) fn is_deref_trait(&self, tcx: TyCtxt<'_>) -> bool { + tcx.lang_items().deref_trait() == Some(self.def_id()) + } } #[derive(Clone, PartialEq, Eq, Debug, Hash)] diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index 29a8fd5e2678e..13437e5a008c6 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -69,16 +69,3 @@ fn should_not_trim() { run_test("\t line1 \n\t line2", "line1 \nline2"); run_test(" \tline1 \n \tline2", "line1 \nline2"); } - -#[test] -fn is_same_generic() { - use crate::clean::types::{PrimitiveType, Type}; - use crate::formats::cache::Cache; - create_default_session_globals_then(|| { - let cache = Cache::new(false, false); - let generic = Type::Generic(sym::character('T')); - let unit = Type::Primitive(PrimitiveType::Unit); - assert!(!generic.is_doc_subtype_of(&unit, &cache)); - assert!(unit.is_doc_subtype_of(&generic, &cache)); - }) -} diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs index 80c110ec07f56..3238484c3a424 100644 --- a/src/librustdoc/formats/mod.rs +++ b/src/librustdoc/formats/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod renderer; pub(crate) use renderer::{FormatRenderer, run_format}; use rustc_hir::def_id::DefId; +use rustc_middle::ty::TyCtxt; use crate::clean::{self, ItemId}; use crate::html::render::Context; @@ -26,6 +27,14 @@ impl Impl { self.inner_impl().trait_.as_ref().map(|t| t.def_id()) } + pub(crate) fn is_deref_trait(&self, tcx: TyCtxt<'_>) -> bool { + self.trait_did() == tcx.lang_items().deref_trait() + } + + pub(crate) fn is_deref_mut_trait(&self, tcx: TyCtxt<'_>) -> bool { + self.trait_did() == tcx.lang_items().deref_mut_trait() + } + /// This function is used to extract a `DefId` to be used as a key for the `Cache::impls` field. /// /// It allows to prevent having duplicated implementations showing up (the biggest issue was diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index c2d8cc9ebae3e..53c6976d8874a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -39,7 +39,7 @@ mod sorted_template; mod type_layout; mod write_shared; -use std::borrow::Cow; +use std::borrow::{Borrow, Cow}; use std::cmp::Ordering; use std::collections::{BTreeMap, VecDeque}; use std::fmt::{self, Display as _, Write}; @@ -1467,16 +1467,77 @@ fn render_all_impls( Ok(()) } +fn aliased_type_deref_impl<'a>( + cx: &'a Context<'_>, + alias_def_id: DefId, + aliased_def_id: DefId, +) -> Option<(&'a Impl, bool)> { + let tcx = cx.tcx(); + let applies = |impl_: &Impl| { + impl_.impl_item.item_id.as_def_id().is_some_and(|impl_def_id| { + clean::types::impl_may_apply_to_type_alias(tcx, alias_def_id, impl_def_id) + }) + }; + let impls = cx.cache().impls.get(&aliased_def_id)?; + let mut deref_impl = None; + let mut has_deref_mut = false; + + for impl_ in impls { + if impl_.is_deref_trait(tcx) { + if !impl_.is_negative_trait_impl() && applies(impl_) { + deref_impl = Some(impl_); + } + } else if impl_.is_deref_mut_trait(tcx) && applies(impl_) { + has_deref_mut = true; + } + } + Some((deref_impl?, has_deref_mut)) +} + +/// Try to get the `Deref` trait `Impl` for `item`. If it cannot be found, it will try to do the +/// same with `aliased_type`. Returns a tuple containing the `Deref` `Impl`, and a bool set to +/// `true` if `DerefMut` is also implemented. +fn get_deref_impls<'a, I: Borrow>( + cx: &'a Context<'_>, + trait_impls: &'a [I], + item: DefId, + aliased_type: Option, +) -> Option<(&'a Impl, bool)> { + let tcx = cx.tcx(); + let mut impls = trait_impls.iter().map(|t| t.borrow()); + if let Some(impl_) = + impls.clone().find(|t| t.is_deref_trait(tcx) && !t.is_negative_trait_impl()) + { + let has_deref_mut = impls.any(|t| t.is_deref_mut_trait(tcx)); + Some((impl_, has_deref_mut)) + } else { + aliased_type.and_then(|aliased_did| aliased_type_deref_impl(cx, item, aliased_did)) + } +} + fn render_assoc_items( cx: &Context<'_>, containing_item: &clean::Item, it: DefId, what: AssocItemRender<'_>, +) -> impl fmt::Display { + render_assoc_items_with_aliased_type(cx, containing_item, it, what, None) +} + +fn render_assoc_items_with_aliased_type( + cx: &Context<'_>, + containing_item: &clean::Item, + it: DefId, + what: AssocItemRender<'_>, + aliased_type: Option, ) -> impl fmt::Display { fmt::from_fn(move |f| { let mut derefs = DefIdSet::default(); derefs.insert(it); - render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs) + if let Some(aliased_type) = aliased_type { + derefs.insert(aliased_type); + } + render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs, aliased_type) }) } @@ -1487,10 +1548,15 @@ fn render_assoc_items_inner( it: DefId, what: AssocItemRender<'_>, derefs: &mut DefIdSet, + aliased_type: Option, ) -> fmt::Result { info!("Documenting associated items of {:?}", containing_item.name); let cache = &cx.shared.cache; - let Some(impls) = cache.impls.get(&it) else { return Ok(()) }; + let impls = match cache.impls.get(&it) { + Some(impls) => impls.as_slice(), + None if aliased_type.is_some() => &[], + None => return Ok(()), + }; let (mut inherent_impls, trait_impls): (Vec<_>, _) = impls.iter().partition(|i| i.inner_impl().trait_.is_none()); if !inherent_impls.is_empty() { @@ -1516,9 +1582,8 @@ fn render_assoc_items_inner( // we should not show methods from `[MaybeUninit]`. // this `retain` filters out any instances where // the types do not line up perfectly. - inherent_impls.retain(|impl_| { - type_.is_doc_subtype_of(&impl_.inner_impl().for_, &cx.shared.cache) - }); + inherent_impls + .retain(|impl_| type_.is_doc_subtype_of(&impl_.inner_impl().for_, &cx)); let derived_id = cx.derive_id(&id); if let Some(def_id) = type_.def_id(cx.cache()) { cx.deref_id_map.borrow_mut().insert(def_id, id.clone()); @@ -1578,17 +1643,11 @@ fn render_assoc_items_inner( } } - if !trait_impls.is_empty() { - let deref_impl = trait_impls.iter().find(|t| { - t.trait_did() == cx.tcx().lang_items().deref_trait() && !t.is_negative_trait_impl() - }); - if let Some(impl_) = deref_impl { - let has_deref_mut = trait_impls - .iter() - .any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait()); - render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs)?; - } + if let Some((impl_, has_deref_mut)) = get_deref_impls(cx, &trait_impls, it, aliased_type) { + render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs)?; + } + if !trait_impls.is_empty() { // If we were already one level into rendering deref methods, we don't want to render // anything after recursing into any further deref methods above. if let AssocItemRender::DerefFor { .. } = what { @@ -1646,11 +1705,11 @@ fn render_deref_methods( return Ok(()); } } - render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs)?; + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs, None)?; } else if let Some(prim) = target.primitive_type() && let Some(&did) = cache.primitive_locations.get(&prim) { - render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs)?; + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs, None)?; } Ok(()) } @@ -1711,7 +1770,7 @@ fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option) -> (String, String) { .filter(|impl_| impl_.polarity == ty::ImplPolarity::Positive) .filter(|impl_| { // Two different types might have the same did, without actually being the same. - ty.is_doc_subtype_of(&impl_.for_, cx.cache()) + ty.is_doc_subtype_of(&impl_.for_, cx) }) .filter_map(|impl_| { if let Some(trait_) = &impl_.trait_ diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 544d57d92613c..b34a778889728 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -23,9 +23,9 @@ use super::{ AssocItemLink, AssocItemRender, Context, ImplRenderingParameters, RenderMode, collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference, item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls, - render_assoc_item, render_assoc_items, render_attributes_in_code, render_impl, - render_repr_attribute_in_code, render_rightside, render_stability_since_raw, - render_stability_since_raw_with_extra, write_section_heading, + render_assoc_item, render_assoc_items, render_assoc_items_with_aliased_type, + render_attributes_in_code, render_impl, render_repr_attribute_in_code, render_rightside, + render_stability_since_raw, render_stability_since_raw_with_extra, write_section_heading, }; use crate::clean; use crate::config::ModuleSorting; @@ -1499,7 +1499,13 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { fn render_assoc_items(&self) -> impl fmt::Display { let def_id = self.it.item_id.expect_def_id(); - render_assoc_items(self.cx, self.it, def_id, AssocItemRender::All) + render_assoc_items_with_aliased_type( + self.cx, + self.it, + def_id, + AssocItemRender::All, + if self.is_type_alias { Some(self.def_id) } else { None }, + ) } fn render_union(&self) -> impl Display { @@ -1642,7 +1648,13 @@ impl<'clean> DisplayEnum<'clean> { write!( w, "{}{}", - render_assoc_items(cx, it, def_id, AssocItemRender::All), + render_assoc_items_with_aliased_type( + cx, + it, + def_id, + AssocItemRender::All, + if is_type_alias { Some(self.def_id) } else { None }, + ), document_type_layout(cx, layout_def_id) ) } @@ -2098,7 +2110,13 @@ impl<'a> DisplayStruct<'a> { w, "{}{}{}", item_fields(cx, it, self.fields, self.ctor_kind), - render_assoc_items(cx, it, def_id, AssocItemRender::All), + render_assoc_items_with_aliased_type( + cx, + it, + def_id, + AssocItemRender::All, + if is_type_alias { Some(self.def_id) } else { None }, + ), document_type_layout(cx, def_id), ) } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 7b282b0c61d7e..6198b47b95dc9 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -5,7 +5,7 @@ use std::fmt; use askama::Template; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::CtorKind; -use rustc_hir::def_id::{DefIdMap, DefIdSet}; +use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet}; use tracing::debug; use super::{Context, ItemSection, impl_trait_key, item_ty_to_section}; @@ -284,7 +284,7 @@ fn sidebar_struct<'a>( if let Some(name) = field_name { items.push(LinkBlock::new(Link::new("fields", name), "structfield", fields)); } - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } fn sidebar_trait<'a>( @@ -352,7 +352,7 @@ fn sidebar_trait<'a>( blocks.push(LinkBlock::new(Link::new("provided-methods", "Provided Methods"), "", prov_method)); - sidebar_assoc_items(cx, it, blocks, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), blocks, deref_id_map); if !t.is_dyn_compatible(tcx) { blocks.push(LinkBlock::forced( @@ -383,7 +383,7 @@ fn sidebar_primitive<'a>( deref_id_map: &'a DefIdMap, ) { if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } else { let (concrete, synthetic, blanket_impl) = super::get_filtered_impls_for_reference(&cx.shared, it); @@ -420,7 +420,17 @@ fn sidebar_type_alias<'a>( } } } - sidebar_assoc_items(cx, it, items, deref_id_map); + let alias_def_id = it.item_id.expect_def_id(); + let aliased_type = t.inner_type.as_ref().and_then(|_| { + cx.tcx().type_of(alias_def_id).instantiate_identity().skip_norm_wip().ty_adt_def() + }); + sidebar_assoc_items_with_aliased_type( + cx, + alias_def_id, + items, + deref_id_map, + aliased_type.map(|adt| adt.did()), + ); } fn sidebar_union<'a>( @@ -432,106 +442,106 @@ fn sidebar_union<'a>( ) { let fields = get_struct_fields_name(&u.fields); items.push(LinkBlock::new(Link::new("fields", "Fields"), "structfield", fields)); - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } /// Adds trait implementations into the blocks of links fn sidebar_assoc_items<'a>( cx: &'a Context<'_>, - it: &'a clean::Item, + item_def_id: DefId, + links: &mut Vec>, + deref_id_map: &'a DefIdMap, +) { + sidebar_assoc_items_with_aliased_type(cx, item_def_id, links, deref_id_map, None) +} + +fn sidebar_assoc_items_with_aliased_type<'a>( + cx: &'a Context<'_>, + item_def_id: DefId, links: &mut Vec>, deref_id_map: &'a DefIdMap, + aliased_type: Option, ) { - let did = it.item_id.expect_def_id(); let cache = cx.cache(); + let v = match cache.impls.get(&item_def_id) { + Some(impls) => impls.as_slice(), + None if aliased_type.is_some() => &[], + None => return, + }; let mut assoc_consts = Vec::new(); let mut assoc_types = Vec::new(); let mut assoc_fns = Vec::new(); let mut methods = Vec::new(); - if let Some(v) = cache.impls.get(&did) { - let mut used_links = UsedLinks::default(); - let mut id_map = IdMap::new(); + let mut used_links = UsedLinks::default(); + let mut id_map = IdMap::new(); - { - let used_links_bor = &mut used_links; - for impl_ in v.iter().map(|i| i.inner_impl()).filter(|i| i.trait_.is_none()) { - assoc_consts.extend(get_associated_constants(impl_, used_links_bor)); - assoc_types.extend(get_associated_types(impl_, used_links_bor)); - methods.extend(get_methods( - impl_, - GetMethodsMode::AlsoCollectAssocFns { assoc_fns: &mut assoc_fns }, - used_links_bor, - cx, - )); - } - // We want links' order to be reproducible so we don't use unstable sort. - assoc_consts.sort(); - assoc_types.sort(); - postprocess_method_list(&mut assoc_fns); - postprocess_method_list(&mut methods); + { + let used_links_bor = &mut used_links; + for impl_ in v.iter().map(|i| i.inner_impl()).filter(|i| i.trait_.is_none()) { + assoc_consts.extend(get_associated_constants(impl_, used_links_bor)); + assoc_types.extend(get_associated_types(impl_, used_links_bor)); + methods.extend(get_methods( + impl_, + GetMethodsMode::AlsoCollectAssocFns { assoc_fns: &mut assoc_fns }, + used_links_bor, + cx, + )); } + // We want links' order to be reproducible so we don't use unstable sort. + assoc_consts.sort(); + assoc_types.sort(); + postprocess_method_list(&mut assoc_fns); + postprocess_method_list(&mut methods); + } - let mut blocks = vec![ - LinkBlock::new( - Link::new("implementations", "Associated Constants"), - "associatedconstant", - assoc_consts, - ), - LinkBlock::new( - Link::new("implementations", "Associated Types"), - "associatedtype", - assoc_types, - ), - LinkBlock::new( - Link::new("implementations", "Associated Functions"), - "method", - assoc_fns, - ), - LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), - ]; - - if v.iter().any(|i| i.inner_impl().trait_.is_some()) { - if let Some(impl_) = v.iter().find(|i| { - i.trait_did() == cx.tcx().lang_items().deref_trait() && !i.is_negative_trait_impl() - }) { - let mut derefs = DefIdSet::default(); - derefs.insert(did); - sidebar_deref_methods( - cx, - &mut blocks, - impl_, - v, - &mut derefs, - &mut used_links, - deref_id_map, - ); - } - - let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = - v.iter().partition::, _>(|i| i.inner_impl().kind.is_auto()); - let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = - concrete.into_iter().partition::, _>(|i| i.inner_impl().kind.is_blanket()); + let mut blocks = vec![ + LinkBlock::new( + Link::new("implementations", "Associated Constants"), + "associatedconstant", + assoc_consts, + ), + LinkBlock::new( + Link::new("implementations", "Associated Types"), + "associatedtype", + assoc_types, + ), + LinkBlock::new(Link::new("implementations", "Associated Functions"), "method", assoc_fns), + LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), + ]; + + if let Some((impl_, deref_mut)) = super::get_deref_impls(cx, v, item_def_id, aliased_type) { + let mut derefs = DefIdSet::default(); + derefs.insert(item_def_id); + derefs.extend(aliased_type); + sidebar_deref_methods( + cx, + &mut blocks, + impl_, + deref_mut, + &mut derefs, + &mut used_links, + deref_id_map, + ); + } - sidebar_render_assoc_items( - cx, - &mut id_map, - concrete, - synthetic, - blanket_impl, - &mut blocks, - ); - } + if v.iter().any(|i| i.inner_impl().trait_.is_some()) { + let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = + v.iter().partition::, _>(|i| i.inner_impl().kind.is_auto()); + let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = + concrete.into_iter().partition::, _>(|i| i.inner_impl().kind.is_blanket()); - links.append(&mut blocks); + sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl, &mut blocks); } + + links.append(&mut blocks); } fn sidebar_deref_methods<'a>( cx: &'a Context<'_>, out: &mut Vec>, impl_: &Impl, - v: &[Impl], + deref_mut: bool, derefs: &mut DefIdSet, used_links: &mut UsedLinks, deref_id_map: &'a DefIdMap, @@ -549,6 +559,7 @@ fn sidebar_deref_methods<'a>( }) { debug!("found target, real_target: {target:?} {real_target:?}"); + if let Some(did) = target.def_id(c) && let Some(type_did) = impl_.inner_impl().for_.def_id(c) && // `impl Deref for S` @@ -558,7 +569,6 @@ fn sidebar_deref_methods<'a>( return; } let tcx = cx.tcx(); - let deref_mut = v.iter().any(|i| i.trait_did() == tcx.lang_items().deref_mut_trait()); let inner_impl = target .def_id(c) .or_else(|| { @@ -573,7 +583,7 @@ fn sidebar_deref_methods<'a>( .iter() .filter(|i| { i.inner_impl().trait_.is_none() - && real_target.is_doc_subtype_of(&i.inner_impl().for_, c) + && real_target.is_doc_subtype_of(&i.inner_impl().for_, cx) }) .flat_map(|i| { get_methods( @@ -611,11 +621,7 @@ fn sidebar_deref_methods<'a>( if let Some(target_did) = target.def_id(c) && let Some(target_impls) = c.impls.get(&target_did) && let Some(target_deref_impl) = target_impls.iter().find(|i| { - i.inner_impl() - .trait_ - .as_ref() - .map(|t| Some(t.def_id()) == tcx.lang_items().deref_trait()) - .unwrap_or(false) + i.inner_impl().trait_.as_ref().map(|t| t.is_deref_trait(tcx)).unwrap_or(false) && !i.is_negative_trait_impl() }) { @@ -623,7 +629,7 @@ fn sidebar_deref_methods<'a>( cx, out, target_deref_impl, - target_impls, + deref_mut, derefs, used_links, deref_id_map, @@ -647,7 +653,7 @@ fn sidebar_enum<'a>( variants.sort_unstable(); items.push(LinkBlock::new(Link::new("variants", "Variants"), "variant", variants)); - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } pub(crate) fn sidebar_module_like( @@ -708,7 +714,7 @@ fn sidebar_foreign_type<'a>( items: &mut Vec>, deref_id_map: &'a DefIdMap, ) { - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } /// Renders the trait implementations for this type diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 258970bbb3b8f..58d53f5adaf8c 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -28,7 +28,6 @@ use indexmap::IndexMap; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_middle::ty::TyCtxt; -use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_session::Session; use rustc_span::Symbol; use rustc_span::def_id::DefId; @@ -37,6 +36,7 @@ use serde::ser::SerializeSeq; use serde::{Deserialize, Serialize, Serializer}; use super::{Context, RenderMode, collect_paths_for_type, ensure_trailing_slash}; +use crate::clean::types::impl_may_apply_to_type_alias; use crate::clean::{Crate, Item, ItemId, ItemKind}; use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge}; use crate::docfs::PathError; @@ -1015,7 +1015,6 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else { return; }; - let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder(); // Exclude impls that are directly on this type. They're already in the HTML. // Some inlining scenarios can cause there to be two versions of the same // impl: one on the type alias and one on the underlying target type. @@ -1024,15 +1023,8 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ { // Only include this impl if it actually unifies with this alias. // Synthetic impls are not included; those are also included in the HTML. - // - // FIXME(checked_type_alias): Once the feature is complete or stable, rewrite this - // to use type unification. - // Be aware of `tests/rustdoc-html/type-alias/deeply-nested-112515.rs` which might - // regress. let Some(impl_did) = impl_item_id.as_def_id() else { continue }; - let for_ty = self.cx.tcx().type_of(impl_did).skip_binder(); - let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx()); - if !reject_cx.types_may_unify(aliased_ty, for_ty) { + if !impl_may_apply_to_type_alias(self.cx.tcx(), self_did, impl_did) { continue; } // Avoid duplicates diff --git a/tests/rustdoc-html/deref-to-alias.rs b/tests/rustdoc-html/deref-to-alias.rs new file mode 100644 index 0000000000000..696c7dcf7c29f --- /dev/null +++ b/tests/rustdoc-html/deref-to-alias.rs @@ -0,0 +1,32 @@ +// This test ensures that dereferencing to a type alias will still list +// matching methods of the type alias. + +#![crate_name = "foo"] + +use std::ops::Deref; + +pub struct Foo(T); + +impl Foo { + pub fn get_i32(&self) -> i32 { self.0 } +} + +impl Foo { + pub fn get_u32(&self) -> u32 { self.0 } +} + +pub type X = Foo; + +//@ has 'foo/struct.Bar.html' +// FIXME: Only `get_i32` should be listed, this is a bug. +//@ count - '//*[@id="deref-methods-X-1"]//h4' 2 +//@ has - '//*[@id="deref-methods-X-1"]//h4' 'get_i32' +//@ has - '//*[@id="deref-methods-X-1"]//h4' 'get_u32' +pub struct Bar; + +impl Deref for Bar { + type Target = X; + fn deref(&self) -> &Self::Target { + &X(0) + } +} diff --git a/tests/rustdoc-html/deref/deref-methods-aliased-type.rs b/tests/rustdoc-html/deref/deref-methods-aliased-type.rs new file mode 100644 index 0000000000000..5e0c9a1d4e769 --- /dev/null +++ b/tests/rustdoc-html/deref/deref-methods-aliased-type.rs @@ -0,0 +1,87 @@ +// Ensures that aliased types get the "Methods from Deref" but only when it matches +// the aliased type. +// Regression test for . + +// ignore-tidy-file-linelength + +#![crate_name = "foo"] + +use std::ops::{Deref, DerefMut}; + +pub struct Target; + +impl Target { + pub fn target_ref(&self) {} + pub fn target_mut(&mut self) {} +} + +pub struct Other; + +impl Other { + pub fn other_ref(&self) {} + pub fn other_mut(&mut self) {} +} + +pub struct Wrapper(T); + +impl Deref for Wrapper { + type Target = Target; + fn deref(&self) -> &Target { + &Target + } +} + +impl Deref for Wrapper { + type Target = Other; + fn deref(&self) -> &Other { + &Other + } +} + +impl DerefMut for Wrapper { + fn deref_mut(&mut self) -> &mut Other { + &mut Other + } +} + +//@ has 'foo/type.Byte.html' +// Only one deref. +//@ count - '//h2[@id="deref-methods-Target"]' 1 +// Which makes two section headers. One for the "Aliased Type" and one for "Methods from Deref<>". +//@ count - '//*[@class="section-header"]' 2 +//@ has - '//*[@class="section-header"]' 'Aliased Type' +//@ has - '//*[@class="section-header"]' 'Methods from Deref' +// Only one method from the deref (not the one taking `&mut` since `DerefMut` is not +// implemented for `Wrapper`. +//@ count - '//*[@id="deref-methods-Target-1"]/*[@class="method"]' 1 +//@ has - '//*[@id="method.target_ref"]//h4' 'pub fn target_ref(&self)' + +// Same checks but in the sidebar. +//@ count - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a' 1 +//@ has - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.target_ref"]' 'target_ref' +pub type Byte = Wrapper; + +//@ has 'foo/type.Word.html' +// There should be only one `Deref` block (for both `Deref` and `DerefMut`). +//@ count - '//h2[@id="deref-methods-Other"]' 1 +// Which makes two section headers. One for the "Aliased Type" and one for "Methods from Deref<>". +//@ count - '//*[@class="section-header"]' 2 +//@ has - '//*[@class="section-header"]' 'Aliased Type' +//@ has - '//*[@class="section-header"]' 'Methods from Deref' +// Since it implements both `Deref` and `DerefMut`, we should see both methods. +//@ count - '//*[@id="deref-methods-Other-1"]/*[@class="method"]' 2 +//@ has - '//*[@id="method.other_ref"]//h4' 'pub fn other_ref(&self)' +//@ has - '//*[@id="method.other_mut"]//h4' 'pub fn other_mut(&mut self)' + +// Same checks but in the sidebar. +//@ count - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a' 2 +//@ has - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.other_ref"]' 'other_ref' +//@ has - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.other_mut"]' 'other_mut' +pub type Word = Wrapper; + +//@ has 'foo/type.Dword.html' +// There is no `Deref` implementation for `Wrapper` so we confirm that. +// Which makes two section headers. One for the "Aliased Type" and one for "Methods from Deref<>". +//@ count - '//*[@class="section-header"]' 1 +//@ has - '//*[@class="section-header"]' 'Aliased Type' +pub type Dword = Wrapper; diff --git a/tests/rustdoc-html/deref/deref-on-aliased.rs b/tests/rustdoc-html/deref/deref-on-aliased.rs new file mode 100644 index 0000000000000..cf9f189ecc710 --- /dev/null +++ b/tests/rustdoc-html/deref/deref-on-aliased.rs @@ -0,0 +1,30 @@ +// Ensures that aliased types also get the "methods from deref" content. +// Regression test for . + +#![crate_name = "foo"] + +use std::ops::Deref; + +// First we check for the `Col` struct. +//@ has 'foo/struct.Col.html' +//@ has - '//*[@id="main-content"]//h2[@id="deref-methods-%5BT%5D"]' 'Methods from Deref' +//@ has - '//*[@id="rustdoc-toc"]//a[@href="#deref-methods-%5BT%5D"]' 'Methods from Deref' + +pub struct Col { + data: [T; N], +} + +// Then for the type alias. +//@ has 'foo/type.Mat.html' +//@ has - '//*[@id="main-content"]//h2[@id="deref-methods-%5BT%5D"]' 'Methods from Deref' +//@ has - '//*[@id="rustdoc-toc"]//a[@href="#deref-methods-%5BT%5D"]' 'Methods from Deref' + +pub type Mat = Col; + +impl Deref for Col { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + &self.data + } +} diff --git a/tests/rustdoc-html/deref/not-mut-deref.rs b/tests/rustdoc-html/deref/not-mut-deref.rs new file mode 100644 index 0000000000000..02aac49e46935 --- /dev/null +++ b/tests/rustdoc-html/deref/not-mut-deref.rs @@ -0,0 +1,35 @@ +// This test ensures that `get_u32` is not listed as the `Deref` impl +// doesn't match it. +// Regression test for . + +#![crate_name = "foo"] + +//@ has 'foo/struct.Bar.html' +//@ count - '//*[@id="deref-methods-Foo%3Ci32%3E-1"]/*[@class="method"]' 1 +//@ has - '//*[@id="deref-methods-Foo%3Ci32%3E-1"]/*[@class="method"]/h4' \ +// 'pub fn get_i32(&self) -> i32' + +// Same check for the sidebar. +//@ count - '//*[@id="rustdoc-toc"]/*[@class="block deref-methods"]//a' 1 +//@ has - '//*[@id="rustdoc-toc"]/*[@class="block deref-methods"]//a' 'get_i32' + +use std::ops::Deref; + +pub struct Foo(T); + +impl Foo { + pub fn get_i32(&self) -> i32 { self.0 } +} + +impl Foo { + pub fn get_u32(&self) -> u32 { self.0 } +} + +pub struct Bar(Foo); + +impl Deref for Bar { + type Target = Foo; + fn deref(&self) -> &Foo { + &self.0 + } +} diff --git a/tests/rustdoc-html/typedef/deref-32077.rs b/tests/rustdoc-html/typedef/deref-32077.rs index faab4b6f522ba..5c6949936e43e 100644 --- a/tests/rustdoc-html/typedef/deref-32077.rs +++ b/tests/rustdoc-html/typedef/deref-32077.rs @@ -54,7 +54,7 @@ impl std::ops::Deref for GenericStruct { pub struct Wrap(GenericStruct); //@ has 'foo/type.Alias.html' -//@ !has - '//h2' 'Methods from Deref' +//@ has - '//h2' 'Methods from Deref' //@ !has - '//*[@id="impl-Deref-for-Wrap%3CT%3E"]/h3' 'impl Deref for Wrap' //@ hasraw 'type.impl/foo/struct.Wrap.js' 'impl-Deref-for-Wrap%3CT%3E' // Deref Methods aren't gathered for type aliases, though the actual impl is.