Skip to content

fix: Prevent infinite recursion of bounds formatting #19020

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 95 additions & 35 deletions crates/hir-ty/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<'_> {
Expand All @@ -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 {
Expand Down Expand Up @@ -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!"),
Expand Down Expand Up @@ -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(),
})
}

Expand Down Expand Up @@ -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,
)
});
}
}
}
}

Expand Down
34 changes: 34 additions & 0 deletions crates/ide/src/inlay_hints/bind_pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,40 @@ fn f5<G: T<Assoc = ()>>(it: G) {
let l = it.f();
//^ ()
}
"#,
);
}

#[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
}
}
"#,
);
}
Expand Down
Loading