|
| 1 | +use std::ops::ControlFlow; |
| 2 | + |
1 | 3 | use clippy_utils::diagnostics::span_lint_and_then;
|
2 | 4 | use clippy_utils::eq_expr_value;
|
3 | 5 | use clippy_utils::source::snippet;
|
4 | 6 | use clippy_utils::ty::is_type_diagnostic_item;
|
| 7 | +use clippy_utils::visitors::for_each_expr; |
5 | 8 | use rustc_ast::LitKind;
|
6 | 9 | use rustc_errors::Applicability;
|
7 |
| -use rustc_hir::{ExprKind, Local, StmtKind, UnOp}; |
| 10 | +use rustc_hir::{Expr, ExprKind, Local, Node, UnOp}; |
8 | 11 | use rustc_lint::{LateContext, LateLintPass};
|
9 | 12 | use rustc_session::declare_lint_pass;
|
10 |
| -use rustc_span::{sym, Span}; |
| 13 | +use rustc_span::sym; |
11 | 14 |
|
12 | 15 | declare_clippy_lint! {
|
13 | 16 | /// ### What it does
|
@@ -53,111 +56,106 @@ impl LateLintPass<'_> for UnnecessaryIndexing {
|
53 | 56 | && (expr_ty.is_array_slice() || expr_ty.is_array() || is_type_diagnostic_item(cx, expr_ty, sym::Vec))
|
54 | 57 | && let ExprKind::Block(block, _) = if_expr.then.kind
|
55 | 58 | {
|
56 |
| - // checked if conditional is calling `is_empty` on a sequence, now check if the first |
57 |
| - // statement inside the 'if' statement does unchecked get of first element |
| 59 | + // the receiver for the index operation |
| 60 | + let mut index_receiver: Option<&Expr<'_>> = None; |
| 61 | + // first local in the block - used as pattern for `Some(pat)` |
| 62 | + let mut first_local: Option<&Local<'_>> = None; |
| 63 | + // any other locals to be aware of, these are set to the value of `pat` |
| 64 | + let mut extra_locals: Vec<&Local<'_>> = vec![]; |
| 65 | + // any other index expressions to replace with `pat` (or "element" if no local exists) |
| 66 | + let mut extra_exprs: Vec<&Expr<'_>> = vec![]; |
58 | 67 |
|
59 |
| - // if calling a function on the indexing - local |
60 |
| - if let Some(first) = block.stmts.first() |
61 |
| - && let StmtKind::Local(local) = first.kind |
62 |
| - && let Some(init) = local.init |
63 |
| - && let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = init.kind |
64 |
| - { |
65 |
| - for arg_expr in args { |
66 |
| - if check_arg_stmt(cx, expr, arg_expr, conditional_receiver, if_expr.cond.span) { |
67 |
| - break; |
68 |
| - } |
69 |
| - } |
70 |
| - // if calling a function on the indexing - statement or expression |
71 |
| - } else if let Some(first) = block.stmts.first() |
72 |
| - && let StmtKind::Expr(stmt_expr) | StmtKind::Semi(stmt_expr) = first.kind |
73 |
| - && let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = stmt_expr.kind |
74 |
| - { |
75 |
| - for arg_expr in args { |
76 |
| - if check_arg_stmt(cx, expr, arg_expr, conditional_receiver, if_expr.cond.span) { |
77 |
| - break; |
78 |
| - } |
79 |
| - } |
80 |
| - // if calling on a local which is not in itself a call or methodcall |
81 |
| - } else if let Some(first) = block.stmts.first() |
82 |
| - && let StmtKind::Local(local) = first.kind |
83 |
| - && let Some(_) = local.init |
84 |
| - { |
85 |
| - check_local_stmt(cx, expr, local, conditional_receiver, if_expr.cond.span); |
86 |
| - } |
87 |
| - } |
88 |
| - } |
89 |
| -} |
| 68 | + for_each_expr(block.stmts, |x| { |
| 69 | + if let ExprKind::Index(receiver, index, _) = x.kind |
| 70 | + && let ExprKind::Lit(lit) = index.kind |
| 71 | + && let LitKind::Int(val, _) = lit.node |
| 72 | + && eq_expr_value(cx, receiver, conditional_receiver) |
| 73 | + && val.0 == 0 |
| 74 | + { |
| 75 | + index_receiver = Some(receiver); |
| 76 | + if let Node::Local(local) = cx.tcx.parent_hir_node(x.hir_id) { |
| 77 | + if first_local.is_none() { |
| 78 | + first_local = Some(local); |
| 79 | + } else { |
| 80 | + extra_locals.push(local); |
| 81 | + }; |
| 82 | + } else { |
| 83 | + extra_exprs.push(x); |
| 84 | + }; |
| 85 | + }; |
90 | 86 |
|
91 |
| -fn check_local_stmt( |
92 |
| - cx: &LateContext<'_>, |
93 |
| - expr: &'_ rustc_hir::Expr<'_>, |
94 |
| - local: &Local<'_>, |
95 |
| - conditional_receiver: &'_ rustc_hir::Expr<'_>, |
96 |
| - cond_span: Span, |
97 |
| -) { |
98 |
| - if let ExprKind::Index(receiver, index, _) = local.init.unwrap().kind |
99 |
| - && let ExprKind::Lit(lit) = index.kind |
100 |
| - && let LitKind::Int(val, _) = lit.node |
101 |
| - && eq_expr_value(cx, receiver, conditional_receiver) |
102 |
| - && val.0 == 0 |
103 |
| - { |
104 |
| - span_lint_and_then( |
105 |
| - cx, |
106 |
| - UNNECESSARY_INDEXING, |
107 |
| - expr.span, |
108 |
| - "condition can be simplified with if..let syntax", |
109 |
| - |x| { |
110 |
| - x.span_suggestion( |
111 |
| - cond_span, |
112 |
| - "consider using if..let syntax (variable may need to be dereferenced)", |
113 |
| - format!( |
114 |
| - "let Some({}) = {}.first()", |
115 |
| - snippet(cx, local.pat.span, ".."), |
116 |
| - snippet(cx, receiver.span, "..") |
117 |
| - ), |
118 |
| - Applicability::Unspecified, |
119 |
| - ); |
120 |
| - x.span_suggestion(local.span, "remove this line", "", Applicability::MachineApplicable); |
121 |
| - }, |
122 |
| - ); |
123 |
| - } |
124 |
| -} |
| 87 | + ControlFlow::Continue::<()>(()) |
| 88 | + }); |
125 | 89 |
|
126 |
| -fn check_arg_stmt( |
127 |
| - cx: &LateContext<'_>, |
128 |
| - expr: &'_ rustc_hir::Expr<'_>, |
129 |
| - arg_expr: &'_ rustc_hir::Expr<'_>, |
130 |
| - conditional_receiver: &'_ rustc_hir::Expr<'_>, |
131 |
| - cond_span: Span, |
132 |
| -) -> bool { |
133 |
| - if let ExprKind::Index(receiver, index, _) = arg_expr.kind |
134 |
| - && let ExprKind::Lit(lit) = index.kind |
135 |
| - && let LitKind::Int(val, _) = lit.node |
136 |
| - && eq_expr_value(cx, receiver, conditional_receiver) |
137 |
| - && val.0 == 0 |
138 |
| - { |
139 |
| - span_lint_and_then( |
140 |
| - cx, |
141 |
| - UNNECESSARY_INDEXING, |
142 |
| - expr.span, |
143 |
| - "condition can be simplified with if..let syntax", |
144 |
| - |x| { |
145 |
| - x.multipart_suggestion( |
146 |
| - "consider using if..let syntax (variable may need to be dereferenced)", |
147 |
| - vec![ |
148 |
| - ( |
149 |
| - cond_span, |
150 |
| - format!("let Some(element) = {}.first()", snippet(cx, receiver.span, "..")), |
151 |
| - ), |
152 |
| - (arg_expr.span, "element".to_owned()), |
153 |
| - ], |
154 |
| - Applicability::Unspecified, |
155 |
| - ); |
156 |
| - }, |
157 |
| - ); |
| 90 | + if let Some(receiver) = index_receiver { |
| 91 | + span_lint_and_then( |
| 92 | + cx, |
| 93 | + UNNECESSARY_INDEXING, |
| 94 | + expr.span, |
| 95 | + "condition can be simplified with if..let syntax", |
| 96 | + |x| { |
| 97 | + if let Some(first_local) = first_local { |
| 98 | + x.span_suggestion( |
| 99 | + if_expr.cond.span, |
| 100 | + "consider using if..let syntax (variable may need to be dereferenced)", |
| 101 | + format!( |
| 102 | + "let Some({}) = {}.first()", |
| 103 | + snippet(cx, first_local.pat.span, ".."), |
| 104 | + snippet(cx, receiver.span, "..") |
| 105 | + ), |
| 106 | + Applicability::Unspecified, |
| 107 | + ); |
| 108 | + x.span_suggestion( |
| 109 | + first_local.span, |
| 110 | + "remove this line", |
| 111 | + "", |
| 112 | + Applicability::MachineApplicable, |
| 113 | + ); |
| 114 | + if !extra_locals.is_empty() { |
| 115 | + let extra_local_suggestions = extra_locals |
| 116 | + .iter() |
| 117 | + .map(|x| { |
| 118 | + ( |
| 119 | + x.init.unwrap().span, |
| 120 | + snippet(cx, first_local.pat.span, "..").to_string(), |
| 121 | + ) |
| 122 | + }) |
| 123 | + .collect::<Vec<_>>(); |
158 | 124 |
|
159 |
| - return true; |
160 |
| - } |
| 125 | + x.multipart_suggestion( |
| 126 | + "initialize this variable to be the `Some` variant (may need dereferencing)", |
| 127 | + extra_local_suggestions, |
| 128 | + Applicability::Unspecified, |
| 129 | + ); |
| 130 | + } |
| 131 | + if !extra_exprs.is_empty() { |
| 132 | + let index_accesses = extra_exprs |
| 133 | + .iter() |
| 134 | + .map(|x| (x.span, snippet(cx, first_local.pat.span, "..").to_string())) |
| 135 | + .collect::<Vec<_>>(); |
161 | 136 |
|
162 |
| - false |
| 137 | + x.multipart_suggestion( |
| 138 | + "set this index to be the `Some` variant (may need dereferencing)", |
| 139 | + index_accesses, |
| 140 | + Applicability::Unspecified, |
| 141 | + ); |
| 142 | + } |
| 143 | + } else { |
| 144 | + let mut index_accesses = vec![( |
| 145 | + if_expr.cond.span, |
| 146 | + format!("let Some(element) = {}.first()", snippet(cx, receiver.span, "..")), |
| 147 | + )]; |
| 148 | + index_accesses.extend(extra_exprs.iter().map(|x| (x.span, "element".to_owned()))); |
| 149 | + |
| 150 | + x.multipart_suggestion( |
| 151 | + "consider using if..let syntax (variable may need to be dereferenced)", |
| 152 | + index_accesses, |
| 153 | + Applicability::Unspecified, |
| 154 | + ); |
| 155 | + } |
| 156 | + }, |
| 157 | + ); |
| 158 | + } |
| 159 | + } |
| 160 | + } |
163 | 161 | }
|
0 commit comments