goto where trait method impl

This commit is contained in:
bitgaoshu 2022-06-15 23:13:15 +08:00 committed by Florian Diebold
parent 6fc5c3cd21
commit 1ef5e14c2c
5 changed files with 366 additions and 117 deletions

View file

@ -8,8 +8,9 @@ use arrayvec::ArrayVec;
use base_db::{CrateId, Edition}; use base_db::{CrateId, Edition};
use chalk_ir::{cast::Cast, Mutability, UniverseIndex}; use chalk_ir::{cast::Cast, Mutability, UniverseIndex};
use hir_def::{ use hir_def::{
item_scope::ItemScope, nameres::DefMap, AssocItemId, BlockId, ConstId, FunctionId, data::ImplData, item_scope::ItemScope, nameres::DefMap, AssocItemId, BlockId, ConstId,
GenericDefId, HasModule, ImplId, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, Lookup, ModuleDefId, ModuleId,
TraitId,
}; };
use hir_expand::name::Name; use hir_expand::name::Name;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
@ -247,7 +248,7 @@ impl TraitImpls {
self.map self.map
.get(&trait_) .get(&trait_)
.into_iter() .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()) .flat_map(|v| v.iter().copied())
} }
@ -575,6 +576,32 @@ pub(crate) fn iterate_method_candidates<T>(
slot 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( pub fn iterate_path_candidates(
ty: &Canonical<Ty>, ty: &Canonical<Ty>,
db: &dyn HirDatabase, db: &dyn HirDatabase,
@ -850,7 +877,7 @@ fn iterate_trait_method_candidates(
for &(_, item) in data.items.iter() { for &(_, item) in data.items.iter() {
// Don't pass a `visible_from_module` down to `is_valid_candidate`, // Don't pass a `visible_from_module` down to `is_valid_candidate`,
// since only inherent methods should be included into visibility checking. // 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; continue;
} }
if !known_implemented { if !known_implemented {
@ -932,8 +959,14 @@ fn iterate_inherent_methods(
let impls_for_self_ty = impls.for_self_ty(self_ty); let impls_for_self_ty = impls.for_self_ty(self_ty);
for &impl_def in impls_for_self_ty { for &impl_def in impls_for_self_ty {
for &item in &db.impl_data(impl_def).items { 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; continue;
} }
callback(receiver_adjustments.clone().unwrap_or_default(), item)?; callback(receiver_adjustments.clone().unwrap_or_default(), item)?;
@ -961,8 +994,39 @@ pub fn resolve_indexing_op(
} }
None 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);
fn is_valid_candidate( if !table.unify(self_ty, &impl_ty) {
continue;
}
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, table: &mut InferenceTable,
name: Option<&Name>, name: Option<&Name>,
receiver_ty: Option<&Ty>, receiver_ty: Option<&Ty>,
@ -970,18 +1034,32 @@ fn is_valid_candidate(
self_ty: &Ty, self_ty: &Ty,
visible_from_module: Option<ModuleId>, visible_from_module: Option<ModuleId>,
) -> bool { ) -> bool {
macro_rules! assert {
($cond:expr) => {
if !$cond {
return false;
}
};
}
let db = table.db; let db = table.db;
match item { match item {
AssocItemId::FunctionId(m) => { AssocItemId::FunctionId(m) => {
let data = db.function_data(m); let data = db.function_data(m);
if let Some(name) = name {
if &data.name != name { assert!(name.map_or(true, |n| n == &data.name));
return false; 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| { table.run_in_snapshot(|table| {
let subst = TyBuilder::subst_for_def(db, m).fill_with_inference_vars(table).build(); let subst =
let expected_self_ty = match m.lookup(db.upcast()).container { TyBuilder::subst_for_def(db, m).fill_with_inference_vars(table).build();
let expect_self_ty = match m.lookup(db.upcast()).container {
ItemContainerId::TraitId(_) => { ItemContainerId::TraitId(_) => {
subst.at(Interner, 0).assert_ty_ref(Interner).clone() subst.at(Interner, 0).assert_ty_ref(Interner).clone()
} }
@ -993,49 +1071,31 @@ fn is_valid_candidate(
unreachable!() unreachable!()
} }
}; };
if !table.unify(&expected_self_ty, &self_ty) { assert!(table.unify(&expect_self_ty, self_ty));
return false;
}
if let Some(receiver_ty) = receiver_ty { if let Some(receiver_ty) = receiver_ty {
if !data.has_self_param() { assert!(data.has_self_param());
return false;
}
let sig = db.callable_item_signature(m.into()); let sig = db.callable_item_signature(m.into());
let expected_receiver = let expected_receiver =
sig.map(|s| s.params()[0].clone()).substitute(Interner, &subst); sig.map(|s| s.params()[0].clone()).substitute(Interner, &subst);
let receiver_matches = table.unify(&receiver_ty, &expected_receiver);
if !receiver_matches { assert!(table.unify(&receiver_ty, &expected_receiver));
return false;
} }
}
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);
return false;
}
}
true true
}) })
} }
AssocItemId::ConstId(c) => { AssocItemId::ConstId(c) => {
let data = db.const_data(c); let data = db.const_data(c);
if receiver_ty.is_some() { assert!(receiver_ty.is_none());
return false;
} assert!(name.map_or(true, |n| data.name.as_ref() == Some(n)));
if let Some(name) = name { assert!(visible_from_module.map_or(true, |from_module| {
if data.name.as_ref() != Some(name) { let v = db.const_visibility(c).is_visible_from(db.upcast(), from_module);
return false; if !v {
}
}
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); cov_mark::hit!(const_candidate_not_visible);
return false;
}
} }
v
}));
if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container { if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container {
let self_ty_matches = table.run_in_snapshot(|table| { let self_ty_matches = table.run_in_snapshot(|table| {
let subst = let subst =
@ -1054,6 +1114,7 @@ fn is_valid_candidate(
_ => false, _ => false,
} }
} }
}
pub fn implements_trait( pub fn implements_trait(
ty: &Canonical<Ty>, ty: &Canonical<Ty>,

View file

@ -348,6 +348,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.resolve_method_call(call).map(Function::from) 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> { pub fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> {
self.imp.resolve_method_call_as_callable(call) 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) 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> { fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> {
let source_analyzer = self.analyze(call.syntax())?; let source_analyzer = self.analyze(call.syntax())?;
let (func, subst) = source_analyzer.resolve_method_call(self.db, call)?; let (func, subst) = source_analyzer.resolve_method_call(self.db, call)?;

View file

@ -21,7 +21,8 @@ use hir_def::{
path::{ModPath, Path, PathKind}, path::{ModPath, Path, PathKind},
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
type_ref::Mutability, type_ref::Mutability,
AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, Lookup, ModuleDefId, VariantId, AsMacroCall, DefWithBodyId, FieldId, FunctionId, ItemContainerId, LocalFieldId, Lookup,
ModuleDefId, VariantId,
}; };
use hir_expand::{ use hir_expand::{
builtin_fn_macro::BuiltinFnLikeExpander, hygiene::Hygiene, name::AsName, HirFileId, InFile, 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, record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
UnsafeExpr, UnsafeExpr,
}, },
Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, TyExt, method_resolution, Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution,
TyLoweringContext, TyExt, TyKind, TyLoweringContext,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use syntax::{ use syntax::{
@ -247,6 +248,60 @@ impl SourceAnalyzer {
self.infer.as_ref()?.method_resolution(expr_id) 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( pub(crate) fn resolve_field(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,

View file

@ -162,6 +162,22 @@ impl IdentClass {
.or_else(|| NameClass::classify_lifetime(sema, lifetime).map(IdentClass::NameClass)) .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> { pub fn definitions(self) -> ArrayVec<Definition, 2> {
let mut res = ArrayVec::new(); let mut res = ArrayVec::new();
match self { 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( pub fn classify_lifetime(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
lifetime: &ast::Lifetime, lifetime: &ast::Lifetime,

View file

@ -1,7 +1,7 @@
use std::convert::TryInto; use std::convert::TryInto;
use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, TryToNav}; use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, TryToNav};
use hir::{AsAssocItem, Semantics}; use hir::{AsAssocItem, AssocItem, Semantics};
use ide_db::{ use ide_db::{
base_db::{AnchoredPath, FileId, FileLoader}, base_db::{AnchoredPath, FileId, FileLoader},
defs::{Definition, IdentClass}, defs::{Definition, IdentClass},
@ -65,7 +65,7 @@ pub(crate) fn goto_definition(
.definitions() .definitions()
.into_iter() .into_iter()
.flat_map(|def| { .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)) .unwrap_or_else(|| def_to_nav(sema.db, def))
}) })
.collect(), .collect(),
@ -104,33 +104,39 @@ fn try_lookup_include_path(
docs: None, docs: None,
}) })
} }
/// finds the trait definition of an impl'd item, except function
/// finds the trait definition of an impl'd item
/// e.g. /// e.g.
/// ```rust /// ```rust
/// trait A { fn a(); } /// trait A { type a; }
/// struct S; /// 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( fn try_filter_trait_item_definition(
db: &RootDatabase, sema: &Semantics<RootDatabase>,
def: &Definition, def: &Definition,
token: &SyntaxToken,
) -> Option<Vec<NavigationTarget>> { ) -> Option<Vec<NavigationTarget>> {
let name = def.name(db)?; let db = sema.db;
let assoc = def.as_assoc_item(db)?; let assoc = def.as_assoc_item(db)?;
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) { let imp = match assoc.container(db) {
hir::AssocItemContainer::Impl(imp) => imp, hir::AssocItemContainer::Impl(imp) => imp,
_ => return None, _ => return None,
}; };
let trait_ = imp.trait_(db)?; let trait_ = imp.trait_(db)?;
let name = def.name(db)?;
trait_ trait_
.items(db) .items(db)
.iter() .iter()
.find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten()) .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten())
.map(|it| vec![it]) .map(|it| vec![it])
} }
}
}
fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> { fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
def.try_to_nav(db).map(|it| vec![it]).unwrap_or_default() def.try_to_nav(db).map(|it| vec![it]).unwrap_or_default()
@ -1331,24 +1337,99 @@ fn main() {
"#, "#,
); );
} }
#[cfg(test)]
mod goto_impl_of_trait_fn {
use super::check;
#[test] #[test]
fn goto_def_of_trait_impl_fn() { fn cursor_on_impl() {
check( check(
r#" r#"
trait Twait { trait Twait {
fn a(); fn a();
// ^
} }
struct Stwuct; struct Stwuct;
impl Twait for Stwuct { impl Twait for Stwuct {
fn a$0(); 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] #[test]
fn goto_def_of_trait_impl_const() { fn goto_def_of_trait_impl_const() {