Merge pull request #19020 from ShoyuVanilla/issues-19007

fix: Prevent infinite recursion of bounds formatting
This commit is contained in:
Lukas Wirth 2025-01-25 19:03:54 +00:00 committed by GitHub
commit 90bf50c011
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 128 additions and 34 deletions

View File

@ -34,6 +34,7 @@ use rustc_apfloat::{
ieee::{Half as f16, Quad as f128}, ieee::{Half as f16, Quad as f128},
Float, Float,
}; };
use rustc_hash::FxHashSet;
use smallvec::SmallVec; use smallvec::SmallVec;
use span::Edition; use span::Edition;
use stdx::never; use stdx::never;
@ -87,6 +88,35 @@ pub struct HirFormatter<'a> {
omit_verbose_types: bool, omit_verbose_types: bool,
closure_style: ClosureStyle, closure_style: ClosureStyle,
display_target: DisplayTarget, 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<'_> { impl HirFormatter<'_> {
@ -97,6 +127,30 @@ impl HirFormatter<'_> {
fn end_location_link(&mut self) { fn end_location_link(&mut self) {
self.fmt.end_location_link(); 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 { pub trait HirDisplay {
@ -220,6 +274,7 @@ pub trait HirDisplay {
closure_style: ClosureStyle::ImplFn, closure_style: ClosureStyle::ImplFn,
display_target: DisplayTarget::SourceCode { module_id, allow_opaque }, display_target: DisplayTarget::SourceCode { module_id, allow_opaque },
show_container_bounds: false, show_container_bounds: false,
bounds_formatting_ctx: Default::default(),
}) { }) {
Ok(()) => {} Ok(()) => {}
Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"), Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
@ -427,6 +482,7 @@ impl<T: HirDisplay> HirDisplayWrapper<'_, T> {
display_target: self.display_target, display_target: self.display_target,
closure_style: self.closure_style, closure_style: self.closure_style,
show_container_bounds: self.show_container_bounds, show_container_bounds: self.show_container_bounds,
bounds_formatting_ctx: Default::default(),
}) })
} }
@ -479,42 +535,46 @@ impl HirDisplay for ProjectionTy {
// `<Param as Trait>::Assoc` // `<Param as Trait>::Assoc`
if !f.display_target.is_source_code() { if !f.display_target.is_source_code() {
if let TyKind::Placeholder(idx) = self_ty.kind(Interner) { if let TyKind::Placeholder(idx) = self_ty.kind(Interner) {
let db = f.db; if !f.bounds_formatting_ctx.contains(self) {
let id = from_placeholder_idx(db, *idx); let db = f.db;
let generics = generics(db.upcast(), id.parent); let id = from_placeholder_idx(db, *idx);
let generics = generics(db.upcast(), id.parent);
let substs = generics.placeholder_subst(db); let substs = generics.placeholder_subst(db);
let bounds = db let bounds = db
.generic_predicates(id.parent) .generic_predicates(id.parent)
.iter() .iter()
.map(|pred| pred.clone().substitute(Interner, &substs)) .map(|pred| pred.clone().substitute(Interner, &substs))
.filter(|wc| match wc.skip_binders() { .filter(|wc| match wc.skip_binders() {
WhereClause::Implemented(tr) => { WhereClause::Implemented(tr) => {
match tr.self_type_parameter(Interner).kind(Interner) { matches!(
TyKind::Alias(AliasTy::Projection(proj)) => proj == self, tr.self_type_parameter(Interner).kind(Interner),
_ => false, TyKind::Alias(_)
)
} }
} WhereClause::TypeOutlives(t) => {
WhereClause::TypeOutlives(t) => match t.ty.kind(Interner) { matches!(t.ty.kind(Interner), TyKind::Alias(_))
TyKind::Alias(AliasTy::Projection(proj)) => proj == self, }
_ => false, // We shouldn't be here if these exist
}, WhereClause::AliasEq(_) => false,
// We shouldn't be here if these exist WhereClause::LifetimeOutlives(_) => false,
WhereClause::AliasEq(_) => false, })
WhereClause::LifetimeOutlives(_) => false, .collect::<Vec<_>>();
}) if !bounds.is_empty() {
.collect::<Vec<_>>(); return f.format_bounds_with(self.clone(), |f| {
if !bounds.is_empty() { write_bounds_like_dyn_trait_with_prefix(
return write_bounds_like_dyn_trait_with_prefix( f,
f, "impl",
"impl", Either::Left(
Either::Left( &TyKind::Alias(AliasTy::Projection(self.clone()))
&TyKind::Alias(AliasTy::Projection(self.clone())).intern(Interner), .intern(Interner),
), ),
&bounds, &bounds,
SizedByDefault::NotSized, SizedByDefault::NotSized,
); )
}; });
}
}
} }
} }

View File

@ -1203,6 +1203,40 @@ fn f5<G: T<Assoc = ()>>(it: G) {
let l = it.f(); 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
}
}
"#, "#,
); );
} }