From 1ef5e14c2c072fca9792d3da7c6443850790d779 Mon Sep 17 00:00:00 2001 From: bitgaoshu Date: Wed, 15 Jun 2022 23:13:15 +0800 Subject: [PATCH] goto where trait method impl --- crates/hir-ty/src/method_resolution.rs | 231 ++++++++++++++++--------- crates/hir/src/semantics.rs | 7 + crates/hir/src/source_analyzer.rs | 61 ++++++- crates/ide-db/src/defs.rs | 45 +++++ crates/ide/src/goto_definition.rs | 139 +++++++++++---- 5 files changed, 366 insertions(+), 117 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( slot } +pub fn lookup_trait_m_for_self_ty( + self_ty: &Ty, + db: &dyn HirDatabase, + env: Arc, + implied_trait: TraitId, + name: &Name, +) -> Option { + 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, 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, + table: &mut InferenceTable, + self_ty: &Ty, + ) -> Option> { + 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); -fn is_valid_candidate( - table: &mut InferenceTable, - name: Option<&Name>, - receiver_ty: Option<&Ty>, - item: AssocItemId, - self_ty: &Ty, - visible_from_module: Option, -) -> 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; - } + if !table.unify(self_ty, &impl_ty) { + continue; } - 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) { + + 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); + } + } + None + } + + fn is_valid_item( + table: &mut InferenceTable, + name: Option<&Name>, + receiver_ty: Option<&Ty>, + item: AssocItemId, + self_ty: &Ty, + visible_from_module: Option, + ) -> 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); - if !receiver_matches { - return false; - } - } - if let Some(from_module) = visible_from_module { - if !db.function_visibility(m).is_visible_from(db.upcast(), from_module) { + 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 + })); + + 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)); + } + 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 - }) + } + _ => false, } - 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, } } 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 { + self.imp.resolve_impl_method(call).map(Function::from) + } pub fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option { 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 { + self.analyze(call.syntax())?.resolve_impl_method(self.db, call) + } + fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option { 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 { + 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, + token: &SyntaxToken, + ) -> Option { + 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 { let mut res = ArrayVec::new(); match self { @@ -417,6 +433,35 @@ impl NameRefClass { } } + fn classify_to_impl( + sema: &Semantics, + name_ref: ast::NameRef, + ) -> Option { + 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, 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, def: &Definition, + token: &SyntaxToken, ) -> Option> { - 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 { @@ -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); +impl G for Gen { + fn g(&self) { + } +} +impl G for Gen +where T : Bound +{ + fn g(&self){ + //^ + } +} +struct A; +impl Bound for A{} +fn f() { + let gen = Gen::(A); + gen.g$0(); +} + "#, + ); + } } #[test]