Refactor to unify with method resolution

This commit is contained in:
Florian Diebold 2019-10-31 19:28:33 +01:00
parent c7cedea270
commit 1173c3dab5
7 changed files with 117 additions and 141 deletions

View file

@ -1053,4 +1053,13 @@ impl AssocItem {
AssocItem::TypeAlias(t) => t.module(db), AssocItem::TypeAlias(t) => t.module(db),
} }
} }
pub fn container(self, db: &impl DefDatabase) -> Container {
match self {
AssocItem::Function(f) => f.container(db),
AssocItem::Const(c) => c.container(db),
AssocItem::TypeAlias(t) => t.container(db),
}
.expect("AssocItem without container")
}
} }

View file

@ -77,9 +77,10 @@ impl GenericParams {
let parent = match def { let parent = match def {
GenericDef::Function(it) => it.container(db).map(GenericDef::from), GenericDef::Function(it) => it.container(db).map(GenericDef::from),
GenericDef::TypeAlias(it) => it.container(db).map(GenericDef::from), GenericDef::TypeAlias(it) => it.container(db).map(GenericDef::from),
GenericDef::Const(it) => it.container(db).map(GenericDef::from),
GenericDef::EnumVariant(it) => Some(it.parent_enum(db).into()), GenericDef::EnumVariant(it) => Some(it.parent_enum(db).into()),
GenericDef::Adt(_) | GenericDef::Trait(_) => None, GenericDef::Adt(_) | GenericDef::Trait(_) => None,
GenericDef::ImplBlock(_) | GenericDef::Const(_) => None, GenericDef::ImplBlock(_) => None,
}; };
let mut generics = GenericParams { let mut generics = GenericParams {
def, def,

View file

@ -28,8 +28,8 @@ use crate::{
ids::LocationCtx, ids::LocationCtx,
resolve::{ScopeDef, TypeNs, ValueNs}, resolve::{ScopeDef, TypeNs, ValueNs},
ty::method_resolution::implements_trait, ty::method_resolution::implements_trait,
Const, DefWithBody, Either, Enum, FromSource, Function, HasBody, HirFileId, MacroDef, Module, AssocItem, Const, DefWithBody, Either, Enum, FromSource, Function, HasBody, HirFileId,
Name, Path, Resolver, Static, Struct, Ty, MacroDef, Module, Name, Path, Resolver, Static, Struct, Ty,
}; };
fn try_get_resolver_for_node( fn try_get_resolver_for_node(
@ -327,7 +327,7 @@ impl SourceAnalyzer {
db: &impl HirDatabase, db: &impl HirDatabase,
ty: Ty, ty: Ty,
name: Option<&Name>, name: Option<&Name>,
callback: impl FnMut(&Ty, Function) -> Option<T>, callback: impl FnMut(&Ty, AssocItem) -> Option<T>,
) -> Option<T> { ) -> Option<T> {
// There should be no inference vars in types passed here // There should be no inference vars in types passed here
// FIXME check that? // FIXME check that?
@ -337,6 +337,7 @@ impl SourceAnalyzer {
db, db,
&self.resolver, &self.resolver,
name, name,
crate::ty::method_resolution::LookupMode::MethodCall,
callback, callback,
) )
} }

View file

@ -6,11 +6,9 @@ use super::{ExprOrPatId, InferenceContext, TraitRef};
use crate::{ use crate::{
db::HirDatabase, db::HirDatabase,
resolve::{ResolveValueResult, Resolver, TypeNs, ValueNs}, resolve::{ResolveValueResult, Resolver, TypeNs, ValueNs},
ty::{lower, traits::TraitEnvironment, Canonical}, ty::{method_resolution, Substs, Ty, TypableDef, TypeWalk},
ty::{Substs, Ty, TypableDef, TypeWalk}, AssocItem, Container, HasGenericParams, Name, Namespace, Path,
AssocItem, HasGenericParams, Name, Namespace, Path, Trait,
}; };
use std::sync::Arc;
impl<'a, D: HirDatabase> InferenceContext<'a, D> { impl<'a, D: HirDatabase> InferenceContext<'a, D> {
pub(super) fn infer_path( pub(super) fn infer_path(
@ -184,91 +182,34 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
return None; return None;
} }
self.find_inherent_assoc_candidate(ty.clone(), name, id)
.or_else(|| self.find_trait_assoc_candidate(ty.clone(), name, id))
}
fn find_inherent_assoc_candidate(
&mut self,
ty: Ty,
name: &Name,
id: ExprOrPatId,
) -> Option<(ValueNs, Option<Substs>)> {
let krate = self.resolver.krate()?;
// Find impl
let item = ty.clone().iterate_impl_items(self.db, krate, |item| match item {
AssocItem::Function(func) => {
if *name == func.name(self.db) {
Some(AssocItem::Function(func))
} else {
None
}
}
AssocItem::Const(konst) => {
if konst.name(self.db).map_or(false, |n| n == *name) {
Some(AssocItem::Const(konst))
} else {
None
}
}
AssocItem::TypeAlias(_) => None,
})?;
let def = match item {
AssocItem::Function(f) => ValueNs::Function(f),
AssocItem::Const(c) => ValueNs::Const(c),
AssocItem::TypeAlias(_) => unreachable!(),
};
let substs = self.find_self_types(&def, ty);
self.write_assoc_resolution(id, item);
Some((def, substs))
}
fn find_trait_assoc_candidate(
&mut self,
ty: Ty,
name: &Name,
id: ExprOrPatId,
) -> Option<(ValueNs, Option<Substs>)> {
let krate = self.resolver.krate()?;
let canonical_ty = self.canonicalizer().canonicalize_ty(ty.clone()); let canonical_ty = self.canonicalizer().canonicalize_ty(ty.clone());
let env = lower::trait_env(self.db, &self.resolver); method_resolution::iterate_method_candidates(
// if we have `T: Trait` in the param env, the trait doesn't need to be in scope &canonical_ty.value,
let traits_from_env = env self.db,
.trait_predicates_for_self_ty(&ty) &self.resolver.clone(),
.map(|tr| tr.trait_) Some(name),
.flat_map(|t| t.all_super_traits(self.db)); method_resolution::LookupMode::Path,
let traits = traits_from_env.chain(self.resolver.traits_in_scope(self.db)); move |_ty, item| {
let def = match item {
'traits: for t in traits { AssocItem::Function(f) => ValueNs::Function(f),
let data = t.trait_data(self.db); AssocItem::Const(c) => ValueNs::Const(c),
let mut known_implemented = false; AssocItem::TypeAlias(_) => unreachable!(),
for item in data.items() { };
if let AssocItem::Function(f) = *item { match item.container(self.db) {
if f.name(self.db) == *name { Container::ImplBlock(_) => {
if !known_implemented { let substs = self.find_self_types(&def, ty.clone());
let goal = generic_implements_goal(
self.db,
env.clone(),
t,
canonical_ty.value.clone(),
);
if self.db.trait_solve(krate, goal).is_none() {
continue 'traits;
}
}
known_implemented = true;
self.write_assoc_resolution(id, item);
Some((def, substs))
}
Container::Trait(t) => {
// we're picking this method // we're picking this method
let trait_substs = Substs::build_for_def(self.db, t) let trait_substs = Substs::build_for_def(self.db, t)
.push(ty.clone()) .push(ty.clone())
.fill(std::iter::repeat_with(|| self.new_type_var())) .fill(std::iter::repeat_with(|| self.new_type_var()))
.build(); .build();
let substs = Substs::build_for_def(self.db, f) let substs = Substs::build_for_def(self.db, item)
.use_parent_substs(&trait_substs) .use_parent_substs(&trait_substs)
.fill_with_params() .fill_with_params()
.build(); .build();
@ -277,14 +218,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
substs: trait_substs, substs: trait_substs,
})); }));
self.write_assoc_resolution(id, *item); self.write_assoc_resolution(id, item);
return Some((ValueNs::Function(f), Some(substs))); Some((def, Some(substs)))
} }
} }
} },
} )
None
} }
fn find_self_types(&self, def: &ValueNs, actual_def_ty: Ty) -> Option<Substs> { fn find_self_types(&self, def: &ValueNs, actual_def_ty: Ty) -> Option<Substs> {
@ -317,23 +256,3 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
} }
} }
} }
// TODO remove duplication
/// This creates Substs for a trait with the given Self type and type variables
/// for all other parameters, to query Chalk with it.
fn generic_implements_goal(
db: &impl HirDatabase,
env: Arc<TraitEnvironment>,
trait_: Trait,
self_ty: Canonical<Ty>,
) -> Canonical<super::InEnvironment<super::Obligation>> {
let num_vars = self_ty.num_vars;
let substs = super::Substs::build_for_def(db, trait_)
.push(self_ty.value)
.fill_with_bound_vars(num_vars as u32)
.build();
let num_vars = substs.len() - 1 + self_ty.num_vars;
let trait_ref = TraitRef { trait_, substs };
let obligation = super::Obligation::Trait(trait_ref);
Canonical { num_vars, value: super::InEnvironment::new(env, obligation) }
}

View file

@ -166,7 +166,19 @@ pub(crate) fn lookup_method(
name: &Name, name: &Name,
resolver: &Resolver, resolver: &Resolver,
) -> Option<(Ty, Function)> { ) -> Option<(Ty, Function)> {
iterate_method_candidates(ty, db, resolver, Some(name), |ty, f| Some((ty.clone(), f))) iterate_method_candidates(ty, db, resolver, Some(name), LookupMode::MethodCall, |ty, f| {
if let AssocItem::Function(f) = f {
Some((ty.clone(), f))
} else {
None
}
})
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum LookupMode {
MethodCall,
Path,
} }
// This would be nicer if it just returned an iterator, but that runs into // This would be nicer if it just returned an iterator, but that runs into
@ -176,7 +188,8 @@ pub(crate) fn iterate_method_candidates<T>(
db: &impl HirDatabase, db: &impl HirDatabase,
resolver: &Resolver, resolver: &Resolver,
name: Option<&Name>, name: Option<&Name>,
mut callback: impl FnMut(&Ty, Function) -> Option<T>, mode: LookupMode,
mut callback: impl FnMut(&Ty, AssocItem) -> Option<T>,
) -> Option<T> { ) -> Option<T> {
// For method calls, rust first does any number of autoderef, and then one // For method calls, rust first does any number of autoderef, and then one
// autoref (i.e. when the method takes &self or &mut self). We just ignore // autoref (i.e. when the method takes &self or &mut self). We just ignore
@ -188,13 +201,15 @@ pub(crate) fn iterate_method_candidates<T>(
// rustc does an autoderef and then autoref again). // rustc does an autoderef and then autoref again).
let krate = resolver.krate()?; let krate = resolver.krate()?;
// TODO no autoderef in LookupMode::Path
for derefed_ty in autoderef::autoderef(db, resolver, ty.clone()) { for derefed_ty in autoderef::autoderef(db, resolver, ty.clone()) {
if let Some(result) = iterate_inherent_methods(&derefed_ty, db, name, krate, &mut callback) if let Some(result) =
iterate_inherent_methods(&derefed_ty, db, name, mode, krate, &mut callback)
{ {
return Some(result); return Some(result);
} }
if let Some(result) = if let Some(result) =
iterate_trait_method_candidates(&derefed_ty, db, resolver, name, &mut callback) iterate_trait_method_candidates(&derefed_ty, db, resolver, name, mode, &mut callback)
{ {
return Some(result); return Some(result);
} }
@ -207,7 +222,8 @@ fn iterate_trait_method_candidates<T>(
db: &impl HirDatabase, db: &impl HirDatabase,
resolver: &Resolver, resolver: &Resolver,
name: Option<&Name>, name: Option<&Name>,
mut callback: impl FnMut(&Ty, Function) -> Option<T>, mode: LookupMode,
mut callback: impl FnMut(&Ty, AssocItem) -> Option<T>,
) -> Option<T> { ) -> Option<T> {
let krate = resolver.krate()?; let krate = resolver.krate()?;
// FIXME: maybe put the trait_env behind a query (need to figure out good input parameters for that) // FIXME: maybe put the trait_env behind a query (need to figure out good input parameters for that)
@ -231,21 +247,35 @@ fn iterate_trait_method_candidates<T>(
// trait, but if we find out it doesn't, we'll skip the rest of the // trait, but if we find out it doesn't, we'll skip the rest of the
// iteration // iteration
let mut known_implemented = inherently_implemented; let mut known_implemented = inherently_implemented;
for item in data.items() { for &item in data.items() {
if let AssocItem::Function(m) = *item { // TODO unify with the impl case
let data = m.data(db); match item {
if name.map_or(true, |name| data.name() == name) && data.has_self_param() { AssocItem::Function(m) => {
if !known_implemented { let data = m.data(db);
let goal = generic_implements_goal(db, env.clone(), t, ty.clone()); if !name.map_or(true, |name| data.name() == name)
if db.trait_solve(krate, goal).is_none() { || (!data.has_self_param() && mode != LookupMode::Path)
continue 'traits; {
} continue;
}
known_implemented = true;
if let Some(result) = callback(&ty.value, m) {
return Some(result);
} }
} }
AssocItem::Const(c) => {
if !name.map_or(true, |name| Some(name) == c.name(db).as_ref())
|| (mode != LookupMode::Path)
{
continue;
}
}
_ => {}
};
if !known_implemented {
let goal = generic_implements_goal(db, env.clone(), t, ty.clone());
if db.trait_solve(krate, goal).is_none() {
continue 'traits;
}
}
known_implemented = true;
if let Some(result) = callback(&ty.value, item) {
return Some(result);
} }
} }
} }
@ -256,21 +286,35 @@ fn iterate_inherent_methods<T>(
ty: &Canonical<Ty>, ty: &Canonical<Ty>,
db: &impl HirDatabase, db: &impl HirDatabase,
name: Option<&Name>, name: Option<&Name>,
mode: LookupMode,
krate: Crate, krate: Crate,
mut callback: impl FnMut(&Ty, Function) -> Option<T>, mut callback: impl FnMut(&Ty, AssocItem) -> Option<T>,
) -> Option<T> { ) -> Option<T> {
for krate in def_crates(db, krate, &ty.value)? { for krate in def_crates(db, krate, &ty.value)? {
let impls = db.impls_in_crate(krate); let impls = db.impls_in_crate(krate);
for impl_block in impls.lookup_impl_blocks(&ty.value) { for impl_block in impls.lookup_impl_blocks(&ty.value) {
for item in impl_block.items(db) { for item in impl_block.items(db) {
if let AssocItem::Function(f) = item { match item {
let data = f.data(db); AssocItem::Function(f) => {
if name.map_or(true, |name| data.name() == name) && data.has_self_param() { let data = f.data(db);
if let Some(result) = callback(&ty.value, f) { if !name.map_or(true, |name| data.name() == name)
return Some(result); || (!data.has_self_param() && mode != LookupMode::Path)
{
continue;
} }
} }
AssocItem::Const(c) => {
if !name.map_or(true, |name| Some(name) == c.name(db).as_ref())
|| (mode != LookupMode::Path)
{
continue;
}
}
_ => {}
}
if let Some(result) = callback(&ty.value, item) {
return Some(result);
} }
} }
} }

View file

@ -1841,8 +1841,8 @@ fn test() {
[243; 254) 'Struct::FOO': u32 [243; 254) 'Struct::FOO': u32
[264; 265) 'y': u32 [264; 265) 'y': u32
[268; 277) 'Enum::BAR': u32 [268; 277) 'Enum::BAR': u32
[287; 288) 'z': {unknown} [287; 288) 'z': u32
[291; 304) 'TraitTest::ID': {unknown} [291; 304) 'TraitTest::ID': u32
"### "###
); );
} }

View file

@ -58,10 +58,12 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty)
fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) { fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) {
let mut seen_methods = FxHashSet::default(); let mut seen_methods = FxHashSet::default();
ctx.analyzer.iterate_method_candidates(ctx.db, receiver, None, |_ty, func| { ctx.analyzer.iterate_method_candidates(ctx.db, receiver, None, |_ty, item| {
let data = func.data(ctx.db); if let hir::AssocItem::Function(func) = item {
if data.has_self_param() && seen_methods.insert(data.name().clone()) { let data = func.data(ctx.db);
acc.add_function(ctx, func); if data.has_self_param() && seen_methods.insert(data.name().clone()) {
acc.add_function(ctx, func);
}
} }
None::<()> None::<()>
}); });