From a09079f27aa631b011f6c0703200862d28af81f4 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sat, 15 May 2021 16:00:24 +0200 Subject: Fix coercion of two closures to a function pointer Fixes #8604. --- crates/hir_ty/src/infer/coerce.rs | 50 +++++++++++++++++++++++-------------- crates/hir_ty/src/lib.rs | 1 + crates/hir_ty/src/tests/coercion.rs | 17 ++++++++++++- crates/hir_ty/src/tests/simple.rs | 36 -------------------------- 4 files changed, 48 insertions(+), 56 deletions(-) (limited to 'crates') diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs index 8467ea056..27f59c8bb 100644 --- a/crates/hir_ty/src/infer/coerce.rs +++ b/crates/hir_ty/src/infer/coerce.rs @@ -40,32 +40,45 @@ impl<'a> InferenceContext<'a> { /// Merge two types from different branches, with possible coercion. /// /// Mostly this means trying to coerce one to the other, but - /// - if we have two function types for different functions, we need to + /// - if we have two function types for different functions or closures, 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 { + // 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 sig = match (ty1.kind(&Interner), ty2.kind(&Interner)) { + (TyKind::FnDef(..), TyKind::FnDef(..)) + | (TyKind::Closure(..), TyKind::FnDef(..)) + | (TyKind::FnDef(..), TyKind::Closure(..)) + | (TyKind::Closure(..), TyKind::Closure(..)) => { + // FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure, + // we should be coercing the closure to a fn pointer of the safety of the FnDef + cov_mark::hit!(coerce_fn_reification); + let sig = ty1.callable_sig(self.db).expect("FnDef without callable sig"); + Some(sig) + } + _ => None, + }; + if let Some(sig) = sig { + let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(&Interner); + let result1 = self.coerce_inner(ty1.clone(), &target_ty); + let result2 = self.coerce_inner(ty2.clone(), &target_ty); + if let (Ok(_result1), Ok(_result2)) = (result1, result2) { + // TODO deal with the goals + return target_ty; + } + } + if self.coerce(ty1, ty2) { ty2.clone() } else if self.coerce(ty2, ty1) { ty1.clone() } else { - if let (TyKind::FnDef(..), TyKind::FnDef(..)) = - (ty1.kind(&Interner), ty2.kind(&Interner)) - { - cov_mark::hit!(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 = TyBuilder::fn_ptr(sig1); - let ptr_ty2 = TyBuilder::fn_ptr(sig2); - self.coerce_merge_branch(&ptr_ty1, &ptr_ty2) - } else { - cov_mark::hit!(coerce_merge_fail_fallback); - ty1.clone() - } + // FIXME record a type mismatch + cov_mark::hit!(coerce_merge_fail_fallback); + ty1.clone() } } @@ -236,8 +249,7 @@ impl<'a> InferenceContext<'a> { Ok(result) } - /// Attempts to coerce from the type of a Rust function item into a closure - /// or a function pointer. + /// Attempts to coerce from the type of a Rust function item into a function pointer. fn coerce_from_fn_item(&mut self, from_ty: Ty, to_ty: &Ty) -> InferResult { match to_ty.kind(&Interner) { TyKind::Function(_) => { diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs index 179a27763..06d5cd0b6 100644 --- a/crates/hir_ty/src/lib.rs +++ b/crates/hir_ty/src/lib.rs @@ -167,6 +167,7 @@ pub fn make_canonical>( Canonical { value, binders: chalk_ir::CanonicalVarKinds::from_iter(&Interner, kinds) } } +// FIXME: get rid of this, just replace it by FnPointer /// A function signature as seen by type inference: Several parameter types and /// one return type. #[derive(Clone, PartialEq, Eq, Debug)] diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs index 190471069..67295b663 100644 --- a/crates/hir_ty/src/tests/coercion.rs +++ b/crates/hir_ty/src/tests/coercion.rs @@ -1,6 +1,6 @@ use expect_test::expect; -use super::{check_infer, check_infer_with_mismatches}; +use super::{check_infer, check_infer_with_mismatches, check_types}; #[test] fn infer_block_expr_type_mismatch() { @@ -858,3 +858,18 @@ fn coerce_unsize_generic() { "]], ); } + +#[test] +fn infer_two_closures_lub() { + check_types( + r#" +fn foo(c: i32) { + let add = |a: i32, b: i32| a + b; + let sub = |a, b| a - b; + //^ |i32, i32| -> i32 + if c > 42 { add } else { sub }; + //^ fn(i32, i32) -> i32 +} + "#, + ) +} diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs index a9cd42186..5c70a1fc0 100644 --- a/crates/hir_ty/src/tests/simple.rs +++ b/crates/hir_ty/src/tests/simple.rs @@ -1039,42 +1039,6 @@ fn infer_in_elseif() { ) } -#[test] -fn infer_closure_unify() { - check_infer( - r#" - fn foo(f: bool) { - let a = |x| x; - let b = |x| x; - let id = if f { a } else { b }; - id(123); - } - "#, - expect![[r#" - 7..8 'f': bool - 16..106 '{ ...23); }': () - 26..27 'a': |i32| -> i32 - 30..35 '|x| x': |i32| -> i32 - 31..32 'x': i32 - 34..35 'x': i32 - 45..46 'b': |i32| -> i32 - 49..54 '|x| x': |i32| -> i32 - 50..51 'x': i32 - 53..54 'x': i32 - 64..66 'id': |i32| -> i32 - 69..90 'if f {... { b }': |i32| -> i32 - 72..73 'f': bool - 74..79 '{ a }': |i32| -> i32 - 76..77 'a': |i32| -> i32 - 85..90 '{ b }': |i32| -> i32 - 87..88 'b': |i32| -> i32 - 96..98 'id': |i32| -> i32 - 96..103 'id(123)': i32 - 99..102 '123': i32 - "#]], - ) -} - #[test] fn infer_if_match_with_return() { check_infer( -- cgit v1.2.3