Skip to content

Commit 2e7e17a

Browse files
authored
Rollup merge of #102439 - fmease:rustdoc-simplify-cross-crate-trait-bounds, r=GuillaumeGomez
rustdoc: re-sugar more cross-crate trait bounds Previously, we would only ever re-sugar cross-crate predicates like `Type: Trait, <Type as Trait>::Name == Rhs` to `Type: Trait<Name = Rhs>` if the `Type` was a generic parameter like `Self` or `T`. With this PR, `Type` can be any type. Most notably, this means that we now re-sugar predicates involving associated types (where `Type` is of the form `Self::Name`) which are then picked up by the pre-existing logic that re-sugars them into bounds. As a result of that, the associated type `IntoIter` of `std`'s `IntoIterator` trait (re-exported from `core`) is no longer rendered as: ```rust type IntoIter: Iterator where <Self::IntoIter as Iterator>::Item == Self::Item; ``` but as one would expect: `type IntoIter: Iterator<Item = Self::Item>;`. Cross-crate closure bounds like `F: Fn(i32) -> bool` are now also rendered properly (previously, the return type (`Self::Output`) would not be rendered and we would show the underlying equality predicate). Fixes #77763. Fixes #84579. Fixes #102142. `@rustbot` label T-rustdoc A-cross-crate-reexports r? rustdoc
2 parents 33d3519 + a540234 commit 2e7e17a

6 files changed

+107
-23
lines changed

src/librustdoc/clean/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,15 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
11761176
}
11771177

11781178
if let ty::TraitContainer = assoc_item.container {
1179+
// FIXME(fmease): `tcx.explicit_item_bounds` does not contain the bounds of GATs,
1180+
// e.g. the bounds `Copy`, `Display` & (implicitly) `Sized` in
1181+
// `type Assoc<T: Copy> where T: Display`. This also means that we
1182+
// later incorrectly render `where T: ?Sized`.
1183+
//
1184+
// The result of `tcx.explicit_predicates_of` *does* contain them but
1185+
// it does not contain the other bounds / predicates we need.
1186+
// Either merge those two interned lists somehow or refactor
1187+
// `clean_ty_generics` to call `explicit_item_bounds` by itself.
11791188
let bounds = tcx.explicit_item_bounds(assoc_item.def_id);
11801189
let predicates = ty::GenericPredicates { parent: None, predicates: bounds };
11811190
let mut generics =

src/librustdoc/clean/simplify.rs

+16-23
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use rustc_data_structures::fx::FxIndexMap;
1515
use rustc_hir::def_id::DefId;
1616
use rustc_middle::ty;
17-
use rustc_span::Symbol;
1817

1918
use crate::clean;
2019
use crate::clean::GenericArgs as PP;
@@ -26,21 +25,17 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
2625
//
2726
// We use `FxIndexMap` so that the insertion order is preserved to prevent messing up to
2827
// the order of the generated bounds.
29-
let mut params: FxIndexMap<Symbol, (Vec<_>, Vec<_>)> = FxIndexMap::default();
28+
let mut tybounds = FxIndexMap::default();
3029
let mut lifetimes = Vec::new();
3130
let mut equalities = Vec::new();
32-
let mut tybounds = Vec::new();
3331

3432
for clause in clauses {
3533
match clause {
36-
WP::BoundPredicate { ty, bounds, bound_params } => match ty {
37-
clean::Generic(s) => {
38-
let (b, p) = params.entry(s).or_default();
39-
b.extend(bounds);
40-
p.extend(bound_params);
41-
}
42-
t => tybounds.push((t, (bounds, bound_params))),
43-
},
34+
WP::BoundPredicate { ty, bounds, bound_params } => {
35+
let (b, p): &mut (Vec<_>, Vec<_>) = tybounds.entry(ty).or_default();
36+
b.extend(bounds);
37+
p.extend(bound_params);
38+
}
4439
WP::RegionPredicate { lifetime, bounds } => {
4540
lifetimes.push((lifetime, bounds));
4641
}
@@ -49,14 +44,17 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
4944
}
5045

5146
// Look for equality predicates on associated types that can be merged into
52-
// general bound predicates
47+
// general bound predicates.
5348
equalities.retain(|&(ref lhs, ref rhs)| {
54-
let Some((self_, trait_did, name)) = lhs.projection() else {
55-
return true;
56-
};
57-
let clean::Generic(generic) = self_ else { return true };
58-
let Some((bounds, _)) = params.get_mut(generic) else { return true };
59-
49+
let Some((ty, trait_did, name)) = lhs.projection() else { return true; };
50+
// FIXME(fmease): We don't handle HRTBs correctly here.
51+
// Pass `_bound_params` (higher-rank lifetimes) to a modified version of
52+
// `merge_bounds`. That vector is currently always empty though since we
53+
// don't keep track of late-bound lifetimes when cleaning projection
54+
// predicates to cleaned equality predicates while we should first query
55+
// them with `collect_referenced_late_bound_regions` and then store them
56+
// (or something similar). For prior art, see `clean::auto_trait`.
57+
let Some((bounds, _bound_params)) = tybounds.get_mut(ty) else { return true };
6058
merge_bounds(cx, bounds, trait_did, name, rhs)
6159
});
6260

@@ -65,11 +63,6 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
6563
clauses.extend(
6664
lifetimes.into_iter().map(|(lt, bounds)| WP::RegionPredicate { lifetime: lt, bounds }),
6765
);
68-
clauses.extend(params.into_iter().map(|(k, (bounds, params))| WP::BoundPredicate {
69-
ty: clean::Generic(k),
70-
bounds,
71-
bound_params: params,
72-
}));
7366
clauses.extend(tybounds.into_iter().map(|(ty, (bounds, bound_params))| WP::BoundPredicate {
7467
ty,
7568
bounds,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h4 class="code-header">type <a href="#associatedtype.Out0" class="associatedtype">Out0</a>: <a class="trait" href="../assoc_item_trait_bounds_with_bindings/trait.Support.html" title="trait assoc_item_trait_bounds_with_bindings::Support">Support</a>&lt;Item = <a class="primitive" href="{{channel}}/std/primitive.unit.html">()</a>&gt;</h4>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h4 class="code-header">type <a href="#associatedtype.Out9" class="associatedtype">Out9</a>: <a class="trait" href="{{channel}}/core/ops/function/trait.FnMut.html" title="trait core::ops::function::FnMut">FnMut</a>(<a class="primitive" href="{{channel}}/std/primitive.i32.html">i32</a>) -&gt; <a class="primitive" href="{{channel}}/std/primitive.bool.html">bool</a> + <a class="trait" href="{{channel}}/core/clone/trait.Clone.html" title="trait core::clone::Clone">Clone</a></h4>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Regression test for issues #77763, #84579 and #102142.
2+
#![crate_name = "main"]
3+
4+
// aux-build:assoc_item_trait_bounds_with_bindings.rs
5+
// build-aux-docs
6+
// ignore-cross-compile
7+
extern crate assoc_item_trait_bounds_with_bindings as aux;
8+
9+
// FIXME(fmease): Don't render an incorrect `T: ?Sized` where-clause for parameters
10+
// of GATs like `Main::Out{2,4}`. Add a snapshot test once it's fixed.
11+
// FIXME(fmease): Print the `for<>` parameter list in the bounds of
12+
// `Main::Out{6,11,12}`.
13+
14+
// @has main/trait.Main.html
15+
// @has - '//*[@id="associatedtype.Out0"]' 'type Out0: Support<Item = ()>'
16+
// @has - '//*[@id="associatedtype.Out1"]' 'type Out1: Support<Item = Self::Item>'
17+
// @has - '//*[@id="associatedtype.Out2"]' 'type Out2<T>: Support<Item = T>'
18+
// @has - '//*[@id="associatedtype.Out3"]' 'type Out3: Support<Produce<()> = bool>'
19+
// @has - '//*[@id="associatedtype.Out4"]' 'type Out4<T>: Support<Produce<T> = T>'
20+
// @has - '//*[@id="associatedtype.Out5"]' "type Out5: Support<Output<'static> = &'static ()>"
21+
// @has - '//*[@id="associatedtype.Out6"]' "type Out6: Support<Output<'a> = &'a ()>"
22+
// @has - '//*[@id="associatedtype.Out7"]' "type Out7: Support<Item = String, Produce<i32> = u32> + Unrelated"
23+
// @has - '//*[@id="associatedtype.Out8"]' "type Out8: Unrelated + Protocol<i16, Q1 = u128, Q0 = ()>"
24+
// @has - '//*[@id="associatedtype.Out9"]' "type Out9: FnMut(i32) -> bool + Clone"
25+
// @has - '//*[@id="associatedtype.Out10"]' "type Out10<'q>: Support<Output<'q> = ()>"
26+
// @has - '//*[@id="associatedtype.Out11"]' "type Out11: Helper<A<'s> = &'s (), B<'r> = ()>"
27+
// @has - '//*[@id="associatedtype.Out12"]' "type Out12: Helper<B<'w> = Cow<'w, str>, A<'w> = bool>"
28+
//
29+
// Snapshots: Check that we do not render any where-clauses for those associated types since all of
30+
// the trait bounds contained within were moved to the bounds of the respective item.
31+
//
32+
// @snapshot out0 - '//*[@id="associatedtype.Out0"]/*[@class="code-header"]'
33+
// @snapshot out9 - '//*[@id="associatedtype.Out9"]/*[@class="code-header"]'
34+
//
35+
// @has - '//*[@id="tymethod.make"]' \
36+
// "fn make<F>(F, impl FnMut(&str) -> bool)\
37+
// where \
38+
// F: FnOnce(u32) -> String, \
39+
// Self::Out2<()>: Protocol<u8, Q0 = Self::Item, Q1 = ()>"
40+
pub use aux::Main;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
pub trait Main {
2+
type Item;
3+
4+
type Out0: Support<Item = ()>;
5+
type Out1: Support<Item = Self::Item>;
6+
type Out2<T>: Support<Item = T>;
7+
type Out3: Support<Produce<()> = bool>;
8+
type Out4<T>: Support<Produce<T> = T>;
9+
type Out5: Support<Output<'static> = &'static ()>;
10+
type Out6: for<'a> Support<Output<'a> = &'a ()>;
11+
type Out7: Support<Item = String, Produce<i32> = u32> + Unrelated;
12+
type Out8: Unrelated + Protocol<i16, Q1 = u128, Q0 = ()>;
13+
type Out9: FnMut(i32) -> bool + Clone;
14+
type Out10<'q>: Support<Output<'q> = ()>;
15+
type Out11: for<'r, 's> Helper<A<'s> = &'s (), B<'r> = ()>;
16+
type Out12: for<'w> Helper<B<'w> = std::borrow::Cow<'w, str>, A<'w> = bool>;
17+
18+
fn make<F>(_: F, _: impl FnMut(&str) -> bool)
19+
where
20+
F: FnOnce(u32) -> String,
21+
Self::Out2<()>: Protocol<u8, Q0 = Self::Item, Q1 = ()>;
22+
}
23+
24+
pub trait Support {
25+
type Item;
26+
type Output<'a>;
27+
type Produce<T>;
28+
}
29+
30+
pub trait Protocol<K> {
31+
type Q0;
32+
type Q1;
33+
}
34+
35+
pub trait Unrelated {}
36+
37+
pub trait Helper {
38+
type A<'q>;
39+
type B<'q>;
40+
}

0 commit comments

Comments
 (0)