mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 12:04:43 +00:00 
			
		
		
		
	Use Semantics::resolve_method_call_as_callable to find implementation
This commit is contained in:
		
							parent
							
								
									e8a63e43b1
								
							
						
					
					
						commit
						5384dd8947
					
				
					 6 changed files with 89 additions and 59 deletions
				
			
		|  | @ -398,6 +398,9 @@ macro_rules! __known_path { | |||
|     (core::fmt::Debug) => {}; | ||||
|     (std::fmt::format) => {}; | ||||
|     (core::ops::Try) => {}; | ||||
|     (core::convert::From) => {}; | ||||
|     (core::convert::TryFrom) => {}; | ||||
|     (core::str::FromStr) => {}; | ||||
|     ($path:path) => { | ||||
|         compile_error!("Please register your known path in the path module") | ||||
|     }; | ||||
|  |  | |||
|  | @ -1446,6 +1446,10 @@ impl<'db> SemanticsImpl<'db> { | |||
|         self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call) | ||||
|     } | ||||
| 
 | ||||
|     pub fn resolve_known_blanket_dual_impls(&self, call: &ast::MethodCallExpr) -> Option<Function> { | ||||
|         self.analyze(call.syntax())?.resolve_known_blanket_dual_impls(self.db, call) | ||||
|     } | ||||
| 
 | ||||
|     fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option<StructId> { | ||||
|         self.analyze(range_pat.syntax())?.resolve_range_pat(self.db, range_pat) | ||||
|     } | ||||
|  |  | |||
|  | @ -322,6 +322,68 @@ impl SourceAnalyzer { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // If the method is into(), try_into(), parse(), resolve it to from, try_from, from_str.
 | ||||
|     pub(crate) fn resolve_known_blanket_dual_impls( | ||||
|         &self, | ||||
|         db: &dyn HirDatabase, | ||||
|         call: &ast::MethodCallExpr, | ||||
|     ) -> Option<Function> { | ||||
|         // 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 callable = self.resolve_method_call_as_callable(db, call)?; | ||||
|         let (_, receiver_type) = callable.receiver_param(db)?; | ||||
|         let return_type = callable.return_type(); | ||||
|         let (search_method, substs) = match call.name_ref()?.text().as_str() { | ||||
|             "into" => { | ||||
|                 let trait_ = | ||||
|                     self.resolver.resolve_known_trait(db.upcast(), &path![core::convert::From])?; | ||||
|                 ( | ||||
|                     self.trait_fn(db, trait_, "from")?, | ||||
|                     hir_ty::TyBuilder::subst_for_def(db, trait_, None) | ||||
|                         .push(return_type.ty) | ||||
|                         .push(receiver_type.ty) | ||||
|                         .build(), | ||||
|                 ) | ||||
|             } | ||||
|             "try_into" => { | ||||
|                 let trait_ = self | ||||
|                     .resolver | ||||
|                     .resolve_known_trait(db.upcast(), &path![core::convert::TryFrom])?; | ||||
|                 ( | ||||
|                     self.trait_fn(db, trait_, "try_from")?, | ||||
|                     hir_ty::TyBuilder::subst_for_def(db, trait_, None) | ||||
|                         // If the method is try_into() or parse(), return_type is Result<T, Error>.
 | ||||
|                         // Get T from type arguments of Result<T, Error>.
 | ||||
|                         .push(return_type.type_arguments().next()?.ty) | ||||
|                         .push(receiver_type.ty) | ||||
|                         .build(), | ||||
|                 ) | ||||
|             } | ||||
|             "parse" => { | ||||
|                 let trait_ = | ||||
|                     self.resolver.resolve_known_trait(db.upcast(), &path![core::str::FromStr])?; | ||||
|                 ( | ||||
|                     self.trait_fn(db, trait_, "from_str")?, | ||||
|                     hir_ty::TyBuilder::subst_for_def(db, trait_, None) | ||||
|                         .push(return_type.type_arguments().next()?.ty) | ||||
|                         .build(), | ||||
|                 ) | ||||
|             } | ||||
|             _ => return None, | ||||
|         }; | ||||
| 
 | ||||
|         let found_method = self.resolve_impl_method_or_trait_def(db, search_method, substs); | ||||
|         // If found_method == search_method, the method in trait itself is resolved.
 | ||||
|         // It means the blanket dual impl is not found.
 | ||||
|         if found_method == search_method { | ||||
|             None | ||||
|         } else { | ||||
|             Some(found_method.into()) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn resolve_expr_as_callable( | ||||
|         &self, | ||||
|         db: &dyn HirDatabase, | ||||
|  | @ -1247,6 +1309,18 @@ impl SourceAnalyzer { | |||
|         Some((trait_id, fn_id)) | ||||
|     } | ||||
| 
 | ||||
|     fn trait_fn( | ||||
|         &self, | ||||
|         db: &dyn HirDatabase, | ||||
|         trait_id: TraitId, | ||||
|         method_name: &str, | ||||
|     ) -> Option<FunctionId> { | ||||
|         db.trait_data(trait_id).items.iter().find_map(|(item_name, item)| match item { | ||||
|             AssocItemId::FunctionId(t) if item_name.as_str() == method_name => Some(*t), | ||||
|             _ => None, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     fn ty_of_expr(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<&Ty> { | ||||
|         self.infer.as_ref()?.type_of_expr_or_pat(self.expr_id(db, expr)?) | ||||
|     } | ||||
|  |  | |||
|  | @ -50,14 +50,6 @@ impl FamousDefs<'_, '_> { | |||
|         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> { | ||||
|         self.find_trait("core:convert:Into") | ||||
|     } | ||||
|  |  | |||
|  | @ -5,11 +5,10 @@ use crate::{ | |||
|     navigation_target::{self, ToNav}, | ||||
|     FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, | ||||
| }; | ||||
| use hir::{AsAssocItem, AssocItem, FileRange, Impl, InFile, MacroFileIdExt, ModuleDef, Semantics}; | ||||
| use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics}; | ||||
| use ide_db::{ | ||||
|     base_db::{AnchoredPath, FileLoader, SourceDatabase}, | ||||
|     defs::{Definition, IdentClass}, | ||||
|     famous_defs::FamousDefs, | ||||
|     helpers::pick_best_token, | ||||
|     RootDatabase, SymbolKind, | ||||
| }; | ||||
|  | @ -82,8 +81,7 @@ pub(crate) fn goto_definition( | |||
|         return Some(RangeInfo::new(original_token.text_range(), navs)); | ||||
|     } | ||||
| 
 | ||||
|     if let Some(navs) = find_definition_for_known_blanket_dual_impls(sema, file_id, &original_token) | ||||
|     { | ||||
|     if let Some(navs) = find_definition_for_known_blanket_dual_impls(sema, &original_token) { | ||||
|         return Some(RangeInfo::new(original_token.text_range(), navs)); | ||||
|     } | ||||
| 
 | ||||
|  | @ -134,58 +132,13 @@ pub(crate) fn goto_definition( | |||
| // If the token is into(), try_into(), parse(), search the definition of From, TryFrom, FromStr.
 | ||||
| fn find_definition_for_known_blanket_dual_impls( | ||||
|     sema: &Semantics<'_, RootDatabase>, | ||||
|     file_id: FileId, | ||||
|     original_token: &SyntaxToken, | ||||
| ) -> 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 callable = sema.resolve_method_call_as_callable(&method_call)?; | ||||
|     let (_, receiver_type) = callable.receiver_param(db)?; | ||||
|     let return_type = callable.return_type(); | ||||
| 
 | ||||
|     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 method 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.first().is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty())) | ||||
|     })?; | ||||
|     let target_method = sema.resolve_known_blanket_dual_impls(&method_call)?; | ||||
| 
 | ||||
|     let def = Definition::from(target_method); | ||||
|     Some(def_to_nav(db, def)) | ||||
|     Some(def_to_nav(sema.db, def)) | ||||
| } | ||||
| 
 | ||||
| fn try_lookup_include_path( | ||||
|  |  | |||
|  | @ -174,6 +174,7 @@ define_symbols! { | |||
|     const_param_ty, | ||||
|     Context, | ||||
|     Continue, | ||||
|     convert, | ||||
|     copy, | ||||
|     Copy, | ||||
|     core_panic, | ||||
|  | @ -239,6 +240,8 @@ define_symbols! { | |||
|     format_unsafe_arg, | ||||
|     format, | ||||
|     freeze, | ||||
|     From, | ||||
|     FromStr, | ||||
|     from_output, | ||||
|     from_residual, | ||||
|     from_usize, | ||||
|  | @ -457,6 +460,7 @@ define_symbols! { | |||
|     transmute_trait, | ||||
|     transparent, | ||||
|     Try, | ||||
|     TryFrom, | ||||
|     tuple_trait, | ||||
|     u128, | ||||
|     u16, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 1hakusai1
						1hakusai1