From 82fe7b77a3b4f49540ae1fc319bdd38afd73c877 Mon Sep 17 00:00:00 2001
From: Florian Diebold <flodiebold@gmail.com>
Date: Sat, 23 Feb 2019 22:59:01 +0100
Subject: [PATCH] Refactor associated method resolution a bit and make it work
 with generics

---
 crates/ra_hir/src/ty/infer.rs                 | 86 ++++++++++++-------
 crates/ra_hir/src/ty/lower.rs                 | 67 +++++++++------
 ...sts__infer_associated_method_generics.snap |  8 +-
 crates/ra_hir/src/ty/tests.rs                 |  1 -
 4 files changed, 98 insertions(+), 64 deletions(-)

diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs
index 6ee9080d383..13080b5aa6a 100644
--- a/crates/ra_hir/src/ty/infer.rs
+++ b/crates/ra_hir/src/ty/infer.rs
@@ -360,46 +360,66 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
         // we might have resolved into a type for which
         // we may find some associated item starting at the
         // path.segment pointed to by `remaining_index´
-        let resolved =
+        let mut resolved =
             if remaining_index.is_none() { def.take_values()? } else { def.take_types()? };
 
+        let remaining_index = remaining_index.unwrap_or(path.segments.len());
+
+        // resolve intermediate segments
+        for segment in &path.segments[remaining_index..] {
+            let ty = match resolved {
+                Resolution::Def(def) => {
+                    let typable: Option<TypableDef> = def.into();
+                    let typable = typable?;
+
+                    let substs =
+                        Ty::substs_from_path_segment(self.db, &self.resolver, segment, typable);
+                    self.db.type_for_def(typable, Namespace::Types).apply_substs(substs)
+                }
+                Resolution::LocalBinding(_) => {
+                    // can't have a local binding in an associated item path
+                    return None;
+                }
+                Resolution::GenericParam(..) => {
+                    // TODO associated item of generic param
+                    return None;
+                }
+                Resolution::SelfType(_) => {
+                    // TODO associated item of self type
+                    return None;
+                }
+            };
+
+            // Attempt to find an impl_item for the type which has a name matching
+            // the current segment
+            log::debug!("looking for path segment: {:?}", segment);
+            let item = ty.iterate_impl_items(self.db, |item| match item {
+                crate::ImplItem::Method(func) => {
+                    let sig = func.signature(self.db);
+                    if segment.name == *sig.name() {
+                        return Some(func);
+                    }
+                    None
+                }
+
+                // TODO: Resolve associated const
+                crate::ImplItem::Const(_) => None,
+
+                // TODO: Resolve associated types
+                crate::ImplItem::Type(_) => None,
+            })?;
+            resolved = Resolution::Def(item.into());
+        }
+
         match resolved {
             Resolution::Def(def) => {
                 let typable: Option<TypableDef> = def.into();
                 let typable = typable?;
 
-                if let Some(remaining_index) = remaining_index {
-                    let ty = self.db.type_for_def(typable, Namespace::Types);
-                    // TODO: Keep resolving the segments
-                    // if we have more segments to process
-                    let segment = &path.segments[remaining_index];
-
-                    log::debug!("looking for path segment: {:?}", segment);
-
-                    // Attempt to find an impl_item for the type which has a name matching
-                    // the current segment
-                    let ty = ty.iterate_impl_items(self.db, |item| match item {
-                        crate::ImplItem::Method(func) => {
-                            let sig = func.signature(self.db);
-                            if segment.name == *sig.name() {
-                                return Some(func.ty(self.db));
-                            }
-                            None
-                        }
-
-                        // TODO: Resolve associated const
-                        crate::ImplItem::Const(_) => None,
-
-                        // TODO: Resolve associated types
-                        crate::ImplItem::Type(_) => None,
-                    });
-                    ty
-                } else {
-                    let substs = Ty::substs_from_path(self.db, &self.resolver, path, typable);
-                    let ty = self.db.type_for_def(typable, Namespace::Values).apply_substs(substs);
-                    let ty = self.insert_type_vars(ty);
-                    Some(ty)
-                }
+                let substs = Ty::substs_from_path(self.db, &self.resolver, path, typable);
+                let ty = self.db.type_for_def(typable, Namespace::Values).apply_substs(substs);
+                let ty = self.insert_type_vars(ty);
+                Some(ty)
             }
             Resolution::LocalBinding(pat) => {
                 let ty = self.type_of_pat.get(pat)?;
diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs
index cc9e0fd40e2..63e13a30e8e 100644
--- a/crates/ra_hir/src/ty/lower.rs
+++ b/crates/ra_hir/src/ty/lower.rs
@@ -16,7 +16,7 @@ use crate::{
     name::KnownName,
     nameres::Namespace,
     resolve::{Resolver, Resolution},
-    path::GenericArg,
+    path::{ PathSegment, GenericArg},
     generics::GenericParams,
     adt::VariantDef,
 };
@@ -112,36 +112,18 @@ impl Ty {
         ty.apply_substs(substs)
     }
 
-    /// Collect generic arguments from a path into a `Substs`. See also
-    /// `create_substs_for_ast_path` and `def_to_ty` in rustc.
-    pub(super) fn substs_from_path(
+    pub(super) fn substs_from_path_segment(
         db: &impl HirDatabase,
         resolver: &Resolver,
-        path: &Path,
+        segment: &PathSegment,
         resolved: TypableDef,
     ) -> Substs {
         let mut substs = Vec::new();
-        let last = path.segments.last().expect("path should have at least one segment");
-        let (def_generics, segment) = match resolved {
-            TypableDef::Function(func) => (func.generic_params(db), last),
-            TypableDef::Struct(s) => (s.generic_params(db), last),
-            TypableDef::Enum(e) => (e.generic_params(db), last),
-            TypableDef::EnumVariant(var) => {
-                // the generic args for an enum variant may be either specified
-                // on the segment referring to the enum, or on the segment
-                // referring to the variant. So `Option::<T>::None` and
-                // `Option::None::<T>` are both allowed (though the former is
-                // preferred). See also `def_ids_for_path_segments` in rustc.
-                let len = path.segments.len();
-                let segment = if len >= 2 && path.segments[len - 2].args_and_bindings.is_some() {
-                    // Option::<T>::None
-                    &path.segments[len - 2]
-                } else {
-                    // Option::None::<T>
-                    last
-                };
-                (var.parent_enum(db).generic_params(db), segment)
-            }
+        let def_generics = match resolved {
+            TypableDef::Function(func) => func.generic_params(db),
+            TypableDef::Struct(s) => s.generic_params(db),
+            TypableDef::Enum(e) => e.generic_params(db),
+            TypableDef::EnumVariant(var) => var.parent_enum(db).generic_params(db),
         };
         let parent_param_count = def_generics.count_parent_params();
         substs.extend((0..parent_param_count).map(|_| Ty::Unknown));
@@ -166,6 +148,39 @@ impl Ty {
         assert_eq!(substs.len(), def_generics.count_params_including_parent());
         Substs(substs.into())
     }
+
+    /// Collect generic arguments from a path into a `Substs`. See also
+    /// `create_substs_for_ast_path` and `def_to_ty` in rustc.
+    pub(super) fn substs_from_path(
+        db: &impl HirDatabase,
+        resolver: &Resolver,
+        path: &Path,
+        resolved: TypableDef,
+    ) -> Substs {
+        let last = path.segments.last().expect("path should have at least one segment");
+        let segment = match resolved {
+            TypableDef::Function(_) => last,
+            TypableDef::Struct(_) => last,
+            TypableDef::Enum(_) => last,
+            TypableDef::EnumVariant(_) => {
+                // the generic args for an enum variant may be either specified
+                // on the segment referring to the enum, or on the segment
+                // referring to the variant. So `Option::<T>::None` and
+                // `Option::None::<T>` are both allowed (though the former is
+                // preferred). See also `def_ids_for_path_segments` in rustc.
+                let len = path.segments.len();
+                let segment = if len >= 2 && path.segments[len - 2].args_and_bindings.is_some() {
+                    // Option::<T>::None
+                    &path.segments[len - 2]
+                } else {
+                    // Option::None::<T>
+                    last
+                };
+                segment
+            }
+        };
+        Ty::substs_from_path_segment(db, resolver, segment, resolved)
+    }
 }
 
 /// Build the declared type of an item. This depends on the namespace; e.g. for
diff --git a/crates/ra_hir/src/ty/snapshots/tests__infer_associated_method_generics.snap b/crates/ra_hir/src/ty/snapshots/tests__infer_associated_method_generics.snap
index fe5d6590e66..44694dfdb01 100644
--- a/crates/ra_hir/src/ty/snapshots/tests__infer_associated_method_generics.snap
+++ b/crates/ra_hir/src/ty/snapshots/tests__infer_associated_method_generics.snap
@@ -1,5 +1,5 @@
 ---
-created: "2019-02-21T10:25:18.568887300Z"
+created: "2019-02-23T21:58:35.844769207Z"
 creator: insta@0.6.3
 source: crates/ra_hir/src/ty/tests.rs
 expression: "&result"
@@ -9,8 +9,8 @@ expression: "&result"
 [92; 103) 'Gen { val }': Gen<T>
 [98; 101) 'val': T
 [123; 155) '{     ...32); }': ()
-[133; 134) 'a': Gen<[unknown]>
-[137; 146) 'Gen::make': fn make<[unknown]>(T) -> Gen<T>
-[137; 152) 'Gen::make(0u32)': Gen<[unknown]>
+[133; 134) 'a': Gen<u32>
+[137; 146) 'Gen::make': fn make<u32>(T) -> Gen<T>
+[137; 152) 'Gen::make(0u32)': Gen<u32>
 [147; 151) '0u32': u32
 
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index 77aeca66900..d0da346770e 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -719,7 +719,6 @@ fn test() {
 }
 
 #[test]
-#[ignore] // FIXME: After https://github.com/rust-analyzer/rust-analyzer/pull/866 is merged
 fn infer_associated_method_generics() {
     check_inference(
         "infer_associated_method_generics",