Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support coercion between (FnDef | Closure) and (FnDef | Closure) #71599

Merged
merged 2 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions src/librustc_middle/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2056,24 +2056,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 @@ -2087,7 +2087,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 @@ -759,7 +759,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 @@ -875,23 +876,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(..)) => {
ldm0 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -901,17 +942,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