Repository: rust-analyzer/rust-analyzer
Pull request #450 (2019-01-07 13:59:15)

Implement autoderef for field accesses
Which means we now get completion for fields e.g. in `&self` methods :)


Patch:
diff --git a/crates/ra_analysis/src/completion/complete_dot.rs b/crates/ra_analysis/src/completion/complete_dot.rs
index 54ce1b638b..5d4e60dc52 100644
--- a/crates/ra_analysis/src/completion/complete_dot.rs
+++ b/crates/ra_analysis/src/completion/complete_dot.rs
@@ -23,31 +23,35 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Ca
 }
 
 fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) -> Cancelable<()> {
-    // TODO: autoderef etc.
-    match receiver {
-        Ty::Adt { def_id, .. } => {
-            match def_id.resolve(ctx.db)? {
-                Def::Struct(s) => {
-                    let variant_data = s.variant_data(ctx.db)?;
-                    for field in variant_data.fields() {
-                        CompletionItem::new(CompletionKind::Reference, field.name().to_string())
+    for receiver in receiver.autoderef(ctx.db) {
+        match receiver {
+            Ty::Adt { def_id, .. } => {
+                match def_id.resolve(ctx.db)? {
+                    Def::Struct(s) => {
+                        let variant_data = s.variant_data(ctx.db)?;
+                        for field in variant_data.fields() {
+                            CompletionItem::new(
+                                CompletionKind::Reference,
+                                field.name().to_string(),
+                            )
                             .kind(CompletionItemKind::Field)
                             .add_to(acc);
+                        }
                     }
+                    // TODO unions
+                    _ => {}
                 }
-                // TODO unions
-                _ => {}
             }
-        }
-        Ty::Tuple(fields) => {
-            for (i, _ty) in fields.iter().enumerate() {
-                CompletionItem::new(CompletionKind::Reference, i.to_string())
-                    .kind(CompletionItemKind::Field)
-                    .add_to(acc);
+            Ty::Tuple(fields) => {
+                for (i, _ty) in fields.iter().enumerate() {
+                    CompletionItem::new(CompletionKind::Reference, i.to_string())
+                        .kind(CompletionItemKind::Field)
+                        .add_to(acc);
+                }
             }
-        }
-        _ => {}
-    };
+            _ => {}
+        };
+    }
     Ok(())
 }
 
@@ -87,6 +91,21 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_struct_field_completion_autoderef() {
+        check_ref_completion(
+            r"
+            struct A { the_field: u32 }
+            impl A {
+                fn foo(&self) {
+                    self.<|>
+                }
+            }
+            ",
+            r#"the_field"#,
+        );
+    }
+
     #[test]
     fn test_no_struct_field_completion_for_method_call() {
         check_ref_completion(
diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs
index 033f9d25f7..04249400b9 100644
--- a/crates/ra_hir/src/db.rs
+++ b/crates/ra_hir/src/db.rs
@@ -56,7 +56,7 @@ pub trait HirDatabase: SyntaxDatabase
         use fn crate::ty::type_for_def;
     }
 
-    fn type_for_field(def_id: DefId, field: Name) -> Cancelable<Ty> {
+    fn type_for_field(def_id: DefId, field: Name) -> Cancelable<Option<Ty>> {
         type TypeForFieldQuery;
         use fn crate::ty::type_for_field;
     }
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
index d57990cd20..bba8527b7f 100644
--- a/crates/ra_hir/src/ty.rs
+++ b/crates/ra_hir/src/ty.rs
@@ -13,6 +13,7 @@
 //! the union-find implementation from the `ena` crate, which is extracted from
 //! rustc.
 
+mod autoderef;
 mod primitive;
 #[cfg(test)]
 mod tests;
@@ -36,6 +37,14 @@ use crate::{
     expr::{Body, Expr, ExprId, PatId, UnaryOp, BinaryOp, Statement},
 };
 
+fn transpose<T>(x: Cancelable<Option<T>>) -> Option<Cancelable<T>> {
+    match x {
+        Ok(Some(t)) => Some(Ok(t)),
+        Ok(None) => None,
+        Err(e) => Some(Err(e)),
+    }
+}
+
 /// The ID of a type variable.
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct TypeVarId(u32);
@@ -357,6 +366,14 @@ impl Ty {
         });
         self
     }
+
+    fn builtin_deref(&self) -> Option<Ty> {
+        match self {
+            Ty::Ref(t, _) => Some(Ty::clone(t)),
+            Ty::RawPtr(t, _) => Some(Ty::clone(t)),
+            _ => None,
+        }
+    }
 }
 
 impl fmt::Display for Ty {
@@ -443,7 +460,11 @@ pub(super) fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable<T
     }
 }
 
-pub(super) fn type_for_field(db: &impl HirDatabase, def_id: DefId, field: Name) -> Cancelable<Ty> {
+pub(super) fn type_for_field(
+    db: &impl HirDatabase,
+    def_id: DefId,
+    field: Name,
+) -> Cancelable<Option<Ty>> {
     let def = def_id.resolve(db)?;
     let variant_data = match def {
         Def::Struct(s) => {
@@ -459,12 +480,13 @@ pub(super) fn type_for_field(db: &impl HirDatabase, def_id: DefId, field: Name)
     };
     let module = def_id.module(db)?;
     let impl_block = def_id.impl_block(db)?;
-    let type_ref = if let Some(tr) = variant_data.get_field_type_ref(&field) {
-        tr
-    } else {
-        return Ok(Ty::Unknown);
-    };
-    Ty::from_hir(db, &module, impl_block.as_ref(), &type_ref)
+    let type_ref = ctry!(variant_data.get_field_type_ref(&field));
+    Ok(Some(Ty::from_hir(
+        db,
+        &module,
+        impl_block.as_ref(),
+        &type_ref,
+    )?))
 }
 
 /// The result of type inference: A mapping from expressions and patterns to types.
@@ -802,7 +824,9 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
                 let (ty, def_id) = self.resolve_variant(path.as_ref())?;
                 for field in fields {
                     let field_ty = if let Some(def_id) = def_id {
-                        self.db.type_for_field(def_id, field.name.clone())?
+                        self.db
+                            .type_for_field(def_id, field.name.clone())?
+                            .unwrap_or(Ty::Unknown)
                     } else {
                         Ty::Unknown
                     };
@@ -815,15 +839,20 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
             }
             Expr::Field { expr, name } => {
                 let receiver_ty = self.infer_expr(*expr, &Expectation::none())?;
-                let ty = match receiver_ty {
-                    Ty::Tuple(fields) => {
-                        let i = name.to_string().parse::<usize>().ok();
-                        i.and_then(|i| fields.get(i).cloned())
-                            .unwrap_or(Ty::Unknown)
-                    }
-                    Ty::Adt { def_id, .. } => self.db.type_for_field(def_id, name.clone())?,
-                    _ => Ty::Unknown,
-                };
+                let ty = receiver_ty
+                    .autoderef(self.db)
+                    .find_map(|derefed_ty| match derefed_ty {
+                        // this is more complicated than necessary because type_for_field is cancelable
+                        Ty::Tuple(fields) => {
+                            let i = name.to_string().parse::<usize>().ok();
+                            i.and_then(|i| fields.get(i).cloned()).map(Ok)
+                        }
+                        Ty::Adt { def_id, .. } => {
+                            transpose(self.db.type_for_field(def_id, name.clone()))
+                        }
+                        _ => None,
+                    })
+                    .unwrap_or(Ok(Ty::Unknown))?;
                 self.insert_type_vars(ty)
             }
             Expr::Try { expr } => {
@@ -848,12 +877,11 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
                 let inner_ty = self.infer_expr(*expr, &Expectation::none())?;
                 match op {
                     Some(UnaryOp::Deref) => {
-                        match inner_ty {
-                            // builtin deref:
-                            Ty::Ref(ref_inner, _) => (*ref_inner).clone(),
-                            Ty::RawPtr(ptr_inner, _) => (*ptr_inner).clone(),
+                        if let Some(derefed_ty) = inner_ty.builtin_deref() {
+                            derefed_ty
+                        } else {
                             // TODO Deref::deref
-                            _ => Ty::Unknown,
+                            Ty::Unknown
                         }
                     }
                     _ => Ty::Unknown,
diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs
new file mode 100644
index 0000000000..24a3865583
--- /dev/null
+++ b/crates/ra_hir/src/ty/autoderef.rs
@@ -0,0 +1,21 @@
+//! In certain situations, rust automatically inserts derefs as necessary: For
+//! example, field accesses `foo.bar` still work when `foo` is actually a
+//! reference to a type with the field `bar`. This is an approximation of the
+//! logic in rustc (which lives in librustc_typeck/check/autoderef.rs).
+
+use ra_syntax::algo::generate;
+
+use crate::HirDatabase;
+use super::Ty;
+
+impl Ty {
+    /// Iterates over the possible derefs of `ty`.
+    pub fn autoderef<'a>(self, db: &'a impl HirDatabase) -> impl Iterator<Item = Ty> + 'a {
+        generate(Some(self), move |ty| ty.autoderef_step(db))
+    }
+
+    fn autoderef_step(&self, _db: &impl HirDatabase) -> Option<Ty> {
+        // TODO Deref::deref
+        self.builtin_deref()
+    }
+}


Test patch:
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index 83aedaa005..e6c7e225b3 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -95,7 +95,7 @@ fn test() {
 }
 
 #[test]
-fn infer_refs_and_ptrs() {
+fn infer_refs() {
     check_inference(
         r#"
 fn test(a: &u32, b: &mut u32, c: *const u32, d: *mut u32) {
@@ -180,6 +180,37 @@ fn test() {
     );
 }
 
+#[test]
+fn infer_field_autoderef() {
+    check_inference(
+        r#"
+struct A {
+    b: B,
+}
+struct B;
+
+fn test1(a: A) {
+    let a1 = a;
+    a1.b;
+    let a2 = &a;
+    a2.b;
+    let a3 = &mut a;
+    a3.b;
+    let a4 = &&&&&&&a;
+    a4.b;
+    let a5 = &mut &&mut &&mut a;
+    a5.b;
+}
+
+fn test2(a1: *const A, a2: *mut A) {
+    a1.b;
+    a2.b;
+}
+"#,
+        "field_autoderef.txt",
+    );
+}
+
 fn infer(content: &str) -> String {
     let (db, _, file_id) = MockDatabase::with_single_file(content);
     let source_file = db.source_file(file_id);
diff --git a/crates/ra_hir/src/ty/tests/data/field_autoderef.txt b/crates/ra_hir/src/ty/tests/data/field_autoderef.txt
new file mode 100644
index 0000000000..e1db1db40b
--- /dev/null
+++ b/crates/ra_hir/src/ty/tests/data/field_autoderef.txt
@@ -0,0 +1,43 @@
+[44; 45) 'a': A
+[50; 213) '{     ...5.b; }': ()
+[60; 62) 'a1': A
+[65; 66) 'a': A
+[72; 74) 'a1': A
+[72; 76) 'a1.b': B
+[86; 88) 'a2': &A
+[91; 93) '&a': &A
+[92; 93) 'a': A
+[99; 101) 'a2': &A
+[99; 103) 'a2.b': B
+[113; 115) 'a3': &mut A
+[118; 124) '&mut a': &mut A
+[123; 124) 'a': A
+[130; 132) 'a3': &mut A
+[130; 134) 'a3.b': B
+[144; 146) 'a4': &&&&&&&A
+[149; 157) '&&&&&&&a': &&&&&&&A
+[150; 157) '&&&&&&a': &&&&&&A
+[151; 157) '&&&&&a': &&&&&A
+[152; 157) '&&&&a': &&&&A
+[153; 157) '&&&a': &&&A
+[154; 157) '&&a': &&A
+[155; 157) '&a': &A
+[156; 157) 'a': A
+[163; 165) 'a4': &&&&&&&A
+[163; 167) 'a4.b': B
+[177; 179) 'a5': &mut &&mut &&mut A
+[182; 200) '&mut &...&mut a': &mut &&mut &&mut A
+[187; 200) '&&mut &&mut a': &&mut &&mut A
+[188; 200) '&mut &&mut a': &mut &&mut A
+[193; 200) '&&mut a': &&mut A
+[194; 200) '&mut a': &mut A
+[199; 200) 'a': A
+[206; 208) 'a5': &mut &&mut &&mut A
+[206; 210) 'a5.b': B
+[224; 226) 'a1': *const A
+[238; 240) 'a2': *mut A
+[250; 273) '{     ...2.b; }': ()
+[256; 258) 'a1': *const A
+[256; 260) 'a1.b': B
+[266; 268) 'a2': *mut A
+[266; 270) 'a2.b': B