Skip to content

Commit 1fa0c4d

Browse files
Rollup merge of #114059 - fmease:rustdoc-fix-x-crate-impl-sized, r=GuillaumeGomez
rustdoc: fix cross-crate `impl Sized` & `impl ?Sized` Previously, cross-crate impl-Trait (APIT, RPIT, etc.) that only consists of a single `Sized` bound (modulo outlives-bounds) and ones that are `?Sized` were incorrectly rendered. To give you a taste (before vs. after): ```diff - fn sized(x: impl ) -> impl + fn sized(x: impl Sized) -> impl Sized - fn sized_outlives<'a>(x: impl 'a) -> impl 'a + fn sized_outlives<'a>(x: impl Sized + 'a) -> impl Sized + 'a - fn maybe_sized(x: &impl ) -> &impl + fn maybe_sized(x: &impl ?Sized) -> &impl ?Sized - fn debug_maybe_sized(x: &impl Debug) -> &impl ?Sized + Debug + fn debug_maybe_sized(x: &(impl Debug + ?Sized)) -> &(impl Debug + ?Sized) ``` Moreover, we now surround impl-Trait that has multiple bounds with parentheses if they're the pointee of a reference or raw pointer type. This affects both local and cross-crate docs. The current output isn't correct (rustc would emit the error *ambiguous `+` in a type* if we fed the rendered code back to it). --- Best reviewed commit by commit :) `@rustbot` label A-cross-crate-reexports
2 parents b8414fe + 28d40f1 commit 1fa0c4d

File tree

7 files changed

+136
-48
lines changed

7 files changed

+136
-48
lines changed

src/librustdoc/clean/mod.rs

+49-31
Original file line numberDiff line numberDiff line change
@@ -804,10 +804,10 @@ fn clean_ty_generics<'tcx>(
804804
let where_predicates = preds
805805
.predicates
806806
.iter()
807-
.flat_map(|(p, _)| {
807+
.flat_map(|(pred, _)| {
808808
let mut projection = None;
809809
let param_idx = (|| {
810-
let bound_p = p.kind();
810+
let bound_p = pred.kind();
811811
match bound_p.skip_binder() {
812812
ty::ClauseKind::Trait(pred) => {
813813
if let ty::Param(param) = pred.self_ty().kind() {
@@ -832,33 +832,26 @@ fn clean_ty_generics<'tcx>(
832832
})();
833833

834834
if let Some(param_idx) = param_idx
835-
&& let Some(b) = impl_trait.get_mut(&param_idx.into())
835+
&& let Some(bounds) = impl_trait.get_mut(&param_idx.into())
836836
{
837-
let p: WherePredicate = clean_predicate(*p, cx)?;
837+
let pred = clean_predicate(*pred, cx)?;
838838

839-
b.extend(
840-
p.get_bounds()
839+
bounds.extend(
840+
pred.get_bounds()
841841
.into_iter()
842842
.flatten()
843843
.cloned()
844-
.filter(|b| !b.is_sized_bound(cx)),
845844
);
846845

847-
let proj = projection.map(|p| {
848-
(
849-
clean_projection(p.map_bound(|p| p.projection_ty), cx, None),
850-
p.map_bound(|p| p.term),
851-
)
852-
});
853-
if let Some(((_, trait_did, name), rhs)) = proj
854-
.as_ref()
855-
.and_then(|(lhs, rhs): &(Type, _)| Some((lhs.projection()?, rhs)))
846+
if let Some(proj) = projection
847+
&& let lhs = clean_projection(proj.map_bound(|p| p.projection_ty), cx, None)
848+
&& let Some((_, trait_did, name)) = lhs.projection()
856849
{
857850
impl_trait_proj.entry(param_idx).or_default().push((
858851
trait_did,
859852
name,
860-
*rhs,
861-
p.get_bound_params()
853+
proj.map_bound(|p| p.term),
854+
pred.get_bound_params()
862855
.into_iter()
863856
.flatten()
864857
.cloned()
@@ -869,13 +862,32 @@ fn clean_ty_generics<'tcx>(
869862
return None;
870863
}
871864

872-
Some(p)
865+
Some(pred)
873866
})
874867
.collect::<Vec<_>>();
875868

876869
for (param, mut bounds) in impl_trait {
870+
let mut has_sized = false;
871+
bounds.retain(|b| {
872+
if b.is_sized_bound(cx) {
873+
has_sized = true;
874+
false
875+
} else {
876+
true
877+
}
878+
});
879+
if !has_sized {
880+
bounds.push(GenericBound::maybe_sized(cx));
881+
}
882+
877883
// Move trait bounds to the front.
878-
bounds.sort_by_key(|b| !matches!(b, GenericBound::TraitBound(..)));
884+
bounds.sort_by_key(|b| !b.is_trait_bound());
885+
886+
// Add back a `Sized` bound if there are no *trait* bounds remaining (incl. `?Sized`).
887+
// Since all potential trait bounds are at the front we can just check the first bound.
888+
if bounds.first().map_or(true, |b| !b.is_trait_bound()) {
889+
bounds.insert(0, GenericBound::sized(cx));
890+
}
879891

880892
let crate::core::ImplTraitParam::ParamIndex(idx) = param else { unreachable!() };
881893
if let Some(proj) = impl_trait_proj.remove(&idx) {
@@ -897,7 +909,7 @@ fn clean_ty_generics<'tcx>(
897909
// implicit `Sized` bound unless removed with `?Sized`.
898910
// However, in the list of where-predicates below, `Sized` appears like a
899911
// normal bound: It's either present (the type is sized) or
900-
// absent (the type is unsized) but never *maybe* (i.e. `?Sized`).
912+
// absent (the type might be unsized) but never *maybe* (i.e. `?Sized`).
901913
//
902914
// This is unsuitable for rendering.
903915
// Thus, as a first step remove all `Sized` bounds that should be implicit.
@@ -908,8 +920,8 @@ fn clean_ty_generics<'tcx>(
908920
let mut sized_params = FxHashSet::default();
909921
where_predicates.retain(|pred| {
910922
if let WherePredicate::BoundPredicate { ty: Generic(g), bounds, .. } = pred
911-
&& *g != kw::SelfUpper
912-
&& bounds.iter().any(|b| b.is_sized_bound(cx))
923+
&& *g != kw::SelfUpper
924+
&& bounds.iter().any(|b| b.is_sized_bound(cx))
913925
{
914926
sized_params.insert(*g);
915927
false
@@ -2119,7 +2131,6 @@ fn clean_middle_opaque_bounds<'tcx>(
21192131
cx: &mut DocContext<'tcx>,
21202132
bounds: Vec<ty::Clause<'tcx>>,
21212133
) -> Type {
2122-
let mut regions = vec![];
21232134
let mut has_sized = false;
21242135
let mut bounds = bounds
21252136
.iter()
@@ -2128,10 +2139,7 @@ fn clean_middle_opaque_bounds<'tcx>(
21282139
let trait_ref = match bound_predicate.skip_binder() {
21292140
ty::ClauseKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref),
21302141
ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => {
2131-
if let Some(r) = clean_middle_region(reg) {
2132-
regions.push(GenericBound::Outlives(r));
2133-
}
2134-
return None;
2142+
return clean_middle_region(reg).map(GenericBound::Outlives);
21352143
}
21362144
_ => return None,
21372145
};
@@ -2167,10 +2175,20 @@ fn clean_middle_opaque_bounds<'tcx>(
21672175
Some(clean_poly_trait_ref_with_bindings(cx, trait_ref, bindings))
21682176
})
21692177
.collect::<Vec<_>>();
2170-
bounds.extend(regions);
2171-
if !has_sized && !bounds.is_empty() {
2172-
bounds.insert(0, GenericBound::maybe_sized(cx));
2178+
2179+
if !has_sized {
2180+
bounds.push(GenericBound::maybe_sized(cx));
21732181
}
2182+
2183+
// Move trait bounds to the front.
2184+
bounds.sort_by_key(|b| !b.is_trait_bound());
2185+
2186+
// Add back a `Sized` bound if there are no *trait* bounds remaining (incl. `?Sized`).
2187+
// Since all potential trait bounds are at the front we can just check the first bound.
2188+
if bounds.first().map_or(true, |b| !b.is_trait_bound()) {
2189+
bounds.insert(0, GenericBound::sized(cx));
2190+
}
2191+
21742192
ImplTrait(bounds)
21752193
}
21762194

src/librustdoc/clean/types.rs

+13-4
Original file line numberDiff line numberDiff line change
@@ -1219,15 +1219,24 @@ pub(crate) enum GenericBound {
12191219
}
12201220

12211221
impl GenericBound {
1222+
pub(crate) fn sized(cx: &mut DocContext<'_>) -> GenericBound {
1223+
Self::sized_with(cx, hir::TraitBoundModifier::None)
1224+
}
1225+
12221226
pub(crate) fn maybe_sized(cx: &mut DocContext<'_>) -> GenericBound {
1227+
Self::sized_with(cx, hir::TraitBoundModifier::Maybe)
1228+
}
1229+
1230+
fn sized_with(cx: &mut DocContext<'_>, modifier: hir::TraitBoundModifier) -> GenericBound {
12231231
let did = cx.tcx.require_lang_item(LangItem::Sized, None);
12241232
let empty = ty::Binder::dummy(ty::GenericArgs::empty());
12251233
let path = external_path(cx, did, false, ThinVec::new(), empty);
12261234
inline::record_extern_fqn(cx, did, ItemType::Trait);
1227-
GenericBound::TraitBound(
1228-
PolyTrait { trait_: path, generic_params: Vec::new() },
1229-
hir::TraitBoundModifier::Maybe,
1230-
)
1235+
GenericBound::TraitBound(PolyTrait { trait_: path, generic_params: Vec::new() }, modifier)
1236+
}
1237+
1238+
pub(crate) fn is_trait_bound(&self) -> bool {
1239+
matches!(self, Self::TraitBound(..))
12311240
}
12321241

12331242
pub(crate) fn is_sized_bound(&self, cx: &DocContext<'_>) -> bool {

src/librustdoc/html/format.rs

+24-11
Original file line numberDiff line numberDiff line change
@@ -1102,22 +1102,35 @@ fn fmt_type<'cx>(
11021102
};
11031103
let m = mutability.print_with_space();
11041104
let amp = if f.alternate() { "&" } else { "&amp;" };
1105-
match **ty {
1105+
1106+
if let clean::Generic(name) = **ty {
1107+
return primitive_link(
1108+
f,
1109+
PrimitiveType::Reference,
1110+
&format!("{amp}{lt}{m}{name}"),
1111+
cx,
1112+
);
1113+
}
1114+
1115+
write!(f, "{amp}{lt}{m}")?;
1116+
1117+
let needs_parens = match **ty {
11061118
clean::DynTrait(ref bounds, ref trait_lt)
11071119
if bounds.len() > 1 || trait_lt.is_some() =>
11081120
{
1109-
write!(f, "{}{}{}(", amp, lt, m)?;
1110-
fmt_type(ty, f, use_absolute, cx)?;
1111-
write!(f, ")")
1112-
}
1113-
clean::Generic(name) => {
1114-
primitive_link(f, PrimitiveType::Reference, &format!("{amp}{lt}{m}{name}"), cx)
1115-
}
1116-
_ => {
1117-
write!(f, "{}{}{}", amp, lt, m)?;
1118-
fmt_type(ty, f, use_absolute, cx)
1121+
true
11191122
}
1123+
clean::ImplTrait(ref bounds) if bounds.len() > 1 => true,
1124+
_ => false,
1125+
};
1126+
if needs_parens {
1127+
f.write_str("(")?;
11201128
}
1129+
fmt_type(ty, f, use_absolute, cx)?;
1130+
if needs_parens {
1131+
f.write_str(")")?;
1132+
}
1133+
Ok(())
11211134
}
11221135
clean::ImplTrait(ref bounds) => {
11231136
if f.alternate() {

tests/rustdoc/extern-impl-trait.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ extern crate extern_impl_trait;
77
// @has 'foo/struct.X.html' '//h4[@class="code-header"]' "impl Foo<Associated = ()> + 'a"
88
pub use extern_impl_trait::X;
99

10-
// @has 'foo/struct.Y.html' '//h4[@class="code-header"]' "impl ?Sized + Foo<Associated = ()> + 'a"
10+
// @has 'foo/struct.Y.html' '//h4[@class="code-header"]' "impl Foo<Associated = ()> + ?Sized + 'a"
1111
pub use extern_impl_trait::Y;

tests/rustdoc/impl-everywhere.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ pub fn foo_foo() -> impl Foo + Foo2 {
2525
Bar
2626
}
2727

28-
// @has foo/fn.foo_foo_foo.html '//section[@id="main-content"]//pre' "x: &'x impl Foo + Foo2"
28+
// @has foo/fn.foo_foo_foo.html '//section[@id="main-content"]//pre' "x: &'x (impl Foo + Foo2)"
2929
pub fn foo_foo_foo<'x>(_x: &'x (impl Foo + Foo2)) {
3030
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use std::fmt::Debug;
2+
3+
pub fn sized(x: impl Sized) -> impl Sized {
4+
x
5+
}
6+
7+
pub fn sized_outlives<'a>(x: impl Sized + 'a) -> impl Sized + 'a {
8+
x
9+
}
10+
11+
pub fn maybe_sized(x: &impl ?Sized) -> &impl ?Sized {
12+
x
13+
}
14+
15+
pub fn debug_maybe_sized(x: &(impl Debug + ?Sized)) -> &(impl Debug + ?Sized) {
16+
x
17+
}
18+
19+
pub fn maybe_sized_outlives<'t>(x: &(impl ?Sized + 't)) -> &(impl ?Sized + 't) {
20+
x
21+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![crate_name = "user"]
2+
3+
// aux-crate:impl_sized=impl-sized.rs
4+
// edition:2021
5+
6+
// @has user/fn.sized.html
7+
// @has - '//pre[@class="rust item-decl"]' "sized(x: impl Sized) -> impl Sized"
8+
pub use impl_sized::sized;
9+
10+
// @has user/fn.sized_outlives.html
11+
// @has - '//pre[@class="rust item-decl"]' \
12+
// "sized_outlives<'a>(x: impl Sized + 'a) -> impl Sized + 'a"
13+
pub use impl_sized::sized_outlives;
14+
15+
// @has user/fn.maybe_sized.html
16+
// @has - '//pre[@class="rust item-decl"]' "maybe_sized(x: &impl ?Sized) -> &impl ?Sized"
17+
pub use impl_sized::maybe_sized;
18+
19+
// @has user/fn.debug_maybe_sized.html
20+
// @has - '//pre[@class="rust item-decl"]' \
21+
// "debug_maybe_sized(x: &(impl Debug + ?Sized)) -> &(impl Debug + ?Sized)"
22+
pub use impl_sized::debug_maybe_sized;
23+
24+
// @has user/fn.maybe_sized_outlives.html
25+
// @has - '//pre[@class="rust item-decl"]' \
26+
// "maybe_sized_outlives<'t>(x: &(impl ?Sized + 't)) -> &(impl ?Sized + 't)"
27+
pub use impl_sized::maybe_sized_outlives;

0 commit comments

Comments
 (0)