diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 554b81b14cd3d..6d1f023efe1c0 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -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 } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 901a7fd340afe..0970b4549d77f 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -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 } diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index d529f2300e0c6..a5b5660589f29 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -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}; @@ -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 }; + + 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(¶m.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(), + }); } } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 2f992c622c496..9c757cf857925 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1201,11 +1201,8 @@ impl GenericBound { } fn is_bounded_by_lang_item(&self, tcx: TyCtxt<'_>, lang_item: LangItem) -> bool { - 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; } @@ -1213,8 +1210,8 @@ impl GenericBound { } pub(crate) fn get_trait_path(&self) -> Option { - 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 } diff --git a/tests/rustdoc-html/inline_cross/auxiliary/ext-sized-bounds.rs b/tests/rustdoc-html/inline_cross/auxiliary/ext-sized-bounds.rs new file mode 100644 index 0000000000000..c3a8305e12288 --- /dev/null +++ b/tests/rustdoc-html/inline_cross/auxiliary/ext-sized-bounds.rs @@ -0,0 +1,18 @@ +pub fn sized_param() {} + +pub fn relaxed_sized_on_param() {} + +pub trait SizedOnParentParam { + fn func() where T: Sized; +} + +pub trait SizedSelf: Sized {} + +pub trait SizedOnParentSelf { + fn func(self) -> Self + where + Self: Sized + { + self + } +} diff --git a/tests/rustdoc-html/inline_cross/auxiliary/issue-24183.rs b/tests/rustdoc-html/inline_cross/auxiliary/issue-24183.rs deleted file mode 100644 index e7a13acc6f864..0000000000000 --- a/tests/rustdoc-html/inline_cross/auxiliary/issue-24183.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![crate_type = "lib"] - -pub trait U/*: ?Sized */ { - fn modified(self) -> Self - where - Self: Sized - { - self - } - - fn touch(&self)/* where Self: ?Sized */{} -} - -pub trait S: Sized {} diff --git a/tests/rustdoc-html/inline_cross/self-sized-bounds-24183.method_no_where_self_sized.html b/tests/rustdoc-html/inline_cross/self-sized-bounds-24183.method_no_where_self_sized.html deleted file mode 100644 index f3c1c045202b0..0000000000000 --- a/tests/rustdoc-html/inline_cross/self-sized-bounds-24183.method_no_where_self_sized.html +++ /dev/null @@ -1 +0,0 @@ -

fn touch(&self)

\ No newline at end of file diff --git a/tests/rustdoc-html/inline_cross/self-sized-bounds-24183.rs b/tests/rustdoc-html/inline_cross/self-sized-bounds-24183.rs deleted file mode 100644 index 909005532f505..0000000000000 --- a/tests/rustdoc-html/inline_cross/self-sized-bounds-24183.rs +++ /dev/null @@ -1,19 +0,0 @@ -// https://github.com/rust-lang/rust/issues/24183 -#![crate_type = "lib"] -#![crate_name = "usr"] - -//@ aux-crate:issue_24183=issue-24183.rs -//@ edition: 2021 - -//@ has usr/trait.U.html -//@ has - '//*[@class="rust item-decl"]' "pub trait U {" -//@ has - '//*[@id="method.modified"]' \ -// "fn modified(self) -> Self\ -// where \ -// Self: Sized" -//@ snapshot method_no_where_self_sized - '//*[@id="method.touch"]/*[@class="code-header"]' -pub use issue_24183::U; - -//@ has usr/trait.S.html -//@ has - '//*[@class="rust item-decl"]' 'pub trait S: Sized {' -pub use issue_24183::S; diff --git a/tests/rustdoc-html/inline_cross/sized-bounds.rs b/tests/rustdoc-html/inline_cross/sized-bounds.rs new file mode 100644 index 0000000000000..ac980897b7e66 --- /dev/null +++ b/tests/rustdoc-html/inline_cross/sized-bounds.rs @@ -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 [] 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()" +//@ !has - '//pre[@class="rust item-decl"]' "T: Sized" +pub use ext::sized_param; + +// Ensure that we translate [] 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()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: +// +//@ has it/trait.SizedOnParentParam.html +//@ has - '//*[@class="rust item-decl"]' \ +// "trait SizedOnParentParam\ +// 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: +// +//@ 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;