Skip to content
Merged
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 @@ -202,7 +202,7 @@ fn clean_param_env<'tcx>(
.collect();

let mut generics = clean::Generics { params, where_predicates };
simplify::sized_bounds(cx, &mut generics);
simplify::sizedness_bounds(cx, &mut generics);
generics.where_predicates = simplify::where_clauses(cx.tcx, generics.where_predicates);
generics
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,7 @@ fn clean_ty_generics_inner<'tcx>(
where_predicates.into_iter().flat_map(|p| clean_predicate(*p, cx)).collect();

let mut generics = Generics { params, where_predicates };
simplify::sized_bounds(cx, &mut generics);
simplify::sizedness_bounds(cx, &mut generics);
generics.where_predicates = simplify::where_clauses(cx.tcx, generics.where_predicates);
generics
}
Expand Down
94 changes: 58 additions & 36 deletions src/librustdoc/clean/simplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::thin_vec::ThinVec;
use rustc_data_structures::unord::UnordSet;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{TyCtxt, Unnormalized};

Expand Down Expand Up @@ -121,50 +121,72 @@ fn trait_is_same_or_supertrait(tcx: TyCtxt<'_>, child: DefId, trait_: DefId) ->
.any(|did| trait_is_same_or_supertrait(tcx, did, trait_))
}

pub(crate) fn sized_bounds(cx: &mut DocContext<'_>, generics: &mut clean::Generics) {
let mut sized_params = UnordSet::new();
/// Reconstruct all sizedness bounds on non-`Self` type parameters as they appear in the surface
/// language given generics that were cleaned from the middle::ty IR.
///
/// For example, assuming `T` is a type parameter of the owner of `generics`,
/// `T: Sized` gets dropped and `T: MetaSized` gets rewritten to `T: ?Sized`.
pub(crate) fn sizedness_bounds(cx: &mut DocContext<'_>, generics: &mut clean::Generics) {
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum Sizedness {
PointeeSized,
MetaSized,
Sized,
}

let mut type_params: FxIndexMap<_, _> = generics
.params
.iter()
.filter(|param| matches!(param.kind, clean::GenericParamDefKind::Type { .. }))
.map(|param| (param.name, Sizedness::PointeeSized))
.collect();

// In the surface language, all type parameters except `Self` have an
// implicit `Sized` bound unless removed with `?Sized`.
// However, in the list of where-predicates below, `Sized` appears like a
// normal bound: It's either present (the type is sized) or
// absent (the type might be unsized) but never *maybe* (i.e. `?Sized`).
//
// This is unsuitable for rendering.
// Thus, as a first step remove all `Sized` bounds that should be implicit.
//
// Note that associated types also have an implicit `Sized` bound but we
// don't actually know the set of associated types right here so that
// should be handled when cleaning associated types.
generics.where_predicates.retain(|pred| {
let WP::BoundPredicate { ty: clean::Generic(param), bounds, .. } = pred else {
return true;
};

if bounds.iter().any(|b| b.is_sized_bound(cx.tcx)) {
sized_params.insert(*param);
false
} else if bounds.iter().any(|b| b.is_meta_sized_bound(cx.tcx)) {
// FIXME(sized-hierarchy): Always skip `MetaSized` bounds so that only `?Sized`
// is shown and none of the new sizedness traits leak into documentation.
false
} else {
true
// We require the caller to pass generics that were cleaned from the middle::ty IR.
// We know that that cleaning process never generates more than one bound per predicate.
let [bound] = &*bounds else { unreachable!() };

let clean::GenericBound::TraitBound(trait_ref, hir::TraitBoundModifiers::NONE) = bound
else {
return true;
};

// This transformation is only valid on type parameters defined on the closest item.
// If the parameter was defined by the parent item we know that the sizedness bound
// *has* to be user-written in which case we have to preserve it as is.
let Some(param_sizedness) = type_params.get_mut(param) else { return true };
Copy link
Copy Markdown
Member Author

@fmease fmease Jun 2, 2026

Choose a reason for hiding this comment

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

This is the core change. All changes around it are basically only refactoring because I got so annoyed at the state of things (cc issue #157247) that I couldn't help myself, I had to jettison the jank...

Lmk if I should be more disciplined and split this into two+ commits for ease of reviewing.

View changes since the review


let sizedness = match cx.tcx.as_lang_item(trait_ref.trait_.def_id()) {
Some(hir::LangItem::Sized) => Sizedness::Sized,
Some(hir::LangItem::MetaSized) => Sizedness::MetaSized,
_ => return true,
};

if sizedness > *param_sizedness {
*param_sizedness = sizedness;
}

false
});

// As a final step, go through the type parameters again and insert a
// `?Sized` bound for each one we didn't find to be `Sized`.
for param in &generics.params {
if let clean::GenericParamDefKind::Type { .. } = param.kind
&& !sized_params.contains(&param.name)
{
generics.where_predicates.push(WP::BoundPredicate {
ty: clean::Type::Generic(param.name),
bounds: vec![clean::GenericBound::maybe_sized(cx)],
bound_params: Vec::new(),
})
}
for (param, sizedness) in type_params {
generics.where_predicates.push(WP::BoundPredicate {
ty: clean::Type::Generic(param),
bounds: vec![match sizedness {
// FIXME(sized-hierarchy, #157247): Actually render `MetaSized` as `MetaSized` and
// `PointeeSized` as `PointeeSized` instead of `?Sized` if the crate enables
// `sized_hierarchy` and doesn't set `#![doc(dont_leak…)]`.
Sizedness::MetaSized | Sizedness::PointeeSized => {
clean::GenericBound::maybe_sized(cx)
}
Sizedness::Sized => continue,
}],
bound_params: Vec::new(),
});
}
}

Expand Down
11 changes: 4 additions & 7 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1201,20 +1201,17 @@ impl GenericBound {
}

fn is_bounded_by_lang_item(&self, tcx: TyCtxt<'_>, lang_item: LangItem) -> bool {
Copy link
Copy Markdown
Member Author

@fmease fmease Jun 1, 2026

Choose a reason for hiding this comment

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

drive-by refactoring here and one below

View changes since the review

if let GenericBound::TraitBound(
PolyTrait { ref trait_, .. },
rustc_hir::TraitBoundModifiers::NONE,
) = *self
&& tcx.is_lang_item(trait_.def_id(), lang_item)
if let GenericBound::TraitBound(poly_trait_ref, rustc_hir::TraitBoundModifiers::NONE) = self
&& tcx.is_lang_item(poly_trait_ref.trait_.def_id(), lang_item)
{
return true;
}
false
}

pub(crate) fn get_trait_path(&self) -> Option<Path> {
if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, _) = *self {
Some(trait_.clone())
if let GenericBound::TraitBound(poly_trait_ref, _) = self {
Some(poly_trait_ref.trait_.clone())
} else {
None
}
Expand Down
18 changes: 18 additions & 0 deletions tests/rustdoc-html/inline_cross/auxiliary/ext-sized-bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pub fn sized_param<T>() {}

pub fn relaxed_sized_on_param<T: ?Sized>() {}

pub trait SizedOnParentParam<T: ?Sized> {
fn func() where T: Sized;
}

pub trait SizedSelf: Sized {}

pub trait SizedOnParentSelf {
fn func(self) -> Self
where
Self: Sized
{
self
}
}
14 changes: 0 additions & 14 deletions tests/rustdoc-html/inline_cross/auxiliary/issue-24183.rs
Copy link
Copy Markdown
Member Author

@fmease fmease Jun 1, 2026

Choose a reason for hiding this comment

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

Merged into the new sized-bounds.rs test so we don't have 1000s of micro tests for cross-crate Sized bounds. Of course, there are still more out there which aren't so micro like inline_cross/impl-sized...

View changes since the review

This file was deleted.

This file was deleted.

19 changes: 0 additions & 19 deletions tests/rustdoc-html/inline_cross/self-sized-bounds-24183.rs

This file was deleted.

57 changes: 57 additions & 0 deletions tests/rustdoc-html/inline_cross/sized-bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Check that we properly reconstruct sized bounds from the middle::ty IR to the surface syntax.

#![crate_name = "it"]
//@ aux-build: ext-sized-bounds.rs
extern crate ext_sized_bounds as ext;

// Ensure that we translate [<T as Sized>] to ``.
// Non-`Self` type params are implicitly `Sized`, so we can hide it.
//
//@ has it/fn.sized_param.html
//@ has - '//pre[@class="rust item-decl"]' "fn sized_param<T>()"
//@ !has - '//pre[@class="rust item-decl"]' "T: Sized"
pub use ext::sized_param;

// Ensure that we translate [<T as MetaSized>] to `T: ?Sized`.
// On stable, the user must've opted out of the implicit `Sized` bound using relaxed bound `?Sized`
// which will implicitly add the `MetaSized` bound (which is unstable in the surface language).
//
//@ has it/fn.relaxed_sized_on_param.html
//@ has - '//pre[@class="rust item-decl"]' "fn relaxed_sized_on_param<T>()where T: ?Sized"
pub use ext::relaxed_sized_on_param;

// Ensure that we don't drop the `T: Sized` bound on `func`. Previously, we didn't check if `T`
// actually belongs to the closest item and thought that it was an implicit bound which it isn't.
// issue: <https://github.com/rust-lang/rust/issues/144015>
//
//@ has it/trait.SizedOnParentParam.html
//@ has - '//*[@class="rust item-decl"]' \
// "trait SizedOnParentParam<T>\
// where \
// T: ?Sized"
//@ has - '//*[@id="tymethod.func"]' \
// "fn func()\
// where \
// T: Sized"
pub use ext::SizedOnParentParam;

// Ensure that we don't drop the `Self: Sized` bound on traits.
// Traits are *not* implicitly bounded by `Sized`. They're only implicitly bounded by `MetaSized`.
//
//@ has it/trait.SizedSelf.html
//@ has - '//*[@class="rust item-decl"]' 'trait SizedSelf: Sized {'
pub use ext::SizedSelf;

// Ensure that we don't drop the `Self: Sized` bound.
// First of all, `Self` type params of traits are not implicitly bounded by `Self`.
// Second of all, `Self` appears in a bound on an assoc item but `Self` belongs to the parent item,
// the trait meaning it's definitely user-written and not implicit.
// issue: <https://github.com/rust-lang/rust/issues/24183>
//
//@ has it/trait.SizedOnParentSelf.html
//@ has - '//*[@class="rust item-decl"]' "trait SizedOnParentSelf {"
//@ has - '//*[@id="method.func"]' \
// "fn func(self) -> Self\
// where \
// Self: Sized"
pub use ext::SizedOnParentSelf;
Loading