about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/display.rs130
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs34
2 files changed, 129 insertions, 35 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs
index d960aaf99f3..ae8fbe2ce6d 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs
@@ -34,6 +34,7 @@ use rustc_apfloat::{
     ieee::{Half as f16, Quad as f128},
     Float,
 };
+use rustc_hash::FxHashSet;
 use smallvec::SmallVec;
 use span::Edition;
 use stdx::never;
@@ -87,6 +88,35 @@ pub struct HirFormatter<'a> {
     omit_verbose_types: bool,
     closure_style: ClosureStyle,
     display_target: DisplayTarget,
+    bounds_formatting_ctx: BoundsFormattingCtx,
+}
+
+#[derive(Default)]
+enum BoundsFormattingCtx {
+    Entered {
+        /// We can have recursive bounds like the following case:
+        /// ```rust
+        /// where
+        ///     T: Foo,
+        ///     T::FooAssoc: Baz<<T::FooAssoc as Bar>::BarAssoc> + Bar
+        /// ```
+        /// So, record the projection types met while formatting bounds and
+        //. prevent recursing into their bounds to avoid infinite loops.
+        projection_tys_met: FxHashSet<ProjectionTy>,
+    },
+    #[default]
+    Exited,
+}
+
+impl BoundsFormattingCtx {
+    fn contains(&mut self, proj: &ProjectionTy) -> bool {
+        match self {
+            BoundsFormattingCtx::Entered { projection_tys_met } => {
+                projection_tys_met.contains(proj)
+            }
+            BoundsFormattingCtx::Exited => false,
+        }
+    }
 }
 
 impl HirFormatter<'_> {
@@ -97,6 +127,30 @@ impl HirFormatter<'_> {
     fn end_location_link(&mut self) {
         self.fmt.end_location_link();
     }
+
+    fn format_bounds_with<T, F: FnOnce(&mut Self) -> T>(
+        &mut self,
+        target: ProjectionTy,
+        format_bounds: F,
+    ) -> T {
+        match self.bounds_formatting_ctx {
+            BoundsFormattingCtx::Entered { ref mut projection_tys_met } => {
+                projection_tys_met.insert(target);
+                format_bounds(self)
+            }
+            BoundsFormattingCtx::Exited => {
+                let mut projection_tys_met = FxHashSet::default();
+                projection_tys_met.insert(target);
+                self.bounds_formatting_ctx = BoundsFormattingCtx::Entered { projection_tys_met };
+                let res = format_bounds(self);
+                // Since we want to prevent only the infinite recursions in bounds formatting
+                // and do not want to skip formatting of other separate bounds, clear context
+                // when exiting the formatting of outermost bounds
+                self.bounds_formatting_ctx = BoundsFormattingCtx::Exited;
+                res
+            }
+        }
+    }
 }
 
 pub trait HirDisplay {
@@ -220,6 +274,7 @@ pub trait HirDisplay {
             closure_style: ClosureStyle::ImplFn,
             display_target: DisplayTarget::SourceCode { module_id, allow_opaque },
             show_container_bounds: false,
+            bounds_formatting_ctx: Default::default(),
         }) {
             Ok(()) => {}
             Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
@@ -427,6 +482,7 @@ impl<T: HirDisplay> HirDisplayWrapper<'_, T> {
             display_target: self.display_target,
             closure_style: self.closure_style,
             show_container_bounds: self.show_container_bounds,
+            bounds_formatting_ctx: Default::default(),
         })
     }
 
@@ -479,42 +535,46 @@ impl HirDisplay for ProjectionTy {
         // `<Param as Trait>::Assoc`
         if !f.display_target.is_source_code() {
             if let TyKind::Placeholder(idx) = self_ty.kind(Interner) {
-                let db = f.db;
-                let id = from_placeholder_idx(db, *idx);
-                let generics = generics(db.upcast(), id.parent);
-
-                let substs = generics.placeholder_subst(db);
-                let bounds = db
-                    .generic_predicates(id.parent)
-                    .iter()
-                    .map(|pred| pred.clone().substitute(Interner, &substs))
-                    .filter(|wc| match wc.skip_binders() {
-                        WhereClause::Implemented(tr) => {
-                            match tr.self_type_parameter(Interner).kind(Interner) {
-                                TyKind::Alias(AliasTy::Projection(proj)) => proj == self,
-                                _ => false,
+                if !f.bounds_formatting_ctx.contains(self) {
+                    let db = f.db;
+                    let id = from_placeholder_idx(db, *idx);
+                    let generics = generics(db.upcast(), id.parent);
+
+                    let substs = generics.placeholder_subst(db);
+                    let bounds = db
+                        .generic_predicates(id.parent)
+                        .iter()
+                        .map(|pred| pred.clone().substitute(Interner, &substs))
+                        .filter(|wc| match wc.skip_binders() {
+                            WhereClause::Implemented(tr) => {
+                                matches!(
+                                    tr.self_type_parameter(Interner).kind(Interner),
+                                    TyKind::Alias(_)
+                                )
                             }
-                        }
-                        WhereClause::TypeOutlives(t) => match t.ty.kind(Interner) {
-                            TyKind::Alias(AliasTy::Projection(proj)) => proj == self,
-                            _ => false,
-                        },
-                        // We shouldn't be here if these exist
-                        WhereClause::AliasEq(_) => false,
-                        WhereClause::LifetimeOutlives(_) => false,
-                    })
-                    .collect::<Vec<_>>();
-                if !bounds.is_empty() {
-                    return write_bounds_like_dyn_trait_with_prefix(
-                        f,
-                        "impl",
-                        Either::Left(
-                            &TyKind::Alias(AliasTy::Projection(self.clone())).intern(Interner),
-                        ),
-                        &bounds,
-                        SizedByDefault::NotSized,
-                    );
-                };
+                            WhereClause::TypeOutlives(t) => {
+                                matches!(t.ty.kind(Interner), TyKind::Alias(_))
+                            }
+                            // We shouldn't be here if these exist
+                            WhereClause::AliasEq(_) => false,
+                            WhereClause::LifetimeOutlives(_) => false,
+                        })
+                        .collect::<Vec<_>>();
+                    if !bounds.is_empty() {
+                        return f.format_bounds_with(self.clone(), |f| {
+                            write_bounds_like_dyn_trait_with_prefix(
+                                f,
+                                "impl",
+                                Either::Left(
+                                    &TyKind::Alias(AliasTy::Projection(self.clone()))
+                                        .intern(Interner),
+                                ),
+                                &bounds,
+                                SizedByDefault::NotSized,
+                            )
+                        });
+                    }
+                }
             }
         }
 
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs
index ab5464156f0..01a1a4545c4 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs
@@ -1206,4 +1206,38 @@ fn f5<G: T<Assoc = ()>>(it: G) {
 "#,
         );
     }
+
+    #[test]
+    fn regression_19007() {
+        check_types(
+            r#"
+trait Foo {
+    type Assoc;
+
+    fn foo(&self) -> Self::Assoc;
+}
+
+trait Bar {
+    type Target;
+}
+
+trait Baz<T> {}
+
+struct Struct<T: Foo> {
+    field: T,
+}
+
+impl<T> Struct<T>
+where
+    T: Foo,
+    T::Assoc: Baz<<T::Assoc as Bar>::Target> + Bar,
+{
+    fn f(&self) {
+        let x = self.field.foo();
+          //^ impl Baz<<<T as Foo>::Assoc as Bar>::Target> + Bar
+    }
+}
+"#,
+        );
+    }
 }