Suggest is_some or let when encountering Option and bool type mismatch

This commit is contained in:
Michael Goulet 2022-11-06 01:38:57 +00:00
parent cc9b259b5e
commit 38ada60eb6
5 changed files with 85 additions and 4 deletions

View file

@ -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);

View file

@ -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();
}

View file

@ -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(

View file

@ -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() {}

View file

@ -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`.