Skip to content

Commit adac3d7

Browse files
committed
fix: Prevent infinite recursion of bounds formatting
1 parent 649e65c commit adac3d7

File tree

2 files changed

+100
-35
lines changed

2 files changed

+100
-35
lines changed

crates/hir-ty/src/display.rs

Lines changed: 94 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use rustc_apfloat::{
3434
ieee::{Half as f16, Quad as f128},
3535
Float,
3636
};
37+
use rustc_hash::FxHashSet;
3738
use smallvec::SmallVec;
3839
use span::Edition;
3940
use stdx::never;
@@ -87,6 +88,35 @@ pub struct HirFormatter<'a> {
8788
omit_verbose_types: bool,
8889
closure_style: ClosureStyle,
8990
display_target: DisplayTarget,
91+
bounds_formatting_ctx: BoundsFormattingCtx,
92+
}
93+
94+
#[derive(Default)]
95+
enum BoundsFormattingCtx {
96+
Entered {
97+
/// We can have recursive bounds like the following case:
98+
/// ```rust
99+
/// where
100+
/// T: Foo,
101+
/// T::FooAssoc: Baz<<T::FooAssoc as Bar>::BarAssoc> + Bar
102+
/// ```
103+
/// So, record the projection types met while formatting bounds and
104+
//. prevent recursing into their bounds to avoid infinite loops.
105+
projection_tys_met: FxHashSet<ProjectionTy>,
106+
},
107+
#[default]
108+
Exited,
109+
}
110+
111+
impl BoundsFormattingCtx {
112+
fn insert(&mut self, proj: ProjectionTy) -> bool {
113+
match self {
114+
BoundsFormattingCtx::Entered { projection_tys_met } => {
115+
projection_tys_met.insert(proj.clone())
116+
}
117+
BoundsFormattingCtx::Exited => true,
118+
}
119+
}
90120
}
91121

92122
impl HirFormatter<'_> {
@@ -97,6 +127,22 @@ impl HirFormatter<'_> {
97127
fn end_location_link(&mut self) {
98128
self.fmt.end_location_link();
99129
}
130+
131+
fn format_bounds_with<T, F: FnOnce(&mut Self) -> T>(&mut self, format_bounds: F) -> T {
132+
match self.bounds_formatting_ctx {
133+
BoundsFormattingCtx::Entered { .. } => format_bounds(self),
134+
BoundsFormattingCtx::Exited => {
135+
self.bounds_formatting_ctx =
136+
BoundsFormattingCtx::Entered { projection_tys_met: FxHashSet::default() };
137+
let res = format_bounds(self);
138+
// Since we want to prevent only the infinite recursions in bounds formatting
139+
// and do not want to skip formatting of other separate bounds, clear context
140+
// when exiting the formatting of outermost bounds
141+
self.bounds_formatting_ctx = BoundsFormattingCtx::Exited;
142+
res
143+
}
144+
}
145+
}
100146
}
101147

102148
pub trait HirDisplay {
@@ -220,6 +266,7 @@ pub trait HirDisplay {
220266
closure_style: ClosureStyle::ImplFn,
221267
display_target: DisplayTarget::SourceCode { module_id, allow_opaque },
222268
show_container_bounds: false,
269+
bounds_formatting_ctx: Default::default(),
223270
}) {
224271
Ok(()) => {}
225272
Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
@@ -427,6 +474,7 @@ impl<T: HirDisplay> HirDisplayWrapper<'_, T> {
427474
display_target: self.display_target,
428475
closure_style: self.closure_style,
429476
show_container_bounds: self.show_container_bounds,
477+
bounds_formatting_ctx: Default::default(),
430478
})
431479
}
432480

@@ -479,42 +527,54 @@ impl HirDisplay for ProjectionTy {
479527
// `<Param as Trait>::Assoc`
480528
if !f.display_target.is_source_code() {
481529
if let TyKind::Placeholder(idx) = self_ty.kind(Interner) {
482-
let db = f.db;
483-
let id = from_placeholder_idx(db, *idx);
484-
let generics = generics(db.upcast(), id.parent);
530+
let res = f.format_bounds_with(|f| {
531+
// Avoid infinite recursion
532+
if !f.bounds_formatting_ctx.insert(self.clone()) {
533+
return None;
534+
}
485535

486-
let substs = generics.placeholder_subst(db);
487-
let bounds = db
488-
.generic_predicates(id.parent)
489-
.iter()
490-
.map(|pred| pred.clone().substitute(Interner, &substs))
491-
.filter(|wc| match wc.skip_binders() {
492-
WhereClause::Implemented(tr) => {
493-
match tr.self_type_parameter(Interner).kind(Interner) {
494-
TyKind::Alias(AliasTy::Projection(proj)) => proj == self,
495-
_ => false,
536+
let db = f.db;
537+
let id = from_placeholder_idx(db, *idx);
538+
let generics = generics(db.upcast(), id.parent);
539+
540+
let substs = generics.placeholder_subst(db);
541+
let bounds = db
542+
.generic_predicates(id.parent)
543+
.iter()
544+
.map(|pred| pred.clone().substitute(Interner, &substs))
545+
.filter(|wc| match wc.skip_binders() {
546+
WhereClause::Implemented(tr) => {
547+
matches!(
548+
tr.self_type_parameter(Interner).kind(Interner),
549+
TyKind::Alias(_)
550+
)
496551
}
497-
}
498-
WhereClause::TypeOutlives(t) => match t.ty.kind(Interner) {
499-
TyKind::Alias(AliasTy::Projection(proj)) => proj == self,
500-
_ => false,
501-
},
502-
// We shouldn't be here if these exist
503-
WhereClause::AliasEq(_) => false,
504-
WhereClause::LifetimeOutlives(_) => false,
505-
})
506-
.collect::<Vec<_>>();
507-
if !bounds.is_empty() {
508-
return write_bounds_like_dyn_trait_with_prefix(
509-
f,
510-
"impl",
511-
Either::Left(
512-
&TyKind::Alias(AliasTy::Projection(self.clone())).intern(Interner),
513-
),
514-
&bounds,
515-
SizedByDefault::NotSized,
516-
);
517-
};
552+
WhereClause::TypeOutlives(t) => {
553+
matches!(t.ty.kind(Interner), TyKind::Alias(_))
554+
}
555+
// We shouldn't be here if these exist
556+
WhereClause::AliasEq(_) => false,
557+
WhereClause::LifetimeOutlives(_) => false,
558+
})
559+
.collect::<Vec<_>>();
560+
if !bounds.is_empty() {
561+
Some(write_bounds_like_dyn_trait_with_prefix(
562+
f,
563+
"impl",
564+
Either::Left(
565+
&TyKind::Alias(AliasTy::Projection(self.clone())).intern(Interner),
566+
),
567+
&bounds,
568+
SizedByDefault::NotSized,
569+
))
570+
} else {
571+
None
572+
}
573+
});
574+
575+
if let Some(res) = res {
576+
return res;
577+
}
518578
}
519579
}
520580

crates/ide/src/hover/tests.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10382,7 +10382,12 @@ where
1038210382
"#,
1038310383
expect![
1038410384
r#"
10385-
"#
10385+
*x*
10386+
10387+
```rust
10388+
let x: impl Baz<<<T as Foo>::Assoc as Bar>::Target> + Bar
10389+
```
10390+
"#
1038610391
],
1038710392
);
1038810393
}

0 commit comments

Comments
 (0)