From 73c239e5eb0b3197ee6d0faeaf85a8eaccb8a491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Tue, 4 Oct 2022 14:08:25 +0200 Subject: [PATCH] rustdoc: render more cross-crate hrtbs properly --- src/librustdoc/clean/auto_trait.rs | 13 +++- src/librustdoc/clean/mod.rs | 62 ++++++++++++++----- src/librustdoc/clean/simplify.rs | 37 +++++++---- src/librustdoc/clean/types.rs | 11 +++- src/librustdoc/html/format.rs | 3 +- src/librustdoc/json/conversions.rs | 5 +- .../assoc_item_trait_bounds_with_bindings.rs | 9 ++- .../assoc_item_trait_bounds_with_bindings.rs | 5 ++ .../inline_cross/auxiliary/impl_trait_aux.rs | 13 ++++ src/test/rustdoc/inline_cross/impl_trait.rs | 7 +++ 10 files changed, 124 insertions(+), 41 deletions(-) diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 175472797cb46..efa9242a46784 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -475,6 +475,12 @@ where let mut ty_to_fn: FxHashMap)> = Default::default(); + // FIXME: This code shares much of the logic found in `clean_ty_generics` and + // `simplify::where_clause`. Consider deduplicating it to avoid diverging + // implementations. + // Further, the code below does not merge (partially re-sugared) bounds like + // `Tr` & `Tr` and it does not render higher-ranked parameters + // originating from equality predicates. for p in clean_where_predicates { let (orig_p, p) = (p, clean_predicate(p, self.cx)); if p.is_none() { @@ -549,8 +555,8 @@ where WherePredicate::RegionPredicate { lifetime, bounds } => { lifetime_to_bounds.entry(lifetime).or_default().extend(bounds); } - WherePredicate::EqPredicate { lhs, rhs } => { - match lhs { + WherePredicate::EqPredicate { lhs, rhs, bound_params } => { + match *lhs { Type::QPath(box QPathData { ref assoc, ref self_type, ref trait_, .. }) => { @@ -585,13 +591,14 @@ where GenericArgs::AngleBracketed { ref mut bindings, .. } => { bindings.push(TypeBinding { assoc: assoc.clone(), - kind: TypeBindingKind::Equality { term: rhs }, + kind: TypeBindingKind::Equality { term: *rhs }, }); } GenericArgs::Parenthesized { .. } => { existing_predicates.push(WherePredicate::EqPredicate { lhs: lhs.clone(), rhs, + bound_params, }); continue; // If something other than a Fn ends up // with parentheses, leave it alone diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index c61175ecebf36..11834a53afb37 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -292,8 +292,9 @@ fn clean_where_predicate<'tcx>( }, hir::WherePredicate::EqPredicate(ref wrp) => WherePredicate::EqPredicate { - lhs: clean_ty(wrp.lhs_ty, cx), - rhs: clean_ty(wrp.rhs_ty, cx).into(), + lhs: Box::new(clean_ty(wrp.lhs_ty, cx)), + rhs: Box::new(clean_ty(wrp.rhs_ty, cx).into()), + bound_params: Vec::new(), }, }) } @@ -309,7 +310,9 @@ pub(crate) fn clean_predicate<'tcx>( } ty::PredicateKind::RegionOutlives(pred) => clean_region_outlives_predicate(pred), ty::PredicateKind::TypeOutlives(pred) => clean_type_outlives_predicate(pred, cx), - ty::PredicateKind::Projection(pred) => Some(clean_projection_predicate(pred, cx)), + ty::PredicateKind::Projection(pred) => { + Some(clean_projection_predicate(bound_predicate.rebind(pred), cx)) + } ty::PredicateKind::ConstEvaluatable(..) => None, ty::PredicateKind::WellFormed(..) => None, @@ -387,13 +390,25 @@ fn clean_hir_term<'tcx>(term: &hir::Term<'tcx>, cx: &mut DocContext<'tcx>) -> Te } fn clean_projection_predicate<'tcx>( - pred: ty::ProjectionPredicate<'tcx>, + pred: ty::Binder<'tcx, ty::ProjectionPredicate<'tcx>>, cx: &mut DocContext<'tcx>, ) -> WherePredicate { - let ty::ProjectionPredicate { projection_ty, term } = pred; + let late_bound_regions = cx + .tcx + .collect_referenced_late_bound_regions(&pred) + .into_iter() + .filter_map(|br| match br { + ty::BrNamed(_, name) if name != kw::UnderscoreLifetime => Some(Lifetime(name)), + _ => None, + }) + .collect(); + + let ty::ProjectionPredicate { projection_ty, term } = pred.skip_binder(); + WherePredicate::EqPredicate { - lhs: clean_projection(projection_ty, cx, None), - rhs: clean_middle_term(term, cx), + lhs: Box::new(clean_projection(projection_ty, cx, None)), + rhs: Box::new(clean_middle_term(term, cx)), + bound_params: late_bound_regions, } } @@ -655,8 +670,9 @@ fn clean_ty_generics<'tcx>( }) .collect::>(); - // param index -> [(DefId of trait, associated type name and generics, type)] - let mut impl_trait_proj = FxHashMap::)>>::default(); + // param index -> [(trait DefId, associated type name & generics, type, higher-ranked params)] + let mut impl_trait_proj = + FxHashMap::, Vec)>>::default(); let where_predicates = preds .predicates @@ -715,6 +731,14 @@ fn clean_ty_generics<'tcx>( trait_did, name, rhs.ty().unwrap(), + p.get_bound_params() + .into_iter() + .flatten() + .map(|param| GenericParamDef { + name: param.0, + kind: GenericParamDefKind::Lifetime { outlives: Vec::new() }, + }) + .collect(), )); } @@ -730,15 +754,19 @@ fn clean_ty_generics<'tcx>( // Move trait bounds to the front. bounds.sort_by_key(|b| !matches!(b, GenericBound::TraitBound(..))); - if let crate::core::ImplTraitParam::ParamIndex(idx) = param { - if let Some(proj) = impl_trait_proj.remove(&idx) { - for (trait_did, name, rhs) in proj { - let rhs = clean_middle_ty(rhs, cx, None); - simplify::merge_bounds(cx, &mut bounds, trait_did, name, &Term::Type(rhs)); - } + let crate::core::ImplTraitParam::ParamIndex(idx) = param else { unreachable!() }; + if let Some(proj) = impl_trait_proj.remove(&idx) { + for (trait_did, name, rhs, bound_params) in proj { + let rhs = clean_middle_ty(rhs, cx, None); + simplify::merge_bounds( + cx, + &mut bounds, + bound_params, + trait_did, + name, + &Term::Type(rhs), + ); } - } else { - unreachable!(); } cx.impl_trait_bounds.insert(param, bounds); diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index f82ea8969ab44..1bcb9fcd5a45f 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -39,23 +39,23 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec) -> Vec { WP::RegionPredicate { lifetime, bounds } => { lifetimes.push((lifetime, bounds)); } - WP::EqPredicate { lhs, rhs } => equalities.push((lhs, rhs)), + WP::EqPredicate { lhs, rhs, bound_params } => equalities.push((lhs, rhs, bound_params)), } } // Look for equality predicates on associated types that can be merged into // general bound predicates. - equalities.retain(|&(ref lhs, ref rhs)| { + equalities.retain(|&(ref lhs, ref rhs, ref bound_params)| { let Some((ty, trait_did, name)) = lhs.projection() else { return true; }; - // FIXME(fmease): We don't handle HRTBs correctly here. - // Pass `_bound_params` (higher-rank lifetimes) to a modified version of - // `merge_bounds`. That vector is currently always empty though since we - // don't keep track of late-bound lifetimes when cleaning projection - // predicates to cleaned equality predicates while we should first query - // them with `collect_referenced_late_bound_regions` and then store them - // (or something similar). For prior art, see `clean::auto_trait`. - let Some((bounds, _bound_params)) = tybounds.get_mut(ty) else { return true }; - merge_bounds(cx, bounds, trait_did, name, rhs) + let Some((bounds, _)) = tybounds.get_mut(ty) else { return true }; + let bound_params = bound_params + .into_iter() + .map(|param| clean::GenericParamDef { + name: param.0, + kind: clean::GenericParamDefKind::Lifetime { outlives: Vec::new() }, + }) + .collect(); + merge_bounds(cx, bounds, bound_params, trait_did, name, rhs) }); // And finally, let's reassemble everything @@ -68,13 +68,18 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec) -> Vec { bounds, bound_params, })); - clauses.extend(equalities.into_iter().map(|(lhs, rhs)| WP::EqPredicate { lhs, rhs })); + clauses.extend(equalities.into_iter().map(|(lhs, rhs, bound_params)| WP::EqPredicate { + lhs, + rhs, + bound_params, + })); clauses } pub(crate) fn merge_bounds( cx: &clean::DocContext<'_>, bounds: &mut Vec, + mut bound_params: Vec, trait_did: DefId, assoc: clean::PathSegment, rhs: &clean::Term, @@ -91,6 +96,14 @@ pub(crate) fn merge_bounds( return false; } let last = trait_ref.trait_.segments.last_mut().expect("segments were empty"); + + trait_ref.generic_params.append(&mut bound_params); + // Since the parameters (probably) originate from `tcx.collect_*_late_bound_regions` which + // returns a hash set, sort them alphabetically to guarantee a stable and deterministic + // output (and to fully deduplicate them). + trait_ref.generic_params.sort_unstable_by(|p, q| p.name.as_str().cmp(q.name.as_str())); + trait_ref.generic_params.dedup_by_key(|p| p.name); + match last.args { PP::AngleBracketed { ref mut bindings, .. } => { bindings.push(clean::TypeBinding { diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 5a4fa05e261c9..7e0533800694e 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1350,7 +1350,7 @@ impl Lifetime { pub(crate) enum WherePredicate { BoundPredicate { ty: Type, bounds: Vec, bound_params: Vec }, RegionPredicate { lifetime: Lifetime, bounds: Vec }, - EqPredicate { lhs: Type, rhs: Term }, + EqPredicate { lhs: Box, rhs: Box, bound_params: Vec }, } impl WherePredicate { @@ -1361,6 +1361,15 @@ impl WherePredicate { _ => None, } } + + pub(crate) fn get_bound_params(&self) -> Option<&[Lifetime]> { + match self { + Self::BoundPredicate { bound_params, .. } | Self::EqPredicate { bound_params, .. } => { + Some(bound_params) + } + _ => None, + } + } } #[derive(Clone, PartialEq, Eq, Debug, Hash)] diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 6d46267931bad..943a074366e8e 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -331,7 +331,8 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( bounds_display.truncate(bounds_display.len() - " + ".len()); write!(f, "{}: {bounds_display}", lifetime.print()) } - clean::WherePredicate::EqPredicate { lhs, rhs } => { + // FIXME(fmease): Render bound params. + clean::WherePredicate::EqPredicate { lhs, rhs, bound_params: _ } => { if f.alternate() { write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx)) } else { diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 4170412caef87..cdf59cdd32845 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -432,8 +432,9 @@ impl FromWithTcx for WherePredicate { lifetime: convert_lifetime(lifetime), bounds: bounds.into_tcx(tcx), }, - EqPredicate { lhs, rhs } => { - WherePredicate::EqPredicate { lhs: lhs.into_tcx(tcx), rhs: rhs.into_tcx(tcx) } + // FIXME(fmease): Convert bound parameters as well. + EqPredicate { lhs, rhs, bound_params: _ } => { + WherePredicate::EqPredicate { lhs: (*lhs).into_tcx(tcx), rhs: (*rhs).into_tcx(tcx) } } } } diff --git a/src/test/rustdoc/inline_cross/assoc_item_trait_bounds_with_bindings.rs b/src/test/rustdoc/inline_cross/assoc_item_trait_bounds_with_bindings.rs index b026f399a5667..00976aa74420f 100644 --- a/src/test/rustdoc/inline_cross/assoc_item_trait_bounds_with_bindings.rs +++ b/src/test/rustdoc/inline_cross/assoc_item_trait_bounds_with_bindings.rs @@ -8,8 +8,6 @@ extern crate assoc_item_trait_bounds_with_bindings as aux; // FIXME(fmease): Don't render an incorrect `T: ?Sized` where-clause for parameters // of GATs like `Main::Out{2,4}`. Add a snapshot test once it's fixed. -// FIXME(fmease): Print the `for<>` parameter list in the bounds of -// `Main::Out{6,11,12}`. // @has main/trait.Main.html // @has - '//*[@id="associatedtype.Out0"]' 'type Out0: Support' @@ -18,13 +16,14 @@ extern crate assoc_item_trait_bounds_with_bindings as aux; // @has - '//*[@id="associatedtype.Out3"]' 'type Out3: Support = bool>' // @has - '//*[@id="associatedtype.Out4"]' 'type Out4: Support = T>' // @has - '//*[@id="associatedtype.Out5"]' "type Out5: Support = &'static ()>" -// @has - '//*[@id="associatedtype.Out6"]' "type Out6: Support = &'a ()>" +// @has - '//*[@id="associatedtype.Out6"]' "type Out6: for<'a> Support = &'a ()>" // @has - '//*[@id="associatedtype.Out7"]' "type Out7: Support = u32> + Unrelated" // @has - '//*[@id="associatedtype.Out8"]' "type Out8: Unrelated + Protocol" // @has - '//*[@id="associatedtype.Out9"]' "type Out9: FnMut(i32) -> bool + Clone" // @has - '//*[@id="associatedtype.Out10"]' "type Out10<'q>: Support = ()>" -// @has - '//*[@id="associatedtype.Out11"]' "type Out11: Helper = &'s (), B<'r> = ()>" -// @has - '//*[@id="associatedtype.Out12"]' "type Out12: Helper = Cow<'w, str>, A<'w> = bool>" +// @has - '//*[@id="associatedtype.Out11"]' "type Out11: for<'r, 's> Helper = &'s (), B<'r> = ()>" +// @has - '//*[@id="associatedtype.Out12"]' "type Out12: for<'w> Helper = Cow<'w, str>, A<'w> = bool>" +// @has - '//*[@id="associatedtype.Out13"]' "type Out13: for<'fst, 'snd> Aid<'snd, Result<'fst> = &'fst mut str>" // // Snapshots: Check that we do not render any where-clauses for those associated types since all of // the trait bounds contained within were moved to the bounds of the respective item. diff --git a/src/test/rustdoc/inline_cross/auxiliary/assoc_item_trait_bounds_with_bindings.rs b/src/test/rustdoc/inline_cross/auxiliary/assoc_item_trait_bounds_with_bindings.rs index 7225f2dca10db..f451b1a0e9920 100644 --- a/src/test/rustdoc/inline_cross/auxiliary/assoc_item_trait_bounds_with_bindings.rs +++ b/src/test/rustdoc/inline_cross/auxiliary/assoc_item_trait_bounds_with_bindings.rs @@ -14,6 +14,7 @@ pub trait Main { type Out10<'q>: Support = ()>; type Out11: for<'r, 's> Helper = &'s (), B<'r> = ()>; type Out12: for<'w> Helper = std::borrow::Cow<'w, str>, A<'w> = bool>; + type Out13: for<'fst, 'snd> Aid<'snd, Result<'fst> = &'fst mut str>; fn make(_: F, _: impl FnMut(&str) -> bool) where @@ -38,3 +39,7 @@ pub trait Helper { type A<'q>; type B<'q>; } + +pub trait Aid<'src> { + type Result<'inter>; +} diff --git a/src/test/rustdoc/inline_cross/auxiliary/impl_trait_aux.rs b/src/test/rustdoc/inline_cross/auxiliary/impl_trait_aux.rs index 913ba8f2a1649..19433c9682bb0 100644 --- a/src/test/rustdoc/inline_cross/auxiliary/impl_trait_aux.rs +++ b/src/test/rustdoc/inline_cross/auxiliary/impl_trait_aux.rs @@ -13,6 +13,19 @@ pub fn func3(_x: impl Iterator> + Clone) {} pub fn func4>(_x: T) {} +pub fn func5( + _f: impl for<'any> Fn(&'any str, &'any str) -> bool + for<'r> Other = ()>, + _a: impl for<'alpha, 'beta> Auxiliary<'alpha, Item<'beta> = fn(&'beta ())>, +) {} + +pub trait Other { + type T<'dependency>; +} + +pub trait Auxiliary<'arena> { + type Item<'input>; +} + pub async fn async_fn() {} pub struct Foo; diff --git a/src/test/rustdoc/inline_cross/impl_trait.rs b/src/test/rustdoc/inline_cross/impl_trait.rs index ef615472b0e98..6c1cf8252a9d6 100644 --- a/src/test/rustdoc/inline_cross/impl_trait.rs +++ b/src/test/rustdoc/inline_cross/impl_trait.rs @@ -26,6 +26,13 @@ pub use impl_trait_aux::func3; // @has - '//pre[@class="rust fn"]' "T: Iterator," pub use impl_trait_aux::func4; +// @has impl_trait/fn.func5.html +// @has - '//pre[@class="rust fn"]' "func5(" +// @has - '//pre[@class="rust fn"]' "_f: impl for<'any> Fn(&'any str, &'any str) -> bool + for<'r> Other = ()>," +// @has - '//pre[@class="rust fn"]' "_a: impl for<'alpha, 'beta> Auxiliary<'alpha, Item<'beta> = fn(&'beta ())>" +// @!has - '//pre[@class="rust fn"]' 'where' +pub use impl_trait_aux::func5; + // @has impl_trait/fn.async_fn.html // @has - '//pre[@class="rust fn"]' "pub async fn async_fn()" pub use impl_trait_aux::async_fn;