mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 11:20:54 +00:00
Merge pull request #19020 from ShoyuVanilla/issues-19007
fix: Prevent infinite recursion of bounds formatting
This commit is contained in:
commit
90bf50c011
@ -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,
|
||||||
);
|
)
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user