Skip to content

Commit

Permalink
Rollup merge of rust-lang#71599 - ldm0:fnclo, r=nikomatsakis
Browse files Browse the repository at this point in the history
Support coercion between (FnDef | Closure) and (FnDef | Closure)

Fixes rust-lang#46742, fixes rust-lang#48109
Inject `Closure` into the `FnDef x FnDef` coercion special case, which makes coercion of `(FnDef | Closure) x (FnDef | Closure)` possible, where closures should be **non-capturing**.
  • Loading branch information
Dylan-DPC authored May 18, 2020
2 parents 9e2a6a2 + 42396b1 commit 9a519d4
Show file tree
Hide file tree
Showing 14 changed files with 512 additions and 51 deletions.
21 changes: 11 additions & 10 deletions src/librustc_middle/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2069,24 +2069,25 @@ impl<'tcx> TyCtxt<'tcx> {
self.mk_fn_ptr(sig.map_bound(|sig| ty::FnSig { unsafety: hir::Unsafety::Unsafe, ..sig }))
}

/// Given a closure signature `sig`, returns an equivalent `fn`
/// type with the same signature. Detuples and so forth -- so
/// e.g., if we have a sig with `Fn<(u32, i32)>` then you would get
/// a `fn(u32, i32)`.
/// `unsafety` determines the unsafety of the `fn` type. If you pass
/// Given a closure signature, returns an equivalent fn signature. Detuples
/// and so forth -- so e.g., if we have a sig with `Fn<(u32, i32)>` then
/// you would get a `fn(u32, i32)`.
/// `unsafety` determines the unsafety of the fn signature. If you pass
/// `hir::Unsafety::Unsafe` in the previous example, then you would get
/// an `unsafe fn (u32, i32)`.
/// It cannot convert a closure that requires unsafe.
pub fn coerce_closure_fn_ty(self, sig: PolyFnSig<'tcx>, unsafety: hir::Unsafety) -> Ty<'tcx> {
let converted_sig = sig.map_bound(|s| {
pub fn signature_unclosure(
self,
sig: PolyFnSig<'tcx>,
unsafety: hir::Unsafety,
) -> PolyFnSig<'tcx> {
sig.map_bound(|s| {
let params_iter = match s.inputs()[0].kind {
ty::Tuple(params) => params.into_iter().map(|k| k.expect_ty()),
_ => bug!(),
};
self.mk_fn_sig(params_iter, s.output(), s.c_variadic, unsafety, abi::Abi::Rust)
});

self.mk_fn_ptr(converted_sig)
})
}

#[allow(rustc::usage_of_ty_tykind)]
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/borrow_check/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2088,7 +2088,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
ty::Closure(_, substs) => substs.as_closure().sig(),
_ => bug!(),
};
let ty_fn_ptr_from = tcx.coerce_closure_fn_ty(sig, *unsafety);
let ty_fn_ptr_from = tcx.mk_fn_ptr(tcx.signature_unclosure(sig, *unsafety));

if let Err(terr) = self.eq_types(
ty_fn_ptr_from,
Expand Down
91 changes: 69 additions & 22 deletions src/librustc_typeck/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,8 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// `unsafe fn(arg0,arg1,...) -> _`
let closure_sig = substs_a.as_closure().sig();
let unsafety = fn_ty.unsafety();
let pointer_ty = self.tcx.coerce_closure_fn_ty(closure_sig, unsafety);
let pointer_ty =
self.tcx.mk_fn_ptr(self.tcx.signature_unclosure(closure_sig, unsafety));
debug!("coerce_closure_to_fn(a={:?}, b={:?}, pty={:?})", a, b, pointer_ty);
self.unify_and(
pointer_ty,
Expand Down Expand Up @@ -909,23 +910,63 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
debug!("coercion::try_find_coercion_lub({:?}, {:?})", prev_ty, new_ty);

// Special-case that coercion alone cannot handle:
// Two function item types of differing IDs or InternalSubsts.
if let (&ty::FnDef(..), &ty::FnDef(..)) = (&prev_ty.kind, &new_ty.kind) {
// Don't reify if the function types have a LUB, i.e., they
// are the same function and their parameters have a LUB.
let lub_ty = self
.commit_if_ok(|_| self.at(cause, self.param_env).lub(prev_ty, new_ty))
.map(|ok| self.register_infer_ok_obligations(ok));

if lub_ty.is_ok() {
// We have a LUB of prev_ty and new_ty, just return it.
return lub_ty;
// Function items or non-capturing closures of differing IDs or InternalSubsts.
let (a_sig, b_sig) = {
let is_capturing_closure = |ty| {
if let &ty::Closure(_, substs) = ty {
substs.as_closure().upvar_tys().next().is_some()
} else {
false
}
};
if is_capturing_closure(&prev_ty.kind) || is_capturing_closure(&new_ty.kind) {
(None, None)
} else {
match (&prev_ty.kind, &new_ty.kind) {
(&ty::FnDef(..), &ty::FnDef(..)) => {
// Don't reify if the function types have a LUB, i.e., they
// are the same function and their parameters have a LUB.
match self
.commit_if_ok(|_| self.at(cause, self.param_env).lub(prev_ty, new_ty))
{
// We have a LUB of prev_ty and new_ty, just return it.
Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)),
Err(_) => {
(Some(prev_ty.fn_sig(self.tcx)), Some(new_ty.fn_sig(self.tcx)))
}
}
}
(&ty::Closure(_, substs), &ty::FnDef(..)) => {
let b_sig = new_ty.fn_sig(self.tcx);
let a_sig = self
.tcx
.signature_unclosure(substs.as_closure().sig(), b_sig.unsafety());
(Some(a_sig), Some(b_sig))
}
(&ty::FnDef(..), &ty::Closure(_, substs)) => {
let a_sig = prev_ty.fn_sig(self.tcx);
let b_sig = self
.tcx
.signature_unclosure(substs.as_closure().sig(), a_sig.unsafety());
(Some(a_sig), Some(b_sig))
}
(&ty::Closure(_, substs_a), &ty::Closure(_, substs_b)) => (
Some(self.tcx.signature_unclosure(
substs_a.as_closure().sig(),
hir::Unsafety::Normal,
)),
Some(self.tcx.signature_unclosure(
substs_b.as_closure().sig(),
hir::Unsafety::Normal,
)),
),
_ => (None, None),
}
}

};
if let (Some(a_sig), Some(b_sig)) = (a_sig, b_sig) {
// The signature must match.
let a_sig = prev_ty.fn_sig(self.tcx);
let a_sig = self.normalize_associated_types_in(new.span, &a_sig);
let b_sig = new_ty.fn_sig(self.tcx);
let b_sig = self.normalize_associated_types_in(new.span, &b_sig);
let sig = self
.at(cause, self.param_env)
Expand All @@ -935,17 +976,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

// Reify both sides and return the reified fn pointer type.
let fn_ptr = self.tcx.mk_fn_ptr(sig);
for expr in exprs.iter().map(|e| e.as_coercion_site()).chain(Some(new)) {
// The only adjustment that can produce an fn item is
// `NeverToAny`, so this should always be valid.
let prev_adjustment = match prev_ty.kind {
ty::Closure(..) => Adjust::Pointer(PointerCast::ClosureFnPointer(a_sig.unsafety())),
ty::FnDef(..) => Adjust::Pointer(PointerCast::ReifyFnPointer),
_ => unreachable!(),
};
let next_adjustment = match new_ty.kind {
ty::Closure(..) => Adjust::Pointer(PointerCast::ClosureFnPointer(b_sig.unsafety())),
ty::FnDef(..) => Adjust::Pointer(PointerCast::ReifyFnPointer),
_ => unreachable!(),
};
for expr in exprs.iter().map(|e| e.as_coercion_site()) {
self.apply_adjustments(
expr,
vec![Adjustment {
kind: Adjust::Pointer(PointerCast::ReifyFnPointer),
target: fn_ptr,
}],
vec![Adjustment { kind: prev_adjustment.clone(), target: fn_ptr }],
);
}
self.apply_adjustments(new, vec![Adjustment { kind: next_adjustment, target: fn_ptr }]);
return Ok(fn_ptr);
}

Expand Down
39 changes: 39 additions & 0 deletions src/test/ui/closures/closure_cap_coerce_many_fail.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
// We shouldn't coerce capturing closure to a function
let cap = 0;
let _ = match "+" {
"+" => add,
"-" => |a, b| (a - b + cap) as i32,
_ => unimplemented!(),
};
//~^^^ ERROR `match` arms have incompatible types


// We shouldn't coerce capturing closure to a non-capturing closure
let _ = match "+" {
"+" => |a, b| (a + b) as i32,
"-" => |a, b| (a - b + cap) as i32,
_ => unimplemented!(),
};
//~^^^ ERROR `match` arms have incompatible types


// We shouldn't coerce non-capturing closure to a capturing closure
let _ = match "+" {
"+" => |a, b| (a + b + cap) as i32,
"-" => |a, b| (a - b) as i32,
_ => unimplemented!(),
};
//~^^^ ERROR `match` arms have incompatible types

// We shouldn't coerce capturing closure to a capturing closure
let _ = match "+" {
"+" => |a, b| (a + b + cap) as i32,
"-" => |a, b| (a - b + cap) as i32,
_ => unimplemented!(),
};
//~^^^ ERROR `match` arms have incompatible types
}
73 changes: 73 additions & 0 deletions src/test/ui/closures/closure_cap_coerce_many_fail.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
error[E0308]: `match` arms have incompatible types
--> $DIR/closure_cap_coerce_many_fail.rs:9:16
|
LL | let _ = match "+" {
| _____________-
LL | | "+" => add,
| | --- this is found to be of type `fn(i32, i32) -> i32 {add}`
LL | | "-" => |a, b| (a - b + cap) as i32,
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn item, found closure
LL | | _ => unimplemented!(),
LL | | };
| |_____- `match` arms have incompatible types
|
= note: expected type `fn(i32, i32) -> i32 {add}`
found closure `[closure@$DIR/closure_cap_coerce_many_fail.rs:9:16: 9:43 cap:_]`

error[E0308]: `match` arms have incompatible types
--> $DIR/closure_cap_coerce_many_fail.rs:18:16
|
LL | let _ = match "+" {
| _____________-
LL | | "+" => |a, b| (a + b) as i32,
| | --------------------- this is found to be of type `[closure@$DIR/closure_cap_coerce_many_fail.rs:17:16: 17:37]`
LL | | "-" => |a, b| (a - b + cap) as i32,
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
LL | | _ => unimplemented!(),
LL | | };
| |_____- `match` arms have incompatible types
|
= note: expected type `[closure@$DIR/closure_cap_coerce_many_fail.rs:17:16: 17:37]`
found closure `[closure@$DIR/closure_cap_coerce_many_fail.rs:18:16: 18:43 cap:_]`
= note: no two closures, even if identical, have the same type
= help: consider boxing your closure and/or using it as a trait object

error[E0308]: `match` arms have incompatible types
--> $DIR/closure_cap_coerce_many_fail.rs:27:16
|
LL | let _ = match "+" {
| _____________-
LL | | "+" => |a, b| (a + b + cap) as i32,
| | --------------------------- this is found to be of type `[closure@$DIR/closure_cap_coerce_many_fail.rs:26:16: 26:43 cap:_]`
LL | | "-" => |a, b| (a - b) as i32,
| | ^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
LL | | _ => unimplemented!(),
LL | | };
| |_____- `match` arms have incompatible types
|
= note: expected type `[closure@$DIR/closure_cap_coerce_many_fail.rs:26:16: 26:43 cap:_]`
found closure `[closure@$DIR/closure_cap_coerce_many_fail.rs:27:16: 27:37]`
= note: no two closures, even if identical, have the same type
= help: consider boxing your closure and/or using it as a trait object

error[E0308]: `match` arms have incompatible types
--> $DIR/closure_cap_coerce_many_fail.rs:35:16
|
LL | let _ = match "+" {
| _____________-
LL | | "+" => |a, b| (a + b + cap) as i32,
| | --------------------------- this is found to be of type `[closure@$DIR/closure_cap_coerce_many_fail.rs:34:16: 34:43 cap:_]`
LL | | "-" => |a, b| (a - b + cap) as i32,
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
LL | | _ => unimplemented!(),
LL | | };
| |_____- `match` arms have incompatible types
|
= note: expected type `[closure@$DIR/closure_cap_coerce_many_fail.rs:34:16: 34:43 cap:_]`
found closure `[closure@$DIR/closure_cap_coerce_many_fail.rs:35:16: 35:43 cap:_]`
= note: no two closures, even if identical, have the same type
= help: consider boxing your closure and/or using it as a trait object

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0308`.
Loading

0 comments on commit 9a519d4

Please sign in to comment.