|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg;
|
2 |
| -use clippy_utils::get_parent_expr; |
3 | 2 | use clippy_utils::msrvs::{self, Msrv};
|
4 |
| -use clippy_utils::source::{snippet, snippet_opt}; |
| 3 | +use clippy_utils::source::{snippet, snippet_with_applicability}; |
| 4 | +use clippy_utils::sugg::Sugg; |
5 | 5 | use clippy_utils::ty::is_type_diagnostic_item;
|
| 6 | +use clippy_utils::{get_parent_expr, sym}; |
| 7 | +use rustc_ast::LitKind; |
6 | 8 | use rustc_errors::Applicability;
|
7 | 9 | use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
8 |
| -use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; |
| 10 | +use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, QPath}; |
9 | 11 | use rustc_lint::LateContext;
|
10 | 12 | use rustc_middle::ty;
|
11 |
| -use rustc_span::{BytePos, Span, sym}; |
| 13 | +use rustc_span::{Span, Symbol}; |
12 | 14 |
|
13 | 15 | use super::MANUAL_IS_VARIANT_AND;
|
14 | 16 |
|
@@ -62,54 +64,156 @@ pub(super) fn check(
|
62 | 64 | );
|
63 | 65 | }
|
64 | 66 |
|
65 |
| -fn emit_lint(cx: &LateContext<'_>, op: BinOpKind, parent: &Expr<'_>, method_span: Span, is_option: bool) { |
66 |
| - if let Some(before_map_snippet) = snippet_opt(cx, parent.span.with_hi(method_span.lo())) |
67 |
| - && let Some(after_map_snippet) = snippet_opt(cx, method_span.with_lo(method_span.lo() + BytePos(3))) |
| 67 | +#[derive(Clone, Copy, PartialEq)] |
| 68 | +enum Flavor { |
| 69 | + Option, |
| 70 | + Result, |
| 71 | +} |
| 72 | + |
| 73 | +impl Flavor { |
| 74 | + const fn symbol(self) -> Symbol { |
| 75 | + match self { |
| 76 | + Self::Option => sym::Option, |
| 77 | + Self::Result => sym::Result, |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + const fn positive(self) -> Symbol { |
| 82 | + match self { |
| 83 | + Self::Option => sym::Some, |
| 84 | + Self::Result => sym::Ok, |
| 85 | + } |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +#[derive(Clone, Copy, PartialEq)] |
| 90 | +enum Op { |
| 91 | + Eq, |
| 92 | + Ne, |
| 93 | +} |
| 94 | + |
| 95 | +impl std::fmt::Display for Op { |
| 96 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 97 | + match self { |
| 98 | + Self::Eq => write!(f, "=="), |
| 99 | + Self::Ne => write!(f, "!="), |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +impl TryFrom<BinOpKind> for Op { |
| 105 | + type Error = (); |
| 106 | + fn try_from(op: BinOpKind) -> Result<Self, Self::Error> { |
| 107 | + match op { |
| 108 | + BinOpKind::Eq => Ok(Self::Eq), |
| 109 | + BinOpKind::Ne => Ok(Self::Ne), |
| 110 | + _ => Err(()), |
| 111 | + } |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +/// Represents the argument of the `.map()` function, as a closure or as a path |
| 116 | +/// in case η-reduction is used. |
| 117 | +enum MapFunc<'hir> { |
| 118 | + Closure(&'hir Closure<'hir>), |
| 119 | + Path(&'hir Expr<'hir>), |
| 120 | +} |
| 121 | + |
| 122 | +impl<'hir> TryFrom<&'hir Expr<'hir>> for MapFunc<'hir> { |
| 123 | + type Error = (); |
| 124 | + |
| 125 | + fn try_from(expr: &'hir Expr<'hir>) -> Result<Self, Self::Error> { |
| 126 | + match expr.kind { |
| 127 | + ExprKind::Closure(closure) => Ok(Self::Closure(closure)), |
| 128 | + ExprKind::Path(_) => Ok(Self::Path(expr)), |
| 129 | + _ => Err(()), |
| 130 | + } |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +impl<'hir> MapFunc<'hir> { |
| 135 | + /// Build a suggestion suitable for use in a `.map()`-like function. η-expansion will be applied |
| 136 | + /// as needed. |
| 137 | + fn sugg(self, cx: &LateContext<'hir>, invert: bool, app: &mut Applicability) -> String { |
| 138 | + match self { |
| 139 | + Self::Closure(closure) => { |
| 140 | + let body = Sugg::hir_with_applicability(cx, cx.tcx.hir_body(closure.body).value, "..", app); |
| 141 | + format!( |
| 142 | + "{} {}", |
| 143 | + snippet_with_applicability(cx, closure.fn_decl_span, "|..|", app), |
| 144 | + if invert { !body } else { body } |
| 145 | + ) |
| 146 | + }, |
| 147 | + Self::Path(expr) => { |
| 148 | + let path = snippet_with_applicability(cx, expr.span, "_", app); |
| 149 | + if invert { |
| 150 | + format!("|x| !{path}(x)") |
| 151 | + } else { |
| 152 | + path.to_string() |
| 153 | + } |
| 154 | + }, |
| 155 | + } |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +fn emit_lint<'tcx>( |
| 160 | + cx: &LateContext<'tcx>, |
| 161 | + span: Span, |
| 162 | + op: Op, |
| 163 | + flavor: Flavor, |
| 164 | + in_some_or_ok: bool, |
| 165 | + map_func: MapFunc<'tcx>, |
| 166 | + recv: &Expr<'_>, |
| 167 | +) { |
| 168 | + let mut app = Applicability::MachineApplicable; |
| 169 | + let recv = snippet_with_applicability(cx, recv.span, "_", &mut app); |
| 170 | + |
| 171 | + let (invert_expr, method, invert_body) = match (flavor, op, in_some_or_ok) { |
| 172 | + (Flavor::Option, Op::Eq, bool_cst) => (false, "is_some_and", !bool_cst), |
| 173 | + (Flavor::Option, Op::Ne, bool_cst) => (false, "is_none_or", bool_cst), |
| 174 | + (Flavor::Result, Op::Eq, bool_cst) => (false, "is_ok_and", !bool_cst), |
| 175 | + (Flavor::Result, Op::Ne, bool_cst) => (true, "is_ok_and", !bool_cst), |
| 176 | + }; |
68 | 177 | {
|
69 | 178 | span_lint_and_sugg(
|
70 | 179 | cx,
|
71 | 180 | MANUAL_IS_VARIANT_AND,
|
72 |
| - parent.span, |
| 181 | + span, |
| 182 | + format!("called `.map() {op} {pos}()`", pos = flavor.positive(),), |
| 183 | + "use", |
73 | 184 | format!(
|
74 |
| - "called `.map() {}= {}()`", |
75 |
| - if op == BinOpKind::Eq { '=' } else { '!' }, |
76 |
| - if is_option { "Some" } else { "Ok" }, |
| 185 | + "{inversion}{recv}.{method}({body})", |
| 186 | + inversion = if invert_expr { "!" } else { "" }, |
| 187 | + body = map_func.sugg(cx, invert_body, &mut app), |
77 | 188 | ),
|
78 |
| - "use", |
79 |
| - if is_option && op == BinOpKind::Ne { |
80 |
| - format!("{before_map_snippet}is_none_or{after_map_snippet}",) |
81 |
| - } else { |
82 |
| - format!( |
83 |
| - "{}{before_map_snippet}{}{after_map_snippet}", |
84 |
| - if op == BinOpKind::Eq { "" } else { "!" }, |
85 |
| - if is_option { "is_some_and" } else { "is_ok_and" }, |
86 |
| - ) |
87 |
| - }, |
88 |
| - Applicability::MachineApplicable, |
| 189 | + app, |
89 | 190 | );
|
90 |
| - } |
| 191 | + }; |
91 | 192 | }
|
92 | 193 |
|
93 | 194 | pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
94 | 195 | if let Some(parent_expr) = get_parent_expr(cx, expr)
|
95 | 196 | && let ExprKind::Binary(op, left, right) = parent_expr.kind
|
96 |
| - && matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) |
97 | 197 | && op.span.eq_ctxt(expr.span)
|
| 198 | + && let Ok(op) = Op::try_from(op.node) |
98 | 199 | {
|
99 | 200 | // Check `left` and `right` expression in any order, and for `Option` and `Result`
|
100 | 201 | for (expr1, expr2) in [(left, right), (right, left)] {
|
101 |
| - for item in [sym::Option, sym::Result] { |
102 |
| - if let ExprKind::Call(call, ..) = expr1.kind |
| 202 | + for flavor in [Flavor::Option, Flavor::Result] { |
| 203 | + if let ExprKind::Call(call, [arg]) = expr1.kind |
| 204 | + && let ExprKind::Lit(lit) = arg.kind |
| 205 | + && let LitKind::Bool(bool_cst) = lit.node |
103 | 206 | && let ExprKind::Path(QPath::Resolved(_, path)) = call.kind
|
104 | 207 | && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res
|
105 | 208 | && let ty = cx.typeck_results().expr_ty(expr1)
|
106 | 209 | && let ty::Adt(adt, args) = ty.kind()
|
107 |
| - && cx.tcx.is_diagnostic_item(item, adt.did()) |
| 210 | + && cx.tcx.is_diagnostic_item(flavor.symbol(), adt.did()) |
108 | 211 | && args.type_at(0).is_bool()
|
109 |
| - && let ExprKind::MethodCall(_, recv, _, span) = expr2.kind |
110 |
| - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), item) |
| 212 | + && let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind |
| 213 | + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), flavor.symbol()) |
| 214 | + && let Ok(map_func) = MapFunc::try_from(map_expr) |
111 | 215 | {
|
112 |
| - return emit_lint(cx, op.node, parent_expr, span, item == sym::Option); |
| 216 | + return emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv); |
113 | 217 | }
|
114 | 218 | }
|
115 | 219 | }
|
|
0 commit comments