1
Fork 0

rustdoc: render more cross-crate hrtbs properly

This commit is contained in:
León Orell Valerian Liehr 2022-10-04 14:08:25 +02:00
parent f1112099eb
commit 73c239e5eb
No known key found for this signature in database
GPG key ID: CD75E31CE2CFD7D9
10 changed files with 124 additions and 41 deletions

View file

@ -475,6 +475,12 @@ where
let mut ty_to_fn: FxHashMap<Type, (PolyTrait, Option<Type>)> = Default::default(); let mut ty_to_fn: FxHashMap<Type, (PolyTrait, Option<Type>)> = 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<A = T>` & `Tr<B = U>` and it does not render higher-ranked parameters
// originating from equality predicates.
for p in clean_where_predicates { for p in clean_where_predicates {
let (orig_p, p) = (p, clean_predicate(p, self.cx)); let (orig_p, p) = (p, clean_predicate(p, self.cx));
if p.is_none() { if p.is_none() {
@ -549,8 +555,8 @@ where
WherePredicate::RegionPredicate { lifetime, bounds } => { WherePredicate::RegionPredicate { lifetime, bounds } => {
lifetime_to_bounds.entry(lifetime).or_default().extend(bounds); lifetime_to_bounds.entry(lifetime).or_default().extend(bounds);
} }
WherePredicate::EqPredicate { lhs, rhs } => { WherePredicate::EqPredicate { lhs, rhs, bound_params } => {
match lhs { match *lhs {
Type::QPath(box QPathData { Type::QPath(box QPathData {
ref assoc, ref self_type, ref trait_, .. ref assoc, ref self_type, ref trait_, ..
}) => { }) => {
@ -585,13 +591,14 @@ where
GenericArgs::AngleBracketed { ref mut bindings, .. } => { GenericArgs::AngleBracketed { ref mut bindings, .. } => {
bindings.push(TypeBinding { bindings.push(TypeBinding {
assoc: assoc.clone(), assoc: assoc.clone(),
kind: TypeBindingKind::Equality { term: rhs }, kind: TypeBindingKind::Equality { term: *rhs },
}); });
} }
GenericArgs::Parenthesized { .. } => { GenericArgs::Parenthesized { .. } => {
existing_predicates.push(WherePredicate::EqPredicate { existing_predicates.push(WherePredicate::EqPredicate {
lhs: lhs.clone(), lhs: lhs.clone(),
rhs, rhs,
bound_params,
}); });
continue; // If something other than a Fn ends up continue; // If something other than a Fn ends up
// with parentheses, leave it alone // with parentheses, leave it alone

View file

@ -292,8 +292,9 @@ fn clean_where_predicate<'tcx>(
}, },
hir::WherePredicate::EqPredicate(ref wrp) => WherePredicate::EqPredicate { hir::WherePredicate::EqPredicate(ref wrp) => WherePredicate::EqPredicate {
lhs: clean_ty(wrp.lhs_ty, cx), lhs: Box::new(clean_ty(wrp.lhs_ty, cx)),
rhs: clean_ty(wrp.rhs_ty, cx).into(), 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::RegionOutlives(pred) => clean_region_outlives_predicate(pred),
ty::PredicateKind::TypeOutlives(pred) => clean_type_outlives_predicate(pred, cx), 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::ConstEvaluatable(..) => None,
ty::PredicateKind::WellFormed(..) => 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>( fn clean_projection_predicate<'tcx>(
pred: ty::ProjectionPredicate<'tcx>, pred: ty::Binder<'tcx, ty::ProjectionPredicate<'tcx>>,
cx: &mut DocContext<'tcx>, cx: &mut DocContext<'tcx>,
) -> WherePredicate { ) -> 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 { WherePredicate::EqPredicate {
lhs: clean_projection(projection_ty, cx, None), lhs: Box::new(clean_projection(projection_ty, cx, None)),
rhs: clean_middle_term(term, cx), rhs: Box::new(clean_middle_term(term, cx)),
bound_params: late_bound_regions,
} }
} }
@ -655,8 +670,9 @@ fn clean_ty_generics<'tcx>(
}) })
.collect::<Vec<GenericParamDef>>(); .collect::<Vec<GenericParamDef>>();
// param index -> [(DefId of trait, associated type name and generics, type)] // param index -> [(trait DefId, associated type name & generics, type, higher-ranked params)]
let mut impl_trait_proj = FxHashMap::<u32, Vec<(DefId, PathSegment, Ty<'_>)>>::default(); let mut impl_trait_proj =
FxHashMap::<u32, Vec<(DefId, PathSegment, Ty<'_>, Vec<GenericParamDef>)>>::default();
let where_predicates = preds let where_predicates = preds
.predicates .predicates
@ -715,6 +731,14 @@ fn clean_ty_generics<'tcx>(
trait_did, trait_did,
name, name,
rhs.ty().unwrap(), 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. // Move trait bounds to the front.
bounds.sort_by_key(|b| !matches!(b, GenericBound::TraitBound(..))); bounds.sort_by_key(|b| !matches!(b, GenericBound::TraitBound(..)));
if let crate::core::ImplTraitParam::ParamIndex(idx) = param { let crate::core::ImplTraitParam::ParamIndex(idx) = param else { unreachable!() };
if let Some(proj) = impl_trait_proj.remove(&idx) { if let Some(proj) = impl_trait_proj.remove(&idx) {
for (trait_did, name, rhs) in proj { for (trait_did, name, rhs, bound_params) in proj {
let rhs = clean_middle_ty(rhs, cx, None); let rhs = clean_middle_ty(rhs, cx, None);
simplify::merge_bounds(cx, &mut bounds, trait_did, name, &Term::Type(rhs)); simplify::merge_bounds(
} cx,
&mut bounds,
bound_params,
trait_did,
name,
&Term::Type(rhs),
);
} }
} else {
unreachable!();
} }
cx.impl_trait_bounds.insert(param, bounds); cx.impl_trait_bounds.insert(param, bounds);

View file

@ -39,23 +39,23 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
WP::RegionPredicate { lifetime, bounds } => { WP::RegionPredicate { lifetime, bounds } => {
lifetimes.push((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 // Look for equality predicates on associated types that can be merged into
// general bound predicates. // 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; }; let Some((ty, trait_did, name)) = lhs.projection() else { return true; };
// FIXME(fmease): We don't handle HRTBs correctly here. let Some((bounds, _)) = tybounds.get_mut(ty) else { return true };
// Pass `_bound_params` (higher-rank lifetimes) to a modified version of let bound_params = bound_params
// `merge_bounds`. That vector is currently always empty though since we .into_iter()
// don't keep track of late-bound lifetimes when cleaning projection .map(|param| clean::GenericParamDef {
// predicates to cleaned equality predicates while we should first query name: param.0,
// them with `collect_referenced_late_bound_regions` and then store them kind: clean::GenericParamDefKind::Lifetime { outlives: Vec::new() },
// (or something similar). For prior art, see `clean::auto_trait`. })
let Some((bounds, _bound_params)) = tybounds.get_mut(ty) else { return true }; .collect();
merge_bounds(cx, bounds, trait_did, name, rhs) merge_bounds(cx, bounds, bound_params, trait_did, name, rhs)
}); });
// And finally, let's reassemble everything // And finally, let's reassemble everything
@ -68,13 +68,18 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
bounds, bounds,
bound_params, 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 clauses
} }
pub(crate) fn merge_bounds( pub(crate) fn merge_bounds(
cx: &clean::DocContext<'_>, cx: &clean::DocContext<'_>,
bounds: &mut Vec<clean::GenericBound>, bounds: &mut Vec<clean::GenericBound>,
mut bound_params: Vec<clean::GenericParamDef>,
trait_did: DefId, trait_did: DefId,
assoc: clean::PathSegment, assoc: clean::PathSegment,
rhs: &clean::Term, rhs: &clean::Term,
@ -91,6 +96,14 @@ pub(crate) fn merge_bounds(
return false; return false;
} }
let last = trait_ref.trait_.segments.last_mut().expect("segments were empty"); 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 { match last.args {
PP::AngleBracketed { ref mut bindings, .. } => { PP::AngleBracketed { ref mut bindings, .. } => {
bindings.push(clean::TypeBinding { bindings.push(clean::TypeBinding {

View file

@ -1350,7 +1350,7 @@ impl Lifetime {
pub(crate) enum WherePredicate { pub(crate) enum WherePredicate {
BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<Lifetime> }, BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<Lifetime> },
RegionPredicate { lifetime: Lifetime, bounds: Vec<GenericBound> }, RegionPredicate { lifetime: Lifetime, bounds: Vec<GenericBound> },
EqPredicate { lhs: Type, rhs: Term }, EqPredicate { lhs: Box<Type>, rhs: Box<Term>, bound_params: Vec<Lifetime> },
} }
impl WherePredicate { impl WherePredicate {
@ -1361,6 +1361,15 @@ impl WherePredicate {
_ => None, _ => 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)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]

View file

@ -331,7 +331,8 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
bounds_display.truncate(bounds_display.len() - " + ".len()); bounds_display.truncate(bounds_display.len() - " + ".len());
write!(f, "{}: {bounds_display}", lifetime.print()) 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() { if f.alternate() {
write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx)) write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx))
} else { } else {

View file

@ -432,8 +432,9 @@ impl FromWithTcx<clean::WherePredicate> for WherePredicate {
lifetime: convert_lifetime(lifetime), lifetime: convert_lifetime(lifetime),
bounds: bounds.into_tcx(tcx), bounds: bounds.into_tcx(tcx),
}, },
EqPredicate { lhs, rhs } => { // FIXME(fmease): Convert bound parameters as well.
WherePredicate::EqPredicate { lhs: lhs.into_tcx(tcx), rhs: rhs.into_tcx(tcx) } EqPredicate { lhs, rhs, bound_params: _ } => {
WherePredicate::EqPredicate { lhs: (*lhs).into_tcx(tcx), rhs: (*rhs).into_tcx(tcx) }
} }
} }
} }

View file

@ -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 // 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. // 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 main/trait.Main.html
// @has - '//*[@id="associatedtype.Out0"]' 'type Out0: Support<Item = ()>' // @has - '//*[@id="associatedtype.Out0"]' 'type Out0: Support<Item = ()>'
@ -18,13 +16,14 @@ extern crate assoc_item_trait_bounds_with_bindings as aux;
// @has - '//*[@id="associatedtype.Out3"]' 'type Out3: Support<Produce<()> = bool>' // @has - '//*[@id="associatedtype.Out3"]' 'type Out3: Support<Produce<()> = bool>'
// @has - '//*[@id="associatedtype.Out4"]' 'type Out4<T>: Support<Produce<T> = T>' // @has - '//*[@id="associatedtype.Out4"]' 'type Out4<T>: Support<Produce<T> = T>'
// @has - '//*[@id="associatedtype.Out5"]' "type Out5: Support<Output<'static> = &'static ()>" // @has - '//*[@id="associatedtype.Out5"]' "type Out5: Support<Output<'static> = &'static ()>"
// @has - '//*[@id="associatedtype.Out6"]' "type Out6: Support<Output<'a> = &'a ()>" // @has - '//*[@id="associatedtype.Out6"]' "type Out6: for<'a> Support<Output<'a> = &'a ()>"
// @has - '//*[@id="associatedtype.Out7"]' "type Out7: Support<Item = String, Produce<i32> = u32> + Unrelated" // @has - '//*[@id="associatedtype.Out7"]' "type Out7: Support<Item = String, Produce<i32> = u32> + Unrelated"
// @has - '//*[@id="associatedtype.Out8"]' "type Out8: Unrelated + Protocol<i16, Q1 = u128, Q0 = ()>" // @has - '//*[@id="associatedtype.Out8"]' "type Out8: Unrelated + Protocol<i16, Q1 = u128, Q0 = ()>"
// @has - '//*[@id="associatedtype.Out9"]' "type Out9: FnMut(i32) -> bool + Clone" // @has - '//*[@id="associatedtype.Out9"]' "type Out9: FnMut(i32) -> bool + Clone"
// @has - '//*[@id="associatedtype.Out10"]' "type Out10<'q>: Support<Output<'q> = ()>" // @has - '//*[@id="associatedtype.Out10"]' "type Out10<'q>: Support<Output<'q> = ()>"
// @has - '//*[@id="associatedtype.Out11"]' "type Out11: Helper<A<'s> = &'s (), B<'r> = ()>" // @has - '//*[@id="associatedtype.Out11"]' "type Out11: for<'r, 's> Helper<A<'s> = &'s (), B<'r> = ()>"
// @has - '//*[@id="associatedtype.Out12"]' "type Out12: Helper<B<'w> = Cow<'w, str>, A<'w> = bool>" // @has - '//*[@id="associatedtype.Out12"]' "type Out12: for<'w> Helper<B<'w> = 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 // 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. // the trait bounds contained within were moved to the bounds of the respective item.

View file

@ -14,6 +14,7 @@ pub trait Main {
type Out10<'q>: Support<Output<'q> = ()>; type Out10<'q>: Support<Output<'q> = ()>;
type Out11: for<'r, 's> Helper<A<'s> = &'s (), B<'r> = ()>; type Out11: for<'r, 's> Helper<A<'s> = &'s (), B<'r> = ()>;
type Out12: for<'w> Helper<B<'w> = std::borrow::Cow<'w, str>, A<'w> = bool>; type Out12: for<'w> Helper<B<'w> = std::borrow::Cow<'w, str>, A<'w> = bool>;
type Out13: for<'fst, 'snd> Aid<'snd, Result<'fst> = &'fst mut str>;
fn make<F>(_: F, _: impl FnMut(&str) -> bool) fn make<F>(_: F, _: impl FnMut(&str) -> bool)
where where
@ -38,3 +39,7 @@ pub trait Helper {
type A<'q>; type A<'q>;
type B<'q>; type B<'q>;
} }
pub trait Aid<'src> {
type Result<'inter>;
}

View file

@ -13,6 +13,19 @@ pub fn func3(_x: impl Iterator<Item = impl Iterator<Item = u8>> + Clone) {}
pub fn func4<T: Iterator<Item = impl Clone>>(_x: T) {} pub fn func4<T: Iterator<Item = impl Clone>>(_x: T) {}
pub fn func5(
_f: impl for<'any> Fn(&'any str, &'any str) -> bool + for<'r> Other<T<'r> = ()>,
_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 async fn async_fn() {}
pub struct Foo; pub struct Foo;

View file

@ -26,6 +26,13 @@ pub use impl_trait_aux::func3;
// @has - '//pre[@class="rust fn"]' "T: Iterator<Item = impl Clone>," // @has - '//pre[@class="rust fn"]' "T: Iterator<Item = impl Clone>,"
pub use impl_trait_aux::func4; 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<T<'r> = ()>,"
// @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 impl_trait/fn.async_fn.html
// @has - '//pre[@class="rust fn"]' "pub async fn async_fn()" // @has - '//pre[@class="rust fn"]' "pub async fn async_fn()"
pub use impl_trait_aux::async_fn; pub use impl_trait_aux::async_fn;