about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbitgaoshu <bitgaoshu@gmail.com>2022-06-15 23:13:15 +0800
committerFlorian Diebold <flodiebold@gmail.com>2022-06-23 14:01:22 +0200
commit1ef5e14c2c072fca9792d3da7c6443850790d779 (patch)
tree5590c3556326f7a5fe304e616464cdef3a1200b8
parent6fc5c3cd2117a29981ba9b7cef8a51c1d6804089 (diff)
downloadrust-1ef5e14c2c072fca9792d3da7c6443850790d779.tar.gz
rust-1ef5e14c2c072fca9792d3da7c6443850790d779.zip
goto where trait method impl
-rw-r--r--crates/hir-ty/src/method_resolution.rs229
-rw-r--r--crates/hir/src/semantics.rs7
-rw-r--r--crates/hir/src/source_analyzer.rs61
-rw-r--r--crates/ide-db/src/defs.rs45
-rw-r--r--crates/ide/src/goto_definition.rs139
5 files changed, 365 insertions, 116 deletions
diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs
index d5285c17106..afac92b2662 100644
--- a/crates/hir-ty/src/method_resolution.rs
+++ b/crates/hir-ty/src/method_resolution.rs
@@ -8,8 +8,9 @@ use arrayvec::ArrayVec;
 use base_db::{CrateId, Edition};
 use chalk_ir::{cast::Cast, Mutability, UniverseIndex};
 use hir_def::{
-    item_scope::ItemScope, nameres::DefMap, AssocItemId, BlockId, ConstId, FunctionId,
-    GenericDefId, HasModule, ImplId, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId,
+    data::ImplData, item_scope::ItemScope, nameres::DefMap, AssocItemId, BlockId, ConstId,
+    FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, Lookup, ModuleDefId, ModuleId,
+    TraitId,
 };
 use hir_expand::name::Name;
 use rustc_hash::{FxHashMap, FxHashSet};
@@ -247,7 +248,7 @@ impl TraitImpls {
         self.map
             .get(&trait_)
             .into_iter()
-            .flat_map(move |map| map.get(&None).into_iter().chain(map.get(&Some(self_ty))))
+            .flat_map(move |map| map.get(&Some(self_ty)).into_iter().chain(map.get(&None)))
             .flat_map(|v| v.iter().copied())
     }
 
@@ -575,6 +576,32 @@ pub(crate) fn iterate_method_candidates<T>(
     slot
 }
 
+pub fn lookup_trait_m_for_self_ty(
+    self_ty: &Ty,
+    db: &dyn HirDatabase,
+    env: Arc<TraitEnvironment>,
+    implied_trait: TraitId,
+    name: &Name,
+) -> Option<FunctionId> {
+    let self_ty_tp = TyFingerprint::for_trait_impl(self_ty)?;
+    let trait_impls = TraitImpls::trait_impls_in_deps_query(db, env.krate);
+    let impls = trait_impls.for_trait_and_self_ty(implied_trait, self_ty_tp);
+    let mut table = InferenceTable::new(db, env.clone());
+    if let Some(data) = Valid::valid_impl(impls, &mut table, &self_ty) {
+        for &impl_item in data.items.iter() {
+            if Valid::is_valid_item(&mut table, Some(name), None, impl_item, self_ty, None) {
+                match impl_item {
+                    AssocItemId::FunctionId(f) => {
+                        return Some(f);
+                    }
+                    _ => (),
+                }
+            }
+        }
+    }
+    None
+}
+
 pub fn iterate_path_candidates(
     ty: &Canonical<Ty>,
     db: &dyn HirDatabase,
@@ -850,7 +877,7 @@ fn iterate_trait_method_candidates(
         for &(_, item) in data.items.iter() {
             // Don't pass a `visible_from_module` down to `is_valid_candidate`,
             // since only inherent methods should be included into visibility checking.
-            if !is_valid_candidate(table, name, receiver_ty, item, self_ty, None) {
+            if !Valid::is_valid_item(table, name, receiver_ty, item, self_ty, None) {
                 continue;
             }
             if !known_implemented {
@@ -932,8 +959,14 @@ fn iterate_inherent_methods(
         let impls_for_self_ty = impls.for_self_ty(self_ty);
         for &impl_def in impls_for_self_ty {
             for &item in &db.impl_data(impl_def).items {
-                if !is_valid_candidate(table, name, receiver_ty, item, self_ty, visible_from_module)
-                {
+                if !Valid::is_valid_item(
+                    table,
+                    name,
+                    receiver_ty,
+                    item,
+                    self_ty,
+                    visible_from_module,
+                ) {
                     continue;
                 }
                 callback(receiver_adjustments.clone().unwrap_or_default(), item)?;
@@ -961,97 +994,125 @@ pub fn resolve_indexing_op(
     }
     None
 }
+struct Valid;
+impl Valid {
+    fn valid_impl(
+        impls: impl Iterator<Item = ImplId>,
+        table: &mut InferenceTable,
+        self_ty: &Ty,
+    ) -> Option<Arc<ImplData>> {
+        let db = table.db;
+        for impl_ in impls {
+            let impl_data = db.impl_data(impl_);
+            let substs =
+                TyBuilder::subst_for_def(db, impl_).fill_with_inference_vars(table).build();
+            let impl_ty =
+                substs.apply(db.impl_self_ty(impl_).into_value_and_skipped_binders().0, Interner);
+
+            if !table.unify(self_ty, &impl_ty) {
+                continue;
+            }
 
-fn is_valid_candidate(
-    table: &mut InferenceTable,
-    name: Option<&Name>,
-    receiver_ty: Option<&Ty>,
-    item: AssocItemId,
-    self_ty: &Ty,
-    visible_from_module: Option<ModuleId>,
-) -> bool {
-    let db = table.db;
-    match item {
-        AssocItemId::FunctionId(m) => {
-            let data = db.function_data(m);
-            if let Some(name) = name {
-                if &data.name != name {
-                    return false;
-                }
+            let wh_goals = crate::chalk_db::convert_where_clauses(db, impl_.into(), &substs)
+                .into_iter()
+                .map(|b| b.into_well_formed_goal(Interner).cast(Interner));
+
+            let goal = crate::Goal::all(Interner, wh_goals);
+
+            if table.try_obligation(goal).is_some() {
+                return Some(impl_data);
             }
-            table.run_in_snapshot(|table| {
-                let subst = TyBuilder::subst_for_def(db, m).fill_with_inference_vars(table).build();
-                let expected_self_ty = match m.lookup(db.upcast()).container {
-                    ItemContainerId::TraitId(_) => {
-                        subst.at(Interner, 0).assert_ty_ref(Interner).clone()
-                    }
-                    ItemContainerId::ImplId(impl_id) => {
-                        subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner)
-                    }
-                    // We should only get called for associated items (impl/trait)
-                    ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => {
-                        unreachable!()
-                    }
-                };
-                if !table.unify(&expected_self_ty, &self_ty) {
+        }
+        None
+    }
+
+    fn is_valid_item(
+        table: &mut InferenceTable,
+        name: Option<&Name>,
+        receiver_ty: Option<&Ty>,
+        item: AssocItemId,
+        self_ty: &Ty,
+        visible_from_module: Option<ModuleId>,
+    ) -> bool {
+        macro_rules! assert {
+            ($cond:expr) => {
+                if !$cond {
                     return false;
                 }
-                if let Some(receiver_ty) = receiver_ty {
-                    if !data.has_self_param() {
-                        return false;
-                    }
+            };
+        }
 
-                    let sig = db.callable_item_signature(m.into());
-                    let expected_receiver =
-                        sig.map(|s| s.params()[0].clone()).substitute(Interner, &subst);
-                    let receiver_matches = table.unify(&receiver_ty, &expected_receiver);
+        let db = table.db;
+        match item {
+            AssocItemId::FunctionId(m) => {
+                let data = db.function_data(m);
+
+                assert!(name.map_or(true, |n| n == &data.name));
+                assert!(visible_from_module.map_or(true, |from_module| {
+                    let v = db.function_visibility(m).is_visible_from(db.upcast(), from_module);
+                    if !v {
+                        cov_mark::hit!(autoderef_candidate_not_visible);
+                    }
+                    v
+                }));
 
-                    if !receiver_matches {
-                        return false;
+                table.run_in_snapshot(|table| {
+                    let subst =
+                        TyBuilder::subst_for_def(db, m).fill_with_inference_vars(table).build();
+                    let expect_self_ty = match m.lookup(db.upcast()).container {
+                        ItemContainerId::TraitId(_) => {
+                            subst.at(Interner, 0).assert_ty_ref(Interner).clone()
+                        }
+                        ItemContainerId::ImplId(impl_id) => {
+                            subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner)
+                        }
+                        // We should only get called for associated items (impl/trait)
+                        ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => {
+                            unreachable!()
+                        }
+                    };
+                    assert!(table.unify(&expect_self_ty, self_ty));
+                    if let Some(receiver_ty) = receiver_ty {
+                        assert!(data.has_self_param());
+
+                        let sig = db.callable_item_signature(m.into());
+                        let expected_receiver =
+                            sig.map(|s| s.params()[0].clone()).substitute(Interner, &subst);
+
+                        assert!(table.unify(&receiver_ty, &expected_receiver));
                     }
-                }
-                if let Some(from_module) = visible_from_module {
-                    if !db.function_visibility(m).is_visible_from(db.upcast(), from_module) {
-                        cov_mark::hit!(autoderef_candidate_not_visible);
+                    true
+                })
+            }
+            AssocItemId::ConstId(c) => {
+                let data = db.const_data(c);
+                assert!(receiver_ty.is_none());
+
+                assert!(name.map_or(true, |n| data.name.as_ref() == Some(n)));
+                assert!(visible_from_module.map_or(true, |from_module| {
+                    let v = db.const_visibility(c).is_visible_from(db.upcast(), from_module);
+                    if !v {
+                        cov_mark::hit!(const_candidate_not_visible);
+                    }
+                    v
+                }));
+                if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container {
+                    let self_ty_matches = table.run_in_snapshot(|table| {
+                        let subst =
+                            TyBuilder::subst_for_def(db, c).fill_with_inference_vars(table).build();
+                        let expected_self_ty =
+                            subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner);
+                        table.unify(&expected_self_ty, &self_ty)
+                    });
+                    if !self_ty_matches {
+                        cov_mark::hit!(const_candidate_self_type_mismatch);
                         return false;
                     }
                 }
-
                 true
-            })
-        }
-        AssocItemId::ConstId(c) => {
-            let data = db.const_data(c);
-            if receiver_ty.is_some() {
-                return false;
-            }
-            if let Some(name) = name {
-                if data.name.as_ref() != Some(name) {
-                    return false;
-                }
-            }
-            if let Some(from_module) = visible_from_module {
-                if !db.const_visibility(c).is_visible_from(db.upcast(), from_module) {
-                    cov_mark::hit!(const_candidate_not_visible);
-                    return false;
-                }
-            }
-            if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container {
-                let self_ty_matches = table.run_in_snapshot(|table| {
-                    let subst =
-                        TyBuilder::subst_for_def(db, c).fill_with_inference_vars(table).build();
-                    let expected_self_ty =
-                        subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner);
-                    table.unify(&expected_self_ty, &self_ty)
-                });
-                if !self_ty_matches {
-                    cov_mark::hit!(const_candidate_self_type_mismatch);
-                    return false;
-                }
             }
-            true
+            _ => false,
         }
-        _ => false,
     }
 }
 
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index aa10b0f878f..2574adb35a5 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -348,6 +348,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.resolve_method_call(call).map(Function::from)
     }
 
+    pub fn resolve_impl_method(&self, call: &ast::Expr) -> Option<Function> {
+        self.imp.resolve_impl_method(call).map(Function::from)
+    }
     pub fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> {
         self.imp.resolve_method_call_as_callable(call)
     }
@@ -978,6 +981,10 @@ impl<'db> SemanticsImpl<'db> {
         self.analyze(call.syntax())?.resolve_method_call(self.db, call).map(|(id, _)| id)
     }
 
+    fn resolve_impl_method(&self, call: &ast::Expr) -> Option<FunctionId> {
+        self.analyze(call.syntax())?.resolve_impl_method(self.db, call)
+    }
+
     fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> {
         let source_analyzer = self.analyze(call.syntax())?;
         let (func, subst) = source_analyzer.resolve_method_call(self.db, call)?;
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index d624d375774..4d8d6a14608 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -21,7 +21,8 @@ use hir_def::{
     path::{ModPath, Path, PathKind},
     resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
     type_ref::Mutability,
-    AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, Lookup, ModuleDefId, VariantId,
+    AsMacroCall, DefWithBodyId, FieldId, FunctionId, ItemContainerId, LocalFieldId, Lookup,
+    ModuleDefId, VariantId,
 };
 use hir_expand::{
     builtin_fn_macro::BuiltinFnLikeExpander, hygiene::Hygiene, name::AsName, HirFileId, InFile,
@@ -31,8 +32,8 @@ use hir_ty::{
         record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
         UnsafeExpr,
     },
-    Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, TyExt,
-    TyLoweringContext,
+    method_resolution, Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution,
+    TyExt, TyKind, TyLoweringContext,
 };
 use smallvec::SmallVec;
 use syntax::{
@@ -247,6 +248,60 @@ impl SourceAnalyzer {
         self.infer.as_ref()?.method_resolution(expr_id)
     }
 
+    pub(crate) fn resolve_impl_method(
+        &self,
+        db: &dyn HirDatabase,
+        call: &ast::Expr,
+    ) -> Option<FunctionId> {
+        let infered = self.infer.as_ref()?;
+        let expr_id = self.expr_id(db, call)?;
+
+        let mut fun_info = None;
+        match call {
+            &ast::Expr::MethodCallExpr(..) => {
+                let (func, subs) = infered.method_resolution(expr_id)?;
+                if subs.is_empty(Interner) {
+                    return None;
+                }
+                fun_info.replace((func, subs.at(Interner, 0).ty(Interner)?.clone()));
+            }
+            &ast::Expr::PathExpr(..) => {
+                let func_ty = infered.type_of_expr.get(expr_id)?;
+                if let TyKind::FnDef(fn_def, subs) = func_ty.kind(Interner) {
+                    if subs.is_empty(Interner) {
+                        return None;
+                    }
+                    if let hir_ty::CallableDefId::FunctionId(f_id) =
+                        db.lookup_intern_callable_def(fn_def.clone().into())
+                    {
+                        fun_info.replace((f_id, subs.at(Interner, 0).ty(Interner)?.clone()));
+                    }
+                }
+            }
+            _ => (),
+        };
+        let (func, self_ty) = fun_info?;
+        let implied_trait = match func.lookup(db.upcast()).container {
+            ItemContainerId::TraitId(trait_id) => trait_id,
+            _ => return None,
+        };
+
+        let krate = self.resolver.krate();
+        let trait_env = self.resolver.body_owner()?.as_generic_def_id().map_or_else(
+            || Arc::new(hir_ty::TraitEnvironment::empty(krate)),
+            |d| db.trait_environment(d),
+        );
+
+        let fun_data = db.function_data(func);
+        method_resolution::lookup_trait_m_for_self_ty(
+            &self_ty,
+            db,
+            trait_env,
+            implied_trait,
+            &fun_data.name,
+        )
+    }
+
     pub(crate) fn resolve_field(
         &self,
         db: &dyn HirDatabase,
diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs
index 540cc70dd52..e5390eeb326 100644
--- a/crates/ide-db/src/defs.rs
+++ b/crates/ide-db/src/defs.rs
@@ -162,6 +162,22 @@ impl IdentClass {
             .or_else(|| NameClass::classify_lifetime(sema, lifetime).map(IdentClass::NameClass))
     }
 
+    pub fn classify_token_to_impl(
+        sema: &Semantics<RootDatabase>,
+        token: &SyntaxToken,
+    ) -> Option<Definition> {
+        let p = token.parent()?;
+        match_ast! {
+            match p {
+                ast::NameRef(name_ref) => match NameRefClass::classify_to_impl(sema, name_ref)? {
+                    NameRefClass::Definition(d) => Some(d),
+                    _ => None,
+                },
+                _ => None,
+            }
+        }
+    }
+
     pub fn definitions(self) -> ArrayVec<Definition, 2> {
         let mut res = ArrayVec::new();
         match self {
@@ -417,6 +433,35 @@ impl NameRefClass {
         }
     }
 
+    fn classify_to_impl(
+        sema: &Semantics<RootDatabase>,
+        name_ref: ast::NameRef,
+    ) -> Option<NameRefClass> {
+        let parent = name_ref.syntax().parent()?;
+        match_ast! {
+           match parent {
+               ast::MethodCallExpr(method_call) => {
+                   sema.resolve_impl_method(&ast::Expr::MethodCallExpr(method_call))
+                       .map(Definition::Function)
+                       .map(NameRefClass::Definition)
+               },
+               ast::PathSegment(ps) => {
+                   ps.syntax().parent().and_then(ast::Path::cast)
+                   .map(|p|
+                       p.syntax()
+                       .parent()
+                       .and_then(ast::PathExpr::cast)
+                       .map(|pe|
+                           sema.resolve_impl_method(&ast::Expr::PathExpr(pe))
+                           .map(Definition::Function)
+                           .map(NameRefClass::Definition)
+                       ).flatten()
+                   ).flatten()
+               },
+               _=> None
+           }
+        }
+    }
     pub fn classify_lifetime(
         sema: &Semantics<RootDatabase>,
         lifetime: &ast::Lifetime,
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index df73879aed7..ea7fd3e0728 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,7 +1,7 @@
 use std::convert::TryInto;
 
 use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, TryToNav};
-use hir::{AsAssocItem, Semantics};
+use hir::{AsAssocItem, AssocItem, Semantics};
 use ide_db::{
     base_db::{AnchoredPath, FileId, FileLoader},
     defs::{Definition, IdentClass},
@@ -65,7 +65,7 @@ pub(crate) fn goto_definition(
                     .definitions()
                     .into_iter()
                     .flat_map(|def| {
-                        try_find_trait_item_definition(sema.db, &def)
+                        try_filter_trait_item_definition(sema, &def, &token)
                             .unwrap_or_else(|| def_to_nav(sema.db, def))
                     })
                     .collect(),
@@ -104,32 +104,38 @@ fn try_lookup_include_path(
         docs: None,
     })
 }
-
-/// finds the trait definition of an impl'd item
+/// finds the trait definition of an impl'd item, except function
 /// e.g.
 /// ```rust
-/// trait A { fn a(); }
+/// trait A { type a; }
 /// struct S;
-/// impl A for S { fn a(); } // <-- on this function, will get the location of a() in the trait
+/// impl A for S { type a = i32; } // <-- on this associate type, will get the location of a in the trait
 /// ```
-fn try_find_trait_item_definition(
-    db: &RootDatabase,
+fn try_filter_trait_item_definition(
+    sema: &Semantics<RootDatabase>,
     def: &Definition,
+    token: &SyntaxToken,
 ) -> Option<Vec<NavigationTarget>> {
-    let name = def.name(db)?;
+    let db = sema.db;
     let assoc = def.as_assoc_item(db)?;
-
-    let imp = match assoc.container(db) {
-        hir::AssocItemContainer::Impl(imp) => imp,
-        _ => return None,
-    };
-
-    let trait_ = imp.trait_(db)?;
-    trait_
-        .items(db)
-        .iter()
-        .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten())
-        .map(|it| vec![it])
+    match assoc {
+        AssocItem::Function(..) => {
+            IdentClass::classify_token_to_impl(sema, &token).map(|def| def_to_nav(db, def))
+        }
+        AssocItem::Const(..) | AssocItem::TypeAlias(..) => {
+            let imp = match assoc.container(db) {
+                hir::AssocItemContainer::Impl(imp) => imp,
+                _ => return None,
+            };
+            let trait_ = imp.trait_(db)?;
+            let name = def.name(db)?;
+            trait_
+                .items(db)
+                .iter()
+                .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten())
+                .map(|it| vec![it])
+        }
+    }
 }
 
 fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
@@ -1331,23 +1337,98 @@ fn main() {
 "#,
         );
     }
-
-    #[test]
-    fn goto_def_of_trait_impl_fn() {
-        check(
-            r#"
+    #[cfg(test)]
+    mod goto_impl_of_trait_fn {
+        use super::check;
+        #[test]
+        fn cursor_on_impl() {
+            check(
+                r#"
 trait Twait {
     fn a();
-    // ^
 }
 
 struct Stwuct;
 
 impl Twait for Stwuct {
     fn a$0();
+     //^
 }
-"#,
-        );
+        "#,
+            );
+        }
+        #[test]
+        fn method_call() {
+            check(
+                r#"
+trait Twait {
+    fn a(&self);
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+    fn a(&self){};
+     //^
+}
+fn f() {
+    let s = Stwuct;
+    s.a$0();
+}
+        "#,
+            );
+        }
+        #[test]
+        fn path_call() {
+            check(
+                r#"
+trait Twait {
+    fn a(&self);
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+    fn a(&self){};
+     //^
+}
+fn f() {
+    let s = Stwuct;
+    Stwuct::a$0(&s);
+}
+        "#,
+            );
+        }
+        #[test]
+        fn where_clause_can_work() {
+            check(
+                r#"
+trait G {
+    fn g(&self);
+}
+trait Bound{}
+trait EA{}
+struct Gen<T>(T);
+impl <T:EA> G for Gen<T> {
+    fn g(&self) {
+    }
+}
+impl <T> G for Gen<T>
+where T : Bound
+{
+    fn g(&self){
+     //^
+    }
+}
+struct A;
+impl Bound for A{}
+fn f() {
+    let gen = Gen::<A>(A);
+    gen.g$0();
+}
+                "#,
+            );
+        }
     }
 
     #[test]