Auto merge of #12549 - bitgaoshu:goto_where_trait_m_impl, r=Veykril

feat: Go to implementation of trait methods

try goto where the trait method implies,  #4558
This commit is contained in:
bors 2022-07-18 16:29:23 +00:00
commit 22e53f1d33
11 changed files with 419 additions and 130 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,59 @@ pub(crate) fn iterate_method_candidates<T>(
slot slot
} }
pub fn lookup_impl_method(
self_ty: &Ty,
db: &dyn HirDatabase,
env: Arc<TraitEnvironment>,
trait_: TraitId,
name: &Name,
) -> Option<FunctionId> {
let self_ty_fp = 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(trait_, self_ty_fp);
let mut table = InferenceTable::new(db, env.clone());
find_matching_impl(impls, &mut table, &self_ty).and_then(|data| {
data.items.iter().find_map(|it| match it {
AssocItemId::FunctionId(f) => (db.function_data(*f).name == *name).then(|| *f),
_ => None,
})
})
}
fn find_matching_impl(
mut impls: impl Iterator<Item = ImplId>,
table: &mut InferenceTable,
self_ty: &Ty,
) -> Option<Arc<ImplData>> {
let db = table.db;
loop {
let impl_ = impls.next()?;
let r = table.run_in_snapshot(|table| {
let impl_data = db.impl_data(impl_);
let substs =
TyBuilder::subst_for_def(db, impl_).fill_with_inference_vars(table).build();
let impl_ty = db.impl_self_ty(impl_).substitute(Interner, &substs);
table
.unify(self_ty, &impl_ty)
.then(|| {
let wh_goals =
crate::chalk_db::convert_where_clauses(db, impl_.into(), &substs)
.into_iter()
.map(|b| b.cast(Interner));
let goal = crate::Goal::all(Interner, wh_goals);
table.try_obligation(goal).map(|_| impl_data)
})
.flatten()
});
if r.is_some() {
break r;
}
}
}
pub fn iterate_path_candidates( pub fn iterate_path_candidates(
ty: &Canonical<Ty>, ty: &Canonical<Ty>,
db: &dyn HirDatabase, db: &dyn HirDatabase,
@ -970,18 +1024,31 @@ fn is_valid_candidate(
self_ty: &Ty, self_ty: &Ty,
visible_from_module: Option<ModuleId>, visible_from_module: Option<ModuleId>,
) -> bool { ) -> bool {
macro_rules! check_that {
($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 { check_that!(name.map_or(true, |n| n == &data.name));
return false; check_that!(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 = TyBuilder::subst_for_def(db, m).fill_with_inference_vars(table).build();
let expected_self_ty = match m.lookup(db.upcast()).container { 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 +1060,31 @@ fn is_valid_candidate(
unreachable!() unreachable!()
} }
}; };
if !table.unify(&expected_self_ty, &self_ty) { check_that!(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() { check_that!(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 { check_that!(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() { check_that!(receiver_ty.is_none());
return false;
} check_that!(name.map_or(true, |n| data.name.as_ref() == Some(n)));
if let Some(name) = name { check_that!(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 =

View file

@ -16,7 +16,6 @@ use hir_expand::{
name::{known, AsName}, name::{known, AsName},
ExpansionInfo, MacroCallId, ExpansionInfo, MacroCallId,
}; };
use hir_ty::Interner;
use itertools::Itertools; use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
@ -975,18 +974,11 @@ impl<'db> SemanticsImpl<'db> {
} }
fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<FunctionId> { fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<FunctionId> {
self.analyze(call.syntax())?.resolve_method_call(self.db, call).map(|(id, _)| id) self.analyze(call.syntax())?.resolve_method_call(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())?; self.analyze(call.syntax())?.resolve_method_call_as_callable(self.db, call)
let (func, subst) = source_analyzer.resolve_method_call(self.db, call)?;
let ty = self.db.value_ty(func.into()).substitute(Interner, &subst);
let resolver = source_analyzer.resolver;
let ty = Type::new_with_resolver(self.db, &resolver, ty);
let mut res = ty.as_callable(self.db)?;
res.is_bound_method = true;
Some(res)
} }
fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> { fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> {

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, AssocItemId, 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::{
@ -42,8 +43,8 @@ use syntax::{
use crate::{ use crate::{
db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr, db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr,
BuiltinType, Const, Field, Function, Local, Macro, ModuleDef, Static, Struct, ToolModule, BuiltinType, Callable, Const, Field, Function, Local, Macro, ModuleDef, Static, Struct,
Trait, Type, TypeAlias, Variant, ToolModule, Trait, Type, TypeAlias, Variant,
}; };
/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
@ -232,13 +233,29 @@ impl SourceAnalyzer {
) )
} }
pub(crate) fn resolve_method_call_as_callable(
&self,
db: &dyn HirDatabase,
call: &ast::MethodCallExpr,
) -> Option<Callable> {
let expr_id = self.expr_id(db, &call.clone().into())?;
let (func, substs) = self.infer.as_ref()?.method_resolution(expr_id)?;
let ty = db.value_ty(func.into()).substitute(Interner, &substs);
let ty = Type::new_with_resolver(db, &self.resolver, ty);
let mut res = ty.as_callable(db)?;
res.is_bound_method = true;
Some(res)
}
pub(crate) fn resolve_method_call( pub(crate) fn resolve_method_call(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
call: &ast::MethodCallExpr, call: &ast::MethodCallExpr,
) -> Option<(FunctionId, Substitution)> { ) -> Option<FunctionId> {
let expr_id = self.expr_id(db, &call.clone().into())?; let expr_id = self.expr_id(db, &call.clone().into())?;
self.infer.as_ref()?.method_resolution(expr_id) let (f_in_trait, substs) = self.infer.as_ref()?.method_resolution(expr_id)?;
let f_in_impl = self.resolve_impl_method(db, f_in_trait, &substs);
f_in_impl.or(Some(f_in_trait))
} }
pub(crate) fn resolve_field( pub(crate) fn resolve_field(
@ -336,6 +353,25 @@ impl SourceAnalyzer {
let expr_id = self.expr_id(db, &path_expr.into())?; let expr_id = self.expr_id(db, &path_expr.into())?;
let infer = self.infer.as_ref()?; let infer = self.infer.as_ref()?;
if let Some(assoc) = infer.assoc_resolutions_for_expr(expr_id) { if let Some(assoc) = infer.assoc_resolutions_for_expr(expr_id) {
let assoc = match assoc {
AssocItemId::FunctionId(f_in_trait) => {
match infer.type_of_expr.get(expr_id) {
None => assoc,
Some(func_ty) => {
if let TyKind::FnDef(_fn_def, subs) = func_ty.kind(Interner) {
self.resolve_impl_method(db, f_in_trait, subs)
.map(AssocItemId::FunctionId)
.unwrap_or(assoc)
} else {
assoc
}
}
}
}
_ => assoc,
};
return Some(PathResolution::Def(AssocItem::from(assoc).into())); return Some(PathResolution::Def(AssocItem::from(assoc).into()));
} }
if let Some(VariantId::EnumVariantId(variant)) = if let Some(VariantId::EnumVariantId(variant)) =
@ -563,6 +599,30 @@ impl SourceAnalyzer {
} }
false false
} }
fn resolve_impl_method(
&self,
db: &dyn HirDatabase,
func: FunctionId,
substs: &Substitution,
) -> Option<FunctionId> {
let impled_trait = match func.lookup(db.upcast()).container {
ItemContainerId::TraitId(trait_id) => trait_id,
_ => return None,
};
if substs.is_empty(Interner) {
return None;
}
let self_ty = substs.at(Interner, 0).ty(Interner)?;
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_impl_method(self_ty, db, trait_env, impled_trait, &fun_data.name)
}
} }
fn scope_for( fn scope_for(

View file

@ -1,8 +1,5 @@
use hir::{ItemInNs, ModuleDef}; use hir::{db::HirDatabase, AsAssocItem, AssocItem, AssocItemContainer, ItemInNs, ModuleDef};
use ide_db::{ use ide_db::assists::{AssistId, AssistKind};
assists::{AssistId, AssistKind},
imports::import_assets::item_for_path_search,
};
use syntax::{ast, AstNode}; use syntax::{ast, AstNode};
use crate::{ use crate::{
@ -67,6 +64,26 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext) -> Opt
Some(()) Some(())
} }
fn item_for_path_search(db: &dyn HirDatabase, item: ItemInNs) -> Option<ItemInNs> {
Some(match item {
ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
Some(assoc_item) => match assoc_item.container(db) {
AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
AssocItemContainer::Impl(impl_) => match impl_.trait_(db) {
None => ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?)),
Some(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
},
},
None => item,
},
ItemInNs::Macros(_) => item,
})
}
fn item_as_assoc(db: &dyn HirDatabase, item: ItemInNs) -> Option<AssocItem> {
item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -460,6 +460,21 @@ fn foo() { S.bar($01$0, 2) }
); );
} }
#[test]
fn method_on_impl_trait() {
check(
r#"
struct S;
trait T {
fn bar(&self, n: i32, m: u32);
}
impl T for S { fn bar(&self, n: i32, m: u32); }
fn foo() { S.bar($01$0, 2) }
"#,
"n",
);
}
#[test] #[test]
fn method_ufcs() { fn method_ufcs() {
check( check(

View file

@ -24,7 +24,7 @@ use std::fmt;
use base_db::{AnchoredPathBuf, FileId, FileRange}; use base_db::{AnchoredPathBuf, FileId, FileRange};
use either::Either; use either::Either;
use hir::{AsAssocItem, FieldSource, HasSource, InFile, ModuleSource, Semantics}; use hir::{FieldSource, HasSource, InFile, ModuleSource, Semantics};
use stdx::never; use stdx::never;
use syntax::{ use syntax::{
ast::{self, HasName}, ast::{self, HasName},
@ -37,6 +37,7 @@ use crate::{
search::FileReference, search::FileReference,
source_change::{FileSystemEdit, SourceChange}, source_change::{FileSystemEdit, SourceChange},
syntax_helpers::node_ext::expr_as_name_ref, syntax_helpers::node_ext::expr_as_name_ref,
traits::convert_to_def_in_trait,
RootDatabase, RootDatabase,
}; };
@ -248,7 +249,7 @@ fn rename_mod(
fn rename_reference( fn rename_reference(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
mut def: Definition, def: Definition,
new_name: &str, new_name: &str,
) -> Result<SourceChange> { ) -> Result<SourceChange> {
let ident_kind = IdentifierKind::classify(new_name)?; let ident_kind = IdentifierKind::classify(new_name)?;
@ -275,41 +276,7 @@ fn rename_reference(
} }
} }
let assoc_item = match def { let def = convert_to_def_in_trait(sema.db, def);
// HACK: resolve trait impl items to the item def of the trait definition
// so that we properly resolve all trait item references
Definition::Function(it) => it.as_assoc_item(sema.db),
Definition::TypeAlias(it) => it.as_assoc_item(sema.db),
Definition::Const(it) => it.as_assoc_item(sema.db),
_ => None,
};
def = match assoc_item {
Some(assoc) => assoc
.containing_trait_impl(sema.db)
.and_then(|trait_| {
trait_.items(sema.db).into_iter().find_map(|it| match (it, assoc) {
(hir::AssocItem::Function(trait_func), hir::AssocItem::Function(func))
if trait_func.name(sema.db) == func.name(sema.db) =>
{
Some(Definition::Function(trait_func))
}
(hir::AssocItem::Const(trait_konst), hir::AssocItem::Const(konst))
if trait_konst.name(sema.db) == konst.name(sema.db) =>
{
Some(Definition::Const(trait_konst))
}
(
hir::AssocItem::TypeAlias(trait_type_alias),
hir::AssocItem::TypeAlias(type_alias),
) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => {
Some(Definition::TypeAlias(trait_type_alias))
}
_ => None,
})
})
.unwrap_or(def),
None => def,
};
let usages = def.usages(sema).all(); let usages = def.usages(sema).all();
if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {

View file

@ -16,6 +16,7 @@ use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
use crate::{ use crate::{
defs::{Definition, NameClass, NameRefClass}, defs::{Definition, NameClass, NameRefClass},
traits::convert_to_def_in_trait,
RootDatabase, RootDatabase,
}; };
@ -619,7 +620,9 @@ impl<'a> FindUsages<'a> {
}; };
sink(file_id, reference) sink(file_id, reference)
} }
Some(NameRefClass::Definition(def)) if def == self.def => { Some(NameRefClass::Definition(def))
if convert_to_def_in_trait(self.sema.db, def) == self.def =>
{
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let reference = FileReference { let reference = FileReference {
range, range,

View file

@ -1,7 +1,7 @@
//! Functionality for obtaining data related to traits from the DB. //! Functionality for obtaining data related to traits from the DB.
use crate::RootDatabase; use crate::{defs::Definition, RootDatabase};
use hir::Semantics; use hir::{db::HirDatabase, AsAssocItem, Semantics};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use syntax::{ast, AstNode}; use syntax::{ast, AstNode};
@ -69,6 +69,28 @@ pub fn get_missing_assoc_items(
}) })
} }
/// Converts associated trait impl items to their trait definition counterpart
pub(crate) fn convert_to_def_in_trait(db: &dyn HirDatabase, def: Definition) -> Definition {
use hir::AssocItem::*;
(|| {
let assoc = def.as_assoc_item(db)?;
let trait_ = assoc.containing_trait_impl(db)?;
let name = match assoc {
Function(it) => it.name(db),
Const(it) => it.name(db)?,
TypeAlias(it) => it.name(db),
};
let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
(Function(trait_func), Function(_)) => trait_func.name(db) == name,
(Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
(TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
_ => false,
})?;
Some(Definition::from(item))
})()
.unwrap_or(def)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use base_db::{fixture::ChangeFixture, FilePosition}; use base_db::{fixture::ChangeFixture, FilePosition};

View file

@ -1,7 +1,7 @@
use std::convert::TryInto; use std::{convert::TryInto, mem::discriminant};
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)
.unwrap_or_else(|| def_to_nav(sema.db, def)) .unwrap_or_else(|| def_to_nav(sema.db, def))
}) })
.collect(), .collect(),
@ -104,32 +104,37 @@ 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,
) -> 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 {
let imp = match assoc.container(db) { AssocItem::Function(..) => None,
hir::AssocItemContainer::Impl(imp) => imp, AssocItem::Const(..) | AssocItem::TypeAlias(..) => {
_ => return None, let imp = match assoc.container(db) {
}; hir::AssocItemContainer::Impl(imp) => imp,
_ => return None,
let trait_ = imp.trait_(db)?; };
trait_ let trait_ = imp.trait_(db)?;
.items(db) let name = def.name(db)?;
.iter() let discri_value = discriminant(&assoc);
.find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten()) trait_
.map(|it| vec![it]) .items(db)
.iter()
.filter(|itm| discriminant(*itm) == discri_value)
.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> { fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
@ -172,6 +177,23 @@ mod tests {
assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs) assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs)
} }
#[test]
fn goto_def_if_items_same_name() {
check(
r#"
trait Trait {
type A;
const A: i32;
//^
}
struct T;
impl Trait for T {
type A = i32;
const A$0: i32 = -9;
}"#,
);
}
#[test] #[test]
fn goto_def_in_mac_call_in_attr_invoc() { fn goto_def_in_mac_call_in_attr_invoc() {
check( check(
@ -1331,23 +1353,161 @@ fn main() {
"#, "#,
); );
} }
#[cfg(test)]
#[test] mod goto_impl_of_trait_fn {
fn goto_def_of_trait_impl_fn() { use super::check;
check( #[test]
r#" fn cursor_on_impl() {
check(
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]
fn wc_case_is_ok() {
check(
r#"
trait G {
fn g(&self);
}
trait BParent{}
trait Bound: BParent{}
struct Gen<T>(T);
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]
fn method_call_defaulted() {
check(
r#"
trait Twait {
fn a(&self) {}
//^
}
struct Stwuct;
impl Twait for Stwuct {
}
fn f() {
let s = Stwuct;
s.a$0();
}
"#,
);
}
#[test]
fn method_call_on_generic() {
check(
r#"
trait Twait {
fn a(&self) {}
//^
}
fn f<T: Twait>(s: T) {
s.a$0();
}
"#,
);
}
} }
#[test] #[test]

View file

@ -510,7 +510,11 @@ fn highlight_method_call(
if func.is_async(sema.db) { if func.is_async(sema.db) {
h |= HlMod::Async; h |= HlMod::Async;
} }
if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() { if func
.as_assoc_item(sema.db)
.and_then(|it| it.containing_trait_or_trait_impl(sema.db))
.is_some()
{
h |= HlMod::Trait; h |= HlMod::Trait;
} }

View file

@ -919,7 +919,7 @@ pub fn foo(_input: TokenStream) -> TokenStream {
expect![[r#" expect![[r#"
```rust ```rust
foo::Bar foo::Foo
``` ```
```rust ```rust