From 2f7ae61690661989a049ef6e8addae478f13e431 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Mon, 20 Jul 2026 18:48:28 +0000 Subject: [PATCH 1/4] rustdoc: Filter external trait impls before building them Building inlined impls is expensive, and most of them end up being unneeded and stripped later in this function. So we should filter them ahead of time. This requires inlining external auto traits when we construct auto impls, since this no longer happens as a side effect. We inline external impls when they are * for generics (i.e., blanket impls) * for primitive types (probably this should be handled below really) * for a type (inlined) in the current crate * of a trait (inlined) in the current crate * of `Deref` These rules are based on the existing filtering rules that are applied after building the inlined impls. --- src/librustdoc/clean/auto_trait.rs | 2 ++ src/librustdoc/clean/inline.rs | 3 ++- src/librustdoc/passes/collect_trait_impls.rs | 19 ++++++++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 31ff38b78e70f..32042b24c1acd 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -114,6 +114,8 @@ fn synthesize_auto_trait_impl<'tcx>( auto_trait::AutoTraitResult::ExplicitImpl => return None, }; + super::inline::record_extern_trait(cx, trait_def_id); + Some(clean::Item { inner: Box::new(clean::ItemInner { name: None, diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 46b8683137c60..cb7ddd0ec58e7 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -14,7 +14,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_span::def_id::LOCAL_CRATE; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{Symbol, sym}; -use tracing::{debug, trace}; +use tracing::{debug, instrument, trace}; use super::{Item, extract_cfg_from_attrs}; use crate::clean::{ @@ -453,6 +453,7 @@ pub(crate) fn merge_attrs( } /// Inline an `impl`, inherent or of a trait. The `did` must be for an `impl`. +#[instrument(level = "debug", skip(cx, ret))] pub(crate) fn build_impl( cx: &mut DocContext<'_>, did: DefId, diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 9d603117ea5ac..02c4908aa77dc 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -53,7 +53,24 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> for &cnum in tcx.crates(()) { for &impl_def_id in tcx.trait_impls_in_crate(cnum) { cx.with_param_env(impl_def_id, |cx| { - inline::build_impl(cx, impl_def_id, None, &mut new_items_external); + let opt_trait_ref = tcx.impl_opt_trait_ref(impl_def_id); + let self_ty = tcx.type_of(impl_def_id).instantiate_identity().skip_norm_wip(); + let self_ty = + clean_middle_ty(ty::Binder::dummy(self_ty), cx, Some(impl_def_id), None); + if self_ty.is_full_generic() + || self_ty + .primitive_type() + .is_some_and(|primitive| prims.contains(&primitive)) + || self_ty + .def_id(&cx.cache) + .is_some_and(|did| crate_items.contains(&ItemId::DefId(did))) + || opt_trait_ref.is_some_and(|trait_ref| { + crate_items.contains(&ItemId::DefId(trait_ref.def_id())) + || Some(trait_ref.def_id()) == tcx.lang_items().deref_trait() + }) + { + inline::build_impl(cx, impl_def_id, None, &mut new_items_external); + } }); } } From 88f7ca5d48f7688d63330403a5c490e09d5d7a70 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Mon, 20 Jul 2026 20:12:19 +0000 Subject: [PATCH 2/4] rustdoc: Remove newly-redundant filtering of inlined impls --- src/librustdoc/passes/collect_trait_impls.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 02c4908aa77dc..c50702cfc0376 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -210,19 +210,6 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> } } - // Filter out external items that are not needed - new_items_external.retain(|it| { - if let ImplItem(Impl { ref for_, ref trait_, ref kind, .. }) = it.kind { - cleaner.keep_impl( - for_, - trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait(), - ) || trait_.as_ref().is_some_and(|t| cleaner.keep_impl_with_def_id(t.def_id().into())) - || kind.is_blanket() - } else { - true - } - }); - if let ModuleItem(Module { items, .. }) = &mut krate.module.inner.kind { items.extend(synth_impls); items.extend(new_items_external); From dd0ba295c6479deff85fbd4792095b15365e484f Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Mon, 20 Jul 2026 20:14:56 +0000 Subject: [PATCH 3/4] rustdoc: Remove `BadImplStripper` and unused deref-following logic Now that we do filtering ahead of time, the `BadImplStripper` is unnecessary. Also, it appears that all this `Deref` logic was pointless in the first place since following derefs already happens elsewhere in rustdoc, where it is actually needed. --- src/librustdoc/passes/collect_trait_impls.rs | 95 +------------------- 1 file changed, 1 insertion(+), 94 deletions(-) diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index c50702cfc0376..6d6330be742a2 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -4,10 +4,9 @@ use rustc_data_structures::fx::FxHashSet; use rustc_hir::attrs::{AttributeKind, DocAttribute}; -use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE}; +use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::{Attribute, find_attr}; use rustc_middle::ty; -use tracing::debug; use super::Pass; use crate::clean::*; @@ -143,73 +142,6 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> } }); - let mut cleaner = BadImplStripper { prims, items: crate_items, cache: &cx.cache }; - let mut type_did_to_deref_target: DefIdMap<&Type> = DefIdMap::default(); - - // Follow all `Deref` targets of included items and recursively add them as valid - fn add_deref_target( - cx: &DocContext<'_>, - map: &DefIdMap<&Type>, - cleaner: &mut BadImplStripper<'_>, - targets: &mut DefIdSet, - type_did: DefId, - ) { - if let Some(target) = map.get(&type_did) { - debug!("add_deref_target: type {:?}, target {:?}", type_did, target); - if let Some(target_prim) = target.primitive_type() { - cleaner.prims.insert(target_prim); - } else if let Some(target_did) = target.def_id(&cx.cache) { - // `impl Deref for S` - if !targets.insert(target_did) { - // Avoid infinite cycles - return; - } - cleaner.items.insert(target_did.into()); - add_deref_target(cx, map, cleaner, targets, target_did); - } - } - } - - // scan through included items ahead of time to splice in Deref targets to the "valid" sets - for it in new_items_external.iter().chain(new_items_local.iter()) { - if let ImplItem(Impl { ref for_, ref trait_, ref items, polarity, .. }) = it.kind - && trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() - && polarity != ty::ImplPolarity::Negative - && cleaner.keep_impl(for_, true) - { - let target = items - .iter() - .find_map(|item| match item.kind { - AssocTypeItem(ref t, _) => Some(&t.type_), - _ => None, - }) - .expect("Deref impl without Target type"); - - if let Some(prim) = target.primitive_type() { - cleaner.prims.insert(prim); - } else if let Some(did) = target.def_id(&cx.cache) { - cleaner.items.insert(did.into()); - } - if let Some(for_did) = for_.def_id(&cx.cache) - && type_did_to_deref_target.insert(for_did, target).is_none() - // Since only the `DefId` portion of the `Type` instances is known to be same for both the - // `Deref` target type and the impl for type positions, this map of types is keyed by - // `DefId` and for convenience uses a special cleaner that accepts `DefId`s directly. - && cleaner.keep_impl_with_def_id(for_did.into()) - { - let mut targets = DefIdSet::default(); - targets.insert(for_did); - add_deref_target( - cx, - &type_did_to_deref_target, - &mut cleaner, - &mut targets, - for_did, - ); - } - } - } - if let ModuleItem(Module { items, .. }) = &mut krate.module.inner.kind { items.extend(synth_impls); items.extend(new_items_external); @@ -268,28 +200,3 @@ impl DocVisitor<'_> for ItemAndAliasCollector<'_> { self.visit_item_recur(i) } } - -struct BadImplStripper<'a> { - prims: FxHashSet, - items: FxHashSet, - cache: &'a Cache, -} - -impl BadImplStripper<'_> { - fn keep_impl(&self, ty: &Type, is_deref: bool) -> bool { - if let Generic(_) = ty { - // keep impls made on generics - true - } else if let Some(prim) = ty.primitive_type() { - self.prims.contains(&prim) - } else if let Some(did) = ty.def_id(self.cache) { - is_deref || self.keep_impl_with_def_id(did.into()) - } else { - false - } - } - - fn keep_impl_with_def_id(&self, item_id: ItemId) -> bool { - self.items.contains(&item_id) - } -} From afaa98372c25b31bc42dbcc175c4a15ef0eee75e Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Tue, 21 Jul 2026 15:43:41 +0000 Subject: [PATCH 4/4] rustdoc: Keep external impls of notable traits The code predating this PR was actually incorrect since it did not keep these impls. However, removing the redundant deref-following logic had the side effect of breaking a GUI test for the notable traits tooltip. It seems like previously an unrelated type with a deref impl to `Vec` ended up causing the impl of `Write` for it to be kept. In fact, this change required some snapshot tests to be updated because it fixed a latent bug where the notable trait tooltip would not appear for many external traits (if they didn't happen to get pulled in like `Write for Vec` did). --- src/librustdoc/passes/collect_trait_impls.rs | 1 + .../notable-trait/spotlight-from-dependency.odd.html | 2 +- tests/rustdoc-html/where.SWhere_TraitWhere_item-decl.html | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 6d6330be742a2..2d4678ec4bcc7 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -66,6 +66,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> || opt_trait_ref.is_some_and(|trait_ref| { crate_items.contains(&ItemId::DefId(trait_ref.def_id())) || Some(trait_ref.def_id()) == tcx.lang_items().deref_trait() + || tcx.is_doc_notable_trait(trait_ref.def_id()) }) { inline::build_impl(cx, impl_def_id, None, &mut new_items_external); diff --git a/tests/rustdoc-html/notable-trait/spotlight-from-dependency.odd.html b/tests/rustdoc-html/notable-trait/spotlight-from-dependency.odd.html index cce8739df1650..910698a5854e9 100644 --- a/tests/rustdoc-html/notable-trait/spotlight-from-dependency.odd.html +++ b/tests/rustdoc-html/notable-trait/spotlight-from-dependency.odd.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc-html/where.SWhere_TraitWhere_item-decl.html b/tests/rustdoc-html/where.SWhere_TraitWhere_item-decl.html index 8e9465ef6126b..c0fea95878005 100644 --- a/tests/rustdoc-html/where.SWhere_TraitWhere_item-decl.html +++ b/tests/rustdoc-html/where.SWhere_TraitWhere_item-decl.html @@ -5,7 +5,7 @@ // Provided methods fn func(self) where Self: Sized { ... } - fn lines(self) -> Lines<Self> + fn lines(self) -> Lines<Self> ⓘ where Self: Sized { ... } fn merge<T>(self, a: T) where Self: Sized,