diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs
index c909a0b49e2..73e94da165f 100644
--- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs
+++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs
@@ -6,13 +6,13 @@ use super::*;
 use crate::errors::UnableToConstructConstantValue;
 use crate::infer::region_constraints::{Constraint, RegionConstraintData};
 use crate::traits::project::ProjectAndUnifyResult;
+
+use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry};
+use rustc_data_structures::unord::UnordSet;
 use rustc_infer::infer::DefineOpaqueTypes;
 use rustc_middle::mir::interpret::ErrorHandled;
 use rustc_middle::ty::{Region, RegionVid};
 
-use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
-
-use std::collections::hash_map::Entry;
 use std::collections::VecDeque;
 use std::iter;
 
@@ -25,8 +25,8 @@ pub enum RegionTarget<'tcx> {
 
 #[derive(Default, Debug, Clone)]
 pub struct RegionDeps<'tcx> {
-    larger: FxIndexSet<RegionTarget<'tcx>>,
-    smaller: FxIndexSet<RegionTarget<'tcx>>,
+    pub larger: FxIndexSet<RegionTarget<'tcx>>,
+    pub smaller: FxIndexSet<RegionTarget<'tcx>>,
 }
 
 pub enum AutoTraitResult<A> {
@@ -35,17 +35,10 @@ pub enum AutoTraitResult<A> {
     NegativeImpl,
 }
 
-#[allow(dead_code)]
-impl<A> AutoTraitResult<A> {
-    fn is_auto(&self) -> bool {
-        matches!(self, AutoTraitResult::PositiveImpl(_) | AutoTraitResult::NegativeImpl)
-    }
-}
-
 pub struct AutoTraitInfo<'cx> {
     pub full_user_env: ty::ParamEnv<'cx>,
     pub region_data: RegionConstraintData<'cx>,
-    pub vid_to_region: FxHashMap<ty::RegionVid, ty::Region<'cx>>,
+    pub vid_to_region: FxIndexMap<ty::RegionVid, ty::Region<'cx>>,
 }
 
 pub struct AutoTraitFinder<'tcx> {
@@ -88,19 +81,12 @@ impl<'tcx> AutoTraitFinder<'tcx> {
 
         let infcx = tcx.infer_ctxt().build();
         let mut selcx = SelectionContext::new(&infcx);
-        for polarity in [true, false] {
+        for polarity in [ty::PredicatePolarity::Positive, ty::PredicatePolarity::Negative] {
             let result = selcx.select(&Obligation::new(
                 tcx,
                 ObligationCause::dummy(),
                 orig_env,
-                ty::TraitPredicate {
-                    trait_ref,
-                    polarity: if polarity {
-                        ty::PredicatePolarity::Positive
-                    } else {
-                        ty::PredicatePolarity::Negative
-                    },
-                },
+                ty::TraitPredicate { trait_ref, polarity },
             ));
             if let Ok(Some(ImplSource::UserDefined(_))) = result {
                 debug!(
@@ -114,7 +100,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
         }
 
         let infcx = tcx.infer_ctxt().build();
-        let mut fresh_preds = FxHashSet::default();
+        let mut fresh_preds = FxIndexSet::default();
 
         // Due to the way projections are handled by SelectionContext, we need to run
         // evaluate_predicates twice: once on the original param env, and once on the result of
@@ -239,7 +225,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
         ty: Ty<'tcx>,
         param_env: ty::ParamEnv<'tcx>,
         user_env: ty::ParamEnv<'tcx>,
-        fresh_preds: &mut FxHashSet<ty::Predicate<'tcx>>,
+        fresh_preds: &mut FxIndexSet<ty::Predicate<'tcx>>,
     ) -> Option<(ty::ParamEnv<'tcx>, ty::ParamEnv<'tcx>)> {
         let tcx = infcx.tcx;
 
@@ -252,7 +238,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
 
         let mut select = SelectionContext::new(infcx);
 
-        let mut already_visited = FxHashSet::default();
+        let mut already_visited = UnordSet::new();
         let mut predicates = VecDeque::new();
         predicates.push_back(ty::Binder::dummy(ty::TraitPredicate {
             trait_ref: ty::TraitRef::new(infcx.tcx, trait_did, [ty]),
@@ -473,9 +459,9 @@ impl<'tcx> AutoTraitFinder<'tcx> {
     fn map_vid_to_region<'cx>(
         &self,
         regions: &RegionConstraintData<'cx>,
-    ) -> FxHashMap<ty::RegionVid, ty::Region<'cx>> {
-        let mut vid_map: FxHashMap<RegionTarget<'cx>, RegionDeps<'cx>> = FxHashMap::default();
-        let mut finished_map = FxHashMap::default();
+    ) -> FxIndexMap<ty::RegionVid, ty::Region<'cx>> {
+        let mut vid_map = FxIndexMap::<RegionTarget<'cx>, RegionDeps<'cx>>::default();
+        let mut finished_map = FxIndexMap::default();
 
         for (constraint, _) in &regions.constraints {
             match constraint {
@@ -513,25 +499,22 @@ impl<'tcx> AutoTraitFinder<'tcx> {
         }
 
         while !vid_map.is_empty() {
-            #[allow(rustc::potential_query_instability)]
-            let target = *vid_map.keys().next().expect("Keys somehow empty");
-            let deps = vid_map.remove(&target).expect("Entry somehow missing");
+            let target = *vid_map.keys().next().unwrap();
+            let deps = vid_map.swap_remove(&target).unwrap();
 
             for smaller in deps.smaller.iter() {
                 for larger in deps.larger.iter() {
                     match (smaller, larger) {
                         (&RegionTarget::Region(_), &RegionTarget::Region(_)) => {
-                            if let Entry::Occupied(v) = vid_map.entry(*smaller) {
+                            if let IndexEntry::Occupied(v) = vid_map.entry(*smaller) {
                                 let smaller_deps = v.into_mut();
                                 smaller_deps.larger.insert(*larger);
-                                // FIXME(#120456) - is `swap_remove` correct?
                                 smaller_deps.larger.swap_remove(&target);
                             }
 
-                            if let Entry::Occupied(v) = vid_map.entry(*larger) {
+                            if let IndexEntry::Occupied(v) = vid_map.entry(*larger) {
                                 let larger_deps = v.into_mut();
                                 larger_deps.smaller.insert(*smaller);
-                                // FIXME(#120456) - is `swap_remove` correct?
                                 larger_deps.smaller.swap_remove(&target);
                             }
                         }
@@ -542,17 +525,15 @@ impl<'tcx> AutoTraitFinder<'tcx> {
                             // Do nothing; we don't care about regions that are smaller than vids.
                         }
                         (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => {
-                            if let Entry::Occupied(v) = vid_map.entry(*smaller) {
+                            if let IndexEntry::Occupied(v) = vid_map.entry(*smaller) {
                                 let smaller_deps = v.into_mut();
                                 smaller_deps.larger.insert(*larger);
-                                // FIXME(#120456) - is `swap_remove` correct?
                                 smaller_deps.larger.swap_remove(&target);
                             }
 
-                            if let Entry::Occupied(v) = vid_map.entry(*larger) {
+                            if let IndexEntry::Occupied(v) = vid_map.entry(*larger) {
                                 let larger_deps = v.into_mut();
                                 larger_deps.smaller.insert(*smaller);
-                                // FIXME(#120456) - is `swap_remove` correct?
                                 larger_deps.smaller.swap_remove(&target);
                             }
                         }
@@ -560,6 +541,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
                 }
             }
         }
+
         finished_map
     }
 
@@ -588,7 +570,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
         ty: Ty<'_>,
         nested: impl Iterator<Item = PredicateObligation<'tcx>>,
         computed_preds: &mut FxIndexSet<ty::Predicate<'tcx>>,
-        fresh_preds: &mut FxHashSet<ty::Predicate<'tcx>>,
+        fresh_preds: &mut FxIndexSet<ty::Predicate<'tcx>>,
         predicates: &mut VecDeque<ty::PolyTraitPredicate<'tcx>>,
         selcx: &mut SelectionContext<'_, 'tcx>,
     ) -> bool {
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
index fbc2c3c5af4..08c186c9d38 100644
--- a/src/librustdoc/clean/auto_trait.rs
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -1,747 +1,361 @@
+use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry};
 use rustc_hir as hir;
-use rustc_hir::lang_items::LangItem;
-use rustc_middle::ty::{Region, RegionVid, TypeFoldable};
-use rustc_trait_selection::traits::auto_trait::{self, AutoTraitResult};
+use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData};
+use rustc_middle::bug;
+use rustc_middle::ty::{self, Region, Ty};
+use rustc_span::def_id::DefId;
+use rustc_span::symbol::{kw, Symbol};
+use rustc_trait_selection::traits::auto_trait::{self, RegionTarget};
 
-use std::fmt::Debug;
+use thin_vec::ThinVec;
 
-use super::*;
+use crate::clean::{self, simplify, Lifetime};
+use crate::clean::{
+    clean_generic_param_def, clean_middle_ty, clean_predicate, clean_trait_ref_with_bindings,
+    clean_ty_generics,
+};
+use crate::core::DocContext;
 
-#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
-enum RegionTarget<'tcx> {
-    Region(Region<'tcx>),
-    RegionVid(RegionVid),
-}
+#[instrument(level = "debug", skip(cx))]
+pub(crate) fn synthesize_auto_trait_impls<'tcx>(
+    cx: &mut DocContext<'tcx>,
+    item_def_id: DefId,
+) -> Vec<clean::Item> {
+    let tcx = cx.tcx;
+    let param_env = tcx.param_env(item_def_id);
+    let ty = tcx.type_of(item_def_id).instantiate_identity();
 
-#[derive(Default, Debug, Clone)]
-struct RegionDeps<'tcx> {
-    larger: FxHashSet<RegionTarget<'tcx>>,
-    smaller: FxHashSet<RegionTarget<'tcx>>,
-}
-
-pub(crate) struct AutoTraitFinder<'a, 'tcx> {
-    pub(crate) cx: &'a mut core::DocContext<'tcx>,
-}
-
-impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx>
-where
-    'tcx: 'a, // should be an implied bound; rustc bug #98852.
-{
-    pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> Self {
-        AutoTraitFinder { cx }
-    }
-
-    fn generate_for_trait(
-        &mut self,
-        ty: Ty<'tcx>,
-        trait_def_id: DefId,
-        param_env: ty::ParamEnv<'tcx>,
-        item_def_id: DefId,
-        f: &auto_trait::AutoTraitFinder<'tcx>,
-        // If this is set, show only negative trait implementations, not positive ones.
-        discard_positive_impl: bool,
-    ) -> Option<Item> {
-        let tcx = self.cx.tcx;
-        let trait_ref = ty::Binder::dummy(ty::TraitRef::new(tcx, trait_def_id, [ty]));
-        if !self.cx.generated_synthetics.insert((ty, trait_def_id)) {
-            debug!("get_auto_trait_impl_for({trait_ref:?}): already generated, aborting");
-            return None;
-        }
-
-        let result = f.find_auto_trait_generics(ty, param_env, trait_def_id, |info| {
-            let region_data = info.region_data;
-
-            let names_map = tcx
-                .generics_of(item_def_id)
-                .params
-                .iter()
-                .filter_map(|param| match param.kind {
-                    ty::GenericParamDefKind::Lifetime => Some(param.name),
-                    _ => None,
-                })
-                .map(|name| (name, Lifetime(name)))
-                .collect();
-            let lifetime_predicates = Self::handle_lifetimes(&region_data, &names_map);
-            let new_generics = self.param_env_to_generics(
+    let finder = auto_trait::AutoTraitFinder::new(tcx);
+    let mut auto_trait_impls: Vec<_> = cx
+        .auto_traits
+        .clone()
+        .into_iter()
+        .filter_map(|trait_def_id| {
+            synthesize_auto_trait_impl(
+                cx,
+                ty,
+                trait_def_id,
+                param_env,
                 item_def_id,
-                info.full_user_env,
-                lifetime_predicates,
-                info.vid_to_region,
-            );
-
-            debug!(
-                "find_auto_trait_generics(item_def_id={:?}, trait_def_id={:?}): \
-                    finished with {:?}",
-                item_def_id, trait_def_id, new_generics
-            );
-
-            new_generics
-        });
-
-        let polarity;
-        let new_generics = match result {
-            AutoTraitResult::PositiveImpl(new_generics) => {
-                polarity = ty::ImplPolarity::Positive;
-                if discard_positive_impl {
-                    return None;
-                }
-                new_generics
-            }
-            AutoTraitResult::NegativeImpl => {
-                polarity = ty::ImplPolarity::Negative;
-
-                // For negative impls, we use the generic params, but *not* the predicates,
-                // from the original type. Otherwise, the displayed impl appears to be a
-                // conditional negative impl, when it's really unconditional.
-                //
-                // For example, consider the struct Foo<T: Copy>(*mut T). Using
-                // the original predicates in our impl would cause us to generate
-                // `impl !Send for Foo<T: Copy>`, which makes it appear that Foo
-                // implements Send where T is not copy.
-                //
-                // Instead, we generate `impl !Send for Foo<T>`, which better
-                // expresses the fact that `Foo<T>` never implements `Send`,
-                // regardless of the choice of `T`.
-                let raw_generics = clean_ty_generics(
-                    self.cx,
-                    tcx.generics_of(item_def_id),
-                    ty::GenericPredicates::default(),
-                );
-                let params = raw_generics.params;
-
-                Generics { params, where_predicates: ThinVec::new() }
-            }
-            AutoTraitResult::ExplicitImpl => return None,
-        };
-
-        Some(Item {
-            name: None,
-            attrs: Default::default(),
-            item_id: ItemId::Auto { trait_: trait_def_id, for_: item_def_id },
-            kind: Box::new(ImplItem(Box::new(Impl {
-                unsafety: hir::Unsafety::Normal,
-                generics: new_generics,
-                trait_: Some(clean_trait_ref_with_bindings(self.cx, trait_ref, ThinVec::new())),
-                for_: clean_middle_ty(ty::Binder::dummy(ty), self.cx, None, None),
-                items: Vec::new(),
-                polarity,
-                kind: ImplKind::Auto,
-            }))),
-            cfg: None,
-            inline_stmt_id: None,
+                &finder,
+                DiscardPositiveImpls::No,
+            )
         })
+        .collect();
+    // We are only interested in case the type *doesn't* implement the `Sized` trait.
+    if !ty.is_sized(tcx, param_env)
+        && let Some(sized_trait_def_id) = tcx.lang_items().sized_trait()
+        && let Some(impl_item) = synthesize_auto_trait_impl(
+            cx,
+            ty,
+            sized_trait_def_id,
+            param_env,
+            item_def_id,
+            &finder,
+            DiscardPositiveImpls::Yes,
+        )
+    {
+        auto_trait_impls.push(impl_item);
     }
-
-    pub(crate) fn get_auto_trait_impls(&mut self, item_def_id: DefId) -> Vec<Item> {
-        let tcx = self.cx.tcx;
-        let param_env = tcx.param_env(item_def_id);
-        let ty = tcx.type_of(item_def_id).instantiate_identity();
-        let f = auto_trait::AutoTraitFinder::new(tcx);
-
-        debug!("get_auto_trait_impls({ty:?})");
-        let auto_traits: Vec<_> = self.cx.auto_traits.to_vec();
-        let mut auto_traits: Vec<Item> = auto_traits
-            .into_iter()
-            .filter_map(|trait_def_id| {
-                self.generate_for_trait(ty, trait_def_id, param_env, item_def_id, &f, false)
-            })
-            .collect();
-        // We are only interested in case the type *doesn't* implement the Sized trait.
-        if !ty.is_sized(tcx, param_env) {
-            // In case `#![no_core]` is used, `sized_trait` returns nothing.
-            if let Some(item) = tcx.lang_items().sized_trait().and_then(|sized_trait_did| {
-                self.generate_for_trait(ty, sized_trait_did, param_env, item_def_id, &f, true)
-            }) {
-                auto_traits.push(item);
-            }
-        }
-        auto_traits
-    }
-
-    fn get_lifetime(region: Region<'_>, names_map: &FxHashMap<Symbol, Lifetime>) -> Lifetime {
-        region_name(region)
-            .map(|name| {
-                names_map
-                    .get(&name)
-                    .unwrap_or_else(|| panic!("Missing lifetime with name {name:?} for {region:?}"))
-            })
-            .unwrap_or(&Lifetime::statik())
-            .clone()
-    }
-
-    /// This method calculates two things: Lifetime constraints of the form `'a: 'b`,
-    /// and region constraints of the form `RegionVid: 'a`
-    ///
-    /// This is essentially a simplified version of lexical_region_resolve. However,
-    /// handle_lifetimes determines what *needs be* true in order for an impl to hold.
-    /// lexical_region_resolve, along with much of the rest of the compiler, is concerned
-    /// with determining if a given set up constraints/predicates *are* met, given some
-    /// starting conditions (e.g., user-provided code). For this reason, it's easier
-    /// to perform the calculations we need on our own, rather than trying to make
-    /// existing inference/solver code do what we want.
-    fn handle_lifetimes<'cx>(
-        regions: &RegionConstraintData<'cx>,
-        names_map: &FxHashMap<Symbol, Lifetime>,
-    ) -> ThinVec<WherePredicate> {
-        // Our goal is to 'flatten' the list of constraints by eliminating
-        // all intermediate RegionVids. At the end, all constraints should
-        // be between Regions (aka region variables). This gives us the information
-        // we need to create the Generics.
-        let mut finished: FxHashMap<_, Vec<_>> = Default::default();
-
-        let mut vid_map: FxHashMap<RegionTarget<'_>, RegionDeps<'_>> = Default::default();
-
-        // Flattening is done in two parts. First, we insert all of the constraints
-        // into a map. Each RegionTarget (either a RegionVid or a Region) maps
-        // to its smaller and larger regions. Note that 'larger' regions correspond
-        // to sub-regions in Rust code (e.g., in 'a: 'b, 'a is the larger region).
-        for (constraint, _) in &regions.constraints {
-            match *constraint {
-                Constraint::VarSubVar(r1, r2) => {
-                    {
-                        let deps1 = vid_map.entry(RegionTarget::RegionVid(r1)).or_default();
-                        deps1.larger.insert(RegionTarget::RegionVid(r2));
-                    }
-
-                    let deps2 = vid_map.entry(RegionTarget::RegionVid(r2)).or_default();
-                    deps2.smaller.insert(RegionTarget::RegionVid(r1));
-                }
-                Constraint::RegSubVar(region, vid) => {
-                    let deps = vid_map.entry(RegionTarget::RegionVid(vid)).or_default();
-                    deps.smaller.insert(RegionTarget::Region(region));
-                }
-                Constraint::VarSubReg(vid, region) => {
-                    let deps = vid_map.entry(RegionTarget::RegionVid(vid)).or_default();
-                    deps.larger.insert(RegionTarget::Region(region));
-                }
-                Constraint::RegSubReg(r1, r2) => {
-                    // The constraint is already in the form that we want, so we're done with it
-                    // Desired order is 'larger, smaller', so flip then
-                    if region_name(r1) != region_name(r2) {
-                        finished
-                            .entry(region_name(r2).expect("no region_name found"))
-                            .or_default()
-                            .push(r1);
-                    }
-                }
-            }
-        }
-
-        // Here, we 'flatten' the map one element at a time.
-        // All of the element's sub and super regions are connected
-        // to each other. For example, if we have a graph that looks like this:
-        //
-        // (A, B) - C - (D, E)
-        // Where (A, B) are subregions, and (D,E) are super-regions
-        //
-        // then after deleting 'C', the graph will look like this:
-        //  ... - A - (D, E ...)
-        //  ... - B - (D, E, ...)
-        //  (A, B, ...) - D - ...
-        //  (A, B, ...) - E - ...
-        //
-        //  where '...' signifies the existing sub and super regions of an entry
-        //  When two adjacent ty::Regions are encountered, we've computed a final
-        //  constraint, and add it to our list. Since we make sure to never re-add
-        //  deleted items, this process will always finish.
-        while !vid_map.is_empty() {
-            let target = *vid_map.keys().next().expect("Keys somehow empty");
-            let deps = vid_map.remove(&target).expect("Entry somehow missing");
-
-            for smaller in deps.smaller.iter() {
-                for larger in deps.larger.iter() {
-                    match (smaller, larger) {
-                        (&RegionTarget::Region(r1), &RegionTarget::Region(r2)) => {
-                            if region_name(r1) != region_name(r2) {
-                                finished
-                                    .entry(region_name(r2).expect("no region name found"))
-                                    .or_default()
-                                    .push(r1) // Larger, smaller
-                            }
-                        }
-                        (&RegionTarget::RegionVid(_), &RegionTarget::Region(_)) => {
-                            if let Entry::Occupied(v) = vid_map.entry(*smaller) {
-                                let smaller_deps = v.into_mut();
-                                smaller_deps.larger.insert(*larger);
-                                smaller_deps.larger.remove(&target);
-                            }
-                        }
-                        (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => {
-                            if let Entry::Occupied(v) = vid_map.entry(*larger) {
-                                let deps = v.into_mut();
-                                deps.smaller.insert(*smaller);
-                                deps.smaller.remove(&target);
-                            }
-                        }
-                        (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => {
-                            if let Entry::Occupied(v) = vid_map.entry(*smaller) {
-                                let smaller_deps = v.into_mut();
-                                smaller_deps.larger.insert(*larger);
-                                smaller_deps.larger.remove(&target);
-                            }
-
-                            if let Entry::Occupied(v) = vid_map.entry(*larger) {
-                                let larger_deps = v.into_mut();
-                                larger_deps.smaller.insert(*smaller);
-                                larger_deps.smaller.remove(&target);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        let lifetime_predicates = names_map
-            .iter()
-            .flat_map(|(name, lifetime)| {
-                let empty = Vec::new();
-                let bounds: FxHashSet<GenericBound> = finished
-                    .get(name)
-                    .unwrap_or(&empty)
-                    .iter()
-                    .map(|region| GenericBound::Outlives(Self::get_lifetime(*region, names_map)))
-                    .collect();
-
-                if bounds.is_empty() {
-                    return None;
-                }
-                Some(WherePredicate::RegionPredicate {
-                    lifetime: lifetime.clone(),
-                    bounds: bounds.into_iter().collect(),
-                })
-            })
-            .collect();
-
-        lifetime_predicates
-    }
-
-    fn extract_for_generics(&self, pred: ty::Clause<'tcx>) -> FxHashSet<GenericParamDef> {
-        let bound_predicate = pred.kind();
-        let tcx = self.cx.tcx;
-        let regions =
-            match bound_predicate.skip_binder() {
-                ty::ClauseKind::Trait(poly_trait_pred) => tcx
-                    .collect_referenced_late_bound_regions(bound_predicate.rebind(poly_trait_pred)),
-                ty::ClauseKind::Projection(poly_proj_pred) => tcx
-                    .collect_referenced_late_bound_regions(bound_predicate.rebind(poly_proj_pred)),
-                _ => return FxHashSet::default(),
-            };
-
-        regions
-            .into_iter()
-            .filter_map(|br| {
-                match br {
-                    // We only care about named late bound regions, as we need to add them
-                    // to the 'for<>' section
-                    ty::BrNamed(def_id, name) => Some(GenericParamDef::lifetime(def_id, name)),
-                    _ => None,
-                }
-            })
-            .collect()
-    }
-
-    fn make_final_bounds(
-        &self,
-        ty_to_bounds: FxHashMap<Type, FxHashSet<GenericBound>>,
-        ty_to_fn: FxHashMap<Type, (PolyTrait, Option<Type>)>,
-        lifetime_to_bounds: FxHashMap<Lifetime, FxHashSet<GenericBound>>,
-    ) -> Vec<WherePredicate> {
-        ty_to_bounds
-            .into_iter()
-            .flat_map(|(ty, mut bounds)| {
-                if let Some((ref poly_trait, ref output)) = ty_to_fn.get(&ty) {
-                    let mut new_path = poly_trait.trait_.clone();
-                    let last_segment = new_path.segments.pop().expect("segments were empty");
-
-                    let (old_input, old_output) = match last_segment.args {
-                        GenericArgs::AngleBracketed { args, .. } => {
-                            let types = args
-                                .iter()
-                                .filter_map(|arg| match arg {
-                                    GenericArg::Type(ty) => Some(ty.clone()),
-                                    _ => None,
-                                })
-                                .collect();
-                            (types, None)
-                        }
-                        GenericArgs::Parenthesized { inputs, output } => (inputs, output),
-                    };
-
-                    let output = output.as_ref().cloned().map(Box::new);
-                    if old_output.is_some() && old_output != output {
-                        panic!("Output mismatch for {ty:?} {old_output:?} {output:?}");
-                    }
-
-                    let new_params = GenericArgs::Parenthesized { inputs: old_input, output };
-
-                    new_path
-                        .segments
-                        .push(PathSegment { name: last_segment.name, args: new_params });
-
-                    bounds.insert(GenericBound::TraitBound(
-                        PolyTrait {
-                            trait_: new_path,
-                            generic_params: poly_trait.generic_params.clone(),
-                        },
-                        hir::TraitBoundModifier::None,
-                    ));
-                }
-                if bounds.is_empty() {
-                    return None;
-                }
-
-                let mut bounds_vec = bounds.into_iter().collect();
-                self.sort_where_bounds(&mut bounds_vec);
-
-                Some(WherePredicate::BoundPredicate {
-                    ty,
-                    bounds: bounds_vec,
-                    bound_params: Vec::new(),
-                })
-            })
-            .chain(lifetime_to_bounds.into_iter().filter(|(_, bounds)| !bounds.is_empty()).map(
-                |(lifetime, bounds)| {
-                    let mut bounds_vec = bounds.into_iter().collect();
-                    self.sort_where_bounds(&mut bounds_vec);
-                    WherePredicate::RegionPredicate { lifetime, bounds: bounds_vec }
-                },
-            ))
-            .collect()
-    }
-
-    /// Converts the calculated `ParamEnv` and lifetime information to a [`clean::Generics`](Generics), suitable for
-    /// display on the docs page. Cleaning the `Predicates` produces sub-optimal [`WherePredicate`]s,
-    /// so we fix them up:
-    ///
-    /// * Multiple bounds for the same type are coalesced into one: e.g., `T: Copy`, `T: Debug`
-    /// becomes `T: Copy + Debug`
-    /// * `Fn` bounds are handled specially - instead of leaving it as `T: Fn(), <T as Fn::Output> =
-    /// K`, we use the dedicated syntax `T: Fn() -> K`
-    /// * We explicitly add a `?Sized` bound if we didn't find any `Sized` predicates for a type
-    fn param_env_to_generics(
-        &mut self,
-        item_def_id: DefId,
-        param_env: ty::ParamEnv<'tcx>,
-        mut existing_predicates: ThinVec<WherePredicate>,
-        vid_to_region: FxHashMap<ty::RegionVid, ty::Region<'tcx>>,
-    ) -> Generics {
-        debug!(
-            "param_env_to_generics(item_def_id={:?}, param_env={:?}, \
-             existing_predicates={:?})",
-            item_def_id, param_env, existing_predicates
-        );
-
-        let tcx = self.cx.tcx;
-
-        // The `Sized` trait must be handled specially, since we only display it when
-        // it is *not* required (i.e., '?Sized')
-        let sized_trait = tcx.require_lang_item(LangItem::Sized, None);
-
-        let mut replacer = RegionReplacer { vid_to_region: &vid_to_region, tcx };
-
-        let orig_bounds: FxHashSet<_> = tcx.param_env(item_def_id).caller_bounds().iter().collect();
-        let clean_where_predicates = param_env
-            .caller_bounds()
-            .iter()
-            .filter(|p| {
-                !orig_bounds.contains(p)
-                    || match p.kind().skip_binder() {
-                        ty::ClauseKind::Trait(pred) => pred.def_id() == sized_trait,
-                        _ => false,
-                    }
-            })
-            .map(|p| p.fold_with(&mut replacer));
-
-        let raw_generics = clean_ty_generics(
-            self.cx,
-            tcx.generics_of(item_def_id),
-            tcx.explicit_predicates_of(item_def_id),
-        );
-        let mut generic_params = raw_generics.params;
-
-        debug!("param_env_to_generics({item_def_id:?}): generic_params={generic_params:?}");
-
-        let mut has_sized = FxHashSet::default();
-        let mut ty_to_bounds: FxHashMap<_, FxHashSet<_>> = Default::default();
-        let mut lifetime_to_bounds: FxHashMap<_, FxHashSet<_>> = Default::default();
-        let mut ty_to_traits: FxHashMap<Type, FxHashSet<Path>> = 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 {
-            let (orig_p, p) = (p, clean_predicate(p, self.cx));
-            if p.is_none() {
-                continue;
-            }
-            let p = p.unwrap();
-            match p {
-                WherePredicate::BoundPredicate { ty, mut bounds, .. } => {
-                    // Writing a projection trait bound of the form
-                    // <T as Trait>::Name : ?Sized
-                    // is illegal, because ?Sized bounds can only
-                    // be written in the (here, nonexistent) definition
-                    // of the type.
-                    // Therefore, we make sure that we never add a ?Sized
-                    // bound for projections
-                    if let Type::QPath { .. } = ty {
-                        has_sized.insert(ty.clone());
-                    }
-
-                    if bounds.is_empty() {
-                        continue;
-                    }
-
-                    let mut for_generics = self.extract_for_generics(orig_p);
-
-                    assert!(bounds.len() == 1);
-                    let mut b = bounds.pop().expect("bounds were empty");
-
-                    if b.is_sized_bound(self.cx) {
-                        has_sized.insert(ty.clone());
-                    } else if !b
-                        .get_trait_path()
-                        .and_then(|trait_| {
-                            ty_to_traits
-                                .get(&ty)
-                                .map(|bounds| bounds.contains(&strip_path_generics(trait_)))
-                        })
-                        .unwrap_or(false)
-                    {
-                        // If we've already added a projection bound for the same type, don't add
-                        // this, as it would be a duplicate
-
-                        // Handle any 'Fn/FnOnce/FnMut' bounds specially,
-                        // as we want to combine them with any 'Output' qpaths
-                        // later
-
-                        let is_fn = match b {
-                            GenericBound::TraitBound(ref mut p, _) => {
-                                // Insert regions into the for_generics hash map first, to ensure
-                                // that we don't end up with duplicate bounds (e.g., for<'b, 'b>)
-                                for_generics.extend(p.generic_params.drain(..));
-                                p.generic_params.extend(for_generics);
-                                self.is_fn_trait(&p.trait_)
-                            }
-                            _ => false,
-                        };
-
-                        let poly_trait = b.get_poly_trait().expect("Cannot get poly trait");
-
-                        if is_fn {
-                            ty_to_fn
-                                .entry(ty.clone())
-                                .and_modify(|e| *e = (poly_trait.clone(), e.1.clone()))
-                                .or_insert(((poly_trait.clone()), None));
-
-                            ty_to_bounds.entry(ty.clone()).or_default();
-                        } else {
-                            ty_to_bounds.entry(ty.clone()).or_default().insert(b.clone());
-                        }
-                    }
-                }
-                WherePredicate::RegionPredicate { lifetime, bounds } => {
-                    lifetime_to_bounds.entry(lifetime).or_default().extend(bounds);
-                }
-                WherePredicate::EqPredicate { lhs, rhs } => {
-                    match lhs {
-                        Type::QPath(box QPathData {
-                            ref assoc,
-                            ref self_type,
-                            trait_: Some(ref trait_),
-                            ..
-                        }) => {
-                            let ty = &*self_type;
-                            let mut new_trait = trait_.clone();
-
-                            if self.is_fn_trait(trait_) && assoc.name == sym::Output {
-                                ty_to_fn
-                                    .entry(ty.clone())
-                                    .and_modify(|e| {
-                                        *e = (e.0.clone(), Some(rhs.ty().unwrap().clone()))
-                                    })
-                                    .or_insert((
-                                        PolyTrait {
-                                            trait_: trait_.clone(),
-                                            generic_params: Vec::new(),
-                                        },
-                                        Some(rhs.ty().unwrap().clone()),
-                                    ));
-                                continue;
-                            }
-
-                            let args = &mut new_trait
-                                .segments
-                                .last_mut()
-                                .expect("segments were empty")
-                                .args;
-
-                            match args {
-                                // Convert something like '<T as Iterator::Item> = u8'
-                                // to 'T: Iterator<Item=u8>'
-                                GenericArgs::AngleBracketed { ref mut bindings, .. } => {
-                                    bindings.push(TypeBinding {
-                                        assoc: assoc.clone(),
-                                        kind: TypeBindingKind::Equality { term: rhs },
-                                    });
-                                }
-                                GenericArgs::Parenthesized { .. } => {
-                                    existing_predicates.push(WherePredicate::EqPredicate {
-                                        lhs: lhs.clone(),
-                                        rhs,
-                                    });
-                                    continue; // If something other than a Fn ends up
-                                    // with parentheses, leave it alone
-                                }
-                            }
-
-                            let bounds = ty_to_bounds.entry(ty.clone()).or_default();
-
-                            bounds.insert(GenericBound::TraitBound(
-                                PolyTrait { trait_: new_trait, generic_params: Vec::new() },
-                                hir::TraitBoundModifier::None,
-                            ));
-
-                            // Remove any existing 'plain' bound (e.g., 'T: Iterator`) so
-                            // that we don't see a
-                            // duplicate bound like `T: Iterator + Iterator<Item=u8>`
-                            // on the docs page.
-                            bounds.remove(&GenericBound::TraitBound(
-                                PolyTrait { trait_: trait_.clone(), generic_params: Vec::new() },
-                                hir::TraitBoundModifier::None,
-                            ));
-                            // Avoid creating any new duplicate bounds later in the outer
-                            // loop
-                            ty_to_traits.entry(ty.clone()).or_default().insert(trait_.clone());
-                        }
-                        _ => panic!("Unexpected LHS {lhs:?} for {item_def_id:?}"),
-                    }
-                }
-            };
-        }
-
-        let final_bounds = self.make_final_bounds(ty_to_bounds, ty_to_fn, lifetime_to_bounds);
-
-        existing_predicates.extend(final_bounds);
-
-        for param in generic_params.iter_mut() {
-            match param.kind {
-                GenericParamDefKind::Type { ref mut default, ref mut bounds, .. } => {
-                    // We never want something like `impl<T=Foo>`.
-                    default.take();
-                    let generic_ty = Type::Generic(param.name);
-                    if !has_sized.contains(&generic_ty) {
-                        bounds.insert(0, GenericBound::maybe_sized(self.cx));
-                    }
-                }
-                GenericParamDefKind::Lifetime { .. } => {}
-                GenericParamDefKind::Const { ref mut default, .. } => {
-                    // We never want something like `impl<const N: usize = 10>`
-                    default.take();
-                }
-            }
-        }
-
-        self.sort_where_predicates(&mut existing_predicates);
-
-        Generics { params: generic_params, where_predicates: existing_predicates }
-    }
-
-    /// Ensure that the predicates are in a consistent order. The precise
-    /// ordering doesn't actually matter, but it's important that
-    /// a given set of predicates always appears in the same order -
-    /// both for visual consistency between 'rustdoc' runs, and to
-    /// make writing tests much easier
-    #[inline]
-    fn sort_where_predicates(&self, predicates: &mut [WherePredicate]) {
-        // We should never have identical bounds - and if we do,
-        // they're visually identical as well. Therefore, using
-        // an unstable sort is fine.
-        self.unstable_debug_sort(predicates);
-    }
-
-    /// Ensure that the bounds are in a consistent order. The precise
-    /// ordering doesn't actually matter, but it's important that
-    /// a given set of bounds always appears in the same order -
-    /// both for visual consistency between 'rustdoc' runs, and to
-    /// make writing tests much easier
-    #[inline]
-    fn sort_where_bounds(&self, bounds: &mut Vec<GenericBound>) {
-        // We should never have identical bounds - and if we do,
-        // they're visually identical as well. Therefore, using
-        // an unstable sort is fine.
-        self.unstable_debug_sort(bounds);
-    }
-
-    /// This might look horrendously hacky, but it's actually not that bad.
-    ///
-    /// For performance reasons, we use several different FxHashMaps
-    /// in the process of computing the final set of where predicates.
-    /// However, the iteration order of a HashMap is completely unspecified.
-    /// In fact, the iteration of an FxHashMap can even vary between platforms,
-    /// since FxHasher has different behavior for 32-bit and 64-bit platforms.
-    ///
-    /// Obviously, it's extremely undesirable for documentation rendering
-    /// to be dependent on the platform it's run on. Apart from being confusing
-    /// to end users, it makes writing tests much more difficult, as predicates
-    /// can appear in any order in the final result.
-    ///
-    /// To solve this problem, we sort WherePredicates and GenericBounds
-    /// by their Debug string. The thing to keep in mind is that we don't really
-    /// care what the final order is - we're synthesizing an impl or bound
-    /// ourselves, so any order can be considered equally valid. By sorting the
-    /// predicates and bounds, however, we ensure that for a given codebase, all
-    /// auto-trait impls always render in exactly the same way.
-    ///
-    /// Using the Debug implementation for sorting prevents us from needing to
-    /// write quite a bit of almost entirely useless code (e.g., how should two
-    /// Types be sorted relative to each other). It also allows us to solve the
-    /// problem for both WherePredicates and GenericBounds at the same time. This
-    /// approach is probably somewhat slower, but the small number of items
-    /// involved (impls rarely have more than a few bounds) means that it
-    /// shouldn't matter in practice.
-    fn unstable_debug_sort<T: Debug>(&self, vec: &mut [T]) {
-        vec.sort_by_cached_key(|x| format!("{x:?}"))
-    }
-
-    fn is_fn_trait(&self, path: &Path) -> bool {
-        let tcx = self.cx.tcx;
-        let did = path.def_id();
-        did == tcx.require_lang_item(LangItem::Fn, None)
-            || did == tcx.require_lang_item(LangItem::FnMut, None)
-            || did == tcx.require_lang_item(LangItem::FnOnce, None)
-    }
+    auto_trait_impls
 }
 
-fn region_name(region: Region<'_>) -> Option<Symbol> {
+#[instrument(level = "debug", skip(cx, finder))]
+fn synthesize_auto_trait_impl<'tcx>(
+    cx: &mut DocContext<'tcx>,
+    ty: Ty<'tcx>,
+    trait_def_id: DefId,
+    param_env: ty::ParamEnv<'tcx>,
+    item_def_id: DefId,
+    finder: &auto_trait::AutoTraitFinder<'tcx>,
+    discard_positive_impls: DiscardPositiveImpls,
+) -> Option<clean::Item> {
+    let tcx = cx.tcx;
+    let trait_ref = ty::Binder::dummy(ty::TraitRef::new(tcx, trait_def_id, [ty]));
+    if !cx.generated_synthetics.insert((ty, trait_def_id)) {
+        debug!("already generated, aborting");
+        return None;
+    }
+
+    let result = finder.find_auto_trait_generics(ty, param_env, trait_def_id, |info| {
+        clean_param_env(cx, item_def_id, info.full_user_env, info.region_data, info.vid_to_region)
+    });
+
+    let (generics, polarity) = match result {
+        auto_trait::AutoTraitResult::PositiveImpl(generics) => {
+            if let DiscardPositiveImpls::Yes = discard_positive_impls {
+                return None;
+            }
+
+            (generics, ty::ImplPolarity::Positive)
+        }
+        auto_trait::AutoTraitResult::NegativeImpl => {
+            // For negative impls, we use the generic params, but *not* the predicates,
+            // from the original type. Otherwise, the displayed impl appears to be a
+            // conditional negative impl, when it's really unconditional.
+            //
+            // For example, consider the struct Foo<T: Copy>(*mut T). Using
+            // the original predicates in our impl would cause us to generate
+            // `impl !Send for Foo<T: Copy>`, which makes it appear that Foo
+            // implements Send where T is not copy.
+            //
+            // Instead, we generate `impl !Send for Foo<T>`, which better
+            // expresses the fact that `Foo<T>` never implements `Send`,
+            // regardless of the choice of `T`.
+            let mut generics = clean_ty_generics(
+                cx,
+                tcx.generics_of(item_def_id),
+                ty::GenericPredicates::default(),
+            );
+            generics.where_predicates.clear();
+
+            (generics, ty::ImplPolarity::Negative)
+        }
+        auto_trait::AutoTraitResult::ExplicitImpl => return None,
+    };
+
+    Some(clean::Item {
+        name: None,
+        attrs: Default::default(),
+        item_id: clean::ItemId::Auto { trait_: trait_def_id, for_: item_def_id },
+        kind: Box::new(clean::ImplItem(Box::new(clean::Impl {
+            unsafety: hir::Unsafety::Normal,
+            generics,
+            trait_: Some(clean_trait_ref_with_bindings(cx, trait_ref, ThinVec::new())),
+            for_: clean_middle_ty(ty::Binder::dummy(ty), cx, None, None),
+            items: Vec::new(),
+            polarity,
+            kind: clean::ImplKind::Auto,
+        }))),
+        cfg: None,
+        inline_stmt_id: None,
+    })
+}
+
+#[derive(Debug)]
+enum DiscardPositiveImpls {
+    Yes,
+    No,
+}
+
+#[instrument(level = "debug", skip(cx, region_data, vid_to_region))]
+fn clean_param_env<'tcx>(
+    cx: &mut DocContext<'tcx>,
+    item_def_id: DefId,
+    param_env: ty::ParamEnv<'tcx>,
+    region_data: RegionConstraintData<'tcx>,
+    vid_to_region: FxIndexMap<ty::RegionVid, ty::Region<'tcx>>,
+) -> clean::Generics {
+    let tcx = cx.tcx;
+    let generics = tcx.generics_of(item_def_id);
+
+    let params: ThinVec<_> = generics
+        .params
+        .iter()
+        .inspect(|param| {
+            if cfg!(debug_assertions) {
+                debug_assert!(!param.is_anonymous_lifetime() && !param.is_host_effect());
+                if let ty::GenericParamDefKind::Type { synthetic, .. } = param.kind {
+                    debug_assert!(!synthetic && param.name != kw::SelfUpper);
+                }
+            }
+        })
+        // We're basing the generics of the synthetic auto trait impl off of the generics of the
+        // implementing type. Its generic parameters may have defaults, don't copy them over:
+        // Generic parameter defaults are meaningless in impls.
+        .map(|param| clean_generic_param_def(param, clean::ParamDefaults::No, cx))
+        .collect();
+
+    // FIXME(#111101): Incorporate the explicit predicates of the item here...
+    let item_predicates: FxIndexSet<_> =
+        tcx.predicates_of(item_def_id).predicates.iter().map(|(pred, _)| pred).collect();
+    let where_predicates = param_env
+        .caller_bounds()
+        .iter()
+        // FIXME: ...which hopefully allows us to simplify this:
+        .filter(|pred| {
+            !item_predicates.contains(pred)
+                || pred
+                    .as_trait_clause()
+                    .is_some_and(|pred| tcx.lang_items().sized_trait() == Some(pred.def_id()))
+        })
+        .map(|pred| {
+            tcx.fold_regions(pred, |r, _| match *r {
+                ty::ReVar(vid) => vid_to_region[&vid],
+                ty::ReEarlyParam(_) | ty::ReStatic | ty::ReBound(..) | ty::ReError(_) => r,
+                ty::ReLateParam(_) | ty::RePlaceholder(_) | ty::ReErased => {
+                    bug!("unexpected region kind: {r:?}")
+                }
+            })
+        })
+        .flat_map(|pred| clean_predicate(pred, cx))
+        .chain(clean_region_outlives_constraints(&region_data, generics))
+        .collect();
+
+    let mut generics = clean::Generics { params, where_predicates };
+    simplify::sized_bounds(cx, &mut generics);
+    generics.where_predicates = simplify::where_clauses(cx, generics.where_predicates);
+    generics
+}
+
+/// Clean region outlives constraints to where-predicates.
+///
+/// This is essentially a simplified version of `lexical_region_resolve`.
+///
+/// However, here we determine what *needs to be* true in order for an impl to hold.
+/// `lexical_region_resolve`, along with much of the rest of the compiler, is concerned
+/// with determining if a given set up constraints / predicates *are* met, given some
+/// starting conditions like user-provided code.
+///
+/// For this reason, it's easier to perform the calculations we need on our own,
+/// rather than trying to make existing inference/solver code do what we want.
+fn clean_region_outlives_constraints<'tcx>(
+    regions: &RegionConstraintData<'tcx>,
+    generics: &'tcx ty::Generics,
+) -> ThinVec<clean::WherePredicate> {
+    // Our goal is to "flatten" the list of constraints by eliminating all intermediate
+    // `RegionVids` (region inference variables). At the end, all constraints should be
+    // between `Region`s. This gives us the information we need to create the where-predicates.
+    // This flattening is done in two parts.
+
+    let mut outlives_predicates = FxIndexMap::<_, Vec<_>>::default();
+    let mut map = FxIndexMap::<RegionTarget<'_>, auto_trait::RegionDeps<'_>>::default();
+
+    // (1)  We insert all of the constraints into a map.
+    // Each `RegionTarget` (a `RegionVid` or a `Region`) maps to its smaller and larger regions.
+    // Note that "larger" regions correspond to sub regions in the surface language.
+    // E.g., in `'a: 'b`, `'a` is the larger region.
+    for (constraint, _) in &regions.constraints {
+        match *constraint {
+            Constraint::VarSubVar(vid1, vid2) => {
+                let deps1 = map.entry(RegionTarget::RegionVid(vid1)).or_default();
+                deps1.larger.insert(RegionTarget::RegionVid(vid2));
+
+                let deps2 = map.entry(RegionTarget::RegionVid(vid2)).or_default();
+                deps2.smaller.insert(RegionTarget::RegionVid(vid1));
+            }
+            Constraint::RegSubVar(region, vid) => {
+                let deps = map.entry(RegionTarget::RegionVid(vid)).or_default();
+                deps.smaller.insert(RegionTarget::Region(region));
+            }
+            Constraint::VarSubReg(vid, region) => {
+                let deps = map.entry(RegionTarget::RegionVid(vid)).or_default();
+                deps.larger.insert(RegionTarget::Region(region));
+            }
+            Constraint::RegSubReg(r1, r2) => {
+                // The constraint is already in the form that we want, so we're done with it
+                // The desired order is [larger, smaller], so flip them.
+                if early_bound_region_name(r1) != early_bound_region_name(r2) {
+                    outlives_predicates
+                        .entry(early_bound_region_name(r2).expect("no region_name found"))
+                        .or_default()
+                        .push(r1);
+                }
+            }
+        }
+    }
+
+    // (2)  Here, we "flatten" the map one element at a time. All of the elements' sub and super
+    // regions are connected to each other. For example, if we have a graph that looks like this:
+    //
+    //     (A, B) - C - (D, E)
+    //
+    // where (A, B) are sub regions, and (D,E) are super regions.
+    // Then, after deleting 'C', the graph will look like this:
+    //
+    //             ... - A - (D, E, ...)
+    //             ... - B - (D, E, ...)
+    //     (A, B, ...) - D - ...
+    //     (A, B, ...) - E - ...
+    //
+    // where '...' signifies the existing sub and super regions of an entry. When two adjacent
+    // `Region`s are encountered, we've computed a final constraint, and add it to our list.
+    // Since we make sure to never re-add deleted items, this process will always finish.
+    while !map.is_empty() {
+        let target = *map.keys().next().unwrap();
+        let deps = map.swap_remove(&target).unwrap();
+
+        for smaller in &deps.smaller {
+            for larger in &deps.larger {
+                match (smaller, larger) {
+                    (&RegionTarget::Region(smaller), &RegionTarget::Region(larger)) => {
+                        if early_bound_region_name(smaller) != early_bound_region_name(larger) {
+                            outlives_predicates
+                                .entry(
+                                    early_bound_region_name(larger).expect("no region name found"),
+                                )
+                                .or_default()
+                                .push(smaller)
+                        }
+                    }
+                    (&RegionTarget::RegionVid(_), &RegionTarget::Region(_)) => {
+                        if let IndexEntry::Occupied(v) = map.entry(*smaller) {
+                            let smaller_deps = v.into_mut();
+                            smaller_deps.larger.insert(*larger);
+                            smaller_deps.larger.swap_remove(&target);
+                        }
+                    }
+                    (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => {
+                        if let IndexEntry::Occupied(v) = map.entry(*larger) {
+                            let deps = v.into_mut();
+                            deps.smaller.insert(*smaller);
+                            deps.smaller.swap_remove(&target);
+                        }
+                    }
+                    (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => {
+                        if let IndexEntry::Occupied(v) = map.entry(*smaller) {
+                            let smaller_deps = v.into_mut();
+                            smaller_deps.larger.insert(*larger);
+                            smaller_deps.larger.swap_remove(&target);
+                        }
+                        if let IndexEntry::Occupied(v) = map.entry(*larger) {
+                            let larger_deps = v.into_mut();
+                            larger_deps.smaller.insert(*smaller);
+                            larger_deps.smaller.swap_remove(&target);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    let region_params: FxIndexSet<_> = generics
+        .params
+        .iter()
+        .filter_map(|param| match param.kind {
+            ty::GenericParamDefKind::Lifetime => Some(param.name),
+            _ => None,
+        })
+        .collect();
+
+    region_params
+        .iter()
+        .filter_map(|&name| {
+            let bounds: FxIndexSet<_> = outlives_predicates
+                .get(&name)?
+                .iter()
+                .map(|&region| {
+                    let lifetime = early_bound_region_name(region)
+                        .inspect(|name| assert!(region_params.contains(name)))
+                        .map(|name| Lifetime(name))
+                        .unwrap_or(Lifetime::statik());
+                    clean::GenericBound::Outlives(lifetime)
+                })
+                .collect();
+            if bounds.is_empty() {
+                return None;
+            }
+            Some(clean::WherePredicate::RegionPredicate {
+                lifetime: Lifetime(name),
+                bounds: bounds.into_iter().collect(),
+            })
+        })
+        .collect()
+}
+
+fn early_bound_region_name(region: Region<'_>) -> Option<Symbol> {
     match *region {
         ty::ReEarlyParam(r) => Some(r.name),
         _ => None,
     }
 }
-
-/// Replaces all [`ty::RegionVid`]s in a type with [`ty::Region`]s, using the provided map.
-struct RegionReplacer<'a, 'tcx> {
-    vid_to_region: &'a FxHashMap<ty::RegionVid, ty::Region<'tcx>>,
-    tcx: TyCtxt<'tcx>,
-}
-
-impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for RegionReplacer<'a, 'tcx> {
-    fn interner(&self) -> TyCtxt<'tcx> {
-        self.tcx
-    }
-
-    fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
-        match *r {
-            // These are the regions that can be seen in the AST.
-            ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned().unwrap_or(r),
-            ty::ReEarlyParam(_) | ty::ReStatic | ty::ReBound(..) | ty::ReError(_) => r,
-            r => bug!("unexpected region: {r:?}"),
-        }
-    }
-}
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 0cdf52bfb00..a25a506d9c5 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -21,10 +21,8 @@ use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId, LOCAL_CRATE};
 use rustc_hir::PredicateOrigin;
 use rustc_hir_analysis::lower_ty;
-use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData};
 use rustc_middle::metadata::Reexport;
 use rustc_middle::middle::resolve_bound_vars as rbv;
-use rustc_middle::ty::fold::TypeFolder;
 use rustc_middle::ty::GenericArgsRef;
 use rustc_middle::ty::TypeVisitableExt;
 use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt};
@@ -35,9 +33,7 @@ use rustc_span::{self, ExpnKind};
 use rustc_trait_selection::traits::wf::object_region_bounds;
 
 use std::borrow::Cow;
-use std::collections::hash_map::Entry;
 use std::collections::BTreeMap;
-use std::hash::Hash;
 use std::mem;
 use thin_vec::ThinVec;
 
@@ -502,6 +498,7 @@ fn projection_to_path_segment<'tcx>(
 
 fn clean_generic_param_def<'tcx>(
     def: &ty::GenericParamDef,
+    defaults: ParamDefaults,
     cx: &mut DocContext<'tcx>,
 ) -> GenericParamDef {
     let (name, kind) = match def.kind {
@@ -509,7 +506,9 @@ fn clean_generic_param_def<'tcx>(
             (def.name, GenericParamDefKind::Lifetime { outlives: ThinVec::new() })
         }
         ty::GenericParamDefKind::Type { has_default, synthetic, .. } => {
-            let default = if has_default {
+            let default = if let ParamDefaults::Yes = defaults
+                && has_default
+            {
                 Some(clean_middle_ty(
                     ty::Binder::dummy(cx.tcx.type_of(def.def_id).instantiate_identity()),
                     cx,
@@ -542,11 +541,14 @@ fn clean_generic_param_def<'tcx>(
                     Some(def.def_id),
                     None,
                 )),
-                default: match has_default {
-                    true => Some(Box::new(
+                default: if let ParamDefaults::Yes = defaults
+                    && has_default
+                {
+                    Some(Box::new(
                         cx.tcx.const_param_default(def.def_id).instantiate_identity().to_string(),
-                    )),
-                    false => None,
+                    ))
+                } else {
+                    None
                 },
                 is_host_effect,
             },
@@ -556,6 +558,12 @@ fn clean_generic_param_def<'tcx>(
     GenericParamDef { name, def_id: def.def_id, kind }
 }
 
+/// Whether to clean generic parameter defaults or not.
+enum ParamDefaults {
+    Yes,
+    No,
+}
+
 fn clean_generic_param<'tcx>(
     cx: &mut DocContext<'tcx>,
     generics: Option<&hir::Generics<'tcx>>,
@@ -759,34 +767,30 @@ fn clean_ty_generics<'tcx>(
     gens: &ty::Generics,
     preds: ty::GenericPredicates<'tcx>,
 ) -> Generics {
-    // Don't populate `cx.impl_trait_bounds` before `clean`ning `where` clauses,
-    // since `Clean for ty::Predicate` would consume them.
+    // Don't populate `cx.impl_trait_bounds` before cleaning where clauses,
+    // since `clean_predicate` would consume them.
     let mut impl_trait = BTreeMap::<u32, Vec<GenericBound>>::default();
 
-    // Bounds in the type_params and lifetimes fields are repeated in the
-    // predicates field (see rustc_hir_analysis::collect::ty_generics), so remove
-    // them.
-    let stripped_params = gens
+    let params: ThinVec<_> = gens
         .params
         .iter()
-        .filter_map(|param| match param.kind {
-            ty::GenericParamDefKind::Lifetime if param.is_anonymous_lifetime() => None,
-            ty::GenericParamDefKind::Lifetime => Some(clean_generic_param_def(param, cx)),
+        .filter(|param| match param.kind {
+            ty::GenericParamDefKind::Lifetime => !param.is_anonymous_lifetime(),
             ty::GenericParamDefKind::Type { synthetic, .. } => {
                 if param.name == kw::SelfUpper {
-                    assert_eq!(param.index, 0);
-                    return None;
+                    debug_assert_eq!(param.index, 0);
+                    return false;
                 }
                 if synthetic {
                     impl_trait.insert(param.index, vec![]);
-                    return None;
+                    return false;
                 }
-                Some(clean_generic_param_def(param, cx))
+                true
             }
-            ty::GenericParamDefKind::Const { is_host_effect: true, .. } => None,
-            ty::GenericParamDefKind::Const { .. } => Some(clean_generic_param_def(param, cx)),
+            ty::GenericParamDefKind::Const { is_host_effect, .. } => !is_host_effect,
         })
-        .collect::<ThinVec<GenericParamDef>>();
+        .map(|param| clean_generic_param_def(param, ParamDefaults::Yes, cx))
+        .collect();
 
     // param index -> [(trait DefId, associated type name & generics, term)]
     let mut impl_trait_proj =
@@ -882,56 +886,13 @@ fn clean_ty_generics<'tcx>(
 
     // Now that `cx.impl_trait_bounds` is populated, we can process
     // remaining predicates which could contain `impl Trait`.
-    let mut where_predicates =
-        where_predicates.into_iter().flat_map(|p| clean_predicate(*p, cx)).collect::<Vec<_>>();
+    let where_predicates =
+        where_predicates.into_iter().flat_map(|p| clean_predicate(*p, cx)).collect();
 
-    // In the surface language, all type parameters except `Self` have an
-    // implicit `Sized` bound unless removed with `?Sized`.
-    // However, in the list of where-predicates below, `Sized` appears like a
-    // normal bound: It's either present (the type is sized) or
-    // absent (the type might be unsized) but never *maybe* (i.e. `?Sized`).
-    //
-    // This is unsuitable for rendering.
-    // Thus, as a first step remove all `Sized` bounds that should be implicit.
-    //
-    // Note that associated types also have an implicit `Sized` bound but we
-    // don't actually know the set of associated types right here so that's
-    // handled when cleaning associated types.
-    let mut sized_params = FxHashSet::default();
-    where_predicates.retain(|pred| {
-        if let WherePredicate::BoundPredicate { ty: Generic(g), bounds, .. } = pred
-            && *g != kw::SelfUpper
-            && bounds.iter().any(|b| b.is_sized_bound(cx))
-        {
-            sized_params.insert(*g);
-            false
-        } else {
-            true
-        }
-    });
-
-    // As a final step, go through the type parameters again and insert a
-    // `?Sized` bound for each one we didn't find to be `Sized`.
-    for tp in &stripped_params {
-        if let types::GenericParamDefKind::Type { .. } = tp.kind
-            && !sized_params.contains(&tp.name)
-        {
-            where_predicates.push(WherePredicate::BoundPredicate {
-                ty: Type::Generic(tp.name),
-                bounds: vec![GenericBound::maybe_sized(cx)],
-                bound_params: Vec::new(),
-            })
-        }
-    }
-
-    // It would be nice to collect all of the bounds on a type and recombine
-    // them if possible, to avoid e.g., `where T: Foo, T: Bar, T: Sized, T: 'a`
-    // and instead see `where T: Foo + Bar + Sized + 'a`
-
-    Generics {
-        params: stripped_params,
-        where_predicates: simplify::where_clauses(cx, where_predicates),
-    }
+    let mut generics = Generics { params, where_predicates };
+    simplify::sized_bounds(cx, &mut generics);
+    generics.where_predicates = simplify::where_clauses(cx, generics.where_predicates);
+    generics
 }
 
 fn clean_ty_alias_inner_type<'tcx>(
diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs
index c35fb9ec788..5a3ccb6239a 100644
--- a/src/librustdoc/clean/simplify.rs
+++ b/src/librustdoc/clean/simplify.rs
@@ -12,6 +12,7 @@
 //! bounds by special casing scenarios such as these. Fun!
 
 use rustc_data_structures::fx::FxIndexMap;
+use rustc_data_structures::unord::UnordSet;
 use rustc_hir::def_id::DefId;
 use rustc_middle::ty;
 use thin_vec::ThinVec;
@@ -21,7 +22,7 @@ use crate::clean::GenericArgs as PP;
 use crate::clean::WherePredicate as WP;
 use crate::core::DocContext;
 
-pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> ThinVec<WP> {
+pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: ThinVec<WP>) -> ThinVec<WP> {
     // First, partition the where clause into its separate components.
     //
     // We use `FxIndexMap` so that the insertion order is preserved to prevent messing up to
@@ -128,6 +129,48 @@ fn trait_is_same_or_supertrait(cx: &DocContext<'_>, child: DefId, trait_: DefId)
         .any(|did| trait_is_same_or_supertrait(cx, did, trait_))
 }
 
+pub(crate) fn sized_bounds(cx: &mut DocContext<'_>, generics: &mut clean::Generics) {
+    let mut sized_params = UnordSet::new();
+
+    // In the surface language, all type parameters except `Self` have an
+    // implicit `Sized` bound unless removed with `?Sized`.
+    // However, in the list of where-predicates below, `Sized` appears like a
+    // normal bound: It's either present (the type is sized) or
+    // absent (the type might be unsized) but never *maybe* (i.e. `?Sized`).
+    //
+    // This is unsuitable for rendering.
+    // Thus, as a first step remove all `Sized` bounds that should be implicit.
+    //
+    // Note that associated types also have an implicit `Sized` bound but we
+    // don't actually know the set of associated types right here so that
+    // should be handled when cleaning associated types.
+    generics.where_predicates.retain(|pred| {
+        if let WP::BoundPredicate { ty: clean::Generic(param), bounds, .. } = pred
+            && *param != rustc_span::symbol::kw::SelfUpper
+            && bounds.iter().any(|b| b.is_sized_bound(cx))
+        {
+            sized_params.insert(*param);
+            false
+        } else {
+            true
+        }
+    });
+
+    // As a final step, go through the type parameters again and insert a
+    // `?Sized` bound for each one we didn't find to be `Sized`.
+    for param in &generics.params {
+        if let clean::GenericParamDefKind::Type { .. } = param.kind
+            && !sized_params.contains(&param.name)
+        {
+            generics.where_predicates.push(WP::BoundPredicate {
+                ty: clean::Type::Generic(param.name),
+                bounds: vec![clean::GenericBound::maybe_sized(cx)],
+                bound_params: Vec::new(),
+            })
+        }
+    }
+}
+
 /// Move bounds that are (likely) directly attached to generic parameters from the where-clause to
 /// the respective parameter.
 ///
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index a51f6360df2..6793ea9f485 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -1277,13 +1277,6 @@ impl GenericBound {
         false
     }
 
-    pub(crate) fn get_poly_trait(&self) -> Option<PolyTrait> {
-        if let GenericBound::TraitBound(ref p, _) = *self {
-            return Some(p.clone());
-        }
-        None
-    }
-
     pub(crate) fn get_trait_path(&self) -> Option<Path> {
         if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, _) = *self {
             Some(trait_.clone())
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index 977b4bb45b6..d5e0e83696f 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -1,4 +1,4 @@
-use crate::clean::auto_trait::AutoTraitFinder;
+use crate::clean::auto_trait::synthesize_auto_trait_impls;
 use crate::clean::blanket_impl::BlanketImplFinder;
 use crate::clean::render_macro_matchers::render_macro_matcher;
 use crate::clean::{
@@ -251,15 +251,6 @@ pub(super) fn clean_middle_path<'tcx>(
     }
 }
 
-/// Remove the generic arguments from a path.
-pub(crate) fn strip_path_generics(mut path: Path) -> Path {
-    for ps in path.segments.iter_mut() {
-        ps.args = GenericArgs::AngleBracketed { args: Default::default(), bindings: ThinVec::new() }
-    }
-
-    path
-}
-
 pub(crate) fn qpath_to_string(p: &hir::QPath<'_>) -> String {
     let segments = match *p {
         hir::QPath::Resolved(_, path) => &path.segments,
@@ -486,6 +477,7 @@ pub(crate) fn resolve_type(cx: &mut DocContext<'_>, path: Path) -> Type {
     }
 }
 
+// FIXME(fmease): Update the `get_*` terminology to the `synthesize_` one.
 pub(crate) fn get_auto_trait_and_blanket_impls(
     cx: &mut DocContext<'_>,
     item_def_id: DefId,
@@ -493,8 +485,8 @@ pub(crate) fn get_auto_trait_and_blanket_impls(
     let auto_impls = cx
         .sess()
         .prof
-        .generic_activity("get_auto_trait_impls")
-        .run(|| AutoTraitFinder::new(cx).get_auto_trait_impls(item_def_id));
+        .generic_activity("synthesize_auto_trait_impls")
+        .run(|| synthesize_auto_trait_impls(cx, item_def_id));
     let blanket_impls = cx
         .sess()
         .prof
diff --git a/tests/rustdoc/synthetic_auto/bounds.rs b/tests/rustdoc/synthetic_auto/bounds.rs
new file mode 100644
index 00000000000..17528d01c8d
--- /dev/null
+++ b/tests/rustdoc/synthetic_auto/bounds.rs
@@ -0,0 +1,21 @@
+pub struct Outer<T>(Inner<T>);
+pub struct Inner<T>(T);
+
+// @has bounds/struct.Outer.html
+// @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \
+// "impl<T> Unpin for Outer<T>where \
+//     T: for<'any> Trait<A = (), B<'any> = (), X = ()>,"
+
+impl<T> std::marker::Unpin for Inner<T>
+where
+    T: for<'any> Trait<A = (), B<'any> = (), X = ()>,
+{}
+
+pub trait Trait: SuperTrait {
+    type A;
+    type B<'a>;
+}
+
+pub trait SuperTrait {
+    type X;
+}
diff --git a/tests/rustdoc/synthetic_auto/complex.rs b/tests/rustdoc/synthetic_auto/complex.rs
index 4c39f0bf1e0..2722f6d338f 100644
--- a/tests/rustdoc/synthetic_auto/complex.rs
+++ b/tests/rustdoc/synthetic_auto/complex.rs
@@ -21,8 +21,8 @@ mod foo {
 
 // @has complex/struct.NotOuter.html
 // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \
-// "impl<'a, T, K: ?Sized> Send for Outer<'a, T, K>where K: for<'b> Fn((&'b bool, &'a u8)) \
-// -> &'b i8, T: MyTrait<'a>, <T as MyTrait<'a>>::MyItem: Copy, 'a: 'static"
+// "impl<'a, T, K> Send for Outer<'a, T, K>where 'a: 'static, T: MyTrait<'a>, \
+// K: for<'b> Fn((&'b bool, &'a u8)) -> &'b i8 + ?Sized, <T as MyTrait<'a>>::MyItem: Copy,"
 
 pub use foo::{Foo, Inner as NotInner, MyTrait as NotMyTrait, Outer as NotOuter};
 
diff --git a/tests/rustdoc/synthetic_auto/lifetimes.rs b/tests/rustdoc/synthetic_auto/lifetimes.rs
index 71265b3078a..23e1efdaeef 100644
--- a/tests/rustdoc/synthetic_auto/lifetimes.rs
+++ b/tests/rustdoc/synthetic_auto/lifetimes.rs
@@ -10,7 +10,7 @@ where
 
 // @has lifetimes/struct.Foo.html
 // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \
-// "impl<'c, K> Send for Foo<'c, K>where K: for<'b> Fn(&'b bool) -> &'c u8, 'c: 'static"
+// "impl<'c, K> Send for Foo<'c, K>where 'c: 'static, K: for<'b> Fn(&'b bool) -> &'c u8,"
 //
 // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \
 // "impl<'c, K> Sync for Foo<'c, K>where K: Sync"
diff --git a/tests/rustdoc/synthetic_auto/no-redundancy.rs b/tests/rustdoc/synthetic_auto/no-redundancy.rs
index d30b38dd4dc..64dab429647 100644
--- a/tests/rustdoc/synthetic_auto/no-redundancy.rs
+++ b/tests/rustdoc/synthetic_auto/no-redundancy.rs
@@ -1,6 +1,3 @@
-// FIXME(fmease, #119216): Reenable this test!
-//@ ignore-test
-
 pub struct Inner<T> {
     field: T,
 }
@@ -13,7 +10,7 @@ where
 
 // @has no_redundancy/struct.Outer.html
 // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \
-// "impl<T> Send for Outer<T>where T: Send + Copy"
+// "impl<T> Send for Outer<T>where T: Copy + Send"
 pub struct Outer<T> {
     inner_field: Inner<T>,
 }
diff --git a/tests/rustdoc/synthetic_auto/project.rs b/tests/rustdoc/synthetic_auto/project.rs
index 7c9412ae962..f4ede76e6de 100644
--- a/tests/rustdoc/synthetic_auto/project.rs
+++ b/tests/rustdoc/synthetic_auto/project.rs
@@ -24,11 +24,11 @@ where
 
 // @has project/struct.Foo.html
 // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \
-// "impl<'c, K> Send for Foo<'c, K>where K: MyTrait<MyItem = bool>, 'c: 'static"
+// "impl<'c, K> Send for Foo<'c, K>where 'c: 'static, K: MyTrait<MyItem = bool>,"
 //
 // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \
-// "impl<'c, K> Sync for Foo<'c, K>where K: MyTrait, <K as MyTrait>::MyItem: OtherTrait, \
-// 'c: 'static,"
+// "impl<'c, K> Sync for Foo<'c, K>where 'c: 'static, K: MyTrait, \
+// <K as MyTrait>::MyItem: OtherTrait,"
 pub struct Foo<'c, K: 'c> {
     inner_field: Inner<'c, K>,
 }