From c3864bee809f41cca3d1eace7e7c7a93f0c1a091 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 26 Jun 2026 20:12:27 -0700 Subject: [PATCH] rustdoc: show impl Trait> for Foreign, etc on Local's docs This is a generalization of PR 92940: that PR handled cases like `impl Foreign for Box`, but was missing handling for a few other closely related cases. My particular interest was with showing `impl From> for Box` in camino's documentation. But I ended up handling a bunch of related cases along the way. I'm new to rustdoc so please let me know if I got anything wrong :) took a bit to fully understand how this worked. --- src/librustdoc/formats/cache.rs | 45 ++++++++------ .../impl/impl-fundamental-nesting.rs | 58 +++++++++++++++++++ .../rustdoc-json/impls/fundamental_nesting.rs | 57 ++++++++++++++++++ 3 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 tests/rustdoc-html/impl/impl-fundamental-nesting.rs create mode 100644 tests/rustdoc-json/impls/fundamental_nesting.rs diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index fee783133133f..ccee062584e01 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -233,6 +233,30 @@ impl Cache { } } +impl CacheBuilder<'_, '_> { + /// Extends `dids` with ones that an impl should be associated with for a type appearing in its + /// `Self` type or trait generic arguments, accounting for references and `#[fundamental]` + /// wrappers. + /// + /// This ensures that impls like `impl Trait> for Foreign`, `impl Trait for + /// Box`, and other variations of these, are documented on `Local`'s page. + fn extend_with_fundamental_dids(&self, ty: &clean::Type, dids: &mut FxIndexSet) { + dids.extend(ty.def_id(self.cache)); + // without_borrowed_ref allows cases like `impl Trait<&Box> for Foreign` to be + // handled by this function. (This is rare in practice, but easy to handle here.) + if let clean::Type::Path { path } = ty.without_borrowed_ref() + && let Some(generics) = path.generics() + && let ty::Adt(adt, _) = + self.tcx.type_of(path.def_id()).instantiate_identity().skip_norm_wip().kind() + && adt.is_fundamental() + { + for inner in generics { + self.extend_with_fundamental_dids(inner, dids); + } + } + } +} + impl DocFolder for CacheBuilder<'_, '_> { fn fold_item(&mut self, item: clean::Item) -> Option { if item.item_id.is_local() { @@ -418,22 +442,9 @@ impl DocFolder for CacheBuilder<'_, '_> { // Note: matching twice to restrict the lifetime of the `i` borrow. let mut dids = FxIndexSet::default(); match i.for_ { - clean::Type::Path { ref path } - | clean::BorrowedRef { type_: clean::Type::Path { ref path }, .. } => { - dids.insert(path.def_id()); - if let Some(generics) = path.generics() - && let ty::Adt(adt, _) = self - .tcx - .type_of(path.def_id()) - .instantiate_identity() - .skip_norm_wip() - .kind() - && adt.is_fundamental() - { - for ty in generics { - dids.extend(ty.def_id(self.cache)); - } - } + clean::Type::Path { .. } + | clean::BorrowedRef { type_: clean::Type::Path { .. }, .. } => { + self.extend_with_fundamental_dids(&i.for_, &mut dids); } clean::DynTrait(ref bounds, _) | clean::BorrowedRef { type_: clean::DynTrait(ref bounds, _), .. } => { @@ -452,7 +463,7 @@ impl DocFolder for CacheBuilder<'_, '_> { && let Some(generics) = trait_.generics() { for bound in generics { - dids.extend(bound.def_id(self.cache)); + self.extend_with_fundamental_dids(bound, &mut dids); } } let impl_item = Impl { impl_item: item }; diff --git a/tests/rustdoc-html/impl/impl-fundamental-nesting.rs b/tests/rustdoc-html/impl/impl-fundamental-nesting.rs new file mode 100644 index 0000000000000..25aed33e53514 --- /dev/null +++ b/tests/rustdoc-html/impl/impl-fundamental-nesting.rs @@ -0,0 +1,58 @@ +// Followup to https://github.com/rust-lang/rust/issues/92940 and impl-box.rs. +// +// Show traits implemented on fundamental types that wrap local ones: nested edition. + +#![crate_name = "foo"] + +use std::pin::Pin; + +pub struct Local; + +//@ has 'foo/struct.Local.html' + +// Nested fundamental + foreign Self. +//@ has '-' '//*[@id="impl-From%3CBox%3CLocal%3E%3E-for-String"]' 'impl From> for String' +impl From> for String { + fn from(_: Box) -> String { + String::new() + } +} + +// Also test with Pin. +//@ has '-' '//*[@id="impl-From%3CPin%3CLocal%3E%3E-for-u8"]' 'impl From> for u8' +impl From> for u8 { + fn from(_: Pin) -> u8 { + 0 + } +} + +// Reference to a fundamental wrapper. +//@ has '-' '//*[@id="impl-From%3C%26Box%3CLocal%3E%3E-for-u16"]' "impl<'a> From<&'a Box> for u16" +impl<'a> From<&'a Box> for u16 { + fn from(_: &'a Box) -> u16 { + 0 + } +} + +// Nested two levels deep in Self. +//@ has '-' '//*[@id="impl-From%3Cu32%3E-for-Box%3CBox%3CLocal%3E%3E"]' 'impl From for Box>' +impl From for Box> { + fn from(_: u32) -> Box> { + Box::new(Box::new(Local)) + } +} + +// Mixed fundamental wrappers in Self. +//@ has '-' '//*[@id="impl-From%3Cu64%3E-for-Pin%3CBox%3CLocal%3E%3E"]' 'impl From for Pin>' +impl From for Pin> { + fn from(_: u64) -> Pin> { + Pin::new(Box::new(Local)) + } +} + +// A non-fundamental wrapper must not show up on Local's page, but it should still be listed on the +// trait's own page. +pub trait Marker {} +//@ has 'foo/trait.Marker.html' '//*[@id="impl-Marker-for-Vec%3CLocal%3E"]' 'impl Marker for Vec' +//@ !has 'foo/struct.Local.html' '//*[@id="impl-Marker-for-Vec%3CLocal%3E"]' 'impl Marker for Vec' +impl Marker for Vec {} diff --git a/tests/rustdoc-json/impls/fundamental_nesting.rs b/tests/rustdoc-json/impls/fundamental_nesting.rs new file mode 100644 index 0000000000000..a6aefb94f961f --- /dev/null +++ b/tests/rustdoc-json/impls/fundamental_nesting.rs @@ -0,0 +1,57 @@ +// Companion to tests/rustdoc-html/impl/impl-fundamental-nesting.rs. +// +// Show traits implemented on fundamental types that wrap local ones: nested edition. + +use std::pin::Pin; + +pub struct Local; + +// Nested fundamental + foreign Self. +/// from box local +impl From> for String { + fn from(_: Box) -> String { + String::new() + } +} +//@ set from_box_local = "$.index[?(@.docs=='from box local')].id" +//@ has "$.index[?(@.name=='Local')].inner.struct.impls[*]" $from_box_local + +// Reference to a fundamental wrapper. +/// from ref box local +impl<'a> From<&'a Box> for u16 { + fn from(_: &'a Box) -> u16 { + 0 + } +} +//@ set from_ref_box_local = "$.index[?(@.docs=='from ref box local')].id" +//@ has "$.index[?(@.name=='Local')].inner.struct.impls[*]" $from_ref_box_local + +// Nested two levels deep in Self. +/// u32 for box box local +impl From for Box> { + fn from(_: u32) -> Box> { + Box::new(Box::new(Local)) + } +} +//@ set u32_for_box_box_local = "$.index[?(@.docs=='u32 for box box local')].id" +//@ has "$.index[?(@.name=='Local')].inner.struct.impls[*]" $u32_for_box_box_local + +// Mixed fundamental wrappers in Self. +/// u64 for pin box local +impl From for Pin> { + fn from(_: u64) -> Pin> { + Pin::new(Box::new(Local)) + } +} +//@ set u64_for_pin_box_local = "$.index[?(@.docs=='u64 for pin box local')].id" +//@ has "$.index[?(@.name=='Local')].inner.struct.impls[*]" $u64_for_pin_box_local + +// A non-fundamental wrapper must not associate the impl with Local, but the impl must still be +// listed on the trait itself. +pub trait Marker {} + +/// marker for vec local +impl Marker for Vec {} +//@ set marker_for_vec_local = "$.index[?(@.docs=='marker for vec local')].id" +//@ !has "$.index[?(@.name=='Local')].inner.struct.impls[*]" $marker_for_vec_local +//@ has "$.index[?(@.name=='Marker')].inner.trait.implementations[*]" $marker_for_vec_local