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