From af45040acd86d8881fd3dfbfdb08525627d63347 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Fri, 2 Dec 2022 19:36:20 +0100
Subject: [PATCH 1/3] Merge generics and where predicates and prevent
 duplicates in where predicates

---
 src/librustdoc/clean/mod.rs | 124 ++++++++++++++++++++++++++----------
 1 file changed, 91 insertions(+), 33 deletions(-)

diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 80909919ba2b6..2a2a9470d25c0 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -12,7 +12,7 @@ pub(crate) mod utils;
 
 use rustc_ast as ast;
 use rustc_attr as attr;
-use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet, IndexEntry};
 use rustc_hir as hir;
 use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::def_id::{DefId, LOCAL_CRATE};
@@ -598,47 +598,105 @@ pub(crate) fn clean_generics<'tcx>(
         })
         .collect::<Vec<_>>();
 
+    let mut bound_predicates = FxIndexMap::default();
+    let mut region_predicates = FxIndexMap::default();
+    let mut eq_predicates = ThinVec::default();
+    for pred in gens.predicates.iter().filter_map(|x| clean_where_predicate(x, cx)) {
+        match pred {
+            WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
+                match bound_predicates.entry(ty) {
+                    IndexEntry::Vacant(v) => {
+                        v.insert((bounds, bound_params));
+                    }
+                    IndexEntry::Occupied(mut o) => {
+                        // we merge both bounds.
+                        for bound in bounds {
+                            if !o.get().0.contains(&bound) {
+                                o.get_mut().0.push(bound);
+                            }
+                        }
+                        for bound_param in bound_params {
+                            if !o.get().1.contains(&bound_param) {
+                                o.get_mut().1.push(bound_param);
+                            }
+                        }
+                    }
+                }
+            }
+            WherePredicate::RegionPredicate { lifetime, bounds } => {
+                match region_predicates.entry(lifetime) {
+                    IndexEntry::Vacant(v) => {
+                        v.insert(bounds);
+                    }
+                    IndexEntry::Occupied(mut o) => {
+                        // we merge both bounds.
+                        for bound in bounds {
+                            if !o.get().contains(&bound) {
+                                o.get_mut().push(bound);
+                            }
+                        }
+                    }
+                }
+            }
+            WherePredicate::EqPredicate { lhs, rhs, bound_params } => {
+                eq_predicates.push(WherePredicate::EqPredicate { lhs, rhs, bound_params });
+            }
+        }
+    }
+
     let mut params = ThinVec::with_capacity(gens.params.len());
+    // In this loop, we gather the generic parameters (`<'a, B: 'a>`) and check if they have
+    // bounds in the where predicates. If so, we move their bounds into the where predicates
+    // while also preventing duplicates.
     for p in gens.params.iter().filter(|p| !is_impl_trait(p) && !is_elided_lifetime(p)) {
-        let p = clean_generic_param(cx, Some(gens), p);
+        let mut p = clean_generic_param(cx, Some(gens), p);
+        match &mut p.kind {
+            GenericParamDefKind::Lifetime { ref mut outlives } => {
+                if let Some(region_pred) = region_predicates.get_mut(&Lifetime(p.name)) {
+                    // We merge bounds in the `where` clause.
+                    for outlive in outlives.drain(..) {
+                        let outlive = GenericBound::Outlives(outlive);
+                        if !region_pred.contains(&outlive) {
+                            region_pred.push(outlive);
+                        }
+                    }
+                }
+            }
+            GenericParamDefKind::Type { bounds, synthetic: false, .. } => {
+                if let Some(bound_pred) = bound_predicates.get_mut(&Type::Generic(p.name)) {
+                    // We merge bounds in the `where` clause.
+                    for bound in bounds.drain(..) {
+                        if !bound_pred.0.contains(&bound) {
+                            bound_pred.0.push(bound);
+                        }
+                    }
+                }
+            }
+            GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => {
+                // nothing to do here.
+            }
+        }
         params.push(p);
     }
     params.extend(impl_trait_params);
 
-    let mut generics = Generics {
+    Generics {
         params,
-        where_predicates: gens
-            .predicates
-            .iter()
-            .filter_map(|x| clean_where_predicate(x, cx))
+        where_predicates: bound_predicates
+            .into_iter()
+            .map(|(ty, (bounds, bound_params))| WherePredicate::BoundPredicate {
+                ty,
+                bounds,
+                bound_params,
+            })
+            .chain(
+                region_predicates
+                    .into_iter()
+                    .map(|(lifetime, bounds)| WherePredicate::RegionPredicate { lifetime, bounds }),
+            )
+            .chain(eq_predicates.into_iter())
             .collect(),
-    };
-
-    // Some duplicates are generated for ?Sized bounds between type params and where
-    // predicates. The point in here is to move the bounds definitions from type params
-    // to where predicates when such cases occur.
-    for where_pred in &mut generics.where_predicates {
-        match *where_pred {
-            WherePredicate::BoundPredicate { ty: Generic(ref name), ref mut bounds, .. } => {
-                if bounds.is_empty() {
-                    for param in &mut generics.params {
-                        match param.kind {
-                            GenericParamDefKind::Lifetime { .. } => {}
-                            GenericParamDefKind::Type { bounds: ref mut ty_bounds, .. } => {
-                                if &param.name == name {
-                                    mem::swap(bounds, ty_bounds);
-                                    break;
-                                }
-                            }
-                            GenericParamDefKind::Const { .. } => {}
-                        }
-                    }
-                }
-            }
-            _ => continue,
-        }
     }
-    generics
 }
 
 fn clean_ty_generics<'tcx>(

From 93d7aa146d8a91c281c32cd050fceba8312ed9a6 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Fri, 2 Dec 2022 19:36:57 +0100
Subject: [PATCH 2/3] Add rustdoc test for bounds de-duplication and merge

---
 src/test/rustdoc/bounds-in-multiple-parts.rs | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 src/test/rustdoc/bounds-in-multiple-parts.rs

diff --git a/src/test/rustdoc/bounds-in-multiple-parts.rs b/src/test/rustdoc/bounds-in-multiple-parts.rs
new file mode 100644
index 0000000000000..279e3c148887e
--- /dev/null
+++ b/src/test/rustdoc/bounds-in-multiple-parts.rs
@@ -0,0 +1,20 @@
+#![crate_name = "foo"]
+
+pub trait Eq {}
+pub trait Eq2 {}
+
+// Checking that "where predicates" and "generics params" are merged.
+// @has 'foo/trait.T.html'
+// @has - "//*[@id='tymethod.f']/h4" "fn f<'a, 'b, 'c, T>()where Self: Eq, T: Eq + 'a, 'c: 'b + 'a,"
+pub trait T {
+    fn f<'a, 'b, 'c: 'a, T: Eq + 'a>()
+        where Self: Eq, Self: Eq, T: Eq, 'c: 'b;
+}
+
+// Checking that a duplicated "where predicate" is removed.
+// @has 'foo/trait.T2.html'
+// @has - "//*[@id='tymethod.f']/h4" "fn f<T>()where Self: Eq + Eq2, T: Eq2 + Eq,"
+pub trait T2 {
+    fn f<T: Eq>()
+        where Self: Eq, Self: Eq2, T: Eq2;
+}

From 269704a4a09502ae64f3916058b157d94e6cba99 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume.gomez@huawei.com>
Date: Fri, 2 Dec 2022 19:37:04 +0100
Subject: [PATCH 3/3] Update existing rustdoc tests

---
 src/test/rustdoc/impl-parts.rs                             | 4 ++--
 src/test/rustdoc/rfc-2632-const-trait-impl.rs              | 2 +-
 src/test/rustdoc/whitespace-after-where-clause.enum.html   | 2 +-
 src/test/rustdoc/whitespace-after-where-clause.struct.html | 2 +-
 src/test/rustdoc/whitespace-after-where-clause.union.html  | 2 +-
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/test/rustdoc/impl-parts.rs b/src/test/rustdoc/impl-parts.rs
index 7b931727e446d..90cbb77cb6b60 100644
--- a/src/test/rustdoc/impl-parts.rs
+++ b/src/test/rustdoc/impl-parts.rs
@@ -6,7 +6,7 @@ pub auto trait AnAutoTrait {}
 pub struct Foo<T> { field: T }
 
 // @has impl_parts/struct.Foo.html '//*[@class="impl has-srclink"]//h3[@class="code-header"]' \
-//     "impl<T: Clone> !AnAutoTrait for Foo<T>where T: Sync,"
+//     "impl<T> !AnAutoTrait for Foo<T>where T: Sync + Clone,"
 // @has impl_parts/trait.AnAutoTrait.html '//*[@id="implementors-list"]//h3[@class="code-header"]' \
-//     "impl<T: Clone> !AnAutoTrait for Foo<T>where T: Sync,"
+//     "impl<T> !AnAutoTrait for Foo<T>where T: Sync + Clone,"
 impl<T: Clone> !AnAutoTrait for Foo<T> where T: Sync {}
diff --git a/src/test/rustdoc/rfc-2632-const-trait-impl.rs b/src/test/rustdoc/rfc-2632-const-trait-impl.rs
index 602ee1b1b1fca..7ed9d6729b647 100644
--- a/src/test/rustdoc/rfc-2632-const-trait-impl.rs
+++ b/src/test/rustdoc/rfc-2632-const-trait-impl.rs
@@ -61,7 +61,7 @@ impl<T> S<T> {
     // @has - '//section[@id="method.foo"]/h4[@class="code-header"]/a[@class="trait"]' 'Clone'
     // @!has - '//section[@id="method.foo"]/h4[@class="code-header"]/span[@class="where"]' '~const'
     // @has - '//section[@id="method.foo"]/h4[@class="code-header"]/span[@class="where fmt-newline"]' ': Clone'
-    pub const fn foo<B: ~const Clone + ~const Destruct>()
+    pub const fn foo<B, C: ~const Clone + ~const Destruct>()
     where
         B: ~const Clone + ~const Destruct,
     {
diff --git a/src/test/rustdoc/whitespace-after-where-clause.enum.html b/src/test/rustdoc/whitespace-after-where-clause.enum.html
index c74866f4a10b8..f7663e4616ae6 100644
--- a/src/test/rustdoc/whitespace-after-where-clause.enum.html
+++ b/src/test/rustdoc/whitespace-after-where-clause.enum.html
@@ -1,4 +1,4 @@
-<div class="item-decl"><pre class="rust enum"><code>pub enum Cow&lt;'a, B:&#160;?<a class="trait" href="{{channel}}/core/marker/trait.Sized.html" title="trait core::marker::Sized">Sized</a> + 'a&gt;<span class="where fmt-newline">where<br />&#160;&#160;&#160;&#160;B: <a class="trait" href="trait.ToOwned.html" title="trait foo::ToOwned">ToOwned</a>&lt;dyn <a class="trait" href="{{channel}}/core/clone/trait.Clone.html" title="trait core::clone::Clone">Clone</a>&gt;,</span>{
+<div class="item-decl"><pre class="rust enum"><code>pub enum Cow&lt;'a, B&gt;<span class="where fmt-newline">where<br />&#160;&#160;&#160;&#160;B: <a class="trait" href="trait.ToOwned.html" title="trait foo::ToOwned">ToOwned</a>&lt;dyn <a class="trait" href="{{channel}}/core/clone/trait.Clone.html" title="trait core::clone::Clone">Clone</a>&gt; + ?<a class="trait" href="{{channel}}/core/marker/trait.Sized.html" title="trait core::marker::Sized">Sized</a> + 'a,</span>{
     Borrowed(<a class="primitive" href="{{channel}}/std/primitive.reference.html">&amp;'a </a>B),
     Whatever(<a class="primitive" href="{{channel}}/std/primitive.u32.html">u32</a>),
 }</code></pre></div>
\ No newline at end of file
diff --git a/src/test/rustdoc/whitespace-after-where-clause.struct.html b/src/test/rustdoc/whitespace-after-where-clause.struct.html
index 1ba1367d20f72..fa3f224e7ad0f 100644
--- a/src/test/rustdoc/whitespace-after-where-clause.struct.html
+++ b/src/test/rustdoc/whitespace-after-where-clause.struct.html
@@ -1,4 +1,4 @@
-<div class="item-decl"><pre class="rust struct"><code>pub struct Struct&lt;'a, B:&#160;?<a class="trait" href="{{channel}}/core/marker/trait.Sized.html" title="trait core::marker::Sized">Sized</a> + 'a&gt;<span class="where fmt-newline">where<br />&#160;&#160;&#160;&#160;B: <a class="trait" href="trait.ToOwned.html" title="trait foo::ToOwned">ToOwned</a>&lt;dyn <a class="trait" href="{{channel}}/core/clone/trait.Clone.html" title="trait core::clone::Clone">Clone</a>&gt;,</span>{
+<div class="item-decl"><pre class="rust struct"><code>pub struct Struct&lt;'a, B&gt;<span class="where fmt-newline">where<br />&#160;&#160;&#160;&#160;B: <a class="trait" href="trait.ToOwned.html" title="trait foo::ToOwned">ToOwned</a>&lt;dyn <a class="trait" href="{{channel}}/core/clone/trait.Clone.html" title="trait core::clone::Clone">Clone</a>&gt; + ?<a class="trait" href="{{channel}}/core/marker/trait.Sized.html" title="trait core::marker::Sized">Sized</a> + 'a,</span>{
     pub a: <a class="primitive" href="{{channel}}/std/primitive.reference.html">&amp;'a </a>B,
     pub b: <a class="primitive" href="{{channel}}/std/primitive.u32.html">u32</a>,
 }</code></pre></div>
\ No newline at end of file
diff --git a/src/test/rustdoc/whitespace-after-where-clause.union.html b/src/test/rustdoc/whitespace-after-where-clause.union.html
index 0dfb6407d45f1..7bb177debc3a8 100644
--- a/src/test/rustdoc/whitespace-after-where-clause.union.html
+++ b/src/test/rustdoc/whitespace-after-where-clause.union.html
@@ -1,3 +1,3 @@
-<div class="item-decl"><pre class="rust union"><code>pub union Union&lt;'a, B:&#160;?<a class="trait" href="{{channel}}/core/marker/trait.Sized.html" title="trait core::marker::Sized">Sized</a> + 'a&gt;<span class="where fmt-newline">where<br />&#160;&#160;&#160;&#160;B: <a class="trait" href="trait.ToOwned.html" title="trait foo::ToOwned">ToOwned</a>&lt;dyn <a class="trait" href="{{channel}}/core/clone/trait.Clone.html" title="trait core::clone::Clone">Clone</a>&gt;,</span>{
+<div class="item-decl"><pre class="rust union"><code>pub union Union&lt;'a, B&gt;<span class="where fmt-newline">where<br />&#160;&#160;&#160;&#160;B: <a class="trait" href="trait.ToOwned.html" title="trait foo::ToOwned">ToOwned</a>&lt;dyn <a class="trait" href="{{channel}}/core/clone/trait.Clone.html" title="trait core::clone::Clone">Clone</a>&gt; + ?<a class="trait" href="{{channel}}/core/marker/trait.Sized.html" title="trait core::marker::Sized">Sized</a> + 'a,</span>{
     /* private fields */
 }</code></pre></div>
\ No newline at end of file