Repository: rust-analyzer/rust-analyzer
Pull request #3380 (2020-02-29 21:52:36)

Unsizing in method resolution & autoderef for indexing
 - do autoderef for indexing
 - do array unsizing (`[T; N]` -> `[T]`) in both method resolution and indexing. It turns out array unsizing is actually the only unsizing coercion that rustc does for method receivers, so this is simpler than I expected.

Sadly, this doesn't fix indexing slices/arrays yet, there are still some trait solving problems...


Patch:
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index 569d46cc38..377f44fa75 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -28,7 +28,7 @@ use hir_def::{
     path::{path, Path},
     resolver::{HasResolver, Resolver, TypeNs},
     type_ref::{Mutability, TypeRef},
-    AdtId, AssocItemId, DefWithBodyId, FunctionId, StructFieldId, TypeAliasId, VariantId,
+    AdtId, AssocItemId, DefWithBodyId, FunctionId, StructFieldId, TraitId, TypeAliasId, VariantId,
 };
 use hir_expand::{diagnostics::DiagnosticSink, name::name};
 use ra_arena::map::ArenaMap;
@@ -540,8 +540,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
         Some(struct_.into())
     }
 
+    fn resolve_ops_index(&self) -> Option<TraitId> {
+        self.resolve_lang_item("index")?.as_trait()
+    }
+
     fn resolve_ops_index_output(&self) -> Option<TypeAliasId> {
-        let trait_ = self.resolve_lang_item("index")?.as_trait()?;
+        let trait_ = self.resolve_ops_index()?;
         self.db.trait_data(trait_).associated_type_by_name(&name![Output])
     }
 }
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs
index 3db5b2b515..e89cc72987 100644
--- a/crates/ra_hir_ty/src/infer/expr.rs
+++ b/crates/ra_hir_ty/src/infer/expr.rs
@@ -429,11 +429,27 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
                 let base_ty = self.infer_expr_inner(*base, &Expectation::none());
                 let index_ty = self.infer_expr(*index, &Expectation::none());
 
-                self.resolve_associated_type_with_params(
-                    base_ty,
-                    self.resolve_ops_index_output(),
-                    &[index_ty],
-                )
+                if let (Some(index_trait), Some(krate)) =
+                    (self.resolve_ops_index(), self.resolver.krate())
+                {
+                    let canonicalized = self.canonicalizer().canonicalize_ty(base_ty);
+                    let self_ty = method_resolution::resolve_indexing_op(
+                        self.db,
+                        &canonicalized.value,
+                        self.trait_env.clone(),
+                        krate,
+                        index_trait,
+                    );
+                    let self_ty =
+                        self_ty.map_or(Ty::Unknown, |t| canonicalized.decanonicalize_ty(t.value));
+                    self.resolve_associated_type_with_params(
+                        self_ty,
+                        self.resolve_ops_index_output(),
+                        &[index_ty],
+                    )
+                } else {
+                    Ty::Unknown
+                }
             }
             Expr::Tuple { exprs } => {
                 let mut tys = match &expected.ty {
diff --git a/crates/ra_hir_ty/src/method_resolution.rs b/crates/ra_hir_ty/src/method_resolution.rs
index df5901835a..74b908c2e0 100644
--- a/crates/ra_hir_ty/src/method_resolution.rs
+++ b/crates/ra_hir_ty/src/method_resolution.rs
@@ -20,7 +20,7 @@ use crate::{
     db::HirDatabase,
     primitive::{FloatBitness, Uncertain},
     utils::all_super_traits,
-    Canonical, InEnvironment, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
+    ApplicationTy, Canonical, InEnvironment, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
 };
 
 /// This is used as a key for indexing impls.
@@ -214,7 +214,7 @@ pub fn iterate_method_candidates<T>(
             // the methods by autoderef order of *receiver types*, not *self
             // types*.
 
-            let deref_chain: Vec<_> = autoderef::autoderef(db, Some(krate), ty).collect();
+            let deref_chain = autoderef_method_receiver(db, krate, ty);
             for i in 0..deref_chain.len() {
                 if let Some(result) = iterate_method_candidates_with_autoref(
                     &deref_chain[i..],
@@ -447,6 +447,25 @@ fn iterate_inherent_methods<T>(
     None
 }
 
+/// Returns the self type for the index trait call.
+pub fn resolve_indexing_op(
+    db: &impl HirDatabase,
+    ty: &Canonical<Ty>,
+    env: Arc<TraitEnvironment>,
+    krate: CrateId,
+    index_trait: TraitId,
+) -> Option<Canonical<Ty>> {
+    let ty = InEnvironment { value: ty.clone(), environment: env.clone() };
+    let deref_chain = autoderef_method_receiver(db, krate, ty);
+    for ty in deref_chain {
+        let goal = generic_implements_goal(db, env.clone(), index_trait, ty.clone());
+        if db.trait_solve(krate, goal).is_some() {
+            return Some(ty);
+        }
+    }
+    None
+}
+
 fn is_valid_candidate(
     db: &impl HirDatabase,
     name: Option<&Name>,
@@ -548,3 +567,20 @@ fn generic_implements_goal(
     let obligation = super::Obligation::Trait(trait_ref);
     Canonical { num_vars, value: InEnvironment::new(env, obligation) }
 }
+
+fn autoderef_method_receiver(
+    db: &impl HirDatabase,
+    krate: CrateId,
+    ty: InEnvironment<Canonical<Ty>>,
+) -> Vec<Canonical<Ty>> {
+    let mut deref_chain: Vec<_> = autoderef::autoderef(db, Some(krate), ty).collect();
+    // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
+    if let Some(Ty::Apply(ApplicationTy { ctor: TypeCtor::Array, parameters })) =
+        deref_chain.last().map(|ty| &ty.value)
+    {
+        let num_vars = deref_chain.last().unwrap().num_vars;
+        let unsized_ty = Ty::apply(TypeCtor::Slice, parameters.clone());
+        deref_chain.push(Canonical { value: unsized_ty, num_vars })
+    }
+    deref_chain
+}


Test patch:
diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs
index 644d59e17c..f9b394f059 100644
--- a/crates/ra_hir_ty/src/tests/method_resolution.rs
+++ b/crates/ra_hir_ty/src/tests/method_resolution.rs
@@ -838,6 +838,24 @@ fn test() { (&S).foo()<|>; }
     assert_eq!(t, "u128");
 }
 
+#[test]
+fn method_resolution_unsize_array() {
+    let t = type_at(
+        r#"
+//- /main.rs
+#[lang = "slice"]
+impl<T> [T] {
+    fn len(&self) -> usize { loop {} }
+}
+fn test() {
+    let a = [1, 2, 3];
+    a.len()<|>;
+}
+"#,
+    );
+    assert_eq!(t, "usize");
+}
+
 #[test]
 fn method_resolution_trait_from_prelude() {
     let (db, pos) = TestDB::with_position(
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs
index 7d796d0b94..547010b351 100644
--- a/crates/ra_hir_ty/src/tests/traits.rs
+++ b/crates/ra_hir_ty/src/tests/traits.rs
@@ -567,6 +567,34 @@ mod ops {
     assert_eq!("Foo", type_at_pos(&db, pos));
 }
 
+#[test]
+fn infer_ops_index_autoderef() {
+    let (db, pos) = TestDB::with_position(
+        r#"
+//- /main.rs crate:main deps:std
+fn test() {
+    let a = &[1u32, 2, 3];
+    let b = a[1];
+    b<|>;
+}
+
+//- /std.rs crate:std
+impl<T> ops::Index<u32> for [T] {
+    type Output = T;
+}
+
+#[prelude_import] use ops::*;
+mod ops {
+    #[lang = "index"]
+    pub trait Index<Idx> {
+        type Output;
+    }
+}
+"#,
+    );
+    assert_eq!("u32", type_at_pos(&db, pos));
+}
+
 #[test]
 fn deref_trait() {
     let t = type_at(