]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs
Auto merge of #107843 - bjorn3:sync_cg_clif-2023-02-09, r=bjorn3
[rust.git] / src / tools / clippy / clippy_lints / src / methods / option_as_ref_deref.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::msrvs::{self, Msrv};
3 use clippy_utils::source::snippet;
4 use clippy_utils::ty::is_type_diagnostic_item;
5 use clippy_utils::{match_def_path, path_to_local_id, paths, peel_blocks};
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir as hir;
9 use rustc_lint::LateContext;
10 use rustc_middle::ty;
11 use rustc_span::sym;
12
13 use super::OPTION_AS_REF_DEREF;
14
15 /// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
16 pub(super) fn check(
17     cx: &LateContext<'_>,
18     expr: &hir::Expr<'_>,
19     as_ref_recv: &hir::Expr<'_>,
20     map_arg: &hir::Expr<'_>,
21     is_mut: bool,
22     msrv: &Msrv,
23 ) {
24     if !msrv.meets(msrvs::OPTION_AS_DEREF) {
25         return;
26     }
27
28     let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not);
29
30     let option_ty = cx.typeck_results().expr_ty(as_ref_recv);
31     if !is_type_diagnostic_item(cx, option_ty, sym::Option) {
32         return;
33     }
34
35     let deref_aliases: [&[&str]; 8] = [
36         &paths::DEREF_MUT_TRAIT_METHOD,
37         &paths::CSTRING_AS_C_STR,
38         &paths::OS_STRING_AS_OS_STR,
39         &paths::PATH_BUF_AS_PATH,
40         &paths::STRING_AS_STR,
41         &paths::STRING_AS_MUT_STR,
42         &paths::VEC_AS_SLICE,
43         &paths::VEC_AS_MUT_SLICE,
44     ];
45
46     let is_deref = match map_arg.kind {
47         hir::ExprKind::Path(ref expr_qpath) => {
48             cx.qpath_res(expr_qpath, map_arg.hir_id)
49                 .opt_def_id()
50                 .map_or(false, |fun_def_id| {
51                     cx.tcx.is_diagnostic_item(sym::deref_method, fun_def_id)
52                         || deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path))
53                 })
54         },
55         hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
56             let closure_body = cx.tcx.hir().body(body);
57             let closure_expr = peel_blocks(closure_body.value);
58
59             match &closure_expr.kind {
60                 hir::ExprKind::MethodCall(_, receiver, [], _) => {
61                     if_chain! {
62                         if path_to_local_id(receiver, closure_body.params[0].pat.hir_id);
63                         let adj = cx
64                             .typeck_results()
65                             .expr_adjustments(receiver)
66                             .iter()
67                             .map(|x| &x.kind)
68                             .collect::<Box<[_]>>();
69                         if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj;
70                         then {
71                             let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap();
72                             cx.tcx.is_diagnostic_item(sym::deref_method, method_did)
73                                 || deref_aliases.iter().any(|path| match_def_path(cx, method_did, path))
74                         } else {
75                             false
76                         }
77                     }
78                 },
79                 hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, inner) if same_mutability(m) => {
80                     if_chain! {
81                         if let hir::ExprKind::Unary(hir::UnOp::Deref, inner1) = inner.kind;
82                         if let hir::ExprKind::Unary(hir::UnOp::Deref, inner2) = inner1.kind;
83                         then {
84                             path_to_local_id(inner2, closure_body.params[0].pat.hir_id)
85                         } else {
86                             false
87                         }
88                     }
89                 },
90                 _ => false,
91             }
92         },
93         _ => false,
94     };
95
96     if is_deref {
97         let current_method = if is_mut {
98             format!(".as_mut().map({})", snippet(cx, map_arg.span, ".."))
99         } else {
100             format!(".as_ref().map({})", snippet(cx, map_arg.span, ".."))
101         };
102         let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
103         let hint = format!("{}.{method_hint}()", snippet(cx, as_ref_recv.span, ".."));
104         let suggestion = format!("try using {method_hint} instead");
105
106         let msg = format!(
107             "called `{current_method}` on an Option value. This can be done more directly \
108             by calling `{hint}` instead"
109         );
110         span_lint_and_sugg(
111             cx,
112             OPTION_AS_REF_DEREF,
113             expr.span,
114             &msg,
115             &suggestion,
116             hint,
117             Applicability::MachineApplicable,
118         );
119     }
120 }