Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 86 additions & 37 deletions crates/hir-ty/src/method_resolution.rs
Original file line number Diff line number Diff line change
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));

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading