]>
Commit | Line | Data |
---|---|---|
ea8adc8c | 1 | use rustc::hir::*; |
abe05a73 | 2 | use rustc::hir::map::*; |
ea8adc8c | 3 | use rustc::hir::intravisit::FnKind; |
ea8adc8c | 4 | use rustc::lint::*; |
abe05a73 | 5 | use rustc::ty::{self, RegionKind, TypeFoldable}; |
ea8adc8c XL |
6 | use rustc::traits; |
7 | use rustc::middle::expr_use_visitor as euv; | |
8 | use rustc::middle::mem_categorization as mc; | |
9 | use syntax::ast::NodeId; | |
10 | use syntax_pos::Span; | |
11 | use syntax::errors::DiagnosticBuilder; | |
abe05a73 XL |
12 | use utils::{get_trait_def_id, implements_trait, in_macro, is_copy, is_self, match_type, multispan_sugg, paths, |
13 | snippet, snippet_opt, span_lint_and_then}; | |
14 | use utils::ptr::get_spans; | |
15 | use std::collections::{HashMap, HashSet}; | |
16 | use std::borrow::Cow; | |
ea8adc8c XL |
17 | |
18 | /// **What it does:** Checks for functions taking arguments by value, but not | |
19 | /// consuming them in its | |
20 | /// body. | |
21 | /// | |
22 | /// **Why is this bad?** Taking arguments by reference is more flexible and can | |
23 | /// sometimes avoid | |
24 | /// unnecessary allocations. | |
25 | /// | |
abe05a73 XL |
26 | /// **Known problems:** |
27 | /// * This lint suggests taking an argument by reference, | |
28 | /// however sometimes it is better to let users decide the argument type | |
29 | /// (by using `Borrow` trait, for example), depending on how the function is used. | |
ea8adc8c XL |
30 | /// |
31 | /// **Example:** | |
32 | /// ```rust | |
33 | /// fn foo(v: Vec<i32>) { | |
34 | /// assert_eq!(v.len(), 42); | |
35 | /// } | |
abe05a73 XL |
36 | /// // should be |
37 | /// fn foo(v: &[i32]) { | |
38 | /// assert_eq!(v.len(), 42); | |
39 | /// } | |
ea8adc8c XL |
40 | /// ``` |
41 | declare_lint! { | |
42 | pub NEEDLESS_PASS_BY_VALUE, | |
43 | Warn, | |
44 | "functions taking arguments by value, but not consuming them in its body" | |
45 | } | |
46 | ||
47 | pub struct NeedlessPassByValue; | |
48 | ||
49 | impl LintPass for NeedlessPassByValue { | |
50 | fn get_lints(&self) -> LintArray { | |
51 | lint_array![NEEDLESS_PASS_BY_VALUE] | |
52 | } | |
53 | } | |
54 | ||
55 | macro_rules! need { | |
56 | ($e: expr) => { if let Some(x) = $e { x } else { return; } }; | |
57 | } | |
58 | ||
59 | impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue { | |
60 | fn check_fn( | |
61 | &mut self, | |
62 | cx: &LateContext<'a, 'tcx>, | |
63 | kind: FnKind<'tcx>, | |
64 | decl: &'tcx FnDecl, | |
65 | body: &'tcx Body, | |
66 | span: Span, | |
67 | node_id: NodeId, | |
68 | ) { | |
69 | if in_macro(span) { | |
70 | return; | |
71 | } | |
72 | ||
73 | match kind { | |
abe05a73 XL |
74 | FnKind::ItemFn(.., attrs) => for a in attrs { |
75 | if_chain! { | |
76 | if a.meta_item_list().is_some(); | |
77 | if let Some(name) = a.name(); | |
78 | if name == "proc_macro_derive"; | |
79 | then { | |
ea8adc8c | 80 | return; |
abe05a73 | 81 | } |
ea8adc8c XL |
82 | } |
83 | }, | |
abe05a73 | 84 | FnKind::Method(..) => (), |
ea8adc8c XL |
85 | _ => return, |
86 | } | |
87 | ||
abe05a73 XL |
88 | // Exclude non-inherent impls |
89 | if let Some(NodeItem(item)) = cx.tcx.hir.find(cx.tcx.hir.get_parent_node(node_id)) { | |
90 | if matches!(item.node, ItemImpl(_, _, _, _, Some(_), _, _) | ItemAutoImpl(..) | | |
91 | ItemTrait(..)) | |
92 | { | |
93 | return; | |
94 | } | |
95 | } | |
96 | ||
97 | // Allow `Borrow` or functions to be taken by value | |
ea8adc8c | 98 | let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT)); |
abe05a73 XL |
99 | let fn_traits = [ |
100 | need!(cx.tcx.lang_items().fn_trait()), | |
101 | need!(cx.tcx.lang_items().fn_once_trait()), | |
102 | need!(cx.tcx.lang_items().fn_mut_trait()), | |
103 | ]; | |
104 | ||
105 | let sized_trait = need!(cx.tcx.lang_items().sized_trait()); | |
ea8adc8c XL |
106 | |
107 | let fn_def_id = cx.tcx.hir.local_def_id(node_id); | |
108 | ||
abe05a73 XL |
109 | let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds.to_vec()) |
110 | .filter(|p| !p.is_global()) | |
111 | .filter_map(|pred| { | |
112 | if let ty::Predicate::Trait(poly_trait_ref) = pred { | |
113 | if poly_trait_ref.def_id() == sized_trait || poly_trait_ref.skip_binder().has_escaping_regions() { | |
114 | return None; | |
115 | } | |
116 | Some(poly_trait_ref) | |
117 | } else { | |
118 | None | |
119 | } | |
120 | }) | |
121 | .collect::<Vec<_>>(); | |
ea8adc8c XL |
122 | |
123 | // Collect moved variables and spans which will need dereferencings from the | |
124 | // function body. | |
125 | let MovedVariablesCtxt { | |
126 | moved_vars, | |
127 | spans_need_deref, | |
128 | .. | |
129 | } = { | |
130 | let mut ctx = MovedVariablesCtxt::new(cx); | |
abe05a73 XL |
131 | let region_scope_tree = &cx.tcx.region_scope_tree(fn_def_id); |
132 | euv::ExprUseVisitor::new(&mut ctx, cx.tcx, cx.param_env, region_scope_tree, cx.tables, None) | |
133 | .consume_body(body); | |
ea8adc8c XL |
134 | ctx |
135 | }; | |
136 | ||
137 | let fn_sig = cx.tcx.fn_sig(fn_def_id); | |
138 | let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig); | |
139 | ||
abe05a73 XL |
140 | for (idx, ((input, &ty), arg)) in decl.inputs |
141 | .iter() | |
142 | .zip(fn_sig.inputs()) | |
143 | .zip(&body.arguments) | |
144 | .enumerate() | |
145 | { | |
146 | // All spans generated from a proc-macro invocation are the same... | |
147 | if span == input.span { | |
148 | return; | |
149 | } | |
ea8adc8c | 150 | |
abe05a73 XL |
151 | // Ignore `self`s. |
152 | if idx == 0 { | |
153 | if let PatKind::Binding(_, _, name, ..) = arg.pat.node { | |
154 | if name.node.as_str() == "self" { | |
155 | continue; | |
156 | } | |
ea8adc8c | 157 | } |
abe05a73 | 158 | } |
ea8adc8c | 159 | |
abe05a73 XL |
160 | // * Exclude a type that is specifically bounded by `Borrow`. |
161 | // * Exclude a type whose reference also fulfills its bound. | |
162 | // (e.g. `std::convert::AsRef`, `serde::Serialize`) | |
163 | let (implements_borrow_trait, all_borrowable_trait) = { | |
164 | let preds = preds | |
165 | .iter() | |
166 | .filter(|t| t.skip_binder().self_ty() == ty) | |
167 | .collect::<Vec<_>>(); | |
168 | ||
169 | ( | |
170 | preds.iter().any(|t| t.def_id() == borrow_trait), | |
171 | !preds.is_empty() && preds.iter().all(|t| { | |
172 | implements_trait( | |
173 | cx, | |
174 | cx.tcx.mk_imm_ref(&RegionKind::ReErased, ty), | |
175 | t.def_id(), | |
176 | &t.skip_binder().input_types().skip(1).collect::<Vec<_>>(), | |
177 | ) | |
178 | }), | |
179 | ) | |
180 | }; | |
181 | ||
182 | if_chain! { | |
183 | if !is_self(arg); | |
184 | if !ty.is_mutable_pointer(); | |
185 | if !is_copy(cx, ty); | |
186 | if !fn_traits.iter().any(|&t| implements_trait(cx, ty, t, &[])); | |
187 | if !implements_borrow_trait; | |
188 | if !all_borrowable_trait; | |
189 | ||
190 | if let PatKind::Binding(mode, canonical_id, ..) = arg.pat.node; | |
191 | if !moved_vars.contains(&canonical_id); | |
192 | then { | |
193 | if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut { | |
194 | continue; | |
ea8adc8c XL |
195 | } |
196 | ||
abe05a73 XL |
197 | // Dereference suggestion |
198 | let sugg = |db: &mut DiagnosticBuilder| { | |
199 | let deref_span = spans_need_deref.get(&canonical_id); | |
200 | if_chain! { | |
201 | if match_type(cx, ty, &paths::VEC); | |
202 | if let Some(clone_spans) = | |
203 | get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]); | |
204 | if let TyPath(QPath::Resolved(_, ref path)) = input.node; | |
205 | if let Some(elem_ty) = path.segments.iter() | |
206 | .find(|seg| seg.name == "Vec") | |
207 | .and_then(|ps| ps.parameters.as_ref()) | |
208 | .map(|params| ¶ms.types[0]); | |
209 | then { | |
210 | let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_")); | |
211 | db.span_suggestion(input.span, | |
212 | "consider changing the type to", | |
213 | slice_ty); | |
214 | ||
215 | for (span, suggestion) in clone_spans { | |
216 | db.span_suggestion( | |
217 | span, | |
218 | &snippet_opt(cx, span) | |
219 | .map_or( | |
220 | "change the call to".into(), | |
221 | |x| Cow::from(format!("change `{}` to", x)), | |
222 | ), | |
223 | suggestion.into() | |
224 | ); | |
225 | } | |
226 | ||
227 | // cannot be destructured, no need for `*` suggestion | |
228 | assert!(deref_span.is_none()); | |
229 | return; | |
230 | } | |
231 | } | |
ea8adc8c | 232 | |
abe05a73 XL |
233 | if match_type(cx, ty, &paths::STRING) { |
234 | if let Some(clone_spans) = | |
235 | get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) { | |
236 | db.span_suggestion(input.span, "consider changing the type to", "&str".to_string()); | |
237 | ||
238 | for (span, suggestion) in clone_spans { | |
239 | db.span_suggestion( | |
240 | span, | |
241 | &snippet_opt(cx, span) | |
242 | .map_or( | |
243 | "change the call to".into(), | |
244 | |x| Cow::from(format!("change `{}` to", x)) | |
245 | ), | |
246 | suggestion.into(), | |
247 | ); | |
248 | } | |
249 | ||
250 | assert!(deref_span.is_none()); | |
251 | return; | |
252 | } | |
253 | } | |
254 | ||
255 | let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))]; | |
256 | ||
257 | // Suggests adding `*` to dereference the added reference. | |
258 | if let Some(deref_span) = deref_span { | |
259 | spans.extend( | |
260 | deref_span | |
261 | .iter() | |
262 | .cloned() | |
263 | .map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))), | |
264 | ); | |
265 | spans.sort_by_key(|&(span, _)| span); | |
266 | } | |
267 | multispan_sugg(db, "consider taking a reference instead".to_string(), spans); | |
268 | }; | |
269 | ||
270 | span_lint_and_then( | |
271 | cx, | |
272 | NEEDLESS_PASS_BY_VALUE, | |
273 | input.span, | |
274 | "this argument is passed by value, but not consumed in the function body", | |
275 | sugg, | |
276 | ); | |
277 | } | |
278 | } | |
ea8adc8c XL |
279 | } |
280 | } | |
281 | } | |
282 | ||
283 | struct MovedVariablesCtxt<'a, 'tcx: 'a> { | |
284 | cx: &'a LateContext<'a, 'tcx>, | |
abe05a73 | 285 | moved_vars: HashSet<NodeId>, |
ea8adc8c | 286 | /// Spans which need to be prefixed with `*` for dereferencing the |
abe05a73 XL |
287 | /// suggested additional reference. |
288 | spans_need_deref: HashMap<NodeId, HashSet<Span>>, | |
ea8adc8c XL |
289 | } |
290 | ||
291 | impl<'a, 'tcx> MovedVariablesCtxt<'a, 'tcx> { | |
292 | fn new(cx: &'a LateContext<'a, 'tcx>) -> Self { | |
293 | Self { | |
294 | cx: cx, | |
295 | moved_vars: HashSet::new(), | |
296 | spans_need_deref: HashMap::new(), | |
297 | } | |
298 | } | |
299 | ||
300 | fn move_common(&mut self, _consume_id: NodeId, _span: Span, cmt: mc::cmt<'tcx>) { | |
301 | let cmt = unwrap_downcast_or_interior(cmt); | |
302 | ||
abe05a73 XL |
303 | if let mc::Categorization::Local(vid) = cmt.cat { |
304 | self.moved_vars.insert(vid); | |
305 | } | |
ea8adc8c XL |
306 | } |
307 | ||
308 | fn non_moving_pat(&mut self, matched_pat: &Pat, cmt: mc::cmt<'tcx>) { | |
309 | let cmt = unwrap_downcast_or_interior(cmt); | |
310 | ||
abe05a73 | 311 | if let mc::Categorization::Local(vid) = cmt.cat { |
ea8adc8c XL |
312 | let mut id = matched_pat.id; |
313 | loop { | |
314 | let parent = self.cx.tcx.hir.get_parent_node(id); | |
315 | if id == parent { | |
316 | // no parent | |
317 | return; | |
318 | } | |
319 | id = parent; | |
320 | ||
321 | if let Some(node) = self.cx.tcx.hir.find(id) { | |
322 | match node { | |
323 | map::Node::NodeExpr(e) => { | |
324 | // `match` and `if let` | |
325 | if let ExprMatch(ref c, ..) = e.node { | |
326 | self.spans_need_deref | |
abe05a73 | 327 | .entry(vid) |
ea8adc8c XL |
328 | .or_insert_with(HashSet::new) |
329 | .insert(c.span); | |
330 | } | |
abe05a73 | 331 | }, |
ea8adc8c XL |
332 | |
333 | map::Node::NodeStmt(s) => { | |
334 | // `let <pat> = x;` | |
abe05a73 XL |
335 | if_chain! { |
336 | if let StmtDecl(ref decl, _) = s.node; | |
337 | if let DeclLocal(ref local) = decl.node; | |
338 | then { | |
339 | self.spans_need_deref | |
340 | .entry(vid) | |
341 | .or_insert_with(HashSet::new) | |
342 | .insert(local.init | |
343 | .as_ref() | |
344 | .map(|e| e.span) | |
345 | .expect("`let` stmt without init aren't caught by match_pat")); | |
346 | } | |
347 | } | |
348 | }, | |
ea8adc8c | 349 | |
abe05a73 | 350 | _ => {}, |
ea8adc8c XL |
351 | } |
352 | } | |
353 | } | |
abe05a73 | 354 | } |
ea8adc8c XL |
355 | } |
356 | } | |
357 | ||
358 | impl<'a, 'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt<'a, 'tcx> { | |
359 | fn consume(&mut self, consume_id: NodeId, consume_span: Span, cmt: mc::cmt<'tcx>, mode: euv::ConsumeMode) { | |
360 | if let euv::ConsumeMode::Move(_) = mode { | |
361 | self.move_common(consume_id, consume_span, cmt); | |
362 | } | |
363 | } | |
364 | ||
365 | fn matched_pat(&mut self, matched_pat: &Pat, cmt: mc::cmt<'tcx>, mode: euv::MatchMode) { | |
366 | if let euv::MatchMode::MovingMatch = mode { | |
367 | self.move_common(matched_pat.id, matched_pat.span, cmt); | |
368 | } else { | |
369 | self.non_moving_pat(matched_pat, cmt); | |
370 | } | |
371 | } | |
372 | ||
373 | fn consume_pat(&mut self, consume_pat: &Pat, cmt: mc::cmt<'tcx>, mode: euv::ConsumeMode) { | |
374 | if let euv::ConsumeMode::Move(_) = mode { | |
375 | self.move_common(consume_pat.id, consume_pat.span, cmt); | |
376 | } | |
377 | } | |
378 | ||
379 | fn borrow(&mut self, _: NodeId, _: Span, _: mc::cmt<'tcx>, _: ty::Region, _: ty::BorrowKind, _: euv::LoanCause) {} | |
380 | ||
381 | fn mutate(&mut self, _: NodeId, _: Span, _: mc::cmt<'tcx>, _: euv::MutateMode) {} | |
382 | ||
383 | fn decl_without_init(&mut self, _: NodeId, _: Span) {} | |
384 | } | |
385 | ||
386 | ||
387 | fn unwrap_downcast_or_interior(mut cmt: mc::cmt) -> mc::cmt { | |
388 | loop { | |
389 | match cmt.cat.clone() { | |
abe05a73 | 390 | mc::Categorization::Downcast(c, _) | mc::Categorization::Interior(c, _) => { |
ea8adc8c XL |
391 | cmt = c; |
392 | }, | |
393 | _ => return cmt, | |
394 | } | |
395 | } | |
396 | } |