Skip to content

Commit

Permalink
Auto merge of #12549 - bitgaoshu:goto_where_trait_m_impl, r=Veykril
Browse files Browse the repository at this point in the history
feat: Go to implementation of trait methods

try goto where the trait method implies,  #4558
  • Loading branch information
bors committed Jul 18, 2022
2 parents e01de19 + dcb4837 commit c40a60b
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 120 deletions.
123 changes: 86 additions & 37 deletions crates/hir-ty/src/method_resolution.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -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())
}

Expand Down Expand Up @@ -575,6 +576,59 @@ pub(crate) fn iterate_method_candidates<T>(
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(
ty: &Canonical<Ty>,
db: &dyn HirDatabase,
Expand Down Expand Up @@ -970,18 +1024,31 @@ fn is_valid_candidate(
self_ty: &Ty,
visible_from_module: Option<ModuleId>,
) -> bool {
macro_rules! check_that {
($cond:expr) => {
if !$cond {
return false;
}
};
}

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;

check_that!(name.map_or(true, |n| n == &data.name));
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| {
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(_) => {
subst.at(Interner, 0).assert_ty_ref(Interner).clone()
}
Expand All @@ -993,49 +1060,31 @@ fn is_valid_candidate(
unreachable!()
}
};
if !table.unify(&expected_self_ty, &self_ty) {
return false;
}
check_that!(table.unify(&expect_self_ty, self_ty));
if let Some(receiver_ty) = receiver_ty {
if !data.has_self_param() {
return false;
}
check_that!(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);
let receiver_matches = table.unify(&receiver_ty, &expected_receiver);

if !receiver_matches {
return false;
}
check_that!(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);
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) {
check_that!(receiver_ty.is_none());

check_that!(name.map_or(true, |n| data.name.as_ref() == Some(n)));
check_that!(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);
return false;
}
}
v
}));
if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container {
let self_ty_matches = table.run_in_snapshot(|table| {
let subst =
Expand Down
12 changes: 2 additions & 10 deletions crates/hir/src/semantics.rs
Expand Up @@ -16,7 +16,6 @@ use hir_expand::{
name::{known, AsName},
ExpansionInfo, MacroCallId,
};
use hir_ty::Interner;
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
Expand Down Expand Up @@ -975,18 +974,11 @@ impl<'db> SemanticsImpl<'db> {
}

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> {
let source_analyzer = self.analyze(call.syntax())?;
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)
self.analyze(call.syntax())?.resolve_method_call_as_callable(self.db, call)
}

fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> {
Expand Down
74 changes: 67 additions & 7 deletions crates/hir/src/source_analyzer.rs
Expand Up @@ -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, AssocItemId, DefWithBodyId, FieldId, FunctionId, ItemContainerId, LocalFieldId,
Lookup, ModuleDefId, VariantId,
};
use hir_expand::{
builtin_fn_macro::BuiltinFnLikeExpander, hygiene::Hygiene, name::AsName, HirFileId, InFile,
Expand All @@ -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::{
Expand All @@ -42,8 +43,8 @@ use syntax::{

use crate::{
db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr,
BuiltinType, Const, Field, Function, Local, Macro, ModuleDef, Static, Struct, ToolModule,
Trait, Type, TypeAlias, Variant,
BuiltinType, Callable, Const, Field, Function, Local, Macro, ModuleDef, Static, Struct,
ToolModule, Trait, Type, TypeAlias, Variant,
};

/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
Expand Down Expand Up @@ -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(
&self,
db: &dyn HirDatabase,
call: &ast::MethodCallExpr,
) -> Option<(FunctionId, Substitution)> {
) -> Option<FunctionId> {
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(
Expand Down Expand Up @@ -336,6 +353,25 @@ impl SourceAnalyzer {
let expr_id = self.expr_id(db, &path_expr.into())?;
let infer = self.infer.as_ref()?;
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()));
}
if let Some(VariantId::EnumVariantId(variant)) =
Expand Down Expand Up @@ -563,6 +599,30 @@ impl SourceAnalyzer {
}
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(
Expand Down
27 changes: 22 additions & 5 deletions crates/ide-assists/src/handlers/qualify_method_call.rs
@@ -1,8 +1,5 @@
use hir::{ItemInNs, ModuleDef};
use ide_db::{
assists::{AssistId, AssistKind},
imports::import_assets::item_for_path_search,
};
use hir::{db::HirDatabase, AsAssocItem, AssocItem, AssocItemContainer, ItemInNs, ModuleDef};
use ide_db::assists::{AssistId, AssistKind};
use syntax::{ast, AstNode};

use crate::{
Expand Down Expand Up @@ -67,6 +64,26 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext) -> Opt
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)]
mod tests {
use super::*;
Expand Down
15 changes: 15 additions & 0 deletions crates/ide-assists/src/utils/suggest_name.rs
Expand Up @@ -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]
fn method_ufcs() {
check(
Expand Down
2 changes: 1 addition & 1 deletion crates/ide-db/src/imports/import_assets.rs
Expand Up @@ -401,7 +401,7 @@ fn import_for_item(
})
}

pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
fn item_for_path_search(db: &RootDatabase, 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) {
Expand Down

0 comments on commit c40a60b

Please sign in to comment.