Repository navigation
[rustdoc] Render Deref methods on aliased items
#163571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a812bac
0c442ea
a388dd0
6e5f760
e1e1dc8
b747376
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<GenericParamDef>, | ||
| } | ||
|
|
||
| // 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this (and my patch) are still subtly wrong, as the special rustdoc-specific rules
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How so?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if i understand the code correctly,
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth a test to confirm that. Gonna add one.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, it works. Adding test. =D
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah no nevermind, the filtering is not working. Too laxist. Oh well, that will be a follow-up. :') |
||
| } | ||
| } | ||
|
|
||
| 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)] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid doing two loops (one to get |
||
| 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<Impl>>( | ||
| cx: &'a Context<'_>, | ||
| trait_impls: &'a [I], | ||
| item: DefId, | ||
| aliased_type: Option<DefId>, | ||
| ) -> 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<DefId>, | ||
| ) -> 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<DefId>, | ||
| ) -> 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<u8>]`. | ||
| // 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<impl fmt: | |
| impl_.polarity == ty::ImplPolarity::Positive | ||
| // 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_| impl_.trait_.as_ref()) | ||
| .filter_map(|trait_| cx.cache().traits.get(&trait_.def_id())) | ||
|
|
@@ -1741,7 +1800,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (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_ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the impl polarity test maybe should go in
is_deref_trait, as we'll usually care about that, but then it might need a new name? subjective.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum... Fair point, gonna rename it
is_non_negative_deref_trait.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, in some cases the the trait polarity is queried ahead of time, so I'll them them separate for now.