1 use clippy_utils
::diagnostics
::{multispan_sugg, span_lint_and_then}
;
2 use clippy_utils
::ptr
::get_spans
;
3 use clippy_utils
::source
::{snippet, snippet_opt}
;
4 use clippy_utils
::ty
::{implements_trait, is_copy, is_type_diagnostic_item}
;
5 use clippy_utils
::{get_trait_def_id, is_self, paths}
;
6 use if_chain
::if_chain
;
7 use rustc_ast
::ast
::Attribute
;
8 use rustc_data_structures
::fx
::FxHashSet
;
9 use rustc_errors
::{Applicability, Diagnostic}
;
10 use rustc_hir
::intravisit
::FnKind
;
11 use rustc_hir
::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Node, PatKind, QPath, TyKind}
;
12 use rustc_hir
::{HirIdMap, HirIdSet}
;
13 use rustc_infer
::infer
::TyCtxtInferExt
;
14 use rustc_lint
::{LateContext, LateLintPass}
;
15 use rustc_middle
::mir
::FakeReadCause
;
16 use rustc_middle
::ty
::{self, TypeFoldable}
;
17 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
18 use rustc_span
::symbol
::kw
;
19 use rustc_span
::{sym, Span}
;
20 use rustc_target
::spec
::abi
::Abi
;
21 use rustc_trait_selection
::traits
;
22 use rustc_trait_selection
::traits
::misc
::can_type_implement_copy
;
23 use rustc_typeck
::expr_use_visitor
as euv
;
26 declare_clippy_lint
! {
28 /// Checks for functions taking arguments by value, but not
29 /// consuming them in its
32 /// ### Why is this bad?
33 /// Taking arguments by reference is more flexible and can
35 /// unnecessary allocations.
37 /// ### Known problems
38 /// * This lint suggests taking an argument by reference,
39 /// however sometimes it is better to let users decide the argument type
40 /// (by using `Borrow` trait, for example), depending on how the function is used.
44 /// fn foo(v: Vec<i32>) {
45 /// assert_eq!(v.len(), 42);
50 /// fn foo(v: &[i32]) {
51 /// assert_eq!(v.len(), 42);
54 #[clippy::version = "pre 1.29.0"]
55 pub NEEDLESS_PASS_BY_VALUE
,
57 "functions taking arguments by value, but not consuming them in its body"
60 declare_lint_pass
!(NeedlessPassByValue
=> [NEEDLESS_PASS_BY_VALUE
]);
72 impl<'tcx
> LateLintPass
<'tcx
> for NeedlessPassByValue
{
73 #[allow(clippy::too_many_lines)]
76 cx
: &LateContext
<'tcx
>,
78 decl
: &'tcx FnDecl
<'_
>,
83 if span
.from_expansion() {
88 FnKind
::ItemFn(.., header
, _
) => {
89 let attrs
= cx
.tcx
.hir().attrs(hir_id
);
90 if header
.abi
!= Abi
::Rust
|| requires_exact_signature(attrs
) {
94 FnKind
::Method(..) => (),
95 FnKind
::Closure
=> return,
98 // Exclude non-inherent impls
99 if let Some(Node
::Item(item
)) = cx
.tcx
.hir().find(cx
.tcx
.hir().get_parent_node(hir_id
)) {
102 ItemKind
::Impl(Impl { of_trait: Some(_), .. }
) | ItemKind
::Trait(..)
108 // Allow `Borrow` or functions to be taken by value
109 let allowed_traits
= [
110 need
!(cx
.tcx
.lang_items().fn_trait()),
111 need
!(cx
.tcx
.lang_items().fn_once_trait()),
112 need
!(cx
.tcx
.lang_items().fn_mut_trait()),
113 need
!(get_trait_def_id(cx
, &paths
::RANGE_ARGUMENT_TRAIT
)),
116 let sized_trait
= need
!(cx
.tcx
.lang_items().sized_trait());
118 let fn_def_id
= cx
.tcx
.hir().local_def_id(hir_id
);
120 let preds
= traits
::elaborate_predicates(cx
.tcx
, cx
.param_env
.caller_bounds().iter())
121 .filter(|p
| !p
.is_global())
122 .filter_map(|obligation
| {
123 // Note that we do not want to deal with qualified predicates here.
124 match obligation
.predicate
.kind().no_bound_vars() {
125 Some(ty
::PredicateKind
::Trait(pred
)) if pred
.def_id() != sized_trait
=> Some(pred
),
129 .collect
::<Vec
<_
>>();
131 // Collect moved variables and spans which will need dereferencings from the
133 let MovedVariablesCtxt
{
138 let mut ctx
= MovedVariablesCtxt
::default();
139 cx
.tcx
.infer_ctxt().enter(|infcx
| {
140 euv
::ExprUseVisitor
::new(&mut ctx
, &infcx
, fn_def_id
, cx
.param_env
, cx
.typeck_results())
146 let fn_sig
= cx
.tcx
.fn_sig(fn_def_id
);
147 let fn_sig
= cx
.tcx
.erase_late_bound_regions(fn_sig
);
149 for (idx
, ((input
, &ty
), arg
)) in decl
.inputs
.iter().zip(fn_sig
.inputs()).zip(body
.params
).enumerate() {
150 // All spans generated from a proc-macro invocation are the same...
151 if span
== input
.span
{
157 if let PatKind
::Binding(.., ident
, _
) = arg
.pat
.kind
{
158 if ident
.name
== kw
::SelfLower
{
165 // * Exclude a type that is specifically bounded by `Borrow`.
166 // * Exclude a type whose reference also fulfills its bound. (e.g., `std::convert::AsRef`,
167 // `serde::Serialize`)
168 let (implements_borrow_trait
, all_borrowable_trait
) = {
169 let preds
= preds
.iter().filter(|t
| t
.self_ty() == ty
).collect
::<Vec
<_
>>();
172 preds
.iter().any(|t
| cx
.tcx
.is_diagnostic_item(sym
::Borrow
, t
.def_id())),
173 !preds
.is_empty() && {
174 let ty_empty_region
= cx
.tcx
.mk_imm_ref(cx
.tcx
.lifetimes
.re_root_empty
, ty
);
175 preds
.iter().all(|t
| {
176 let ty_params
= t
.trait_ref
.substs
.iter().skip(1).collect
::<Vec
<_
>>();
177 implements_trait(cx
, ty_empty_region
, t
.def_id(), &ty_params
)
185 if !ty
.is_mutable_ptr();
187 if !allowed_traits
.iter().any(|&t
| implements_trait(cx
, ty
, t
, &[]));
188 if !implements_borrow_trait
;
189 if !all_borrowable_trait
;
191 if let PatKind
::Binding(mode
, canonical_id
, ..) = arg
.pat
.kind
;
192 if !moved_vars
.contains(&canonical_id
);
194 if mode
== BindingAnnotation
::Mutable
|| mode
== BindingAnnotation
::RefMut
{
198 // Dereference suggestion
199 let sugg
= |diag
: &mut Diagnostic
| {
200 if let ty
::Adt(def
, ..) = ty
.kind() {
201 if let Some(span
) = cx
.tcx
.hir().span_if_local(def
.did()) {
202 if can_type_implement_copy(
206 traits
::ObligationCause
::dummy_with_span(span
),
208 diag
.span_help(span
, "consider marking this type as `Copy`");
213 let deref_span
= spans_need_deref
.get(&canonical_id
);
215 if is_type_diagnostic_item(cx
, ty
, sym
::Vec
);
216 if let Some(clone_spans
) =
217 get_spans(cx
, Some(body
.id()), idx
, &[("clone", ".to_owned()")]);
218 if let TyKind
::Path(QPath
::Resolved(_
, path
)) = input
.kind
;
219 if let Some(elem_ty
) = path
.segments
.iter()
220 .find(|seg
| seg
.ident
.name
== sym
::Vec
)
221 .and_then(|ps
| ps
.args
.as_ref())
222 .map(|params
| params
.args
.iter().find_map(|arg
| match arg
{
223 GenericArg
::Type(ty
) => Some(ty
),
227 let slice_ty
= format
!("&[{}]", snippet(cx
, elem_ty
.span
, "_"));
228 diag
.span_suggestion(
230 "consider changing the type to",
232 Applicability
::Unspecified
,
235 for (span
, suggestion
) in clone_spans
{
236 diag
.span_suggestion(
238 &snippet_opt(cx
, span
)
240 "change the call to".into(),
241 |x
| Cow
::from(format
!("change `{}` to", x
)),
244 Applicability
::Unspecified
,
248 // cannot be destructured, no need for `*` suggestion
249 assert
!(deref_span
.is_none());
254 if is_type_diagnostic_item(cx
, ty
, sym
::String
) {
255 if let Some(clone_spans
) =
256 get_spans(cx
, Some(body
.id()), idx
, &[("clone", ".to_string()"), ("as_str", "")]) {
257 diag
.span_suggestion(
259 "consider changing the type to",
261 Applicability
::Unspecified
,
264 for (span
, suggestion
) in clone_spans
{
265 diag
.span_suggestion(
267 &snippet_opt(cx
, span
)
269 "change the call to".into(),
270 |x
| Cow
::from(format
!("change `{}` to", x
))
273 Applicability
::Unspecified
,
277 assert
!(deref_span
.is_none());
282 let mut spans
= vec
![(input
.span
, format
!("&{}", snippet(cx
, input
.span
, "_")))];
284 // Suggests adding `*` to dereference the added reference.
285 if let Some(deref_span
) = deref_span
{
290 .map(|span
| (span
, format
!("*{}", snippet(cx
, span
, "<expr>")))),
292 spans
.sort_by_key(|&(span
, _
)| span
);
294 multispan_sugg(diag
, "consider taking a reference instead", spans
);
299 NEEDLESS_PASS_BY_VALUE
,
301 "this argument is passed by value, but not consumed in the function body",
310 /// Functions marked with these attributes must have the exact signature.
311 fn requires_exact_signature(attrs
: &[Attribute
]) -> bool
{
312 attrs
.iter().any(|attr
| {
313 [sym
::proc_macro
, sym
::proc_macro_attribute
, sym
::proc_macro_derive
]
315 .any(|&allow
| attr
.has_name(allow
))
320 struct MovedVariablesCtxt
{
321 moved_vars
: HirIdSet
,
322 /// Spans which need to be prefixed with `*` for dereferencing the
323 /// suggested additional reference.
324 spans_need_deref
: HirIdMap
<FxHashSet
<Span
>>,
327 impl MovedVariablesCtxt
{
328 fn move_common(&mut self, cmt
: &euv
::PlaceWithHirId
<'_
>) {
329 if let euv
::PlaceBase
::Local(vid
) = cmt
.place
.base
{
330 self.moved_vars
.insert(vid
);
335 impl<'tcx
> euv
::Delegate
<'tcx
> for MovedVariablesCtxt
{
336 fn consume(&mut self, cmt
: &euv
::PlaceWithHirId
<'tcx
>, _
: HirId
) {
337 self.move_common(cmt
);
340 fn borrow(&mut self, _
: &euv
::PlaceWithHirId
<'tcx
>, _
: HirId
, _
: ty
::BorrowKind
) {}
342 fn mutate(&mut self, _
: &euv
::PlaceWithHirId
<'tcx
>, _
: HirId
) {}
344 fn fake_read(&mut self, _
: rustc_typeck
::expr_use_visitor
::Place
<'tcx
>, _
: FakeReadCause
, _
: HirId
) {}