Skip to content

Commit

Permalink
diagnostics: Note capturing closures can't be coerced to fns
Browse files Browse the repository at this point in the history
Fixes: #72457, #71895
  • Loading branch information
arora-aman committed Nov 29, 2020
1 parent 0d96516 commit 1841a5f
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 1 deletion.
1 change: 1 addition & 0 deletions compiler/rustc_typeck/src/check/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
34 changes: 33 additions & 1 deletion compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<Vec<_>>();

let mut multi_span: MultiSpan =
spans_and_labels.iter().map(|(sp, _)| *sp).collect::<Vec<_>>().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,
Expand Down
5 changes: 5 additions & 0 deletions src/test/ui/closures/closure-no-fn-1.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions src/test/ui/closures/closure-no-fn-2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions src/test/ui/closures/closure-no-fn-4.rs
Original file line number Diff line number Diff line change
@@ -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
};
}
24 changes: 24 additions & 0 deletions src/test/ui/closures/closure-no-fn-4.stderr
Original file line number Diff line number Diff line change
@@ -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`.
12 changes: 12 additions & 0 deletions src/test/ui/closures/closure-no-fn-5.rs
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions src/test/ui/closures/closure-no-fn-5.stderr
Original file line number Diff line number Diff line change
@@ -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`.
5 changes: 5 additions & 0 deletions src/test/ui/closures/closure-reform-bad.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions src/test/ui/closures/print/closure-print-verbose.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 1841a5f

Please sign in to comment.