When expecting closure argument but finding block provide suggestion

Detect if there is a potential typo where the `{` meant to open the
closure body was written before the body.

```
error[E0277]: expected a `FnOnce<({integer},)>` closure, found `Option<usize>`
  --> $DIR/ruby_style_closure_successful_parse.rs:3:31
   |
LL |       let p = Some(45).and_then({|x|
   |  ______________________--------_^
   | |                      |
   | |                      required by a bound introduced by this call
LL | |         1 + 1;
LL | |         Some(x * 2)
   | |         ----------- this tail expression is of type `Option<usize>`
LL | |     });
   | |_____^ expected an `FnOnce<({integer},)>` closure, found `Option<usize>`
   |
   = help: the trait `FnOnce<({integer},)>` is not implemented for `Option<usize>`
note: required by a bound in `Option::<T>::and_then`
  --> $SRC_DIR/core/src/option.rs:LL:COL
help: you might have meant to open the closure body instead of placing a closure within a block
   |
LL -     let p = Some(45).and_then({|x|
LL +     let p = Some(45).and_then(|x| {
   |
```

Detect the potential typo where the closure header is missing.

```
error[E0277]: expected a `FnOnce<(&bool,)>` closure, found `bool`
  --> $DIR/block_instead_of_closure_in_arg.rs:3:23
   |
LL |        Some(true).filter({
   |  _________________------_^
   | |                 |
   | |                 required by a bound introduced by this call
LL | |/         if number % 2 == 0 {
LL | ||             number == 0
LL | ||         } else {
LL | ||             number != 0
LL | ||         }
   | ||_________- this tail expression is of type `bool`
LL | |      });
   | |______^ expected an `FnOnce<(&bool,)>` closure, found `bool`
   |
   = help: the trait `for<'a> FnOnce<(&'a bool,)>` is not implemented for `bool`
note: required by a bound in `Option::<T>::filter`
  --> $SRC_DIR/core/src/option.rs:LL:COL
help: you might have meant to create the closure instead of a block
   |
LL |     Some(true).filter(|_| {
   |                       +++
```

Partially address #27300.
This commit is contained in:
Esteban Küber 2023-10-23 20:41:15 +00:00
parent cc705b8012
commit c1bfd46c7b
5 changed files with 117 additions and 5 deletions

View File

@ -3442,11 +3442,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
if let Some(Node::Expr(expr)) = hir.find(arg_hir_id)
&& let Some(typeck_results) = &self.typeck_results
{
if let hir::Expr { kind: hir::ExprKind::Block(..), .. } = expr {
let expr = expr.peel_blocks();
let ty =
typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(tcx));
let span = expr.span;
if let hir::Expr { kind: hir::ExprKind::Block(block, _), .. } = expr {
let inner_expr = expr.peel_blocks();
let ty = typeck_results.expr_ty_adjusted_opt(inner_expr)
.unwrap_or(Ty::new_misc_error(tcx));
let span = inner_expr.span;
if Some(span) != err.span.primary_span() {
err.span_label(
span,
@ -3457,6 +3457,49 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
format!("this tail expression is of type `{ty}`")
},
);
if let ty::PredicateKind::Clause(clause) = failed_pred.kind().skip_binder()
&& let ty::ClauseKind::Trait(pred) = clause
&& [
tcx.lang_items().fn_once_trait(),
tcx.lang_items().fn_mut_trait(),
tcx.lang_items().fn_trait(),
].contains(&Some(pred.def_id()))
{
if let [stmt, ..] = block.stmts
&& let hir::StmtKind::Semi(value) = stmt.kind
&& let hir::ExprKind::Closure(hir::Closure {
body,
fn_decl_span,
..
}) = value.kind
&& let body = hir.body(*body)
&& !matches!(body.value.kind, hir::ExprKind::Block(..))
{
// Check if the failed predicate was an expectation of a closure type
// and if there might have been a `{ |args|` typo instead of `|args| {`.
err.multipart_suggestion(
"you might have meant to open the closure body instead of placing \
a closure within a block",
vec![
(expr.span.with_hi(value.span.lo()), String::new()),
(fn_decl_span.shrink_to_hi(), " {".to_string()),
],
Applicability::MaybeIncorrect,
);
} else {
// Maybe the bare block was meant to be a closure.
err.span_suggestion_verbose(
expr.span.shrink_to_lo(),
"you might have meant to create the closure instead of a block",
format!(
"|{}| ",
(0..pred.trait_ref.args.len() - 1).map(|_| "_")
.collect::<Vec<_>>()
.join(", ")),
Applicability::MaybeIncorrect,
);
}
}
}
}

View File

@ -0,0 +1,10 @@
fn main() {
let number = 2;
Some(true).filter({ //~ ERROR expected a `FnOnce<(&bool,)>` closure, found `bool`
if number % 2 == 0 {
number == 0
} else {
number != 0
}
});
}

View File

@ -0,0 +1,27 @@
error[E0277]: expected a `FnOnce<(&bool,)>` closure, found `bool`
--> $DIR/block_instead_of_closure_in_arg.rs:3:23
|
LL | Some(true).filter({
| _________________------_^
| | |
| | required by a bound introduced by this call
LL | |/ if number % 2 == 0 {
LL | || number == 0
LL | || } else {
LL | || number != 0
LL | || }
| ||_________- this tail expression is of type `bool`
LL | | });
| |______^ expected an `FnOnce<(&bool,)>` closure, found `bool`
|
= help: the trait `for<'a> FnOnce<(&'a bool,)>` is not implemented for `bool`
note: required by a bound in `Option::<T>::filter`
--> $SRC_DIR/core/src/option.rs:LL:COL
help: you might have meant to create the closure instead of a block
|
LL | Some(true).filter(|_| {
| +++
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.

View File

@ -0,0 +1,7 @@
const x: usize =42;
fn main() {
let p = Some(45).and_then({|x| //~ ERROR expected a `FnOnce<({integer},)>` closure, found `Option<usize>`
1 + 1;
Some(x * 2)
});
}

View File

@ -0,0 +1,25 @@
error[E0277]: expected a `FnOnce<({integer},)>` closure, found `Option<usize>`
--> $DIR/ruby_style_closure_successful_parse.rs:3:31
|
LL | let p = Some(45).and_then({|x|
| ______________________--------_^
| | |
| | required by a bound introduced by this call
LL | | 1 + 1;
LL | | Some(x * 2)
| | ----------- this tail expression is of type `Option<usize>`
LL | | });
| |_____^ expected an `FnOnce<({integer},)>` closure, found `Option<usize>`
|
= help: the trait `FnOnce<({integer},)>` is not implemented for `Option<usize>`
note: required by a bound in `Option::<T>::and_then`
--> $SRC_DIR/core/src/option.rs:LL:COL
help: you might have meant to open the closure body instead of placing a closure within a block
|
LL - let p = Some(45).and_then({|x|
LL + let p = Some(45).and_then(|x| {
|
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.