mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 12:04:43 +00:00 
			
		
		
		
	Add the ability to jump from into to from definitions
				
					
				
			This commit is contained in:
		
							parent
							
								
									8364ef2997
								
							
						
					
					
						commit
						ea87eab4ff
					
				
					 2 changed files with 93 additions and 1 deletions
				
			
		|  | @ -50,6 +50,14 @@ impl FamousDefs<'_, '_> { | ||||||
|         self.find_trait("core:convert:From") |         self.find_trait("core:convert:From") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn core_convert_TryFrom(&self) -> Option<Trait> { | ||||||
|  |         self.find_trait("core:convert:TryFrom") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn core_str_FromStr(&self) -> Option<Trait> { | ||||||
|  |         self.find_trait("core:str:FromStr") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn core_convert_Into(&self) -> Option<Trait> { |     pub fn core_convert_Into(&self) -> Option<Trait> { | ||||||
|         self.find_trait("core:convert:Into") |         self.find_trait("core:convert:Into") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -5,10 +5,11 @@ use crate::{ | ||||||
|     navigation_target::{self, ToNav}, |     navigation_target::{self, ToNav}, | ||||||
|     FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, |     FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, | ||||||
| }; | }; | ||||||
| use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics}; | use hir::{AsAssocItem, AssocItem, FileRange, Impl, InFile, MacroFileIdExt, ModuleDef, Semantics}; | ||||||
| use ide_db::{ | use ide_db::{ | ||||||
|     base_db::{AnchoredPath, FileLoader, SourceDatabase}, |     base_db::{AnchoredPath, FileLoader, SourceDatabase}, | ||||||
|     defs::{Definition, IdentClass}, |     defs::{Definition, IdentClass}, | ||||||
|  |     famous_defs::FamousDefs, | ||||||
|     helpers::pick_best_token, |     helpers::pick_best_token, | ||||||
|     RootDatabase, SymbolKind, |     RootDatabase, SymbolKind, | ||||||
| }; | }; | ||||||
|  | @ -81,6 +82,10 @@ pub(crate) fn goto_definition( | ||||||
|         return Some(RangeInfo::new(original_token.text_range(), navs)); |         return Some(RangeInfo::new(original_token.text_range(), navs)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if let Some(navs) = find_from_definition(file_id, &original_token, sema) { | ||||||
|  |         return Some(RangeInfo::new(original_token.text_range(), navs)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     let navs = sema |     let navs = sema | ||||||
|         .descend_into_macros_no_opaque(original_token.clone()) |         .descend_into_macros_no_opaque(original_token.clone()) | ||||||
|         .into_iter() |         .into_iter() | ||||||
|  | @ -125,6 +130,62 @@ pub(crate) fn goto_definition( | ||||||
|     Some(RangeInfo::new(original_token.text_range(), navs)) |     Some(RangeInfo::new(original_token.text_range(), navs)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // If the token is into(), try_into(), parse(), search the definition of From, TryFrom, FromStr.
 | ||||||
|  | fn find_from_definition( | ||||||
|  |     file_id: FileId, | ||||||
|  |     original_token: &SyntaxToken, | ||||||
|  |     sema: &Semantics<'_, RootDatabase>, | ||||||
|  | ) -> Option<Vec<NavigationTarget>> { | ||||||
|  |     let db = sema.db; | ||||||
|  |     let krate = sema.file_to_module_def(file_id)?.krate(); | ||||||
|  | 
 | ||||||
|  |     // e.g. if the method call is let b = a.into(),
 | ||||||
|  |     // - receiver_type is A (type of a)
 | ||||||
|  |     // - return_type is B (type of b)
 | ||||||
|  |     // We will find the definition of B::from(a: A).
 | ||||||
|  |     let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?; | ||||||
|  |     let receiver_type = sema.type_of_expr(&method_call.receiver()?)?.original(); | ||||||
|  |     let return_type = sema.type_of_expr(&method_call.clone().into())?.original(); | ||||||
|  | 
 | ||||||
|  |     let (search_method, search_trait, return_type) = match method_call.name_ref()?.text().as_str() { | ||||||
|  |         "into" => ("from", FamousDefs(sema, krate).core_convert_From()?, return_type), | ||||||
|  |         // If the mthod is try_into() or parse(), return_type is Result<T, Error>.
 | ||||||
|  |         // Get T from type arguments of Result<T, Error>.
 | ||||||
|  |         "try_into" => ( | ||||||
|  |             "try_from", | ||||||
|  |             FamousDefs(sema, krate).core_convert_TryFrom()?, | ||||||
|  |             return_type.type_arguments().next()?, | ||||||
|  |         ), | ||||||
|  |         "parse" => ( | ||||||
|  |             "from_str", | ||||||
|  |             FamousDefs(sema, krate).core_str_FromStr()?, | ||||||
|  |             return_type.type_arguments().next()?, | ||||||
|  |         ), | ||||||
|  |         _ => return None, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let from_impls = Impl::all_for_type(db, return_type) | ||||||
|  |         .into_iter() | ||||||
|  |         .filter(|impl_| impl_.trait_(db).is_some_and(|trait_| trait_ == search_trait)); | ||||||
|  |     let from_methods = from_impls.flat_map(|impl_| impl_.items(db)).filter_map(|item| match item { | ||||||
|  |         AssocItem::Function(function) if function.name(db).as_str() == search_method => { | ||||||
|  |             Some(function) | ||||||
|  |         } | ||||||
|  |         _ => None, | ||||||
|  |     }); | ||||||
|  |     let target_method = from_methods.into_iter().find(|method| { | ||||||
|  |         let args = method.assoc_fn_params(db); | ||||||
|  | 
 | ||||||
|  |         // FIXME: This condition does not work for complicated cases such as
 | ||||||
|  |         // receiver_type: Vec<i64>
 | ||||||
|  |         // arg.ty(): T: IntoIterator<Item = i64>
 | ||||||
|  |         args.get(0).is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty())) | ||||||
|  |     })?; | ||||||
|  | 
 | ||||||
|  |     let def = Definition::from(target_method); | ||||||
|  |     Some(def_to_nav(db, def)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| fn try_lookup_include_path( | fn try_lookup_include_path( | ||||||
|     sema: &Semantics<'_, RootDatabase>, |     sema: &Semantics<'_, RootDatabase>, | ||||||
|     token: ast::String, |     token: ast::String, | ||||||
|  | @ -3018,6 +3079,29 @@ fn foo() { | ||||||
|  // ^^^^
 |  // ^^^^
 | ||||||
|         m!(continue 'bar$0); |         m!(continue 'bar$0); | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  | "#,
 | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |     #[test] | ||||||
|  |     fn into_call_to_from_definition() { | ||||||
|  |         check( | ||||||
|  |             r#" | ||||||
|  | //- minicore: from
 | ||||||
|  | struct A; | ||||||
|  | 
 | ||||||
|  | struct B; | ||||||
|  | 
 | ||||||
|  | impl From<A> for B { | ||||||
|  |     fn from(value: A) -> Self { | ||||||
|  |      //^^^^  
 | ||||||
|  |         B | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn f() { | ||||||
|  |     let a = A; | ||||||
|  |     let b: B = a.into$0(); | ||||||
| } | } | ||||||
|         "#,
 |         "#,
 | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 1hakusai1
						1hakusai1