From 9c5e7dd849eff7bd6f20aa353feef083d089ff58 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sun, 12 May 2019 18:33:47 +0200 Subject: Implement autoderef using the Deref trait - add support for other lang item targets, since we need the Deref lang item --- crates/ra_assists/src/fill_match_arms.rs | 2 +- crates/ra_hir/src/lang_item.rs | 55 +++++++++++++++---- crates/ra_hir/src/name.rs | 5 ++ crates/ra_hir/src/source_binder.rs | 11 ++++ crates/ra_hir/src/ty.rs | 5 +- crates/ra_hir/src/ty/autoderef.rs | 68 ++++++++++++++++++++---- crates/ra_hir/src/ty/infer.rs | 40 +++++++------- crates/ra_hir/src/ty/method_resolution.rs | 5 +- crates/ra_hir/src/ty/traits.rs | 4 +- crates/ra_ide_api/src/completion/complete_dot.rs | 2 +- crates/ra_ide_api/src/goto_type_definition.rs | 2 +- 11 files changed, 152 insertions(+), 47 deletions(-) diff --git a/crates/ra_assists/src/fill_match_arms.rs b/crates/ra_assists/src/fill_match_arms.rs index a7de6ae9f..c7a8bce20 100644 --- a/crates/ra_assists/src/fill_match_arms.rs +++ b/crates/ra_assists/src/fill_match_arms.rs @@ -22,7 +22,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx) -> Option Some(e), _ => None, })?; diff --git a/crates/ra_hir/src/lang_item.rs b/crates/ra_hir/src/lang_item.rs index cdc9182d6..ada8aeb5b 100644 --- a/crates/ra_hir/src/lang_item.rs +++ b/crates/ra_hir/src/lang_item.rs @@ -4,7 +4,7 @@ use rustc_hash::FxHashMap; use ra_syntax::{SmolStr, ast::AttrsOwner}; use crate::{ - Crate, DefDatabase, Enum, Function, HirDatabase, ImplBlock, Module, Static, Struct, Trait, AstDatabase, + Crate, DefDatabase, Enum, Function, HirDatabase, ImplBlock, Module, Static, Struct, Trait, ModuleDef, AstDatabase, HasSource }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -87,19 +87,48 @@ impl LangItems { let source = module.definition_source(db).ast; for (impl_id, _) in impl_blocks.impls.iter() { let impl_block = source_map.get(&source, impl_id); - let lang_item_name = impl_block - .attrs() - .filter_map(|a| a.as_key_value()) - .filter(|(key, _)| key == "lang") - .map(|(_, val)| val) - .nth(0); - if let Some(lang_item_name) = lang_item_name { + if let Some(lang_item_name) = lang_item_name(&*impl_block) { let imp = ImplBlock::from_id(*module, impl_id); self.items.entry(lang_item_name).or_insert_with(|| LangItemTarget::ImplBlock(imp)); } } - // FIXME we should look for the other lang item targets (traits, structs, ...) + // FIXME make this nicer + for def in module.declarations(db) { + match def { + ModuleDef::Trait(trait_) => { + let node = trait_.source(db).ast; + if let Some(lang_item_name) = lang_item_name(&*node) { + self.items.entry(lang_item_name).or_insert(LangItemTarget::Trait(trait_)); + } + } + ModuleDef::Enum(e) => { + let node = e.source(db).ast; + if let Some(lang_item_name) = lang_item_name(&*node) { + self.items.entry(lang_item_name).or_insert(LangItemTarget::Enum(e)); + } + } + ModuleDef::Struct(s) => { + let node = s.source(db).ast; + if let Some(lang_item_name) = lang_item_name(&*node) { + self.items.entry(lang_item_name).or_insert(LangItemTarget::Struct(s)); + } + } + ModuleDef::Function(f) => { + let node = f.source(db).ast; + if let Some(lang_item_name) = lang_item_name(&*node) { + self.items.entry(lang_item_name).or_insert(LangItemTarget::Function(f)); + } + } + ModuleDef::Static(s) => { + let node = s.source(db).ast; + if let Some(lang_item_name) = lang_item_name(&*node) { + self.items.entry(lang_item_name).or_insert(LangItemTarget::Static(s)); + } + } + _ => {} + } + } // Look for lang items in the children for child in module.children(db) { @@ -107,3 +136,11 @@ impl LangItems { } } } + +fn lang_item_name(node: &T) -> Option { + node.attrs() + .filter_map(|a| a.as_key_value()) + .filter(|(key, _)| key == "lang") + .map(|(_, val)| val) + .nth(0) +} diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs index e9003e00b..ba17958eb 100644 --- a/crates/ra_hir/src/name.rs +++ b/crates/ra_hir/src/name.rs @@ -46,6 +46,11 @@ impl Name { Name::new(idx.to_string().into()) } + // Needed for Deref + pub(crate) fn target() -> Name { + Name::new("Target".into()) + } + // There's should be no way to extract a string out of `Name`: `Name` in the // future, `Name` will include hygiene information, and you can't encode // hygiene into a String. diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 4f9e8c5a9..08e86844d 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -369,6 +369,17 @@ impl SourceAnalyzer { ) } + pub fn autoderef<'a>( + &'a self, + db: &'a impl HirDatabase, + ty: Ty, + ) -> impl Iterator + 'a { + // There should be no inference vars in types passed here + // FIXME check that? + let canonical = crate::ty::Canonical { value: ty, num_vars: 0 }; + crate::ty::autoderef(db, &self.resolver, canonical).map(|canonical| canonical.value) + } + #[cfg(test)] pub(crate) fn body_source_map(&self) -> Arc { self.body_source_map.clone().unwrap() diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index d9a50b230..d2f92a1f2 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -22,6 +22,7 @@ use display::{HirDisplay, HirFormatter}; pub(crate) use lower::{TypableDef, type_for_def, type_for_field, callable_item_sig, generic_predicates, generic_defaults}; pub(crate) use infer::{infer_query, InferenceResult, InferTy}; pub use lower::CallableDef; +pub(crate) use autoderef::autoderef; /// A type constructor or type name: this might be something like the primitive /// type `bool`, a struct like `Vec`, or things like function pointers or @@ -225,8 +226,8 @@ impl Deref for Substs { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct TraitRef { /// FIXME name? - trait_: Trait, - substs: Substs, + pub trait_: Trait, + pub substs: Substs, } impl TraitRef { diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs index a442a856c..bee756d80 100644 --- a/crates/ra_hir/src/ty/autoderef.rs +++ b/crates/ra_hir/src/ty/autoderef.rs @@ -5,17 +5,67 @@ use std::iter::successors; -use crate::HirDatabase; -use super::Ty; +use log::info; -impl Ty { - /// Iterates over the possible derefs of `ty`. - pub fn autoderef<'a>(self, db: &'a impl HirDatabase) -> impl Iterator + 'a { - successors(Some(self), move |ty| ty.autoderef_step(db)) +use crate::{HirDatabase, Name, Resolver}; +use super::{traits::Solution, Ty, Canonical}; + +pub(crate) fn autoderef<'a>( + db: &'a impl HirDatabase, + resolver: &'a Resolver, + ty: Canonical, +) -> impl Iterator> + 'a { + successors(Some(ty), move |ty| deref(db, resolver, ty)) +} + +pub(crate) fn deref( + db: &impl HirDatabase, + resolver: &Resolver, + ty: &Canonical, +) -> Option> { + if let Some(derefed) = ty.value.builtin_deref() { + Some(Canonical { value: derefed, num_vars: ty.num_vars }) + } else { + deref_by_trait(db, resolver, ty) } +} + +fn deref_by_trait( + db: &impl HirDatabase, + resolver: &Resolver, + ty: &Canonical, +) -> Option> { + let krate = resolver.krate()?; + let deref_trait = match db.lang_item(krate, "deref".into())? { + crate::lang_item::LangItemTarget::Trait(t) => t, + _ => return None, + }; + let target = deref_trait.associated_type_by_name(db, Name::target())?; + + // FIXME we should check that Deref has no type parameters, because we assume it below + + // FIXME make the Canonical handling nicer + // TODO shift inference variables in ty + + let projection = super::traits::ProjectionPredicate { + ty: Ty::Bound(0), + projection_ty: super::ProjectionTy { + associated_ty: target, + parameters: vec![ty.value.clone()].into(), + }, + }; + + let canonical = super::Canonical { num_vars: 1 + ty.num_vars, value: projection }; + + let solution = db.normalize(krate, canonical)?; - fn autoderef_step(&self, _db: &impl HirDatabase) -> Option { - // FIXME Deref::deref - self.builtin_deref() + match &solution { + Solution::Unique(vars) => { + Some(Canonical { value: vars.0.value[0].clone(), num_vars: vars.0.num_vars }) + } + Solution::Ambig(_) => { + info!("Ambiguous solution for deref: {:?}", solution); + None + } } } diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index e150d7fd8..fdb444de2 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -46,7 +46,7 @@ use crate::{ use super::{ Ty, TypableDef, Substs, primitive, op, ApplicationTy, TypeCtor, CallableDef, TraitRef, traits::{Solution, Obligation, Guidance}, - method_resolution, + method_resolution, autoderef, }; mod unify; @@ -1074,25 +1074,27 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { } Expr::Field { expr, name } => { let receiver_ty = self.infer_expr(*expr, &Expectation::none()); - let ty = receiver_ty - .autoderef(self.db) - .find_map(|derefed_ty| match derefed_ty { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::Tuple { .. } => { - let i = name.to_string().parse::().ok(); - i.and_then(|i| a_ty.parameters.0.get(i).cloned()) - } - TypeCtor::Adt(AdtDef::Struct(s)) => { - s.field(self.db, name).map(|field| { - self.write_field_resolution(tgt_expr, field); - field.ty(self.db).subst(&a_ty.parameters) - }) - } - _ => None, - }, + let canonicalized = self.canonicalizer().canonicalize_ty(receiver_ty); + let ty = autoderef::autoderef( + self.db, + &self.resolver.clone(), + canonicalized.value.clone(), + ) + .find_map(|derefed_ty| match canonicalized.decanonicalize_ty(derefed_ty.value) { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Tuple { .. } => { + let i = name.to_string().parse::().ok(); + i.and_then(|i| a_ty.parameters.0.get(i).cloned()) + } + TypeCtor::Adt(AdtDef::Struct(s)) => s.field(self.db, name).map(|field| { + self.write_field_resolution(tgt_expr, field); + field.ty(self.db).subst(&a_ty.parameters) + }), _ => None, - }) - .unwrap_or(Ty::Unknown); + }, + _ => None, + }) + .unwrap_or(Ty::Unknown); self.insert_type_vars(ty) } Expr::Try { expr } => { diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 646e58aa9..ad26d591c 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -16,7 +16,7 @@ use crate::{ generics::HasGenericParams, ty::primitive::{UncertainIntTy, UncertainFloatTy} }; -use super::{TraitRef, Canonical}; +use super::{TraitRef, Canonical, autoderef}; /// This is used as a key for indexing impls. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -162,8 +162,7 @@ pub(crate) fn iterate_method_candidates( // rustc does an autoderef and then autoref again). let krate = resolver.krate()?; - for derefed_ty in ty.value.clone().autoderef(db) { - let derefed_ty = Canonical { value: derefed_ty, num_vars: ty.num_vars }; + for derefed_ty in autoderef::autoderef(db, resolver, ty.clone()) { if let Some(result) = iterate_inherent_methods(&derefed_ty, db, name, krate, &mut callback) { return Some(result); diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index f3e488403..6cf3dd70a 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -80,8 +80,8 @@ pub enum Obligation { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ProjectionPredicate { - projection_ty: ProjectionTy, - ty: Ty, + pub projection_ty: ProjectionTy, + pub ty: Ty, } /// Check using Chalk whether trait is implemented for given parameters including `Self` type. diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs index 0822a0e7e..f26fd06b3 100644 --- a/crates/ra_ide_api/src/completion/complete_dot.rs +++ b/crates/ra_ide_api/src/completion/complete_dot.rs @@ -15,7 +15,7 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { } fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) { - for receiver in receiver.autoderef(ctx.db) { + for receiver in ctx.analyzer.autoderef(ctx.db, receiver) { if let Ty::Apply(a_ty) = receiver { match a_ty.ctor { TypeCtor::Adt(AdtDef::Struct(s)) => { diff --git a/crates/ra_ide_api/src/goto_type_definition.rs b/crates/ra_ide_api/src/goto_type_definition.rs index 0f638b170..6f5164e0b 100644 --- a/crates/ra_ide_api/src/goto_type_definition.rs +++ b/crates/ra_ide_api/src/goto_type_definition.rs @@ -30,7 +30,7 @@ pub(crate) fn goto_type_definition( return None; }; - let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt().map(|adt| adt.0))?; + let adt_def = analyzer.autoderef(db, ty).find_map(|ty| ty.as_adt().map(|adt| adt.0))?; let nav = NavigationTarget::from_adt_def(db, adt_def); Some(RangeInfo::new(node.range(), vec![nav])) -- cgit v1.2.3