]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/needless_pass_by_value.rs
Rustup to rustc 1.19.0-nightly (5b13bff52 2017-05-23)
[rust.git] / clippy_lints / src / needless_pass_by_value.rs
1 use rustc::hir::*;
2 use rustc::hir::intravisit::FnKind;
3 use rustc::hir::def_id::DefId;
4 use rustc::lint::*;
5 use rustc::ty::{self, TypeFoldable};
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;
12 use utils::{in_macro, is_self, is_copy, implements_trait, get_trait_def_id, match_type, snippet, span_lint_and_then,
13             multispan_sugg, paths};
14 use std::collections::{HashSet, HashMap};
15
16 /// **What it does:** Checks for functions taking arguments by value, but not consuming them in its
17 /// body.
18 ///
19 /// **Why is this bad?** Taking arguments by reference is more flexible and can sometimes avoid
20 /// unnecessary allocations.
21 ///
22 /// **Known problems:** Hopefully none.
23 ///
24 /// **Example:**
25 /// ```rust
26 /// fn foo(v: Vec<i32>) {
27 ///     assert_eq!(v.len(), 42);
28 /// }
29 /// ```
30 declare_lint! {
31     pub NEEDLESS_PASS_BY_VALUE,
32     Warn,
33     "functions taking arguments by value, but not consuming them in its body"
34 }
35
36 pub struct NeedlessPassByValue;
37
38 impl LintPass for NeedlessPassByValue {
39     fn get_lints(&self) -> LintArray {
40         lint_array![NEEDLESS_PASS_BY_VALUE]
41     }
42 }
43
44 macro_rules! need {
45     ($e: expr) => { if let Some(x) = $e { x } else { return; } };
46 }
47
48 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue {
49     fn check_fn(
50         &mut self,
51         cx: &LateContext<'a, 'tcx>,
52         kind: FnKind<'tcx>,
53         decl: &'tcx FnDecl,
54         body: &'tcx Body,
55         span: Span,
56         node_id: NodeId
57     ) {
58         if in_macro(span) {
59             return;
60         }
61
62         match kind {
63             FnKind::ItemFn(.., attrs) => {
64                 for a in attrs {
65                     if_let_chain!{[
66                         a.meta_item_list().is_some(),
67                         let Some(name) = a.name(),
68                         name == "proc_macro_derive",
69                     ], {
70                         return;
71                     }}
72                 }
73             },
74             _ => return,
75         }
76
77         // Allows these to be passed by value.
78         let fn_trait = need!(cx.tcx.lang_items.fn_trait());
79         let asref_trait = need!(get_trait_def_id(cx, &paths::ASREF_TRAIT));
80         let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT));
81
82         let fn_def_id = cx.tcx.hir.local_def_id(node_id);
83
84         let preds: Vec<ty::Predicate> = {
85             let parameter_env = cx.tcx.param_env(fn_def_id);
86             traits::elaborate_predicates(cx.tcx, parameter_env.caller_bounds.to_vec())
87                 .filter(|p| !p.is_global())
88                 .collect()
89         };
90
91         // Collect moved variables and spans which will need dereferencings from the function body.
92         let MovedVariablesCtxt { moved_vars, spans_need_deref, .. } = {
93             let mut ctx = MovedVariablesCtxt::new(cx);
94             let infcx = cx.tcx.borrowck_fake_infer_ctxt(body.id());
95             let region_maps = &cx.tcx.region_maps(fn_def_id);
96             {
97                 let mut v = euv::ExprUseVisitor::new(&mut ctx, region_maps, &infcx);
98                 v.consume_body(body);
99             }
100             ctx
101         };
102
103         let fn_sig = cx.tcx.type_of(fn_def_id).fn_sig();
104         let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig);
105
106         for ((input, &ty), arg) in decl.inputs.iter().zip(fn_sig.inputs()).zip(&body.arguments) {
107
108             // Determines whether `ty` implements `Borrow<U>` (U != ty) specifically.
109             // This is needed due to the `Borrow<T> for T` blanket impl.
110             let implements_borrow_trait = preds.iter()
111                 .filter_map(|pred| if let ty::Predicate::Trait(ref poly_trait_ref) = *pred {
112                     Some(poly_trait_ref.skip_binder())
113                 } else {
114                     None
115                 })
116                 .filter(|tpred| tpred.def_id() == borrow_trait && tpred.self_ty() == ty)
117                 .any(|tpred| tpred.input_types().nth(1).expect("Borrow trait must have an parameter") != ty);
118
119             if_let_chain! {[
120                 !is_self(arg),
121                 !ty.is_mutable_pointer(),
122                 !is_copy(cx, ty, fn_def_id),
123                 !implements_trait(cx, ty, fn_trait, &[], Some(node_id)),
124                 !implements_trait(cx, ty, asref_trait, &[], Some(node_id)),
125                 !implements_borrow_trait,
126
127                 let PatKind::Binding(mode, defid, ..) = arg.pat.node,
128                 !moved_vars.contains(&defid),
129             ], {
130                 // Note: `toplevel_ref_arg` warns if `BindByRef`
131                 let m = match mode {
132                     BindingMode::BindByRef(m) | BindingMode::BindByValue(m) => m,
133                 };
134                 if m == Mutability::MutMutable {
135                     continue;
136                 }
137
138                 // Suggestion logic
139                 let sugg = |db: &mut DiagnosticBuilder| {
140                     let deref_span = spans_need_deref.get(&defid);
141                     if_let_chain! {[
142                         match_type(cx, ty, &paths::VEC),
143                         let TyPath(QPath::Resolved(_, ref path)) = input.node,
144                         let Some(elem_ty) = path.segments.iter()
145                             .find(|seg| seg.name == "Vec")
146                             .map(|ps| ps.parameters.types()[0]),
147                     ], {
148                         let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_"));
149                         db.span_suggestion(input.span,
150                                         "consider changing the type to",
151                                         slice_ty);
152                         assert!(deref_span.is_none());
153                         return; // `Vec` and `String` cannot be destructured - no need for `*` suggestion
154                     }}
155
156                     if match_type(cx, ty, &paths::STRING) {
157                         db.span_suggestion(input.span,
158                                            "consider changing the type to",
159                                            "&str".to_string());
160                         assert!(deref_span.is_none());
161                         return;
162                     }
163
164                     let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
165
166                     // Suggests adding `*` to dereference the added reference.
167                     if let Some(deref_span) = deref_span {
168                         spans.extend(deref_span.iter().cloned()
169                                      .map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))));
170                         spans.sort_by_key(|&(span, _)| span);
171                     }
172                     multispan_sugg(db, "consider taking a reference instead".to_string(), spans);
173                 };
174
175                 span_lint_and_then(cx,
176                           NEEDLESS_PASS_BY_VALUE,
177                           input.span,
178                           "this argument is passed by value, but not consumed in the function body",
179                           sugg);
180             }}
181         }
182     }
183 }
184
185 struct MovedVariablesCtxt<'a, 'tcx: 'a> {
186     cx: &'a LateContext<'a, 'tcx>,
187     moved_vars: HashSet<DefId>,
188     /// Spans which need to be prefixed with `*` for dereferencing the suggested additional
189     /// reference.
190     spans_need_deref: HashMap<DefId, HashSet<Span>>,
191 }
192
193 impl<'a, 'tcx: 'a> MovedVariablesCtxt<'a, 'tcx> {
194     fn new(cx: &'a LateContext<'a, 'tcx>) -> Self {
195         MovedVariablesCtxt {
196             cx: cx,
197             moved_vars: HashSet::new(),
198             spans_need_deref: HashMap::new(),
199         }
200     }
201
202     fn move_common(&mut self, _consume_id: NodeId, _span: Span, cmt: mc::cmt<'tcx>) {
203         let cmt = unwrap_downcast_or_interior(cmt);
204
205         if_let_chain! {[
206             let mc::Categorization::Local(vid) = cmt.cat,
207             let Some(def_id) = self.cx.tcx.hir.opt_local_def_id(vid),
208         ], {
209                 self.moved_vars.insert(def_id);
210         }}
211     }
212
213     fn non_moving_pat(&mut self, matched_pat: &Pat, cmt: mc::cmt<'tcx>) {
214         let cmt = unwrap_downcast_or_interior(cmt);
215
216         if_let_chain! {[
217             let mc::Categorization::Local(vid) = cmt.cat,
218             let Some(def_id) = self.cx.tcx.hir.opt_local_def_id(vid),
219         ], {
220             let mut id = matched_pat.id;
221             loop {
222                 let parent = self.cx.tcx.hir.get_parent_node(id);
223                 if id == parent {
224                     // no parent
225                     return;
226                 }
227                 id = parent;
228
229                 if let Some(node) = self.cx.tcx.hir.find(id) {
230                     match node {
231                         map::Node::NodeExpr(e) => {
232                             // `match` and `if let`
233                             if let ExprMatch(ref c, ..) = e.node {
234                                 self.spans_need_deref
235                                     .entry(def_id)
236                                     .or_insert_with(HashSet::new)
237                                     .insert(c.span);
238                             }
239                         }
240
241                         map::Node::NodeStmt(s) => {
242                             // `let <pat> = x;`
243                             if_let_chain! {[
244                                 let StmtDecl(ref decl, _) = s.node,
245                                 let DeclLocal(ref local) = decl.node,
246                             ], {
247                                 self.spans_need_deref
248                                     .entry(def_id)
249                                     .or_insert_with(HashSet::new)
250                                     .insert(local.init
251                                         .as_ref()
252                                         .map(|e| e.span)
253                                         .expect("`let` stmt without init aren't caught by match_pat"));
254                             }}
255                         }
256
257                         _ => {}
258                     }
259                 }
260             }
261         }}
262     }
263 }
264
265 impl<'a, 'tcx: 'a> euv::Delegate<'tcx> for MovedVariablesCtxt<'a, 'tcx> {
266     fn consume(&mut self, consume_id: NodeId, consume_span: Span, cmt: mc::cmt<'tcx>, mode: euv::ConsumeMode) {
267         if let euv::ConsumeMode::Move(_) = mode {
268             self.move_common(consume_id, consume_span, cmt);
269         }
270     }
271
272     fn matched_pat(&mut self, matched_pat: &Pat, cmt: mc::cmt<'tcx>, mode: euv::MatchMode) {
273         if let euv::MatchMode::MovingMatch = mode {
274             self.move_common(matched_pat.id, matched_pat.span, cmt);
275         } else {
276             self.non_moving_pat(matched_pat, cmt);
277         }
278     }
279
280     fn consume_pat(&mut self, consume_pat: &Pat, cmt: mc::cmt<'tcx>, mode: euv::ConsumeMode) {
281         if let euv::ConsumeMode::Move(_) = mode {
282             self.move_common(consume_pat.id, consume_pat.span, cmt);
283         }
284     }
285
286     fn borrow(&mut self, _: NodeId, _: Span, _: mc::cmt<'tcx>, _: ty::Region, _: ty::BorrowKind, _: euv::LoanCause) {}
287
288     fn mutate(&mut self, _: NodeId, _: Span, _: mc::cmt<'tcx>, _: euv::MutateMode) {}
289
290     fn decl_without_init(&mut self, _: NodeId, _: Span) {}
291 }
292
293
294 fn unwrap_downcast_or_interior(mut cmt: mc::cmt) -> mc::cmt {
295     loop {
296         match cmt.cat.clone() {
297             mc::Categorization::Downcast(c, _) |
298             mc::Categorization::Interior(c, _) => {
299                 cmt = c;
300             },
301             _ => return cmt,
302         }
303     }
304 }