Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/librustdoc/clean/auto_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ fn synthesize_auto_trait_impl<'tcx>(
) -> Option<clean::Item> {
let tcx = cx.tcx;
let trait_ref = ty::Binder::dummy(ty::TraitRef::new(tcx, trait_def_id, [ty]));
if !cx.generated_synthetics.insert((ty, trait_def_id)) {
if !cx.synthetic_auto_trait_impls.insert((ty, trait_def_id)) {
debug!("already generated, aborting");
return None;
}
Expand Down
4 changes: 2 additions & 2 deletions src/librustdoc/clean/blanket_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub(crate) fn synthesize_blanket_impls(
let mut blanket_impls = Vec::new();
for trait_def_id in tcx.visible_traits() {
if !cx.cache.effective_visibilities.is_reachable(tcx, trait_def_id)
|| cx.generated_synthetics.contains(&(ty.skip_binder(), trait_def_id))
|| cx.synthetic_blanket_impls.contains(&(ty.skip_binder(), trait_def_id))
{
continue;
}
Expand Down Expand Up @@ -81,7 +81,7 @@ pub(crate) fn synthesize_blanket_impls(
}
debug!("found applicable impl for trait ref {trait_ref:?}");

cx.generated_synthetics.insert((ty.skip_binder(), trait_def_id));
cx.synthetic_blanket_impls.insert((ty.skip_binder(), trait_def_id));

blanket_impls.push(clean::Item {
inner: Box::new(clean::ItemInner {
Expand Down
18 changes: 14 additions & 4 deletions src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,18 @@ pub(crate) struct DocContext<'tcx> {
pub(crate) current_type_aliases: DefIdMap<usize>,
/// Table synthetic type parameter for `impl Trait` in argument position -> bounds
pub(crate) impl_trait_bounds: FxHashMap<ImplTraitParam, Vec<clean::GenericBound>>,
/// Auto-trait or blanket impls processed so far, as `(self_ty, trait_def_id)`.
// FIXME(eddyb) make this a `ty::TraitRef<'tcx>` set.
pub(crate) generated_synthetics: FxHashSet<(Ty<'tcx>, DefId)>,

// FIXME: I'm pretty that the only reason we "need" these caches is because we also invoke
// `synthesize_auto_trait_and_blanket_impls` on all impls(!) for primitive types
// instead of calling it only once per primitive type (see also #97129).
// Get rid of that jank and remove both caches!
Comment on lines +60 to +63
Copy link
Copy Markdown
Member Author

@fmease fmease May 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the time or leisure to investigate and implement this now. However, this issue does occupy my mind and I'll tackle it soon(tm).

Tagging the corresp. GH issue: #97129.

View changes since the review

//
/// The set of auto-trait impls generated so far; identified by `(self_ty, trait_def_id)`.
pub(crate) synthetic_auto_trait_impls: FxHashSet<(Ty<'tcx>, DefId)>,
/// The set of blanket impls generated so far; identified by `(self_ty, trait_def_id)`.
pub(crate) synthetic_blanket_impls: FxHashSet<(Ty<'tcx>, DefId)>,

/// All auto traits in the (visible) crate graph.
pub(crate) auto_traits: Vec<DefId>,
/// This same cache is used throughout rustdoc, including in [`crate::html::render`].
pub(crate) cache: Cache,
Expand Down Expand Up @@ -369,7 +378,8 @@ pub(crate) fn run_global_ctxt(
args: Default::default(),
current_type_aliases: Default::default(),
impl_trait_bounds: Default::default(),
generated_synthetics: Default::default(),
synthetic_auto_trait_impls: Default::default(),
synthetic_blanket_impls: Default::default(),
auto_traits,
cache: Cache::new(render_options.document_private, render_options.document_hidden),
inlined: FxHashSet::default(),
Expand Down
61 changes: 35 additions & 26 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1397,13 +1397,13 @@ fn render_all_impls(
mut w: impl Write,
cx: &Context<'_>,
containing_item: &clean::Item,
concrete: &[&Impl],
synthetic: &[&Impl],
blanket_impl: &[&Impl],
concrete_impls: &[&Impl],
auto_trait_impls: &[&Impl],
blanket_impls: &[&Impl],
) -> fmt::Result {
let impls = {
let mut buf = String::new();
render_impls(cx, &mut buf, concrete, containing_item, true)?;
render_impls(cx, &mut buf, concrete_impls, containing_item, true)?;
buf
};
if !impls.is_empty() {
Expand All @@ -1414,23 +1414,24 @@ fn render_all_impls(
)?;
}

if !synthetic.is_empty() {
if !auto_trait_impls.is_empty() {
// FIXME: Change the ID to `auto-trait-implementations-list`!
write!(
w,
"{}<div id=\"synthetic-implementations-list\">",
write_impl_section_heading("Auto Trait Implementations", "synthetic-implementations",)
)?;
render_impls(cx, &mut w, synthetic, containing_item, false)?;
render_impls(cx, &mut w, auto_trait_impls, containing_item, false)?;
w.write_str("</div>")?;
}

if !blanket_impl.is_empty() {
if !blanket_impls.is_empty() {
write!(
w,
"{}<div id=\"blanket-implementations-list\">",
write_impl_section_heading("Blanket Implementations", "blanket-implementations")
)?;
render_impls(cx, &mut w, blanket_impl, containing_item, false)?;
render_impls(cx, &mut w, blanket_impls, containing_item, false)?;
w.write_str("</div>")?;
}
Ok(())
Expand Down Expand Up @@ -1459,10 +1460,10 @@ fn render_assoc_items_inner(
) -> fmt::Result {
info!("Documenting associated items of {:?}", containing_item.name);
let cache = &cx.shared.cache;
let Some(v) = cache.impls.get(&it) else { return Ok(()) };
let (mut non_trait, traits): (Vec<_>, _) =
v.iter().partition(|i| i.inner_impl().trait_.is_none());
if !non_trait.is_empty() {
let Some(impls) = cache.impls.get(&it) else { return Ok(()) };
let (mut inherent_impls, trait_impls): (Vec<_>, _) =
impls.iter().partition(|i| i.inner_impl().trait_.is_none());
if !inherent_impls.is_empty() {
let render_mode = what.render_mode();
let class_html = what
.class()
Expand All @@ -1485,7 +1486,7 @@ 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.
non_trait.retain(|impl_| {
inherent_impls.retain(|impl_| {
type_.is_doc_subtype_of(&impl_.inner_impl().for_, &cx.shared.cache)
});
let derived_id = cx.derive_id(&id);
Expand All @@ -1512,8 +1513,8 @@ fn render_assoc_items_inner(
)
}
};
let impls_buf = fmt::from_fn(|f| {
non_trait
let inherent_impls_buf = fmt::from_fn(|f| {
inherent_impls
.iter()
.map(|i| {
render_impl(
Expand All @@ -1536,24 +1537,25 @@ fn render_assoc_items_inner(
})
.to_string();

if !impls_buf.is_empty() {
if !inherent_impls_buf.is_empty() {
write!(
w,
"{section_heading}<div id=\"{id}\"{class_html}>{impls_buf}</div>{}",
"{section_heading}<div id=\"{id}\"{class_html}>{inherent_impls_buf}</div>{}",
matches!(what, AssocItemRender::DerefFor { .. })
.then_some("</details>")
.maybe_display(),
)?;
}
}

if !traits.is_empty() {
let deref_impl = traits.iter().find(|t| {
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 =
traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
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)?;
}

Expand All @@ -1563,12 +1565,19 @@ fn render_assoc_items_inner(
return Ok(());
}

let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
traits.into_iter().partition(|t| t.inner_impl().kind.is_auto());
let (blanket_impl, concrete): (Vec<&Impl>, _) =
concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
let (auto_trait_impls, trait_impls): (Vec<&Impl>, Vec<&Impl>) =
trait_impls.into_iter().partition(|t| t.inner_impl().kind.is_auto());
let (blanket_impls, concrete_impls): (Vec<&Impl>, _) =
trait_impls.into_iter().partition(|t| t.inner_impl().kind.is_blanket());

render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl)?;
render_all_impls(
w,
cx,
containing_item,
&concrete_impls,
&auto_trait_impls,
&blanket_impls,
)?;
}
Ok(())
}
Expand Down
26 changes: 26 additions & 0 deletions tests/rustdoc-html/impl/blanket-impl-trumps-auto-trait-impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// The presence of built-in or user-written trait impls disqualifies potential built-in auto trait
// impls for a type. Here we have a blanket impl of an auto trait which suppresses all hypothetical
// auto trait impls.
//
// Check that we don't claim that there's an auto trait impl & ensure that we show the blanket impl.
// Erroneously we once used to omit the blanket impl since auto trait impls & blanket impls used to
// share the same cache and auto trait impls get generated first. Now, they have separate caches.
//
// issue: <https://github.com/rust-lang/rust/issues/148980>

#![feature(auto_traits)]
#![crate_name = "it"]

pub auto trait Marker {}

//@ has 'it/struct.Subject.html'
#[derive(Clone, Copy)]
pub struct Subject;

// NOTE: `#synthetic-implementations-list` contains auto trait impls only despite its name.
//@ !has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]' \
// 'Marker for Subject'

//@ has - '//*[@id="blanket-implementations-list"]//*[@class="impl"]' \
// 'Marker for Twhere T: Copy'
Copy link
Copy Markdown
Member Author

@fmease fmease May 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it has to be Twhere for now, T where would fail the htmldocck check. It seems htmldocck incorrectly strips HTML tags. For reference, the HTML looks like for T<div class="where">where.

IIRC I stumble upon this quite often (unsurprisingly) but I usually split the test expectation into two lines, something like Marker for T\ and where T: Copy which looks better and "hides" this issue (despite being equivalent to what's written here). This bug definitely needs addressing at some point.

View changes since the review

impl<T: Copy> Marker for T {}
Loading