about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs3
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics.rs4
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs74
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_definition.rs55
-rw-r--r--src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs4
6 files changed, 89 insertions, 59 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs
index 4f2f9ec40d0..89eae862bd9 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs
+++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
index a1a596675ba..480e2507664 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs
+++ b/src/tools/rust-analyzer/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<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)
     }
diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
index b699ccde412..6b78d7a3631 100644
--- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
+++ b/src/tools/rust-analyzer/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<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)?)
     }
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
index 6f95b698f88..9e3506d6f53 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
+++ b/src/tools/rust-analyzer/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<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")
     }
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
index 112faa7a659..905376e7557 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
+++ b/src/tools/rust-analyzer/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<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(
diff --git a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs
index 131b21a46b8..b3b46421b50 100644
--- a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs
+++ b/src/tools/rust-analyzer/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,