Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Go to implementation of trait methods #12549

Merged
merged 16 commits into from Jul 18, 2022
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.into_well_formed_goal(Interner).cast(Interner));
bitgaoshu marked this conversation as resolved.
Show resolved Hide resolved

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
54 changes: 50 additions & 4 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 Down Expand Up @@ -244,7 +245,9 @@ impl SourceAnalyzer {
call: &ast::MethodCallExpr,
) -> Option<(FunctionId, Substitution)> {
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);
Some((f_in_impl.unwrap_or(f_in_trait), substs))
}

pub(crate) fn resolve_field(
Expand Down Expand Up @@ -342,6 +345,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 @@ -567,6 +589,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
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