|
1 | 1 | use std::borrow::Cow; |
2 | 2 |
|
| 3 | +use clippy_utils::consts::{ConstEvalCtxt, Constant}; |
3 | 4 | use clippy_utils::diagnostics::span_lint_and_then; |
4 | 5 | use clippy_utils::eager_or_lazy::switch_to_eager_eval; |
5 | 6 | use clippy_utils::msrvs::{self, Msrv}; |
6 | 7 | use clippy_utils::res::{MaybeDef as _, MaybeResPath as _}; |
7 | 8 | use clippy_utils::sugg::{Sugg, make_binop}; |
8 | | -use clippy_utils::ty::{implements_trait, is_copy}; |
9 | | -use clippy_utils::visitors::is_local_used; |
| 9 | +use clippy_utils::ty::{implements_trait, is_copy, needs_ordered_drop}; |
| 10 | +use clippy_utils::visitors::{any_temporaries_need_ordered_drop, is_local_used}; |
10 | 11 | use clippy_utils::{get_parent_expr, is_from_proc_macro}; |
11 | 12 | use rustc_ast::LitKind; |
12 | 13 | use rustc_errors::Applicability; |
@@ -36,15 +37,103 @@ impl Variant { |
36 | 37 | } |
37 | 38 | } |
38 | 39 |
|
39 | | -pub(super) fn check<'a>( |
40 | | - cx: &LateContext<'a>, |
41 | | - expr: &Expr<'a>, |
42 | | - recv: &Expr<'_>, |
43 | | - def: &Expr<'_>, |
44 | | - map: &Expr<'_>, |
| 40 | +/// Evaluates `expr` and returns its value if it is a constant boolean. |
| 41 | +fn bool_constant(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<bool> { |
| 42 | + let Some(Constant::Bool(value)) = ConstEvalCtxt::new(cx).eval(expr) else { |
| 43 | + return None; |
| 44 | + }; |
| 45 | + Some(value) |
| 46 | +} |
| 47 | + |
| 48 | +/// Returns the constant boolean produced by a one-parameter closure. |
| 49 | +fn closure_bool_constant(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<bool> { |
| 50 | + let ExprKind::Closure(closure) = expr.kind else { |
| 51 | + return None; |
| 52 | + }; |
| 53 | + let body = cx.tcx.hir_body(closure.body); |
| 54 | + let [_] = body.params else { |
| 55 | + return None; |
| 56 | + }; |
| 57 | + bool_constant(cx, body.value) |
| 58 | +} |
| 59 | + |
| 60 | +/// Checks whether a `Result::{map_or, map_or_else}` call is a variant query. |
| 61 | +/// |
| 62 | +/// `expr` is the complete method call, `recv` is its `Result` receiver, `def` is the default |
| 63 | +/// argument, and `map` is the mapping closure. `check_if_bool` accounts for the eager default in |
| 64 | +/// `map_or` and the closure default in `map_or_else`. |
| 65 | +fn check_result_variant_query<'tcx>( |
| 66 | + cx: &LateContext<'tcx>, |
| 67 | + expr: &'tcx Expr<'tcx>, |
| 68 | + recv: &'tcx Expr<'tcx>, |
| 69 | + def: &'tcx Expr<'tcx>, |
| 70 | + map: &'tcx Expr<'tcx>, |
| 71 | + check_if_bool: impl FnOnce(&LateContext<'tcx>, &'tcx Expr<'tcx>) -> Option<bool>, |
| 72 | +) -> bool { |
| 73 | + let ExprKind::MethodCall(path, _, _, call_span) = expr.kind else { |
| 74 | + return false; |
| 75 | + }; |
| 76 | + let recv_ty = cx.typeck_results().expr_ty_adjusted(recv); |
| 77 | + if recv_ty.opt_diag_name(cx) != Some(sym::Result) { |
| 78 | + return false; |
| 79 | + } |
| 80 | + |
| 81 | + let def_bool = check_if_bool(cx, def); |
| 82 | + let Some((def_bool, map_bool)) = def_bool.zip(closure_bool_constant(cx, map)) else { |
| 83 | + return false; |
| 84 | + }; |
| 85 | + if def_bool == map_bool || is_from_proc_macro(cx, expr) { |
| 86 | + return false; |
| 87 | + } |
| 88 | + |
| 89 | + let suggested_name = if map_bool { "is_ok" } else { "is_err" }; |
| 90 | + let changes_drop_order = needs_ordered_drop(cx, recv_ty) || any_temporaries_need_ordered_drop(cx, recv); |
| 91 | + let applicability = if changes_drop_order { |
| 92 | + Applicability::MaybeIncorrect |
| 93 | + } else { |
| 94 | + Applicability::MachineApplicable |
| 95 | + }; |
| 96 | + |
| 97 | + span_lint_and_then( |
| 98 | + cx, |
| 99 | + UNNECESSARY_MAP_OR, |
| 100 | + path.ident.span, |
| 101 | + format!("this `{}` can be simplified", path.ident.name), |
| 102 | + |diag| { |
| 103 | + diag.span_suggestion( |
| 104 | + call_span, |
| 105 | + format!("use `{suggested_name}` instead"), |
| 106 | + format!("{suggested_name}()"), |
| 107 | + applicability, |
| 108 | + ); |
| 109 | + if changes_drop_order { |
| 110 | + diag.note("this will change drop order of the result, as well as all temporaries"); |
| 111 | + diag.note("add `#[allow(clippy::unnecessary_map_or)]` if this is important"); |
| 112 | + } |
| 113 | + }, |
| 114 | + ); |
| 115 | + true |
| 116 | +} |
| 117 | + |
| 118 | +/// Checks a `map_or` call for both `Result` variant queries and the existing `Option`/`Result` |
| 119 | +/// simplifications. |
| 120 | +/// |
| 121 | +/// `expr` is the complete method call, `recv` is its receiver, `def` is the eager default, and |
| 122 | +/// `map` is the mapping closure. `method_span` identifies `map_or` in diagnostics, while `msrv` |
| 123 | +/// controls which replacement methods can be suggested. |
| 124 | +pub(super) fn check<'tcx>( |
| 125 | + cx: &LateContext<'tcx>, |
| 126 | + expr: &'tcx Expr<'tcx>, |
| 127 | + recv: &'tcx Expr<'tcx>, |
| 128 | + def: &'tcx Expr<'tcx>, |
| 129 | + map: &'tcx Expr<'tcx>, |
45 | 130 | method_span: Span, |
46 | 131 | msrv: Msrv, |
47 | 132 | ) { |
| 133 | + if check_result_variant_query(cx, expr, recv, def, map, bool_constant) { |
| 134 | + return; |
| 135 | + } |
| 136 | + |
48 | 137 | let ExprKind::Lit(def_kind) = def.kind else { |
49 | 138 | return; |
50 | 139 | }; |
@@ -158,3 +247,17 @@ pub(super) fn check<'a>( |
158 | 247 | }, |
159 | 248 | ); |
160 | 249 | } |
| 250 | + |
| 251 | +/// Checks a `map_or_else` call for a `Result` variant query. |
| 252 | +/// |
| 253 | +/// `expr` is the complete method call, `recv` is its `Result` receiver, `def` is the lazy default |
| 254 | +/// closure, and `map` is the mapping closure. |
| 255 | +pub(super) fn check_map_or_else<'tcx>( |
| 256 | + cx: &LateContext<'tcx>, |
| 257 | + expr: &'tcx Expr<'tcx>, |
| 258 | + recv: &'tcx Expr<'tcx>, |
| 259 | + def: &'tcx Expr<'tcx>, |
| 260 | + map: &'tcx Expr<'tcx>, |
| 261 | +) { |
| 262 | + check_result_variant_query(cx, expr, recv, def, map, closure_bool_constant); |
| 263 | +} |
0 commit comments