diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index d14953f1bb786..40a1e214a3b83 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -798,10 +798,10 @@ fn clean_ty_generics<'tcx>( let where_predicates = preds .predicates .iter() - .flat_map(|(p, _)| { + .flat_map(|(pred, _)| { let mut projection = None; let param_idx = (|| { - let bound_p = p.kind(); + let bound_p = pred.kind(); match bound_p.skip_binder() { ty::ClauseKind::Trait(pred) => { if let ty::Param(param) = pred.self_ty().kind() { @@ -826,33 +826,26 @@ fn clean_ty_generics<'tcx>( })(); if let Some(param_idx) = param_idx - && let Some(b) = impl_trait.get_mut(¶m_idx.into()) + && let Some(bounds) = impl_trait.get_mut(¶m_idx.into()) { - let p: WherePredicate = clean_predicate(*p, cx)?; + let pred = clean_predicate(*pred, cx)?; - b.extend( - p.get_bounds() + bounds.extend( + pred.get_bounds() .into_iter() .flatten() .cloned() - .filter(|b| !b.is_sized_bound(cx)), ); - let proj = projection.map(|p| { - ( - clean_projection(p.map_bound(|p| p.projection_ty), cx, None), - p.map_bound(|p| p.term), - ) - }); - if let Some(((_, trait_did, name), rhs)) = proj - .as_ref() - .and_then(|(lhs, rhs): &(Type, _)| Some((lhs.projection()?, rhs))) + if let Some(proj) = projection + && let lhs = clean_projection(proj.map_bound(|p| p.projection_ty), cx, None) + && let Some((_, trait_did, name)) = lhs.projection() { impl_trait_proj.entry(param_idx).or_default().push(( trait_did, name, - *rhs, - p.get_bound_params() + proj.map_bound(|p| p.term), + pred.get_bound_params() .into_iter() .flatten() .cloned() @@ -863,13 +856,32 @@ fn clean_ty_generics<'tcx>( return None; } - Some(p) + Some(pred) }) .collect::<Vec<_>>(); for (param, mut bounds) in impl_trait { + let mut has_sized = false; + bounds.retain(|b| { + if b.is_sized_bound(cx) { + has_sized = true; + false + } else { + true + } + }); + if !has_sized { + bounds.push(GenericBound::maybe_sized(cx)); + } + // Move trait bounds to the front. - bounds.sort_by_key(|b| !matches!(b, GenericBound::TraitBound(..))); + bounds.sort_by_key(|b| !b.is_trait_bound()); + + // Add back a `Sized` bound if there are no *trait* bounds remaining (incl. `?Sized`). + // Since all potential trait bounds are at the front we can just check the first bound. + if bounds.first().map_or(true, |b| !b.is_trait_bound()) { + bounds.insert(0, GenericBound::sized(cx)); + } let crate::core::ImplTraitParam::ParamIndex(idx) = param else { unreachable!() }; if let Some(proj) = impl_trait_proj.remove(&idx) { @@ -891,7 +903,7 @@ fn clean_ty_generics<'tcx>( // 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 is unsized) but never *maybe* (i.e. `?Sized`). + // 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. @@ -902,8 +914,8 @@ fn clean_ty_generics<'tcx>( let mut sized_params = FxHashSet::default(); where_predicates.retain(|pred| { if let WherePredicate::BoundPredicate { ty: Generic(g), bounds, .. } = pred - && *g != kw::SelfUpper - && bounds.iter().any(|b| b.is_sized_bound(cx)) + && *g != kw::SelfUpper + && bounds.iter().any(|b| b.is_sized_bound(cx)) { sized_params.insert(*g); false @@ -2113,7 +2125,6 @@ fn clean_middle_opaque_bounds<'tcx>( cx: &mut DocContext<'tcx>, bounds: Vec<ty::Clause<'tcx>>, ) -> Type { - let mut regions = vec![]; let mut has_sized = false; let mut bounds = bounds .iter() @@ -2122,10 +2133,7 @@ fn clean_middle_opaque_bounds<'tcx>( let trait_ref = match bound_predicate.skip_binder() { ty::ClauseKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref), ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => { - if let Some(r) = clean_middle_region(reg) { - regions.push(GenericBound::Outlives(r)); - } - return None; + return clean_middle_region(reg).map(GenericBound::Outlives); } _ => return None, }; @@ -2161,10 +2169,20 @@ fn clean_middle_opaque_bounds<'tcx>( Some(clean_poly_trait_ref_with_bindings(cx, trait_ref, bindings)) }) .collect::<Vec<_>>(); - bounds.extend(regions); - if !has_sized && !bounds.is_empty() { - bounds.insert(0, GenericBound::maybe_sized(cx)); + + if !has_sized { + bounds.push(GenericBound::maybe_sized(cx)); } + + // Move trait bounds to the front. + bounds.sort_by_key(|b| !b.is_trait_bound()); + + // Add back a `Sized` bound if there are no *trait* bounds remaining (incl. `?Sized`). + // Since all potential trait bounds are at the front we can just check the first bound. + if bounds.first().map_or(true, |b| !b.is_trait_bound()) { + bounds.insert(0, GenericBound::sized(cx)); + } + ImplTrait(bounds) } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index ddef165a05479..647fbcdc3d771 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1219,15 +1219,24 @@ pub(crate) enum GenericBound { } impl GenericBound { + pub(crate) fn sized(cx: &mut DocContext<'_>) -> GenericBound { + Self::sized_with(cx, hir::TraitBoundModifier::None) + } + pub(crate) fn maybe_sized(cx: &mut DocContext<'_>) -> GenericBound { + Self::sized_with(cx, hir::TraitBoundModifier::Maybe) + } + + fn sized_with(cx: &mut DocContext<'_>, modifier: hir::TraitBoundModifier) -> GenericBound { let did = cx.tcx.require_lang_item(LangItem::Sized, None); let empty = ty::Binder::dummy(ty::GenericArgs::empty()); let path = external_path(cx, did, false, ThinVec::new(), empty); inline::record_extern_fqn(cx, did, ItemType::Trait); - GenericBound::TraitBound( - PolyTrait { trait_: path, generic_params: Vec::new() }, - hir::TraitBoundModifier::Maybe, - ) + GenericBound::TraitBound(PolyTrait { trait_: path, generic_params: Vec::new() }, modifier) + } + + pub(crate) fn is_trait_bound(&self) -> bool { + matches!(self, Self::TraitBound(..)) } pub(crate) fn is_sized_bound(&self, cx: &DocContext<'_>) -> bool { diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 1099c68b004cb..eba77157b240a 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -1094,22 +1094,35 @@ fn fmt_type<'cx>( }; let m = mutability.print_with_space(); let amp = if f.alternate() { "&" } else { "&" }; - match **ty { + + if let clean::Generic(name) = **ty { + return primitive_link( + f, + PrimitiveType::Reference, + &format!("{amp}{lt}{m}{name}"), + cx, + ); + } + + write!(f, "{amp}{lt}{m}")?; + + let needs_parens = match **ty { clean::DynTrait(ref bounds, ref trait_lt) if bounds.len() > 1 || trait_lt.is_some() => { - write!(f, "{}{}{}(", amp, lt, m)?; - fmt_type(ty, f, use_absolute, cx)?; - write!(f, ")") - } - clean::Generic(name) => { - primitive_link(f, PrimitiveType::Reference, &format!("{amp}{lt}{m}{name}"), cx) - } - _ => { - write!(f, "{}{}{}", amp, lt, m)?; - fmt_type(ty, f, use_absolute, cx) + true } + clean::ImplTrait(ref bounds) if bounds.len() > 1 => true, + _ => false, + }; + if needs_parens { + f.write_str("(")?; } + fmt_type(ty, f, use_absolute, cx)?; + if needs_parens { + f.write_str(")")?; + } + Ok(()) } clean::ImplTrait(ref bounds) => { if f.alternate() { diff --git a/tests/rustdoc/extern-impl-trait.rs b/tests/rustdoc/extern-impl-trait.rs index 8ab026afd1b8d..4d8672305a756 100644 --- a/tests/rustdoc/extern-impl-trait.rs +++ b/tests/rustdoc/extern-impl-trait.rs @@ -7,5 +7,5 @@ extern crate extern_impl_trait; // @has 'foo/struct.X.html' '//h4[@class="code-header"]' "impl Foo<Associated = ()> + 'a" pub use extern_impl_trait::X; -// @has 'foo/struct.Y.html' '//h4[@class="code-header"]' "impl ?Sized + Foo<Associated = ()> + 'a" +// @has 'foo/struct.Y.html' '//h4[@class="code-header"]' "impl Foo<Associated = ()> + ?Sized + 'a" pub use extern_impl_trait::Y; diff --git a/tests/rustdoc/impl-everywhere.rs b/tests/rustdoc/impl-everywhere.rs index 44885d4301ff8..2311c806c18a6 100644 --- a/tests/rustdoc/impl-everywhere.rs +++ b/tests/rustdoc/impl-everywhere.rs @@ -25,6 +25,6 @@ pub fn foo_foo() -> impl Foo + Foo2 { Bar } -// @has foo/fn.foo_foo_foo.html '//section[@id="main-content"]//pre' "x: &'x impl Foo + Foo2" +// @has foo/fn.foo_foo_foo.html '//section[@id="main-content"]//pre' "x: &'x (impl Foo + Foo2)" pub fn foo_foo_foo<'x>(_x: &'x (impl Foo + Foo2)) { } diff --git a/tests/rustdoc/inline_cross/auxiliary/impl-sized.rs b/tests/rustdoc/inline_cross/auxiliary/impl-sized.rs new file mode 100644 index 0000000000000..65f72a3b9ac35 --- /dev/null +++ b/tests/rustdoc/inline_cross/auxiliary/impl-sized.rs @@ -0,0 +1,21 @@ +use std::fmt::Debug; + +pub fn sized(x: impl Sized) -> impl Sized { + x +} + +pub fn sized_outlives<'a>(x: impl Sized + 'a) -> impl Sized + 'a { + x +} + +pub fn maybe_sized(x: &impl ?Sized) -> &impl ?Sized { + x +} + +pub fn debug_maybe_sized(x: &(impl Debug + ?Sized)) -> &(impl Debug + ?Sized) { + x +} + +pub fn maybe_sized_outlives<'t>(x: &(impl ?Sized + 't)) -> &(impl ?Sized + 't) { + x +} diff --git a/tests/rustdoc/inline_cross/impl-sized.rs b/tests/rustdoc/inline_cross/impl-sized.rs new file mode 100644 index 0000000000000..82bdce4747847 --- /dev/null +++ b/tests/rustdoc/inline_cross/impl-sized.rs @@ -0,0 +1,27 @@ +#![crate_name = "user"] + +// aux-crate:impl_sized=impl-sized.rs +// edition:2021 + +// @has user/fn.sized.html +// @has - '//pre[@class="rust item-decl"]' "sized(x: impl Sized) -> impl Sized" +pub use impl_sized::sized; + +// @has user/fn.sized_outlives.html +// @has - '//pre[@class="rust item-decl"]' \ +// "sized_outlives<'a>(x: impl Sized + 'a) -> impl Sized + 'a" +pub use impl_sized::sized_outlives; + +// @has user/fn.maybe_sized.html +// @has - '//pre[@class="rust item-decl"]' "maybe_sized(x: &impl ?Sized) -> &impl ?Sized" +pub use impl_sized::maybe_sized; + +// @has user/fn.debug_maybe_sized.html +// @has - '//pre[@class="rust item-decl"]' \ +// "debug_maybe_sized(x: &(impl Debug + ?Sized)) -> &(impl Debug + ?Sized)" +pub use impl_sized::debug_maybe_sized; + +// @has user/fn.maybe_sized_outlives.html +// @has - '//pre[@class="rust item-decl"]' \ +// "maybe_sized_outlives<'t>(x: &(impl ?Sized + 't)) -> &(impl ?Sized + 't)" +pub use impl_sized::maybe_sized_outlives;