2 use rustc
::hir
::map
::*;
3 use rustc
::hir
::intravisit
::FnKind
;
5 use rustc
::ty
::{self, RegionKind, TypeFoldable}
;
7 use rustc
::middle
::expr_use_visitor
as euv
;
8 use rustc
::middle
::mem_categorization
as mc
;
9 use rustc_target
::spec
::abi
::Abi
;
10 use syntax
::ast
::NodeId
;
12 use syntax
::errors
::DiagnosticBuilder
;
13 use utils
::{get_trait_def_id
, implements_trait
, in_macro
, is_copy
, is_self
, match_type
, multispan_sugg
, paths
,
14 snippet
, snippet_opt
, span_lint_and_then
};
15 use utils
::ptr
::get_spans
;
16 use std
::collections
::{HashMap, HashSet}
;
19 /// **What it does:** Checks for functions taking arguments by value, but not
20 /// consuming them in its
23 /// **Why is this bad?** Taking arguments by reference is more flexible and can
25 /// unnecessary allocations.
27 /// **Known problems:**
28 /// * This lint suggests taking an argument by reference,
29 /// however sometimes it is better to let users decide the argument type
30 /// (by using `Borrow` trait, for example), depending on how the function is used.
34 /// fn foo(v: Vec<i32>) {
35 /// assert_eq!(v.len(), 42);
38 /// fn foo(v: &[i32]) {
39 /// assert_eq!(v.len(), 42);
42 declare_clippy_lint
! {
43 pub NEEDLESS_PASS_BY_VALUE
,
45 "functions taking arguments by value, but not consuming them in its body"
48 pub struct NeedlessPassByValue
;
50 impl LintPass
for NeedlessPassByValue
{
51 fn get_lints(&self) -> LintArray
{
52 lint_array
![NEEDLESS_PASS_BY_VALUE
]
57 ($e
: expr
) => { if let Some(x) = $e { x }
else { return; }
};
60 impl<'a
, 'tcx
> LateLintPass
<'a
, 'tcx
> for NeedlessPassByValue
{
63 cx
: &LateContext
<'a
, 'tcx
>,
75 FnKind
::ItemFn(.., abi
, _
, attrs
) => {
81 if a
.meta_item_list().is_some();
82 if let Some(name
) = a
.name();
83 if name
== "proc_macro_derive";
90 FnKind
::Method(..) => (),
94 // Exclude non-inherent impls
95 if let Some(NodeItem(item
)) = cx
.tcx
.hir
.find(cx
.tcx
.hir
.get_parent_node(node_id
)) {
96 if matches
!(item
.node
, ItemImpl(_
, _
, _
, _
, Some(_
), _
, _
) |
103 // Allow `Borrow` or functions to be taken by value
104 let borrow_trait
= need
!(get_trait_def_id(cx
, &paths
::BORROW_TRAIT
));
105 let whitelisted_traits
= [
106 need
!(cx
.tcx
.lang_items().fn_trait()),
107 need
!(cx
.tcx
.lang_items().fn_once_trait()),
108 need
!(cx
.tcx
.lang_items().fn_mut_trait()),
109 need
!(get_trait_def_id(cx
, &paths
::RANGE_ARGUMENT_TRAIT
))
112 let sized_trait
= need
!(cx
.tcx
.lang_items().sized_trait());
114 let fn_def_id
= cx
.tcx
.hir
.local_def_id(node_id
);
116 let preds
= traits
::elaborate_predicates(cx
.tcx
, cx
.param_env
.caller_bounds
.to_vec())
117 .filter(|p
| !p
.is_global())
119 if let ty
::Predicate
::Trait(poly_trait_ref
) = pred
{
120 if poly_trait_ref
.def_id() == sized_trait
|| poly_trait_ref
.skip_binder().has_escaping_regions() {
128 .collect
::<Vec
<_
>>();
130 // Collect moved variables and spans which will need dereferencings from the
132 let MovedVariablesCtxt
{
137 let mut ctx
= MovedVariablesCtxt
::new(cx
);
138 let region_scope_tree
= &cx
.tcx
.region_scope_tree(fn_def_id
);
139 euv
::ExprUseVisitor
::new(&mut ctx
, cx
.tcx
, cx
.param_env
, region_scope_tree
, cx
.tables
, None
)
144 let fn_sig
= cx
.tcx
.fn_sig(fn_def_id
);
145 let fn_sig
= cx
.tcx
.erase_late_bound_regions(&fn_sig
);
147 for (idx
, ((input
, &ty
), arg
)) in decl
.inputs
149 .zip(fn_sig
.inputs())
150 .zip(&body
.arguments
)
153 // All spans generated from a proc-macro invocation are the same...
154 if span
== input
.span
{
160 if let PatKind
::Binding(_
, _
, name
, ..) = arg
.pat
.node
{
161 if name
.node
.as_str() == "self" {
167 // * Exclude a type that is specifically bounded by `Borrow`.
168 // * Exclude a type whose reference also fulfills its bound.
169 // (e.g. `std::convert::AsRef`, `serde::Serialize`)
170 let (implements_borrow_trait
, all_borrowable_trait
) = {
173 .filter(|t
| t
.skip_binder().self_ty() == ty
)
174 .collect
::<Vec
<_
>>();
177 preds
.iter().any(|t
| t
.def_id() == borrow_trait
),
178 !preds
.is_empty() && preds
.iter().all(|t
| {
181 cx
.tcx
.mk_imm_ref(&RegionKind
::ReErased
, ty
),
183 &t
.skip_binder().input_types().skip(1).collect
::<Vec
<_
>>(),
191 if !ty
.is_mutable_pointer();
193 if !whitelisted_traits
.iter().any(|&t
| implements_trait(cx
, ty
, t
, &[]));
194 if !implements_borrow_trait
;
195 if !all_borrowable_trait
;
197 if let PatKind
::Binding(mode
, canonical_id
, ..) = arg
.pat
.node
;
198 if !moved_vars
.contains(&canonical_id
);
200 if mode
== BindingAnnotation
::Mutable
|| mode
== BindingAnnotation
::RefMut
{
204 // Dereference suggestion
205 let sugg
= |db
: &mut DiagnosticBuilder
| {
206 if let ty
::TypeVariants
::TyAdt(def
, ..) = ty
.sty
{
207 if let Some(span
) = cx
.tcx
.hir
.span_if_local(def
.did
) {
208 let param_env
= ty
::ParamEnv
::empty();
209 if param_env
.can_type_implement_copy(cx
.tcx
, ty
, span
).is_ok() {
210 db
.span_help(span
, "consider marking this type as Copy");
215 let deref_span
= spans_need_deref
.get(&canonical_id
);
217 if match_type(cx
, ty
, &paths
::VEC
);
218 if let Some(clone_spans
) =
219 get_spans(cx
, Some(body
.id()), idx
, &[("clone", ".to_owned()")]);
220 if let TyPath(QPath
::Resolved(_
, ref path
)) = input
.node
;
221 if let Some(elem_ty
) = path
.segments
.iter()
222 .find(|seg
| seg
.name
== "Vec")
223 .and_then(|ps
| ps
.parameters
.as_ref())
224 .map(|params
| ¶ms
.types
[0]);
226 let slice_ty
= format
!("&[{}]", snippet(cx
, elem_ty
.span
, "_"));
227 db
.span_suggestion(input
.span
,
228 "consider changing the type to",
231 for (span
, suggestion
) in clone_spans
{
234 &snippet_opt(cx
, span
)
236 "change the call to".into(),
237 |x
| Cow
::from(format
!("change `{}` to", x
)),
243 // cannot be destructured, no need for `*` suggestion
244 assert
!(deref_span
.is_none());
249 if match_type(cx
, ty
, &paths
::STRING
) {
250 if let Some(clone_spans
) =
251 get_spans(cx
, Some(body
.id()), idx
, &[("clone", ".to_string()"), ("as_str", "")]) {
252 db
.span_suggestion(input
.span
, "consider changing the type to", "&str".to_string());
254 for (span
, suggestion
) in clone_spans
{
257 &snippet_opt(cx
, span
)
259 "change the call to".into(),
260 |x
| Cow
::from(format
!("change `{}` to", x
))
266 assert
!(deref_span
.is_none());
271 let mut spans
= vec
![(input
.span
, format
!("&{}", snippet(cx
, input
.span
, "_")))];
273 // Suggests adding `*` to dereference the added reference.
274 if let Some(deref_span
) = deref_span
{
279 .map(|span
| (span
, format
!("*{}", snippet(cx
, span
, "<expr>")))),
281 spans
.sort_by_key(|&(span
, _
)| span
);
283 multispan_sugg(db
, "consider taking a reference instead".to_string(), spans
);
288 NEEDLESS_PASS_BY_VALUE
,
290 "this argument is passed by value, but not consumed in the function body",
299 struct MovedVariablesCtxt
<'a
, 'tcx
: 'a
> {
300 cx
: &'a LateContext
<'a
, 'tcx
>,
301 moved_vars
: HashSet
<NodeId
>,
302 /// Spans which need to be prefixed with `*` for dereferencing the
303 /// suggested additional reference.
304 spans_need_deref
: HashMap
<NodeId
, HashSet
<Span
>>,
307 impl<'a
, 'tcx
> MovedVariablesCtxt
<'a
, 'tcx
> {
308 fn new(cx
: &'a LateContext
<'a
, 'tcx
>) -> Self {
311 moved_vars
: HashSet
::new(),
312 spans_need_deref
: HashMap
::new(),
316 fn move_common(&mut self, _consume_id
: NodeId
, _span
: Span
, cmt
: mc
::cmt
<'tcx
>) {
317 let cmt
= unwrap_downcast_or_interior(cmt
);
319 if let mc
::Categorization
::Local(vid
) = cmt
.cat
{
320 self.moved_vars
.insert(vid
);
324 fn non_moving_pat(&mut self, matched_pat
: &Pat
, cmt
: mc
::cmt
<'tcx
>) {
325 let cmt
= unwrap_downcast_or_interior(cmt
);
327 if let mc
::Categorization
::Local(vid
) = cmt
.cat
{
328 let mut id
= matched_pat
.id
;
330 let parent
= self.cx
.tcx
.hir
.get_parent_node(id
);
337 if let Some(node
) = self.cx
.tcx
.hir
.find(id
) {
339 map
::Node
::NodeExpr(e
) => {
340 // `match` and `if let`
341 if let ExprMatch(ref c
, ..) = e
.node
{
342 self.spans_need_deref
344 .or_insert_with(HashSet
::new
)
349 map
::Node
::NodeStmt(s
) => {
352 if let StmtDecl(ref decl
, _
) = s
.node
;
353 if let DeclLocal(ref local
) = decl
.node
;
355 self.spans_need_deref
357 .or_insert_with(HashSet
::new
)
361 .expect("`let` stmt without init aren't caught by match_pat"));
374 impl<'a
, 'tcx
> euv
::Delegate
<'tcx
> for MovedVariablesCtxt
<'a
, 'tcx
> {
375 fn consume(&mut self, consume_id
: NodeId
, consume_span
: Span
, cmt
: mc
::cmt
<'tcx
>, mode
: euv
::ConsumeMode
) {
376 if let euv
::ConsumeMode
::Move(_
) = mode
{
377 self.move_common(consume_id
, consume_span
, cmt
);
381 fn matched_pat(&mut self, matched_pat
: &Pat
, cmt
: mc
::cmt
<'tcx
>, mode
: euv
::MatchMode
) {
382 if let euv
::MatchMode
::MovingMatch
= mode
{
383 self.move_common(matched_pat
.id
, matched_pat
.span
, cmt
);
385 self.non_moving_pat(matched_pat
, cmt
);
389 fn consume_pat(&mut self, consume_pat
: &Pat
, cmt
: mc
::cmt
<'tcx
>, mode
: euv
::ConsumeMode
) {
390 if let euv
::ConsumeMode
::Move(_
) = mode
{
391 self.move_common(consume_pat
.id
, consume_pat
.span
, cmt
);
395 fn borrow(&mut self, _
: NodeId
, _
: Span
, _
: mc
::cmt
<'tcx
>, _
: ty
::Region
, _
: ty
::BorrowKind
, _
: euv
::LoanCause
) {}
397 fn mutate(&mut self, _
: NodeId
, _
: Span
, _
: mc
::cmt
<'tcx
>, _
: euv
::MutateMode
) {}
399 fn decl_without_init(&mut self, _
: NodeId
, _
: Span
) {}
403 fn unwrap_downcast_or_interior(mut cmt
: mc
::cmt
) -> mc
::cmt
{
405 match cmt
.cat
.clone() {
406 mc
::Categorization
::Downcast(c
, _
) | mc
::Categorization
::Interior(c
, _
) => {