Skip to content

Commit a3d866e

Browse files
committed
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).
1 parent f9ec7ce commit a3d866e

File tree

4 files changed

+72
-11
lines changed

4 files changed

+72
-11
lines changed

crates/ra_hir_ty/src/infer/coerce.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,35 @@ impl<'a> InferenceContext<'a> {
2020
self.coerce_inner(from_ty, &to_ty)
2121
}
2222

23-
/// Merge two types from different branches, with possible implicit coerce.
23+
/// Merge two types from different branches, with possible coercion.
2424
///
25-
/// Note that it is only possible that one type are coerced to another.
26-
/// Coercing both types to another least upper bound type is not possible in rustc,
27-
/// which will simply result in "incompatible types" error.
25+
/// Mostly this means trying to coerce one to the other, but
26+
/// - if we have two function types for different functions, we need to
27+
/// coerce both to function pointers;
28+
/// - if we were concerned with lifetime subtyping, we'd need to look for a
29+
/// least upper bound.
2830
pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty {
2931
if self.coerce(ty1, ty2) {
3032
ty2.clone()
3133
} else if self.coerce(ty2, ty1) {
3234
ty1.clone()
3335
} else {
34-
tested_by!(coerce_merge_fail_fallback);
35-
// For incompatible types, we use the latter one as result
36-
// to be better recovery for `if` without `else`.
37-
ty2.clone()
36+
if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) {
37+
tested_by!(coerce_fn_reification);
38+
// Special case: two function types. Try to coerce both to
39+
// pointers to have a chance at getting a match. See
40+
// https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
41+
let sig1 = ty1.callable_sig(self.db).expect("FnDef without callable sig");
42+
let sig2 = ty2.callable_sig(self.db).expect("FnDef without callable sig");
43+
let ptr_ty1 = Ty::fn_ptr(sig1);
44+
let ptr_ty2 = Ty::fn_ptr(sig2);
45+
self.coerce_merge_branch(&ptr_ty1, &ptr_ty2)
46+
} else {
47+
tested_by!(coerce_merge_fail_fallback);
48+
// For incompatible types, we use the latter one as result
49+
// to be better recovery for `if` without `else`.
50+
ty2.clone()
51+
}
3852
}
3953
}
4054

@@ -84,9 +98,7 @@ impl<'a> InferenceContext<'a> {
8498
match from_ty.callable_sig(self.db) {
8599
None => return false,
86100
Some(sig) => {
87-
let num_args = sig.params_and_return.len() as u16 - 1;
88-
from_ty =
89-
Ty::apply(TypeCtor::FnPtr { num_args }, Substs(sig.params_and_return));
101+
from_ty = Ty::fn_ptr(sig);
90102
}
91103
}
92104
}

crates/ra_hir_ty/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,12 @@ impl Ty {
683683
pub fn unit() -> Self {
684684
Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty())
685685
}
686+
pub fn fn_ptr(sig: FnSig) -> Self {
687+
Ty::apply(
688+
TypeCtor::FnPtr { num_args: sig.params().len() as u16 },
689+
Substs(sig.params_and_return),
690+
)
691+
}
686692

687693
pub fn as_reference(&self) -> Option<(&Ty, Mutability)> {
688694
match self {

crates/ra_hir_ty/src/marks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ test_utils::marks!(
77
impl_self_type_match_without_receiver
88
match_ergonomics_ref
99
coerce_merge_fail_fallback
10+
coerce_fn_reification
1011
trait_self_implements_self
1112
);

crates/ra_hir_ty/src/tests/coercion.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,48 @@ fn test() {
545545
);
546546
}
547547

548+
#[test]
549+
fn coerce_fn_items_in_match_arms() {
550+
covers!(coerce_fn_reification);
551+
assert_snapshot!(
552+
infer_with_mismatches(r#"
553+
fn foo1(x: u32) -> isize { 1 }
554+
fn foo2(x: u32) -> isize { 2 }
555+
fn foo3(x: u32) -> isize { 3 }
556+
fn test() {
557+
let x = match 1 {
558+
1 => foo1,
559+
2 => foo2,
560+
_ => foo3,
561+
};
562+
}
563+
"#, true),
564+
@r###"
565+
9..10 'x': u32
566+
26..31 '{ 1 }': isize
567+
28..29 '1': isize
568+
40..41 'x': u32
569+
57..62 '{ 2 }': isize
570+
59..60 '2': isize
571+
71..72 'x': u32
572+
88..93 '{ 3 }': isize
573+
90..91 '3': isize
574+
104..193 '{ ... }; }': ()
575+
114..115 'x': fn(u32) -> isize
576+
118..190 'match ... }': fn(u32) -> isize
577+
124..125 '1': i32
578+
136..137 '1': i32
579+
136..137 '1': i32
580+
141..145 'foo1': fn foo1(u32) -> isize
581+
155..156 '2': i32
582+
155..156 '2': i32
583+
160..164 'foo2': fn foo2(u32) -> isize
584+
174..175 '_': i32
585+
179..183 'foo3': fn foo3(u32) -> isize
586+
"###
587+
);
588+
}
589+
548590
#[test]
549591
fn coerce_closure_to_fn_ptr() {
550592
assert_snapshot!(

0 commit comments

Comments
 (0)