Repository: rust-analyzer/rust-analyzer
Pull request #4379 (2020-05-08 20:15:20)

Handle coercing function types to function pointers in match
E.g. in
```rust
match x {
    1 => function1,
    2 => function2,
}
```
we need to try coercing both to pointers. Turns out this is a special case in
rustc as well (see the link in the comment).


Patch:
diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs
index 89200255a2..173ec59edf 100644
--- a/crates/ra_hir_ty/src/infer/coerce.rs
+++ b/crates/ra_hir_ty/src/infer/coerce.rs
@@ -20,21 +20,35 @@ impl<'a> InferenceContext<'a> {
         self.coerce_inner(from_ty, &to_ty)
     }
 
-    /// Merge two types from different branches, with possible implicit coerce.
+    /// Merge two types from different branches, with possible coercion.
     ///
-    /// Note that it is only possible that one type are coerced to another.
-    /// Coercing both types to another least upper bound type is not possible in rustc,
-    /// which will simply result in "incompatible types" error.
+    /// Mostly this means trying to coerce one to the other, but
+    ///  - if we have two function types for different functions, we need to
+    ///    coerce both to function pointers;
+    ///  - if we were concerned with lifetime subtyping, we'd need to look for a
+    ///    least upper bound.
     pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty {
         if self.coerce(ty1, ty2) {
             ty2.clone()
         } else if self.coerce(ty2, ty1) {
             ty1.clone()
         } else {
-            tested_by!(coerce_merge_fail_fallback);
-            // For incompatible types, we use the latter one as result
-            // to be better recovery for `if` without `else`.
-            ty2.clone()
+            if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) {
+                tested_by!(coerce_fn_reification);
+                // Special case: two function types. Try to coerce both to
+                // pointers to have a chance at getting a match. See
+                // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
+                let sig1 = ty1.callable_sig(self.db).expect("FnDef without callable sig");
+                let sig2 = ty2.callable_sig(self.db).expect("FnDef without callable sig");
+                let ptr_ty1 = Ty::fn_ptr(sig1);
+                let ptr_ty2 = Ty::fn_ptr(sig2);
+                self.coerce_merge_branch(&ptr_ty1, &ptr_ty2)
+            } else {
+                tested_by!(coerce_merge_fail_fallback);
+                // For incompatible types, we use the latter one as result
+                // to be better recovery for `if` without `else`.
+                ty2.clone()
+            }
         }
     }
 
@@ -84,9 +98,7 @@ impl<'a> InferenceContext<'a> {
                 match from_ty.callable_sig(self.db) {
                     None => return false,
                     Some(sig) => {
-                        let num_args = sig.params_and_return.len() as u16 - 1;
-                        from_ty =
-                            Ty::apply(TypeCtor::FnPtr { num_args }, Substs(sig.params_and_return));
+                        from_ty = Ty::fn_ptr(sig);
                     }
                 }
             }
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs
index 3e5f38d0d5..e8f3482fe4 100644
--- a/crates/ra_hir_ty/src/lib.rs
+++ b/crates/ra_hir_ty/src/lib.rs
@@ -683,6 +683,12 @@ impl Ty {
     pub fn unit() -> Self {
         Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty())
     }
+    pub fn fn_ptr(sig: FnSig) -> Self {
+        Ty::apply(
+            TypeCtor::FnPtr { num_args: sig.params().len() as u16 },
+            Substs(sig.params_and_return),
+        )
+    }
 
     pub fn as_reference(&self) -> Option<(&Ty, Mutability)> {
         match self {
diff --git a/crates/ra_hir_ty/src/marks.rs b/crates/ra_hir_ty/src/marks.rs
index de5cb1d6bc..a397401434 100644
--- a/crates/ra_hir_ty/src/marks.rs
+++ b/crates/ra_hir_ty/src/marks.rs
@@ -7,5 +7,6 @@ test_utils::marks!(
     impl_self_type_match_without_receiver
     match_ergonomics_ref
     coerce_merge_fail_fallback
+    coerce_fn_reification
     trait_self_implements_self
 );


Test patch:
diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs
index 0c3a833bd9..6dc4b2cd1d 100644
--- a/crates/ra_hir_ty/src/tests/coercion.rs
+++ b/crates/ra_hir_ty/src/tests/coercion.rs
@@ -545,6 +545,48 @@ fn test() {
     );
 }
 
+#[test]
+fn coerce_fn_items_in_match_arms() {
+    covers!(coerce_fn_reification);
+    assert_snapshot!(
+        infer_with_mismatches(r#"
+fn foo1(x: u32) -> isize { 1 }
+fn foo2(x: u32) -> isize { 2 }
+fn foo3(x: u32) -> isize { 3 }
+fn test() {
+    let x = match 1 {
+        1 => foo1,
+        2 => foo2,
+        _ => foo3,
+    };
+}
+"#, true),
+        @r###"
+    9..10 'x': u32
+    26..31 '{ 1 }': isize
+    28..29 '1': isize
+    40..41 'x': u32
+    57..62 '{ 2 }': isize
+    59..60 '2': isize
+    71..72 'x': u32
+    88..93 '{ 3 }': isize
+    90..91 '3': isize
+    104..193 '{     ...  }; }': ()
+    114..115 'x': fn(u32) -> isize
+    118..190 'match ...     }': fn(u32) -> isize
+    124..125 '1': i32
+    136..137 '1': i32
+    136..137 '1': i32
+    141..145 'foo1': fn foo1(u32) -> isize
+    155..156 '2': i32
+    155..156 '2': i32
+    160..164 'foo2': fn foo2(u32) -> isize
+    174..175 '_': i32
+    179..183 'foo3': fn foo3(u32) -> isize
+    "###
+    );
+}
+
 #[test]
 fn coerce_closure_to_fn_ptr() {
     assert_snapshot!(