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).
This commit is contained in:
Florian Diebold 2020-05-08 22:12:16 +02:00
parent f9ec7cebef
commit a3d866e776
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)
}
/// 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);
}
}
}

View file

@ -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 {

View file

@ -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
);

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