mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 14:03:35 +00:00
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:
commit
22e53f1d33
11 changed files with 419 additions and 130 deletions
|
@ -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 =
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -919,7 +919,7 @@ pub fn foo(_input: TokenStream) -> TokenStream {
|
||||||
expect![[r#"
|
expect![[r#"
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
foo::Bar
|
foo::Foo
|
||||||
```
|
```
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
Loading…
Reference in a new issue