diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_typeck/src/check/demand.rs index d12d2cb59a5a9..e9efeed3d6e52 100644 --- a/compiler/rustc_typeck/src/check/demand.rs +++ b/compiler/rustc_typeck/src/check/demand.rs @@ -32,6 +32,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if self.suggest_calling_boxed_future_when_appropriate(err, expr, expected, expr_ty) { return; } + self.suggest_no_capture_closure(err, expected, expr_ty); self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty); self.suggest_missing_parentheses(err, expr); self.note_need_for_fn_pointer(err, expected, expr_ty); diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 17dbf989d6683..9fbf330fe0208 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -2,7 +2,7 @@ use super::FnCtxt; use crate::astconv::AstConv; use rustc_ast::util::parser::ExprPrecedence; -use rustc_span::{self, Span}; +use rustc_span::{self, MultiSpan, Span}; use rustc_errors::{Applicability, DiagnosticBuilder}; use rustc_hir as hir; @@ -287,6 +287,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// When encountering a closure that captures variables, where a FnPtr is expected, + /// suggest a non-capturing closure + pub(in super::super) fn suggest_no_capture_closure( + &self, + err: &mut DiagnosticBuilder<'_>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + ) { + if let (ty::FnPtr(_), ty::Closure(def_id, _)) = (expected.kind(), found.kind()) { + if let Some(upvars) = self.tcx.upvars_mentioned(*def_id) { + // Report upto four upvars being captured to reduce the amount error messages + // reported back to the user. + let spans_and_labels = upvars + .iter() + .take(4) + .map(|(var_hir_id, upvar)| { + let var_name = self.tcx.hir().name(*var_hir_id).to_string(); + let msg = format!("`{}` captured here", var_name); + (upvar.span, msg) + }) + .collect::>(); + + let mut multi_span: MultiSpan = + spans_and_labels.iter().map(|(sp, _)| *sp).collect::>().into(); + for (sp, label) in spans_and_labels { + multi_span.push_span_label(sp, label); + } + err.span_note(multi_span, "closures can only be coerced to `fn` types if they do not capture any variables"); + } + } + } + /// When encountering an `impl Future` where `BoxFuture` is expected, suggest `Box::pin`. pub(in super::super) fn suggest_calling_boxed_future_when_appropriate( &self, diff --git a/src/test/ui/closures/closure-no-fn-1.stderr b/src/test/ui/closures/closure-no-fn-1.stderr index 76136315a1b8b..2b53802fea793 100644 --- a/src/test/ui/closures/closure-no-fn-1.stderr +++ b/src/test/ui/closures/closure-no-fn-1.stderr @@ -8,6 +8,11 @@ LL | let foo: fn(u8) -> u8 = |v: u8| { a += v; a }; | = note: expected fn pointer `fn(u8) -> u8` found closure `[closure@$DIR/closure-no-fn-1.rs:6:29: 6:50]` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/closure-no-fn-1.rs:6:39 + | +LL | let foo: fn(u8) -> u8 = |v: u8| { a += v; a }; + | ^ `a` captured here error: aborting due to previous error diff --git a/src/test/ui/closures/closure-no-fn-2.stderr b/src/test/ui/closures/closure-no-fn-2.stderr index 85cbdbe7c18e1..ed9f87a2c94a8 100644 --- a/src/test/ui/closures/closure-no-fn-2.stderr +++ b/src/test/ui/closures/closure-no-fn-2.stderr @@ -8,6 +8,11 @@ LL | let bar: fn() -> u8 = || { b }; | = note: expected fn pointer `fn() -> u8` found closure `[closure@$DIR/closure-no-fn-2.rs:6:27: 6:35]` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/closure-no-fn-2.rs:6:32 + | +LL | let bar: fn() -> u8 = || { b }; + | ^ `b` captured here error: aborting due to previous error diff --git a/src/test/ui/closures/closure-no-fn-4.rs b/src/test/ui/closures/closure-no-fn-4.rs new file mode 100644 index 0000000000000..275bff645dba6 --- /dev/null +++ b/src/test/ui/closures/closure-no-fn-4.rs @@ -0,0 +1,8 @@ +fn main() { + let b = 2; + let _: fn(usize) -> usize = match true { + true => |a| a + 1, + false => |a| a - b, + //~^ ERROR `match` arms have incompatible types + }; +} diff --git a/src/test/ui/closures/closure-no-fn-4.stderr b/src/test/ui/closures/closure-no-fn-4.stderr new file mode 100644 index 0000000000000..89798ec5dd34f --- /dev/null +++ b/src/test/ui/closures/closure-no-fn-4.stderr @@ -0,0 +1,24 @@ +error[E0308]: `match` arms have incompatible types + --> $DIR/closure-no-fn-4.rs:5:18 + | +LL | let _: fn(usize) -> usize = match true { + | _________________________________- +LL | | true => |a| a + 1, + | | --------- this is found to be of type `fn(usize) -> usize` +LL | | false => |a| a - b, + | | ^^^^^^^^^ expected fn pointer, found closure +LL | | +LL | | }; + | |_____- `match` arms have incompatible types + | + = note: expected fn pointer `fn(usize) -> usize` + found closure `[closure@$DIR/closure-no-fn-4.rs:5:18: 5:27]` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/closure-no-fn-4.rs:5:26 + | +LL | false => |a| a - b, + | ^ `b` captured here + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/closures/closure-no-fn-5.rs b/src/test/ui/closures/closure-no-fn-5.rs new file mode 100644 index 0000000000000..43e3e977e34ab --- /dev/null +++ b/src/test/ui/closures/closure-no-fn-5.rs @@ -0,0 +1,12 @@ +// When providing diagnostics about not being able to coerce a capturing-closure +// to fn type, we want to report only upto 4 captures. + +fn main() { + let a = 0u8; + let b = 0u8; + let c = 0u8; + let d = 0u8; + let e = 0u8; + let bar: fn() -> u8 = || { a; b; c; d; e }; + //~^ ERROR mismatched types +} diff --git a/src/test/ui/closures/closure-no-fn-5.stderr b/src/test/ui/closures/closure-no-fn-5.stderr new file mode 100644 index 0000000000000..1f373f10489e6 --- /dev/null +++ b/src/test/ui/closures/closure-no-fn-5.stderr @@ -0,0 +1,23 @@ +error[E0308]: mismatched types + --> $DIR/closure-no-fn-5.rs:10:27 + | +LL | let bar: fn() -> u8 = || { a; b; c; d; e }; + | ---------- ^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found closure + | | + | expected due to this + | + = note: expected fn pointer `fn() -> u8` + found closure `[closure@$DIR/closure-no-fn-5.rs:10:27: 10:47]` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/closure-no-fn-5.rs:10:32 + | +LL | let bar: fn() -> u8 = || { a; b; c; d; e }; + | ^ ^ ^ ^ `d` captured here + | | | | + | | | `c` captured here + | | `b` captured here + | `a` captured here + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/closures/closure-reform-bad.stderr b/src/test/ui/closures/closure-reform-bad.stderr index 77c8c7ab7948d..37813879ce752 100644 --- a/src/test/ui/closures/closure-reform-bad.stderr +++ b/src/test/ui/closures/closure-reform-bad.stderr @@ -8,6 +8,11 @@ LL | call_bare(f) | = note: expected fn pointer `for<'r> fn(&'r str)` found closure `[closure@$DIR/closure-reform-bad.rs:10:13: 10:50]` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/closure-reform-bad.rs:10:43 + | +LL | let f = |s: &str| println!("{}{}", s, string); + | ^^^^^^ `string` captured here error: aborting due to previous error diff --git a/src/test/ui/closures/print/closure-print-verbose.stderr b/src/test/ui/closures/print/closure-print-verbose.stderr index 9e07137a24195..d19b07acbf175 100644 --- a/src/test/ui/closures/print/closure-print-verbose.stderr +++ b/src/test/ui/closures/print/closure-print-verbose.stderr @@ -8,6 +8,11 @@ LL | let foo: fn(u8) -> u8 = |v: u8| { a += v; a }; | = note: expected fn pointer `fn(u8) -> u8` found closure `[main::{closure#0} closure_substs=(unavailable)]` +note: closures can only be coerced to `fn` types if they do not capture any variables + --> $DIR/closure-print-verbose.rs:10:39 + | +LL | let foo: fn(u8) -> u8 = |v: u8| { a += v; a }; + | ^ `a` captured here error: aborting due to previous error