about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_hir_typeck/src/demand.rs3
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs12
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs49
-rw-r--r--src/test/ui/suggestions/option-to-bool.rs9
-rw-r--r--src/test/ui/suggestions/option-to-bool.stderr16
5 files changed, 85 insertions, 4 deletions
diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs
index 16febfc46da..7f78f5fb8a7 100644
--- a/compiler/rustc_hir_typeck/src/demand.rs
+++ b/compiler/rustc_hir_typeck/src/demand.rs
@@ -42,7 +42,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             || self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty)
             || self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected)
             || self.suggest_copied_or_cloned(err, expr, expr_ty, expected)
-            || self.suggest_into(err, expr, expr_ty, expected);
+            || self.suggest_into(err, expr, expr_ty, expected)
+            || self.suggest_option_to_bool(err, expr, expr_ty, expected);
 
         self.note_type_is_not_clone(err, expected, expr_ty, expr);
         self.note_need_for_fn_pointer(err, expected, expr_ty);
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 43669489e69..e948d832e32 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -103,8 +103,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
 
         if let Some(mut err) = self.demand_suptype_diag(expr.span, expected_ty, ty) {
-            let expr = expr.peel_drop_temps();
-            self.suggest_deref_ref_or_into(&mut err, expr, expected_ty, ty, None);
+            // FIXME(compiler-errors): We probably should fold some of the
+            // `suggest_` functions from  `emit_coerce_suggestions` into here,
+            // since some of those aren't necessarily just coerce suggestions.
+            let _ = self.suggest_deref_ref_or_into(
+                &mut err,
+                expr.peel_drop_temps(),
+                expected_ty,
+                ty,
+                None,
+            ) || self.suggest_option_to_bool(&mut err, expr, ty, expected_ty);
             extend_err(&mut err);
             err.emit();
         }
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
index e3b3fb499b1..a14759e254c 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
@@ -13,7 +13,7 @@ use rustc_hir_analysis::astconv::AstConv;
 use rustc_infer::infer::{self, TyCtxtInferExt};
 use rustc_infer::traits::{self, StatementAsExpression};
 use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::{self, Binder, IsSuggestable, ToPredicate, Ty};
+use rustc_middle::ty::{self, Binder, DefIdTree, IsSuggestable, ToPredicate, Ty};
 use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_span::symbol::sym;
 use rustc_span::Span;
@@ -1116,6 +1116,53 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         false
     }
 
+    /// When expecting a `bool` and finding an `Option`, suggests using `let Some(..)` or `.is_some()`
+    pub(crate) fn suggest_option_to_bool(
+        &self,
+        diag: &mut Diagnostic,
+        expr: &hir::Expr<'_>,
+        expr_ty: Ty<'tcx>,
+        expected_ty: Ty<'tcx>,
+    ) -> bool {
+        if !expected_ty.is_bool() {
+            return false;
+        }
+
+        let ty::Adt(def, _) = expr_ty.peel_refs().kind() else { return false; };
+        if !self.tcx.is_diagnostic_item(sym::Option, def.did()) {
+            return false;
+        }
+
+        let hir = self.tcx.hir();
+        let cond_parent = hir.parent_iter(expr.hir_id).skip_while(|(_, node)| {
+            matches!(node, hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(op, _, _), .. }) if op.node == hir::BinOpKind::And)
+        }).next();
+        // Don't suggest:
+        //     `let Some(_) = a.is_some() && b`
+        //                     ++++++++++
+        // since the user probably just misunderstood how `let else`
+        // and `&&` work together.
+        if let Some((_, hir::Node::Local(local))) = cond_parent
+            && let hir::PatKind::Path(qpath) | hir::PatKind::TupleStruct(qpath, _, _) = &local.pat.kind
+            && let hir::QPath::Resolved(None, path) = qpath
+            && let Some(did) = path.res.opt_def_id()
+                .and_then(|did| self.tcx.opt_parent(did))
+                .and_then(|did| self.tcx.opt_parent(did))
+            && self.tcx.is_diagnostic_item(sym::Option, did)
+        {
+            return false;
+        }
+
+        diag.span_suggestion(
+            expr.span.shrink_to_hi(),
+            "use `Option::is_some` to test if the `Option` has a value",
+            ".is_some()",
+            Applicability::MachineApplicable,
+        );
+
+        true
+    }
+
     /// Suggest wrapping the block in square brackets instead of curly braces
     /// in case the block was mistaken array syntax, e.g. `{ 1 }` -> `[ 1 ]`.
     pub(crate) fn suggest_block_to_brackets(
diff --git a/src/test/ui/suggestions/option-to-bool.rs b/src/test/ui/suggestions/option-to-bool.rs
new file mode 100644
index 00000000000..2a1823b15f5
--- /dev/null
+++ b/src/test/ui/suggestions/option-to-bool.rs
@@ -0,0 +1,9 @@
+#![cfg_attr(let_chains, feature(let_chains))]
+
+fn foo(x: Option<i32>) {
+    if true && x {}
+    //~^ ERROR mismatched types
+    //~| HELP use `Option::is_some` to test if the `Option` has a value
+}
+
+fn main() {}
diff --git a/src/test/ui/suggestions/option-to-bool.stderr b/src/test/ui/suggestions/option-to-bool.stderr
new file mode 100644
index 00000000000..57a934b8342
--- /dev/null
+++ b/src/test/ui/suggestions/option-to-bool.stderr
@@ -0,0 +1,16 @@
+error[E0308]: mismatched types
+  --> $DIR/option-to-bool.rs:4:16
+   |
+LL |     if true && x {}
+   |                ^ expected `bool`, found enum `Option`
+   |
+   = note: expected type `bool`
+              found enum `Option<i32>`
+help: use `Option::is_some` to test if the `Option` has a value
+   |
+LL |     if true && x.is_some() {}
+   |                 ++++++++++
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.