|
4 | 4 |
|
5 | 5 | use clippy_utils::diagnostics::span_lint_and_then;
|
6 | 6 | use clippy_utils::source::{snippet_opt, snippet_with_applicability, snippet_with_context};
|
7 |
| -use clippy_utils::visitors::foreach_local_usage; |
8 |
| -use clippy_utils::{get_parent_expr, get_pat_usable_location}; |
| 7 | +use clippy_utils::{get_parent_expr, path_to_local}; |
9 | 8 | use if_chain::if_chain;
|
| 9 | +use rustc_ast::util::parser::PREC_POSTFIX; |
| 10 | +use rustc_data_structures::fx::FxIndexMap; |
10 | 11 | use rustc_errors::Applicability;
|
11 |
| -use rustc_hir::{BindingAnnotation, BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind, UnOp}; |
12 |
| -use rustc_lint::{LateContext, LateLintPass, LintContext}; |
| 12 | +use rustc_hir::{ |
| 13 | + BindingAnnotation, Block, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, ImplItem, Item, Mutability, Node, Pat, |
| 14 | + PatKind, TraitItem, UnOp, |
| 15 | +}; |
| 16 | +use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; |
13 | 17 | use rustc_middle::lint::in_external_macro;
|
14 | 18 | use rustc_middle::ty;
|
15 | 19 | use rustc_middle::ty::adjustment::{Adjust, Adjustment};
|
16 |
| -use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| 20 | +use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 21 | +use rustc_span::{Span, SyntaxContext}; |
17 | 22 |
|
18 | 23 | declare_clippy_lint! {
|
19 | 24 | /// **What it does:** Checks for address of operations (`&`) that are going to
|
@@ -63,10 +68,36 @@ declare_clippy_lint! {
|
63 | 68 | "`ref` binding to a reference"
|
64 | 69 | }
|
65 | 70 |
|
66 |
| -declare_lint_pass!(NeedlessBorrow => [NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE]); |
| 71 | +impl_lint_pass!(NeedlessBorrow => [NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE]); |
| 72 | +#[derive(Default)] |
| 73 | +pub struct NeedlessBorrow { |
| 74 | + /// The body the first local was found in. Used to emit lints when the traversal of the body has |
| 75 | + /// been finished. Note we can't lint at the end of every body as they can be nested within each |
| 76 | + /// other. |
| 77 | + current_body: Option<BodyId>, |
| 78 | + /// The list of locals currently being checked by the lint. |
| 79 | + ref_locals: FxIndexMap<HirId, Option<RefPat>>, |
| 80 | +} |
| 81 | + |
| 82 | +struct RefPat { |
| 83 | + /// The lint to use for this suggestion. Either `needless_borrow` or `ref_binding_to_reference`. |
| 84 | + lint: &'static Lint, |
| 85 | + /// The spans of all the ref bindings for this local. |
| 86 | + spans: Vec<Span>, |
| 87 | + /// The context replacements are to be made in. |
| 88 | + ctxt: SyntaxContext, |
| 89 | + /// The applicability of this suggestion. |
| 90 | + app: Applicability, |
| 91 | + /// All the replacements which need to be made. |
| 92 | + replacements: Vec<(Span, String)>, |
| 93 | +} |
67 | 94 |
|
68 | 95 | impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {
|
69 | 96 | fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
| 97 | + if let Some(local) = path_to_local(e) { |
| 98 | + self.check_local_usage(cx, e, local); |
| 99 | + } |
| 100 | + |
70 | 101 | if e.span.from_expansion() {
|
71 | 102 | return;
|
72 | 103 | }
|
@@ -107,70 +138,154 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {
|
107 | 138 | }
|
108 | 139 | }
|
109 | 140 | }
|
| 141 | + |
110 | 142 | fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
|
111 |
| - if_chain! { |
112 |
| - if let PatKind::Binding(BindingAnnotation::Ref, id, name, _) = pat.kind; |
113 |
| - if !in_external_macro(cx.sess(), pat.span); |
114 |
| - if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind(); |
115 |
| - // only lint immutable refs, because borrowed `&mut T` cannot be moved out |
116 |
| - if let ty::Ref(_, _, Mutability::Not) = *tam.kind(); |
117 |
| - then { |
118 |
| - let (span, search_loc) = match get_pat_usable_location(cx.tcx, pat) { |
119 |
| - Some(x) => x, |
120 |
| - None => return, |
121 |
| - }; |
122 |
| - let ctxt = span.ctxt(); |
123 |
| - if pat.span.ctxt() != ctxt { |
124 |
| - // The context of the pattern is different than the context using the binding. |
125 |
| - // Changing the pattern might affect other code which needs the ref binding. |
126 |
| - return; |
| 143 | + if let PatKind::Binding(BindingAnnotation::Ref, id, name, _) = pat.kind { |
| 144 | + if let Some(opt_prev_pat) = self.ref_locals.get_mut(&id) { |
| 145 | + // This binding id has been seen before. Add this pattern to the list of changes. |
| 146 | + if let Some(prev_pat) = opt_prev_pat { |
| 147 | + if prev_pat.ctxt == pat.span.ctxt() { |
| 148 | + prev_pat.spans.push(pat.span); |
| 149 | + prev_pat.replacements.push(( |
| 150 | + pat.span, |
| 151 | + snippet_with_context(cx, name.span, prev_pat.ctxt, "..", &mut prev_pat.app) |
| 152 | + .0 |
| 153 | + .into(), |
| 154 | + )); |
| 155 | + } else { |
| 156 | + // Doesn't match the context of the previous pattern. Can't lint here. |
| 157 | + *opt_prev_pat = None; |
| 158 | + } |
127 | 159 | }
|
| 160 | + return; |
| 161 | + } |
128 | 162 |
|
129 |
| - let mut app = Applicability::MachineApplicable; |
130 |
| - let mut can_fix = true; |
131 |
| - let mut lint = NEEDLESS_BORROW; |
132 |
| - let mut changes = vec![ |
133 |
| - (pat.span, snippet_with_context(cx, name.span, ctxt, "..", &mut app).0.into_owned()), |
134 |
| - ]; |
135 |
| - |
136 |
| - foreach_local_usage(cx, id, search_loc, |e| { |
137 |
| - // Check for auto-deref |
138 |
| - if !matches!( |
139 |
| - cx.typeck_results().expr_adjustments(e), |
140 |
| - [Adjustment { kind: Adjust::Deref(_), .. }, Adjustment { kind: Adjust::Deref(_), .. }, ..] |
141 |
| - ) { |
142 |
| - match get_parent_expr(cx, e) { |
143 |
| - Some(&Expr { span, kind: ExprKind::Unary(UnOp::Deref, _), .. }) |
144 |
| - if span.ctxt() == ctxt => { |
145 |
| - // Remove explicit deref. |
146 |
| - let snip = snippet_with_context(cx, e.span, ctxt, "..", &mut app).0; |
147 |
| - changes.push((span, snip.into())); |
148 |
| - } |
149 |
| - _ if e.span.ctxt() == ctxt => { |
150 |
| - // Double reference might be needed at this point. |
151 |
| - lint = REF_BINDING_TO_REFERENCE; |
152 |
| - let snip = snippet_with_applicability(cx, e.span, "..", &mut app); |
153 |
| - changes.push((e.span, format!("&{}", snip))); |
| 163 | + if_chain! { |
| 164 | + if !in_external_macro(cx.sess(), pat.span); |
| 165 | + if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind(); |
| 166 | + // only lint immutable refs, because borrowed `&mut T` cannot be moved out |
| 167 | + if let ty::Ref(_, _, Mutability::Not) = *tam.kind(); |
| 168 | + then { |
| 169 | + let map = cx.tcx.hir(); |
| 170 | + let mut iter = map.parent_iter(pat.hir_id); |
| 171 | + let expr_ctxt = loop { |
| 172 | + // Find the context shared by the pattern, and it's scope use site. |
| 173 | + match iter.next() { |
| 174 | + None => { |
| 175 | + // This shouldn't happen, but mark this as unfixable if it does. |
| 176 | + self.ref_locals.insert(id, None); |
| 177 | + return; |
154 | 178 | }
|
155 |
| - _ => can_fix = false, |
| 179 | + Some((_, Node::ImplItem(ImplItem { span, .. }) |
| 180 | + | Node::TraitItem(TraitItem { span, .. }) |
| 181 | + | Node::Item(Item { span, .. }) |
| 182 | + | Node::Block( Block { span, ..}) |
| 183 | + | Node::Expr( Expr { span, .. }))) => break span.ctxt(), |
| 184 | + _ => (), |
156 | 185 | }
|
157 |
| - } |
158 |
| - }); |
| 186 | + }; |
159 | 187 |
|
160 |
| - if !can_fix { |
161 |
| - return; |
| 188 | + if pat.span.ctxt() == expr_ctxt { |
| 189 | + let mut app = Applicability::MachineApplicable; |
| 190 | + let snip = snippet_with_context(cx, name.span, expr_ctxt, "..", &mut app).0; |
| 191 | + self.current_body = self.current_body.or(cx.enclosing_body); |
| 192 | + self.ref_locals.insert( |
| 193 | + id, |
| 194 | + Some(RefPat { |
| 195 | + lint: NEEDLESS_BORROW, |
| 196 | + spans: vec![pat.span], |
| 197 | + ctxt: expr_ctxt, |
| 198 | + app, |
| 199 | + replacements: vec![(pat.span, snip.into())], |
| 200 | + }), |
| 201 | + ); |
| 202 | + } else { |
| 203 | + // The context of the pattern is different than the context using the binding. |
| 204 | + // Changing the pattern might affect other code which needs the ref binding. |
| 205 | + // Mark the local as having been seen, but not fixable. |
| 206 | + self.ref_locals.insert(id, None); |
| 207 | + } |
162 | 208 | }
|
| 209 | + } |
| 210 | + } |
| 211 | + } |
163 | 212 |
|
| 213 | + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { |
| 214 | + if Some(body.id()) == self.current_body { |
| 215 | + for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) { |
| 216 | + let replacements = pat.replacements; |
| 217 | + let app = pat.app; |
164 | 218 | span_lint_and_then(
|
165 | 219 | cx,
|
166 |
| - lint, |
167 |
| - pat.span, |
| 220 | + pat.lint, |
| 221 | + pat.spans, |
168 | 222 | "this pattern creates a reference to a reference",
|
169 | 223 | |diag| {
|
170 |
| - diag.multipart_suggestion("try this", changes, app); |
171 |
| - } |
| 224 | + diag.multipart_suggestion("try this", replacements, app); |
| 225 | + }, |
172 | 226 | )
|
173 | 227 | }
|
| 228 | + self.current_body = None; |
| 229 | + } |
| 230 | + } |
| 231 | +} |
| 232 | +impl NeedlessBorrow { |
| 233 | + fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, local: HirId) { |
| 234 | + if let Some(outer_pat) = self.ref_locals.get_mut(&local) { |
| 235 | + if let Some(pat) = outer_pat { |
| 236 | + // Check for auto-deref |
| 237 | + if !matches!( |
| 238 | + cx.typeck_results().expr_adjustments(e), |
| 239 | + [ |
| 240 | + Adjustment { |
| 241 | + kind: Adjust::Deref(_), |
| 242 | + .. |
| 243 | + }, |
| 244 | + Adjustment { |
| 245 | + kind: Adjust::Deref(_), |
| 246 | + .. |
| 247 | + }, |
| 248 | + .. |
| 249 | + ] |
| 250 | + ) { |
| 251 | + match get_parent_expr(cx, e) { |
| 252 | + // Field accesses are the same no matter the number of references. |
| 253 | + Some(Expr { |
| 254 | + kind: ExprKind::Field(..), |
| 255 | + .. |
| 256 | + }) => (), |
| 257 | + Some(&Expr { |
| 258 | + span, |
| 259 | + kind: ExprKind::Unary(UnOp::Deref, _), |
| 260 | + .. |
| 261 | + }) if span.ctxt() == pat.ctxt => { |
| 262 | + // Remove explicit deref. |
| 263 | + let snip = snippet_with_context(cx, e.span, pat.ctxt, "..", &mut pat.app).0; |
| 264 | + pat.replacements.push((span, snip.into())); |
| 265 | + }, |
| 266 | + Some(parent) if parent.span.ctxt() == pat.ctxt => { |
| 267 | + // Double reference might be needed at this point. |
| 268 | + if parent.precedence().order() == PREC_POSTFIX { |
| 269 | + // Parenthesis would be needed here, don't lint. |
| 270 | + *outer_pat = None; |
| 271 | + } else { |
| 272 | + let snip = snippet_with_context(cx, e.span, pat.ctxt, "..", &mut pat.app).0; |
| 273 | + pat.replacements.push((e.span, format!("&{}", snip))); |
| 274 | + } |
| 275 | + }, |
| 276 | + _ if e.span.ctxt() == pat.ctxt => { |
| 277 | + // Double reference might be needed at this point. |
| 278 | + pat.lint = REF_BINDING_TO_REFERENCE; |
| 279 | + let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app); |
| 280 | + pat.replacements.push((e.span, format!("&{}", snip))); |
| 281 | + }, |
| 282 | + // Edge case for macros. The span of the identifier will usually match the context of the |
| 283 | + // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc |
| 284 | + // macros |
| 285 | + _ => *outer_pat = None, |
| 286 | + } |
| 287 | + } |
| 288 | + } |
174 | 289 | }
|
175 | 290 | }
|
176 | 291 | }
|
0 commit comments