From ea87eab4ff355eb1dcb10c4f9dfa68b249eaeb56 Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Tue, 14 Jan 2025 16:44:55 +0900 Subject: [PATCH 01/12] Add the ability to jump from `into` to `from` definitions --- crates/ide-db/src/famous_defs.rs | 8 +++ crates/ide/src/goto_definition.rs | 86 ++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs index 9e3506d6f5..6f95b698f8 100644 --- a/crates/ide-db/src/famous_defs.rs +++ b/crates/ide-db/src/famous_defs.rs @@ -50,6 +50,14 @@ impl FamousDefs<'_, '_> { self.find_trait("core:convert:From") } + pub fn core_convert_TryFrom(&self) -> Option { + self.find_trait("core:convert:TryFrom") + } + + pub fn core_str_FromStr(&self) -> Option { + self.find_trait("core:str:FromStr") + } + pub fn core_convert_Into(&self) -> Option { self.find_trait("core:convert:Into") } diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 6c739de82d..c29b5acfff 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -5,10 +5,11 @@ use crate::{ navigation_target::{self, ToNav}, 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::{ base_db::{AnchoredPath, FileLoader, SourceDatabase}, defs::{Definition, IdentClass}, + famous_defs::FamousDefs, helpers::pick_best_token, RootDatabase, SymbolKind, }; @@ -81,6 +82,10 @@ pub(crate) fn goto_definition( 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 .descend_into_macros_no_opaque(original_token.clone()) .into_iter() @@ -125,6 +130,62 @@ pub(crate) fn goto_definition( 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> { + 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. + // Get T from type arguments of Result. + "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 + // arg.ty(): T: IntoIterator + 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( sema: &Semantics<'_, RootDatabase>, token: ast::String, @@ -3022,4 +3083,27 @@ fn foo() { "#, ); } + #[test] + fn into_call_to_from_definition() { + check( + r#" +//- minicore: from +struct A; + +struct B; + +impl From for B { + fn from(value: A) -> Self { + //^^^^ + B + } +} + +fn f() { + let a = A; + let b: B = a.into$0(); +} + "#, + ); + } } From c14cdcf5cc1cb187060451f3c18daf92ad79ca4d Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Tue, 14 Jan 2025 17:43:28 +0900 Subject: [PATCH 02/12] fix_typo --- crates/ide/src/goto_definition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index c29b5acfff..a480741f5f 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -149,7 +149,7 @@ fn find_from_definition( 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. + // If the method is try_into() or parse(), return_type is Result. // Get T from type arguments of Result. "try_into" => ( "try_from", From a0be4b189ce9e18a3f6ef04b9d40632a2fac721a Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Tue, 14 Jan 2025 17:54:15 +0900 Subject: [PATCH 03/12] Remove trailing spaces --- crates/ide/src/goto_definition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index a480741f5f..33274f8bfe 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -3094,7 +3094,7 @@ struct B; impl From for B { fn from(value: A) -> Self { - //^^^^ + //^^^^ B } } From 96ed889cdf7d77412fc1ecc67868e8250c084dde Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Tue, 14 Jan 2025 19:00:35 +0900 Subject: [PATCH 04/12] refactor: get(0) -> first() --- crates/ide/src/goto_definition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 33274f8bfe..bb21bcca83 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -179,7 +179,7 @@ fn find_from_definition( // FIXME: This condition does not work for complicated cases such as // receiver_type: Vec // arg.ty(): T: IntoIterator - args.get(0).is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty())) + args.first().is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty())) })?; let def = Definition::from(target_method); From dc075fd6923577833212f8d806977d9cb24eaa60 Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Wed, 15 Jan 2025 20:43:28 +0900 Subject: [PATCH 05/12] Add test cases --- crates/ide/src/goto_definition.rs | 99 +++++++++++++++++++++++++++++++ crates/test-utils/src/minicore.rs | 33 ++++++++++- 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index bb21bcca83..6c9a1ffe06 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -3102,6 +3102,105 @@ impl From for B { fn f() { let a = A; let b: B = a.into$0(); +} + "#, + ); + } + + #[test] + fn goto_into_definition_if_exists() { + check( + r#" +//- minicore: from +struct A; + +struct B; + +impl Into for A { + fn into(self) -> B { + //^^^^ + B + } +} + +fn f() { + let a = A; + let b: B = a.into$0(); +} + "#, + ); + } + + #[test] + fn try_into_call_to_try_from_definition() { + check( + r#" +//- minicore: from +struct A; + +struct B; + +impl TryFrom for B { + type Error = String; + + fn try_from(value: A) -> Result { + //^^^^^^^^ + Ok(B) + } +} + +fn f() { + let a = A; + let b: Result = a.try_into$0(); +} + "#, + ); + } + + #[test] + fn goto_try_into_definition_if_exists() { + check( + r#" +//- minicore: from +struct A; + +struct B; + +impl TryInto for A { + type Error = String; + + fn try_into(self) -> Result { + //^^^^^^^^ + Ok(B) + } +} + +fn f() { + let a = A; + let b: Result = a.try_into$0(); +} + "#, + ); + } + + #[test] + fn parse_call_to_from_str_definition() { + check( + r#" +//- minicore: from, str +struct A; + +impl FromStr for A { + type Error = String; + + fn from_str(value: &str) -> Result { + //^^^^^^^^ + Ok(A) + } +} + +fn f() { + let a: Result = "aaaaaa".parse$0(); } "#, ); diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 4a2346193b..003e71c3d2 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -32,7 +32,7 @@ //! error: fmt //! fmt: option, result, transmute, coerce_unsized, copy, clone, derive //! fn: tuple -//! from: sized +//! from: sized, result //! future: pin //! coroutine: pin //! dispatch_from_dyn: unsize, pin @@ -332,6 +332,25 @@ pub mod convert { t } } + + pub trait TryFrom: Sized { + type Error; + fn try_from(value: T) -> Result; + } + pub trait TryInto: Sized { + type Error; + fn try_into(self) -> Result; + } + + impl TryInto for T + where + U: TryFrom, + { + type Error = U::Error; + fn try_into(self) -> Result { + U::try_from(self) + } + } // endregion:from // region:as_ref @@ -1532,6 +1551,15 @@ pub mod str { pub const unsafe fn from_utf8_unchecked(v: &[u8]) -> &str { "" } + pub trait FromStr: Sized { + type Err; + fn from_str(s: &str) -> Result; + } + impl str { + pub fn parse(&self) -> Result { + FromStr::from_str(self) + } + } } // endregion:str @@ -1791,7 +1819,7 @@ pub mod prelude { cmp::{Eq, PartialEq}, // :eq cmp::{Ord, PartialOrd}, // :ord convert::AsRef, // :as_ref - convert::{From, Into}, // :from + convert::{From, Into, TryFrom, TryInto}, // :from default::Default, // :default iter::{IntoIterator, Iterator}, // :iterator macros::builtin::{derive, derive_const}, // :derive @@ -1806,6 +1834,7 @@ pub mod prelude { option::Option::{self, None, Some}, // :option panic, // :panic result::Result::{self, Err, Ok}, // :result + str::FromStr, // :from }; } From 65342f4a21e09eaa8bfb8bba5c0999bf12f9890b Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Wed, 15 Jan 2025 21:04:19 +0900 Subject: [PATCH 06/12] Use adjusted type --- crates/ide/src/goto_definition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 6c9a1ffe06..d023e6cbc0 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -144,7 +144,7 @@ fn find_from_definition( // - 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 receiver_type = sema.type_of_expr(&method_call.receiver()?)?.adjusted(); 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() { From b6d63615d2487387e29cc760f5e5370aaf74293c Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Wed, 15 Jan 2025 21:28:37 +0900 Subject: [PATCH 07/12] Fix wrong fixture --- crates/test-utils/src/minicore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 003e71c3d2..0ffc44d6b1 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -1834,7 +1834,7 @@ pub mod prelude { option::Option::{self, None, Some}, // :option panic, // :panic result::Result::{self, Err, Ok}, // :result - str::FromStr, // :from + str::FromStr, // :str }; } From c6317a3e67ec1a86ad62ce88570406879437c4bd Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Thu, 16 Jan 2025 09:33:52 +0900 Subject: [PATCH 08/12] refactor: rename to descriptive name --- crates/ide/src/goto_definition.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index d023e6cbc0..bb1709f9ca 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -82,7 +82,8 @@ pub(crate) fn goto_definition( return Some(RangeInfo::new(original_token.text_range(), navs)); } - if let Some(navs) = find_from_definition(file_id, &original_token, sema) { + if let Some(navs) = find_definition_for_known_blanket_dual_impls(file_id, &original_token, sema) + { return Some(RangeInfo::new(original_token.text_range(), navs)); } @@ -131,7 +132,7 @@ pub(crate) fn goto_definition( } // If the token is into(), try_into(), parse(), search the definition of From, TryFrom, FromStr. -fn find_from_definition( +fn find_definition_for_known_blanket_dual_impls( file_id: FileId, original_token: &SyntaxToken, sema: &Semantics<'_, RootDatabase>, From afde04b4439c68ddd0347ac42bfacce8643ac76b Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Thu, 16 Jan 2025 09:35:07 +0900 Subject: [PATCH 09/12] refactor: Change order of arguments --- crates/ide/src/goto_definition.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index bb1709f9ca..88b8a9f796 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -82,7 +82,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(file_id, &original_token, sema) + if let Some(navs) = find_definition_for_known_blanket_dual_impls(sema, file_id, &original_token) { return Some(RangeInfo::new(original_token.text_range(), navs)); } @@ -133,9 +133,9 @@ 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, - sema: &Semantics<'_, RootDatabase>, ) -> Option> { let db = sema.db; let krate = sema.file_to_module_def(file_id)?.krate(); From e8a63e43b17da7a3b0a60b354a8eb7bb0377a709 Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Thu, 16 Jan 2025 10:00:30 +0900 Subject: [PATCH 10/12] Use resolve_method_call_as_callable to handle function types --- crates/ide/src/goto_definition.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 88b8a9f796..112faa7a65 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -145,8 +145,9 @@ fn find_definition_for_known_blanket_dual_impls( // - 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()?)?.adjusted(); - let return_type = sema.type_of_expr(&method_call.clone().into())?.original(); + 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), From 5384dd8947877d16daff3ab00826a98e60f88f1a Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Mon, 20 Jan 2025 21:17:48 +0900 Subject: [PATCH 11/12] Use Semantics::resolve_method_call_as_callable to find implementation --- crates/hir-expand/src/mod_path.rs | 3 ++ crates/hir/src/semantics.rs | 4 ++ crates/hir/src/source_analyzer.rs | 74 +++++++++++++++++++++++++++++ crates/ide-db/src/famous_defs.rs | 8 ---- crates/ide/src/goto_definition.rs | 55 ++------------------- crates/intern/src/symbol/symbols.rs | 4 ++ 6 files changed, 89 insertions(+), 59 deletions(-) diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs index 4f2f9ec40d..89eae862bd 100644 --- a/crates/hir-expand/src/mod_path.rs +++ b/crates/hir-expand/src/mod_path.rs @@ -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") }; diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index a1a596675b..480e250766 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -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 { + self.analyze(call.syntax())?.resolve_known_blanket_dual_impls(self.db, call) + } + fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option { self.analyze(range_pat.syntax())?.resolve_range_pat(self.db, range_pat) } diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index b699ccde41..6b78d7a363 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -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 { + // 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. + // Get T from type arguments of Result. + .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 { + 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)?) } diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs index 6f95b698f8..9e3506d6f5 100644 --- a/crates/ide-db/src/famous_defs.rs +++ b/crates/ide-db/src/famous_defs.rs @@ -50,14 +50,6 @@ impl FamousDefs<'_, '_> { self.find_trait("core:convert:From") } - pub fn core_convert_TryFrom(&self) -> Option { - self.find_trait("core:convert:TryFrom") - } - - pub fn core_str_FromStr(&self) -> Option { - self.find_trait("core:str:FromStr") - } - pub fn core_convert_Into(&self) -> Option { self.find_trait("core:convert:Into") } diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 112faa7a65..905376e755 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -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> { - 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. - // Get T from type arguments of Result. - "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 - // arg.ty(): T: IntoIterator - 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( diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index 131b21a46b..b3b46421b5 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -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, From 913ec54f8477d6779bb9476a9f8fcf4ee7f874f4 Mon Sep 17 00:00:00 2001 From: 1hakusai1 <1hakusai1@gmail.com> Date: Mon, 20 Jan 2025 21:22:58 +0900 Subject: [PATCH 12/12] Add a test case --- crates/ide/src/goto_definition.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 905376e755..f804cc3677 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -3062,6 +3062,30 @@ fn f() { ); } + #[test] + fn into_call_to_from_definition_with_trait_bounds() { + check( + r#" +//- minicore: from, iterator +struct A; + +impl From for A +where + T: IntoIterator, +{ + fn from(value: T) -> Self { + //^^^^ + A + } +} + +fn f() { + let a: A = [1, 2, 3].into$0(); +} + "#, + ); + } + #[test] fn goto_into_definition_if_exists() { check(