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