From 3888a633b7d594dc77c63854daaa7ef5f754b7d4 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Thu, 26 Mar 2026 11:53:17 +0000 Subject: [PATCH 1/3] Add a regression test for rustdoc ICEing on negative `Deref`/`DerefMut` impls --- tests/rustdoc-ui/deref/negative-deref-ice-128801.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/rustdoc-ui/deref/negative-deref-ice-128801.rs diff --git a/tests/rustdoc-ui/deref/negative-deref-ice-128801.rs b/tests/rustdoc-ui/deref/negative-deref-ice-128801.rs new file mode 100644 index 000000000000..439727f3d3d3 --- /dev/null +++ b/tests/rustdoc-ui/deref/negative-deref-ice-128801.rs @@ -0,0 +1,11 @@ +//@ check-pass + +// Regression test for https://github.com/rust-lang/rust/issues/128801 +// Negative `Deref`/`DerefMut` impls should not cause an ICE. + +#![feature(negative_impls)] + +pub struct Source; + +impl !std::ops::Deref for Source {} +impl !std::ops::DerefMut for Source {} From 23b1e78028a57a55acffb93588dcba4cbf3457a3 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Thu, 26 Mar 2026 22:05:05 +0000 Subject: [PATCH 2/3] rustdoc: When collecting `Deref` impls with their targets, skip the negative ones rustdoc assumed every `Deref` impl has an associated `Target` type, but negative impls (e.g. `impl !Deref for T {}`) have none. Skip them in both the trait-impl collection pass and the HTML render pass to avoid panicking on the missing Target. --- src/librustdoc/html/render/mod.rs | 5 +++-- src/librustdoc/passes/collect_trait_impls.rs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 556d383a0e9f..c0c380447f2c 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1517,8 +1517,9 @@ fn render_assoc_items_inner( } if !traits.is_empty() { - let deref_impl = - traits.iter().find(|t| t.trait_did() == cx.tcx().lang_items().deref_trait()); + let deref_impl = traits.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 = traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait()); diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index b58daceed070..251efa15e240 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -155,8 +155,9 @@ fn add_deref_target( // 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(box Impl { ref for_, ref trait_, ref items, .. }) = it.kind + if let ImplItem(box 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 From 02a73dbe9b98cd0ddb28e6dbd2439534e26c211a Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Fri, 27 Mar 2026 23:29:25 +0000 Subject: [PATCH 3/3] Address review feedback. --- src/librustdoc/clean/inline.rs | 4 +++- src/librustdoc/clean/mod.rs | 6 +++-- src/librustdoc/html/render/sidebar.rs | 7 +++--- .../deref/negative-deref-impl-128801.rs | 23 +++++++++++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 tests/rustdoc-html/deref/negative-deref-impl-128801.rs diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 09b2bc5dcef1..e6a1b7af19f9 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -583,7 +583,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() { + if trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() + && polarity != ty::ImplPolarity::Negative + { super::build_deref_target_impls(cx, &trait_items, ret); } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index f54339429fa5..27a1236e18bd 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2941,9 +2941,11 @@ fn clean_impl<'tcx>( .map(|&ii| clean_impl_item(tcx.hir_impl_item(ii), cx)) .collect::>(); - // If this impl block is an implementation of the Deref trait, then we + // 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().map(|t| t.def_id()) == tcx.lang_items().deref_trait() { + if trait_.as_ref().is_some_and(|t| tcx.lang_items().deref_trait() == Some(t.def_id())) + && tcx.impl_polarity(def_id) != ty::ImplPolarity::Negative + { build_deref_target_impls(cx, &items, &mut ret); } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index a4535792ac3c..472136ac46bc 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -466,9 +466,9 @@ fn sidebar_assoc_items<'a>( ]; 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()) - { + 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( @@ -579,6 +579,7 @@ fn sidebar_deref_methods<'a>( .as_ref() .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait()) .unwrap_or(false) + && !i.is_negative_trait_impl() }) { sidebar_deref_methods( diff --git a/tests/rustdoc-html/deref/negative-deref-impl-128801.rs b/tests/rustdoc-html/deref/negative-deref-impl-128801.rs new file mode 100644 index 000000000000..51be958c33bb --- /dev/null +++ b/tests/rustdoc-html/deref/negative-deref-impl-128801.rs @@ -0,0 +1,23 @@ +#![feature(negative_impls)] +#![crate_name = "foo"] + +// Regression test for https://github.com/rust-lang/rust/issues/128801 +// Negative `Deref`/`DerefMut` impls should not cause an ICE and should still be rendered. + +pub struct Source; + +//@ has foo/struct.Source.html + +// Verify negative Deref impl is rendered in the main content. +//@ has - '//*[@class="impl"]//h3[@class="code-header"]' 'impl !Deref for Source' + +// Verify negative DerefMut impl is rendered in the main content. +//@ has - '//*[@class="impl"]//h3[@class="code-header"]' 'impl !DerefMut for Source' + +// Verify negative impls appear in the sidebar. +//@ has - '//div[@class="sidebar-elems"]//h3/a[@href="#trait-implementations"]' 'Trait Implementations' +//@ has - '//*[@class="sidebar-elems"]//section//a' '!Deref' +//@ has - '//*[@class="sidebar-elems"]//section//a' '!DerefMut' + +impl !std::ops::Deref for Source {} +impl !std::ops::DerefMut for Source {}