diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index 66529b67c17b3..393eca9b24cf4 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -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::>() + .join(", ")), + Applicability::MaybeIncorrect, + ); + } + } } } diff --git a/tests/ui/expr/malformed_closure/block_instead_of_closure_in_arg.rs b/tests/ui/expr/malformed_closure/block_instead_of_closure_in_arg.rs new file mode 100644 index 0000000000000..9c685590b8bba --- /dev/null +++ b/tests/ui/expr/malformed_closure/block_instead_of_closure_in_arg.rs @@ -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 + } + }); +} diff --git a/tests/ui/expr/malformed_closure/block_instead_of_closure_in_arg.stderr b/tests/ui/expr/malformed_closure/block_instead_of_closure_in_arg.stderr new file mode 100644 index 0000000000000..b72360562f261 --- /dev/null +++ b/tests/ui/expr/malformed_closure/block_instead_of_closure_in_arg.stderr @@ -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::::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`. diff --git a/tests/ui/expr/malformed_closure/ruby_style_closure_successful_parse.rs b/tests/ui/expr/malformed_closure/ruby_style_closure_successful_parse.rs new file mode 100644 index 0000000000000..982b9fd00f647 --- /dev/null +++ b/tests/ui/expr/malformed_closure/ruby_style_closure_successful_parse.rs @@ -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` + 1 + 1; + Some(x * 2) + }); +} diff --git a/tests/ui/expr/malformed_closure/ruby_style_closure_successful_parse.stderr b/tests/ui/expr/malformed_closure/ruby_style_closure_successful_parse.stderr new file mode 100644 index 0000000000000..32d34430b7cb7 --- /dev/null +++ b/tests/ui/expr/malformed_closure/ruby_style_closure_successful_parse.stderr @@ -0,0 +1,25 @@ +error[E0277]: expected a `FnOnce<({integer},)>` closure, found `Option` + --> $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` +LL | | }); + | |_____^ expected an `FnOnce<({integer},)>` closure, found `Option` + | + = help: the trait `FnOnce<({integer},)>` is not implemented for `Option` +note: required by a bound in `Option::::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`.