Repository: rust-lang/rust-analyzer
Pull request #12887 (2022-07-27 11:48:37)

fix: Honor ref expressions for compute_ref_match completions
Fixes https://github.com/rust-lang/rust-analyzer/issues/8357


Patch:
diff --git a/crates/hir-ty/src/chalk_ext.rs b/crates/hir-ty/src/chalk_ext.rs
index b0885ab003..a9c124b42d 100644
--- a/crates/hir-ty/src/chalk_ext.rs
+++ b/crates/hir-ty/src/chalk_ext.rs
@@ -34,6 +34,7 @@ pub trait TyExt {
     fn callable_sig(&self, db: &dyn HirDatabase) -> Option<CallableSig>;
 
     fn strip_references(&self) -> &Ty;
+    fn strip_reference(&self) -> &Ty;
 
     /// If this is a `dyn Trait`, returns that trait.
     fn dyn_trait(&self) -> Option<TraitId>;
@@ -182,6 +183,10 @@ impl TyExt for Ty {
         t
     }
 
+    fn strip_reference(&self) -> &Ty {
+        self.as_reference().map_or(self, |(ty, _, _)| ty)
+    }
+
     fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option<Vec<QuantifiedWhereClause>> {
         match self.kind(Interner) {
             TyKind::OpaqueType(opaque_ty_id, subst) => {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index d4925455d7..8f984210e1 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2769,6 +2769,10 @@ impl Type {
         self.derived(self.ty.strip_references().clone())
     }
 
+    pub fn strip_reference(&self) -> Type {
+        self.derived(self.ty.strip_reference().clone())
+    }
+
     pub fn is_unknown(&self) -> bool {
         self.ty.is_unknown()
     }
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index c71ffa0ed8..09a1a99eb6 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -162,11 +162,52 @@ impl<'a> CompletionContext<'a> {
     }
 
     /// Calculate the expected type and name of the cursor position.
-    fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
+    fn expected_type_and_name(
+        &self,
+        name_like: &ast::NameLike,
+    ) -> (Option<Type>, Option<NameOrNameRef>) {
         let mut node = match self.token.parent() {
             Some(it) => it,
             None => return (None, None),
         };
+
+        let strip_refs = |mut ty: Type| match name_like {
+            ast::NameLike::NameRef(n) => {
+                let p = match n.syntax().parent() {
+                    Some(it) => it,
+                    None => return ty,
+                };
+                let top_syn = match_ast! {
+                    match p {
+                        ast::FieldExpr(e) => e
+                            .syntax()
+                            .ancestors()
+                            .map_while(ast::FieldExpr::cast)
+                            .last()
+                            .map(|it| it.syntax().clone()),
+                        ast::PathSegment(e) => e
+                            .syntax()
+                            .ancestors()
+                            .skip(1)
+                            .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind()))
+                            .find_map(ast::PathExpr::cast)
+                            .map(|it| it.syntax().clone()),
+                        _ => None
+                    }
+                };
+                let top_syn = match top_syn {
+                    Some(it) => it,
+                    None => return ty,
+                };
+                for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) {
+                    cov_mark::hit!(expected_type_fn_param_ref);
+                    ty = ty.strip_reference();
+                }
+                ty
+            }
+            _ => ty,
+        };
+
         loop {
             break match_ast! {
                 match node {
@@ -199,13 +240,9 @@ impl<'a> CompletionContext<'a> {
                             self.token.clone(),
                         ).map(|ap| {
                             let name = ap.ident().map(NameOrNameRef::Name);
-                            let ty = if has_ref(&self.token) {
-                                cov_mark::hit!(expected_type_fn_param_ref);
-                                ap.ty.remove_ref()
-                            } else {
-                                Some(ap.ty)
-                            };
-                            (ty, name)
+
+                            let ty = strip_refs(ap.ty);
+                            (Some(ty), name)
                         })
                         .unwrap_or((None, None))
                     },
@@ -330,8 +367,6 @@ impl<'a> CompletionContext<'a> {
             return None;
         }
 
-        (self.expected_type, self.expected_name) = self.expected_type_and_name();
-
         // Overwrite the path kind for derives
         if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
             if let Some(ast::NameLike::NameRef(name_ref)) =
@@ -389,6 +424,7 @@ impl<'a> CompletionContext<'a> {
                 return Some(analysis);
             }
         };
+        (self.expected_type, self.expected_name) = self.expected_type_and_name(&name_like);
         let analysis = match name_like {
             ast::NameLike::Lifetime(lifetime) => CompletionAnalysis::Lifetime(
                 Self::classify_lifetime(&self.sema, original_file, lifetime)?,
@@ -1141,19 +1177,6 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
     Some((use_tree.path()?, true))
 }
 
-fn has_ref(token: &SyntaxToken) -> bool {
-    let mut token = token.clone();
-    for skip in [SyntaxKind::IDENT, SyntaxKind::WHITESPACE, T![mut]] {
-        if token.kind() == skip {
-            token = match token.prev_token() {
-                Some(it) => it,
-                None => return false,
-            }
-        }
-    }
-    token.kind() == T![&]
-}
-
 pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
     // oh my ...
     (|| {


Test patch:
diff --git a/crates/ide-completion/src/context/tests.rs b/crates/ide-completion/src/context/tests.rs
index c5557bdafb..50845b3881 100644
--- a/crates/ide-completion/src/context/tests.rs
+++ b/crates/ide-completion/src/context/tests.rs
@@ -391,3 +391,23 @@ fn foo($0: Foo) {}
         expect![[r#"ty: ?, name: ?"#]],
     );
 }
+
+#[test]
+fn expected_type_ref_prefix_on_field() {
+    check_expected_type_and_name(
+        r#"
+fn foo(_: &mut i32) {}
+struct S {
+    field: i32,
+}
+
+fn main() {
+    let s = S {
+        field: 100,
+    };
+    foo(&mut s.f$0);
+}
+"#,
+        expect!["ty: i32, name: ?"],
+    );
+}