From a3d866e776f43c1ae717740bf0c507f4d9fe47cb Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Fri, 8 May 2020 22:12:16 +0200 Subject: [PATCH] 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). --- crates/ra_hir_ty/src/infer/coerce.rs | 34 ++++++++++++++------- crates/ra_hir_ty/src/lib.rs | 6 ++++ crates/ra_hir_ty/src/marks.rs | 1 + crates/ra_hir_ty/src/tests/coercion.rs | 42 ++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs index 89200255a28..173ec59edf6 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 3e5f38d0d5b..e8f3482fe46 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 de5cb1d6bcb..a397401434b 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 ); diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs index 0c3a833bd9e..6dc4b2cd1d2 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!(