mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 03:54:42 +00:00 
			
		
		
		
	fix: Prevent infinite recursion of bounds formatting
This commit is contained in:
		
							parent
							
								
									649e65ce1e
								
							
						
					
					
						commit
						3fe7415c83
					
				
					 3 changed files with 128 additions and 71 deletions
				
			
		|  | @ -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); | ||||
|                 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) => { | ||||
|                             match tr.self_type_parameter(Interner).kind(Interner) { | ||||
|                                 TyKind::Alias(AliasTy::Projection(proj)) => proj == self, | ||||
|                                 _ => false, | ||||
|                     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, | ||||
|                             ) | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -10349,40 +10349,3 @@ macro_rules! str { | |||
|         "#]],
 | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn regression_19007() { | ||||
|     check( | ||||
|         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$0 = self.field.foo(); | ||||
|     } | ||||
| } | ||||
|         "#,
 | ||||
|         expect![ | ||||
|             r#" | ||||
|         "#
 | ||||
|         ], | ||||
|     ); | ||||
| } | ||||
|  |  | |||
|  | @ -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
 | ||||
|     } | ||||
| } | ||||
| "#,
 | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Shoyu Vanilla
						Shoyu Vanilla