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 syntax
::ast
::NodeId
;
11 use syntax
::errors
::DiagnosticBuilder
;
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}
;
18 /// **What it does:** Checks for functions taking arguments by value, but not
19 /// consuming them in its
22 /// **Why is this bad?** Taking arguments by reference is more flexible and can
24 /// unnecessary allocations.
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.
33 /// fn foo(v: Vec<i32>) {
34 /// assert_eq!(v.len(), 42);
37 /// fn foo(v: &[i32]) {
38 /// assert_eq!(v.len(), 42);
42 pub NEEDLESS_PASS_BY_VALUE
,
44 "functions taking arguments by value, but not consuming them in its body"
47 pub struct NeedlessPassByValue
;
49 impl LintPass
for NeedlessPassByValue
{
50 fn get_lints(&self) -> LintArray
{
51 lint_array
![NEEDLESS_PASS_BY_VALUE
]
56 ($e
: expr
) => { if let Some(x) = $e { x }
else { return; }
};
59 impl<'a
, 'tcx
> LateLintPass
<'a
, 'tcx
> for NeedlessPassByValue
{
62 cx
: &LateContext
<'a
, 'tcx
>,
74 FnKind
::ItemFn(.., attrs
) => for a
in attrs
{
76 if a
.meta_item_list().is_some();
77 if let Some(name
) = a
.name();
78 if name
== "proc_macro_derive";
84 FnKind
::Method(..) => (),
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(..) |
97 // Allow `Borrow` or functions to be taken by value
98 let borrow_trait
= need
!(get_trait_def_id(cx
, &paths
::BORROW_TRAIT
));
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()),
105 let sized_trait
= need
!(cx
.tcx
.lang_items().sized_trait());
107 let fn_def_id
= cx
.tcx
.hir
.local_def_id(node_id
);
109 let preds
= traits
::elaborate_predicates(cx
.tcx
, cx
.param_env
.caller_bounds
.to_vec())
110 .filter(|p
| !p
.is_global())
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() {
121 .collect
::<Vec
<_
>>();
123 // Collect moved variables and spans which will need dereferencings from the
125 let MovedVariablesCtxt
{
130 let mut ctx
= MovedVariablesCtxt
::new(cx
);
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
)
137 let fn_sig
= cx
.tcx
.fn_sig(fn_def_id
);
138 let fn_sig
= cx
.tcx
.erase_late_bound_regions(&fn_sig
);
140 for (idx
, ((input
, &ty
), arg
)) in decl
.inputs
142 .zip(fn_sig
.inputs())
143 .zip(&body
.arguments
)
146 // All spans generated from a proc-macro invocation are the same...
147 if span
== input
.span
{
153 if let PatKind
::Binding(_
, _
, name
, ..) = arg
.pat
.node
{
154 if name
.node
.as_str() == "self" {
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
) = {
166 .filter(|t
| t
.skip_binder().self_ty() == ty
)
167 .collect
::<Vec
<_
>>();
170 preds
.iter().any(|t
| t
.def_id() == borrow_trait
),
171 !preds
.is_empty() && preds
.iter().all(|t
| {
174 cx
.tcx
.mk_imm_ref(&RegionKind
::ReErased
, ty
),
176 &t
.skip_binder().input_types().skip(1).collect
::<Vec
<_
>>(),
184 if !ty
.is_mutable_pointer();
186 if !fn_traits
.iter().any(|&t
| implements_trait(cx
, ty
, t
, &[]));
187 if !implements_borrow_trait
;
188 if !all_borrowable_trait
;
190 if let PatKind
::Binding(mode
, canonical_id
, ..) = arg
.pat
.node
;
191 if !moved_vars
.contains(&canonical_id
);
193 if mode
== BindingAnnotation
::Mutable
|| mode
== BindingAnnotation
::RefMut
{
197 // Dereference suggestion
198 let sugg
= |db
: &mut DiagnosticBuilder
| {
199 let deref_span
= spans_need_deref
.get(&canonical_id
);
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]);
210 let slice_ty
= format
!("&[{}]", snippet(cx
, elem_ty
.span
, "_"));
211 db
.span_suggestion(input
.span
,
212 "consider changing the type to",
215 for (span
, suggestion
) in clone_spans
{
218 &snippet_opt(cx
, span
)
220 "change the call to".into(),
221 |x
| Cow
::from(format
!("change `{}` to", x
)),
227 // cannot be destructured, no need for `*` suggestion
228 assert
!(deref_span
.is_none());
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());
238 for (span
, suggestion
) in clone_spans
{
241 &snippet_opt(cx
, span
)
243 "change the call to".into(),
244 |x
| Cow
::from(format
!("change `{}` to", x
))
250 assert
!(deref_span
.is_none());
255 let mut spans
= vec
![(input
.span
, format
!("&{}", snippet(cx
, input
.span
, "_")))];
257 // Suggests adding `*` to dereference the added reference.
258 if let Some(deref_span
) = deref_span
{
263 .map(|span
| (span
, format
!("*{}", snippet(cx
, span
, "<expr>")))),
265 spans
.sort_by_key(|&(span
, _
)| span
);
267 multispan_sugg(db
, "consider taking a reference instead".to_string(), spans
);
272 NEEDLESS_PASS_BY_VALUE
,
274 "this argument is passed by value, but not consumed in the function body",
283 struct MovedVariablesCtxt
<'a
, 'tcx
: 'a
> {
284 cx
: &'a LateContext
<'a
, 'tcx
>,
285 moved_vars
: HashSet
<NodeId
>,
286 /// Spans which need to be prefixed with `*` for dereferencing the
287 /// suggested additional reference.
288 spans_need_deref
: HashMap
<NodeId
, HashSet
<Span
>>,
291 impl<'a
, 'tcx
> MovedVariablesCtxt
<'a
, 'tcx
> {
292 fn new(cx
: &'a LateContext
<'a
, 'tcx
>) -> Self {
295 moved_vars
: HashSet
::new(),
296 spans_need_deref
: HashMap
::new(),
300 fn move_common(&mut self, _consume_id
: NodeId
, _span
: Span
, cmt
: mc
::cmt
<'tcx
>) {
301 let cmt
= unwrap_downcast_or_interior(cmt
);
303 if let mc
::Categorization
::Local(vid
) = cmt
.cat
{
304 self.moved_vars
.insert(vid
);
308 fn non_moving_pat(&mut self, matched_pat
: &Pat
, cmt
: mc
::cmt
<'tcx
>) {
309 let cmt
= unwrap_downcast_or_interior(cmt
);
311 if let mc
::Categorization
::Local(vid
) = cmt
.cat
{
312 let mut id
= matched_pat
.id
;
314 let parent
= self.cx
.tcx
.hir
.get_parent_node(id
);
321 if let Some(node
) = self.cx
.tcx
.hir
.find(id
) {
323 map
::Node
::NodeExpr(e
) => {
324 // `match` and `if let`
325 if let ExprMatch(ref c
, ..) = e
.node
{
326 self.spans_need_deref
328 .or_insert_with(HashSet
::new
)
333 map
::Node
::NodeStmt(s
) => {
336 if let StmtDecl(ref decl
, _
) = s
.node
;
337 if let DeclLocal(ref local
) = decl
.node
;
339 self.spans_need_deref
341 .or_insert_with(HashSet
::new
)
345 .expect("`let` stmt without init aren't caught by match_pat"));
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
);
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
);
369 self.non_moving_pat(matched_pat
, cmt
);
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
);
379 fn borrow(&mut self, _
: NodeId
, _
: Span
, _
: mc
::cmt
<'tcx
>, _
: ty
::Region
, _
: ty
::BorrowKind
, _
: euv
::LoanCause
) {}
381 fn mutate(&mut self, _
: NodeId
, _
: Span
, _
: mc
::cmt
<'tcx
>, _
: euv
::MutateMode
) {}
383 fn decl_without_init(&mut self, _
: NodeId
, _
: Span
) {}
387 fn unwrap_downcast_or_interior(mut cmt
: mc
::cmt
) -> mc
::cmt
{
389 match cmt
.cat
.clone() {
390 mc
::Categorization
::Downcast(c
, _
) | mc
::Categorization
::Interior(c
, _
) => {