4379: Handle coercing function types to function pointers in match r=matklad a=flodiebold

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).

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
This commit is contained in:
bors[bot] 2020-05-08 20:37:14 +00:00 committed by GitHub
commit 1acb556907
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 11 deletions

View file

@ -20,21 +20,35 @@ impl<'a> InferenceContext<'a> {
self.coerce_inner(from_ty, &to_ty) 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. /// Mostly this means trying to coerce one to the other, but
/// Coercing both types to another least upper bound type is not possible in rustc, /// - if we have two function types for different functions, we need to
/// which will simply result in "incompatible types" error. /// 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 { pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty {
if self.coerce(ty1, ty2) { if self.coerce(ty1, ty2) {
ty2.clone() ty2.clone()
} else if self.coerce(ty2, ty1) { } else if self.coerce(ty2, ty1) {
ty1.clone() ty1.clone()
} else { } else {
tested_by!(coerce_merge_fail_fallback); if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) {
// For incompatible types, we use the latter one as result tested_by!(coerce_fn_reification);
// to be better recovery for `if` without `else`. // Special case: two function types. Try to coerce both to
ty2.clone() // 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) { match from_ty.callable_sig(self.db) {
None => return false, None => return false,
Some(sig) => { Some(sig) => {
let num_args = sig.params_and_return.len() as u16 - 1; from_ty = Ty::fn_ptr(sig);
from_ty =
Ty::apply(TypeCtor::FnPtr { num_args }, Substs(sig.params_and_return));
} }
} }
} }

View file

@ -683,6 +683,12 @@ impl Ty {
pub fn unit() -> Self { pub fn unit() -> Self {
Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty()) 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)> { pub fn as_reference(&self) -> Option<(&Ty, Mutability)> {
match self { match self {

View file

@ -7,5 +7,6 @@ test_utils::marks!(
impl_self_type_match_without_receiver impl_self_type_match_without_receiver
match_ergonomics_ref match_ergonomics_ref
coerce_merge_fail_fallback coerce_merge_fail_fallback
coerce_fn_reification
trait_self_implements_self trait_self_implements_self
); );

View file

@ -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] #[test]
fn coerce_closure_to_fn_ptr() { fn coerce_closure_to_fn_ptr() {
assert_snapshot!( assert_snapshot!(