Skip to content

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

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

Merged
merged 2 commits into from
May 19, 2020
Merged
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
@@ -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)]
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
@@ -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,
91 changes: 69 additions & 22 deletions src/librustc_typeck/check/coercion.rs
Original file line number Diff line number Diff line change
@@ -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,
@@ -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(..)) => {
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)
@@ -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);
}

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`.
166 changes: 166 additions & 0 deletions src/test/ui/closures/closure_no_cap_coerce_many_check_pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// check-pass
// Ensure non-capturing Closure passes CoerceMany.
fn foo(x: usize) -> usize {
0
}

fn bar(x: usize) -> usize {
1
}

fn main() {
// One FnDef and one non-capturing Closure
let _ = match 0 {
0 => foo,
2 => |a| 2,
_ => unimplemented!(),
};

let _ = match 0 {
2 => |a| 2,
0 => foo,
_ => unimplemented!(),
};

let _ = [foo, |a| 2];
let _ = [|a| 2, foo];



// Two FnDefs and one non-capturing Closure
let _ = match 0 {
0 => foo,
1 => bar,
2 => |a| 2,
_ => unimplemented!(),
};

let _ = match 0 {
0 => foo,
2 => |a| 2,
1 => bar,
_ => unimplemented!(),
};

let _ = match 0 {
2 => |a| 2,
0 => foo,
1 => bar,
_ => unimplemented!(),
};

let _ = [foo, bar, |a| 2];
let _ = [foo, |a| 2, bar];
let _ = [|a| 2, foo, bar];



// One FnDef and two non-capturing Closures
let _ = match 0 {
0 => foo,
1 => |a| 1,
2 => |a| 2,
_ => unimplemented!(),
};

let _ = match 0 {
1 => |a| 1,
0 => foo,
2 => |a| 2,
_ => unimplemented!(),
};

let _ = match 0 {
1 => |a| 1,
2 => |a| 2,
0 => foo,
_ => unimplemented!(),
};

let _ = [foo, |a| 1, |a| 2];
let _ = [|a| 1, foo, |a| 2];
let _ = [|a| 1, |a| 2, foo];



// Three non-capturing Closures
let _ = match 0 {
0 => |a: usize| 0,
1 => |a| 1,
2 => |a| 2,
_ => unimplemented!(),
};

let _ = [|a: usize| 0, |a| 1, |a| 2];



// Three non-capturing Closures variable
let clo0 = |a: usize| 0;
let clo1 = |a| 1;
let clo2 = |a| 2;
let _ = match 0 {
0 => clo0,
1 => clo1,
2 => clo2,
_ => unimplemented!(),
};

let clo0 = |a: usize| 0;
let clo1 = |a| 1;
let clo2 = |a| 2;
let _ = [clo0, clo1, clo2];



// --- Function pointer related part

// Closure is not in a variable
type FnPointer = fn(usize) -> usize;

let _ = match 0 {
0 => foo as FnPointer,
2 => |a| 2,
_ => unimplemented!(),
};
let _ = match 0 {
2 => |a| 2,
0 => foo as FnPointer,
_ => unimplemented!(),
};
let _ = [foo as FnPointer, |a| 2];
let _ = [|a| 2, foo as FnPointer];
let _ = [foo, bar, |x| x];
let _ = [foo as FnPointer, bar, |x| x];
let _ = [foo, bar as FnPointer, |x| x];
let _ = [foo, bar, (|x| x) as FnPointer];
let _ = [foo as FnPointer, bar as FnPointer, |x| x];

// Closure is in a variable
let x = |a| 2;
let _ = match 0 {
0 => foo as FnPointer,
2 => x,
_ => unimplemented!(),
};
let x = |a| 2;
let _ = match 0 {
2 => x,
0 => foo as FnPointer,
_ => unimplemented!(),
};
let x = |a| 2;
let _ = [foo as FnPointer, x];
let _ = [x, foo as FnPointer];

let x = |a| 2;
let _ = [foo, bar, x];
let x: FnPointer = |a| 2;
let _ = [foo, bar, x];
let x = |a| 2;
let _ = [foo, bar as FnPointer, x];
let x = |a| 2;
let _ = [foo as FnPointer, bar, x];
let x = |a| 2;
let _ = [foo as FnPointer, bar as FnPointer, x];
}
59 changes: 59 additions & 0 deletions src/test/ui/closures/closure_no_cap_coerce_many_run_pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// run-pass
// Ensure non-capturing Closure passing CoerceMany work correctly.
fn foo(_: usize) -> usize {
0
}

fn bar(_: usize) -> usize {
1
}

fn add(a: i32, b: i32) -> i32 {
a + b
}

fn main() {
// Coerce result check

type FnPointer = fn(usize) -> usize;

let c = |x| x;
let c_pointer: FnPointer = c;
assert_eq!(c_pointer(42), 42);

let f = match 0 {
0 => foo,
1 => |_| 1,
_ => unimplemented!(),
};
assert_eq!(f(42), 0);

let f = match 2 {
2 => |_| 2,
0 => foo,
_ => unimplemented!(),
};
assert_eq!(f(42), 2);

let f = match 1 {
0 => foo,
1 => bar,
2 => |_| 2,
_ => unimplemented!(),
};
assert_eq!(f(42), 1);

let clo0 = |_: usize| 0;
let clo1 = |_| 1;
let clo2 = |_| 2;
let f = match 0 {
0 => clo0,
1 => clo1,
2 => clo2,
_ => unimplemented!(),
};
assert_eq!(f(42), 0);

let funcs = [add, |a, b| (a - b) as i32];
assert_eq!([funcs[0](5, 5), funcs[1](5, 5)], [10, 0]);
}
22 changes: 22 additions & 0 deletions src/test/ui/closures/closure_no_cap_coerce_many_unsafe_0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Ensure we get unsafe function after coercion
unsafe fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
// We can coerce non-capturing closure to unsafe function
let foo = match "+" {
"+" => add,
"-" => |a, b| (a - b) as i32,
_ => unimplemented!(),
};
let result: i32 = foo(5, 5); //~ ERROR call to unsafe function


// We can coerce unsafe function to non-capturing closure
let foo = match "+" {
"-" => |a, b| (a - b) as i32,
"+" => add,
_ => unimplemented!(),
};
let result: i32 = foo(5, 5); //~ ERROR call to unsafe function
}
19 changes: 19 additions & 0 deletions src/test/ui/closures/closure_no_cap_coerce_many_unsafe_0.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> $DIR/closure_no_cap_coerce_many_unsafe_0.rs:12:23
|
LL | let result: i32 = foo(5, 5);
| ^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> $DIR/closure_no_cap_coerce_many_unsafe_0.rs:21:23
|
LL | let result: i32 = foo(5, 5);
| ^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0133`.
23 changes: 23 additions & 0 deletions src/test/ui/closures/closure_no_cap_coerce_many_unsafe_1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// run-pass
// Ensure we get correct unsafe function after coercion
unsafe fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
// We can coerce non-capturing closure to unsafe function
let foo = match "+" {
"+" => add,
"-" => |a, b| (a - b) as i32,
_ => unimplemented!(),
};
assert_eq!(unsafe { foo(5, 5) }, 10);


// We can coerce unsafe function to non-capturing closure
let foo = match "-" {
"-" => |a, b| (a - b) as i32,
"+" => add,
_ => unimplemented!(),
};
assert_eq!(unsafe { foo(5, 5) }, 0);
}
9 changes: 9 additions & 0 deletions src/test/ui/closures/issue-46742.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// check-pass
fn main() {
let _: i32 = (match "" {
"+" => ::std::ops::Add::add,
"-" => ::std::ops::Sub::sub,
"<" => |a,b| (a < b) as i32,
_ => unimplemented!(),
})(5, 5);
}
14 changes: 14 additions & 0 deletions src/test/ui/closures/issue-48109.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// check-pass
fn useful(i: usize) -> usize {
i
}

fn useful2(i: usize) -> usize {
i
}

fn main() {
for f in &[useful, useful2, |x| x] {
println!("{}", f(6));
}
}
2 changes: 1 addition & 1 deletion src/test/ui/issues/issue-24036.rs
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ fn closure_from_match() {
2 => |c| c - 1,
_ => |c| c - 1
};
//~^^^ ERROR `match` arms have incompatible types
//~^^^^ ERROR type annotations needed
}

fn main() { }
23 changes: 6 additions & 17 deletions src/test/ui/issues/issue-24036.stderr
Original file line number Diff line number Diff line change
@@ -11,24 +11,13 @@ LL | x = |c| c + 1;
= 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/issue-24036.rs:10:14
error[E0282]: type annotations needed
--> $DIR/issue-24036.rs:9:15
|
LL | let x = match 1usize {
| _____________-
LL | | 1 => |c| c + 1,
| | --------- this is found to be of type `[closure@$DIR/issue-24036.rs:9:14: 9:23]`
LL | | 2 => |c| c - 1,
| | ^^^^^^^^^ expected closure, found a different closure
LL | | _ => |c| c - 1
LL | | };
| |_____- `match` arms have incompatible types
|
= note: expected type `[closure@$DIR/issue-24036.rs:9:14: 9:23]`
found closure `[closure@$DIR/issue-24036.rs:10:14: 10:23]`
= note: no two closures, even if identical, have the same type
= help: consider boxing your closure and/or using it as a trait object
LL | 1 => |c| c + 1,
| ^ consider giving this closure parameter a type

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.
Some errors have detailed explanations: E0282, E0308.
For more information about an error, try `rustc --explain E0282`.