]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/unnecessary_filter_map.rs
Fix `inherent_to_string` false positive
[rust.git] / clippy_lints / src / methods / unnecessary_filter_map.rs
1 use crate::utils::paths;
2 use crate::utils::usage::mutated_variables;
3 use crate::utils::{match_qpath, match_trait_method, span_lint};
4 use rustc::hir;
5 use rustc::hir::def::Res;
6 use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
7 use rustc::lint::LateContext;
8
9 use if_chain::if_chain;
10
11 use super::UNNECESSARY_FILTER_MAP;
12
13 pub(super) fn lint(cx: &LateContext<'_, '_>, expr: &hir::Expr, args: &[hir::Expr]) {
14     if !match_trait_method(cx, expr, &paths::ITERATOR) {
15         return;
16     }
17
18     if let hir::ExprKind::Closure(_, _, body_id, ..) = args[1].node {
19         let body = cx.tcx.hir().body(body_id);
20         let arg_id = body.arguments[0].pat.hir_id;
21         let mutates_arg = match mutated_variables(&body.value, cx) {
22             Some(used_mutably) => used_mutably.contains(&arg_id),
23             None => true,
24         };
25
26         let (mut found_mapping, mut found_filtering) = check_expression(&cx, arg_id, &body.value);
27
28         let mut return_visitor = ReturnVisitor::new(&cx, arg_id);
29         return_visitor.visit_expr(&body.value);
30         found_mapping |= return_visitor.found_mapping;
31         found_filtering |= return_visitor.found_filtering;
32
33         if !found_filtering {
34             span_lint(
35                 cx,
36                 UNNECESSARY_FILTER_MAP,
37                 expr.span,
38                 "this `.filter_map` can be written more simply using `.map`",
39             );
40             return;
41         }
42
43         if !found_mapping && !mutates_arg {
44             span_lint(
45                 cx,
46                 UNNECESSARY_FILTER_MAP,
47                 expr.span,
48                 "this `.filter_map` can be written more simply using `.filter`",
49             );
50             return;
51         }
52     }
53 }
54
55 // returns (found_mapping, found_filtering)
56 fn check_expression<'a, 'tcx>(
57     cx: &'a LateContext<'a, 'tcx>,
58     arg_id: hir::HirId,
59     expr: &'tcx hir::Expr,
60 ) -> (bool, bool) {
61     match &expr.node {
62         hir::ExprKind::Call(ref func, ref args) => {
63             if_chain! {
64                 if let hir::ExprKind::Path(ref path) = func.node;
65                 then {
66                     if match_qpath(path, &paths::OPTION_SOME) {
67                         if_chain! {
68                             if let hir::ExprKind::Path(path) = &args[0].node;
69                             if let Res::Local(ref local) = cx.tables.qpath_res(path, args[0].hir_id);
70                             then {
71                                 if arg_id == *local {
72                                     return (false, false)
73                                 }
74                             }
75                         }
76                         return (true, false);
77                     } else {
78                         // We don't know. It might do anything.
79                         return (true, true);
80                     }
81                 }
82             }
83             (true, true)
84         },
85         hir::ExprKind::Block(ref block, _) => {
86             if let Some(expr) = &block.expr {
87                 check_expression(cx, arg_id, &expr)
88             } else {
89                 (false, false)
90             }
91         },
92         hir::ExprKind::Match(_, ref arms, _) => {
93             let mut found_mapping = false;
94             let mut found_filtering = false;
95             for arm in arms {
96                 let (m, f) = check_expression(cx, arg_id, &arm.body);
97                 found_mapping |= m;
98                 found_filtering |= f;
99             }
100             (found_mapping, found_filtering)
101         },
102         hir::ExprKind::Path(path) if match_qpath(path, &paths::OPTION_NONE) => (false, true),
103         _ => (true, true),
104     }
105 }
106
107 struct ReturnVisitor<'a, 'tcx> {
108     cx: &'a LateContext<'a, 'tcx>,
109     arg_id: hir::HirId,
110     // Found a non-None return that isn't Some(input)
111     found_mapping: bool,
112     // Found a return that isn't Some
113     found_filtering: bool,
114 }
115
116 impl<'a, 'tcx> ReturnVisitor<'a, 'tcx> {
117     fn new(cx: &'a LateContext<'a, 'tcx>, arg_id: hir::HirId) -> ReturnVisitor<'a, 'tcx> {
118         ReturnVisitor {
119             cx,
120             arg_id,
121             found_mapping: false,
122             found_filtering: false,
123         }
124     }
125 }
126
127 impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> {
128     fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
129         if let hir::ExprKind::Ret(Some(expr)) = &expr.node {
130             let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr);
131             self.found_mapping |= found_mapping;
132             self.found_filtering |= found_filtering;
133         } else {
134             walk_expr(self, expr);
135         }
136     }
137
138     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
139         NestedVisitorMap::None
140     }
141 }