Skip to content

Commit b46033e

Browse files
committed
Auto merge of #10900 - GuillaumeGomez:needless-pass-by-ref, r=llogiq
Add `needless_pass_by_ref_mut` lint changelog: [`needless_pass_by_ref_mut`]: This PR add a new lint `needless_pass_by_ref_mut` which emits a warning in case a `&mut` function argument isn't used mutably. It doesn't warn on trait and trait impls functions. Fixes #8863.
2 parents 757fe49 + f048f73 commit b46033e

35 files changed

+603
-103
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5023,6 +5023,7 @@ Released 2018-09-13
50235023
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
50245024
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
50255025
[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals
5026+
[`needless_pass_by_ref_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_ref_mut
50265027
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
50275028
[`needless_pub_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pub_self
50285029
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
474474
crate::needless_if::NEEDLESS_IF_INFO,
475475
crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
476476
crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
477+
crate::needless_pass_by_ref_mut::NEEDLESS_PASS_BY_REF_MUT_INFO,
477478
crate::needless_pass_by_value::NEEDLESS_PASS_BY_VALUE_INFO,
478479
crate::needless_question_mark::NEEDLESS_QUESTION_MARK_INFO,
479480
crate::needless_update::NEEDLESS_UPDATE_INFO,

clippy_lints/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ mod needless_for_each;
230230
mod needless_if;
231231
mod needless_late_init;
232232
mod needless_parens_on_range_literals;
233+
mod needless_pass_by_ref_mut;
233234
mod needless_pass_by_value;
234235
mod needless_question_mark;
235236
mod needless_update;
@@ -1058,6 +1059,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10581059
let stack_size_threshold = conf.stack_size_threshold;
10591060
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
10601061
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
1062+
store.register_late_pass(move |_| {
1063+
Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(
1064+
avoid_breaking_exported_api,
1065+
))
1066+
});
10611067
store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
10621068
store.register_late_pass(move |_| {
10631069
Box::new(single_call_fn::SingleCallFn {

clippy_lints/src/literal_representation.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ impl LiteralDigitGrouping {
264264
return;
265265
}
266266

267-
if Self::is_literal_uuid_formatted(&mut num_lit) {
267+
if Self::is_literal_uuid_formatted(&num_lit) {
268268
return;
269269
}
270270

@@ -376,7 +376,7 @@ impl LiteralDigitGrouping {
376376
///
377377
/// Returns `true` if the radix is hexadecimal, and the groups match the
378378
/// UUID format of 8-4-4-4-12.
379-
fn is_literal_uuid_formatted(num_lit: &mut NumericLiteral<'_>) -> bool {
379+
fn is_literal_uuid_formatted(num_lit: &NumericLiteral<'_>) -> bool {
380380
if num_lit.radix != Radix::Hexadecimal {
381381
return false;
382382
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
use super::needless_pass_by_value::requires_exact_signature;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::source::snippet;
4+
use clippy_utils::{is_from_proc_macro, is_self};
5+
use if_chain::if_chain;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::intravisit::FnKind;
8+
use rustc_hir::{Body, FnDecl, HirId, Impl, ItemKind, Mutability, Node, PatKind};
9+
use rustc_hir::{HirIdMap, HirIdSet};
10+
use rustc_hir_typeck::expr_use_visitor as euv;
11+
use rustc_infer::infer::TyCtxtInferExt;
12+
use rustc_lint::{LateContext, LateLintPass};
13+
use rustc_middle::mir::FakeReadCause;
14+
use rustc_middle::ty::{self, Ty};
15+
use rustc_session::{declare_tool_lint, impl_lint_pass};
16+
use rustc_span::def_id::LocalDefId;
17+
use rustc_span::symbol::kw;
18+
use rustc_span::Span;
19+
use rustc_target::spec::abi::Abi;
20+
21+
declare_clippy_lint! {
22+
/// ### What it does
23+
/// Check if a `&mut` function argument is actually used mutably.
24+
///
25+
/// Be careful if the function is publically reexported as it would break compatibility with
26+
/// users of this function.
27+
///
28+
/// ### Why is this bad?
29+
/// Less `mut` means less fights with the borrow checker. It can also lead to more
30+
/// opportunities for parallelization.
31+
///
32+
/// ### Example
33+
/// ```rust
34+
/// fn foo(y: &mut i32) -> i32 {
35+
/// 12 + *y
36+
/// }
37+
/// ```
38+
/// Use instead:
39+
/// ```rust
40+
/// fn foo(y: &i32) -> i32 {
41+
/// 12 + *y
42+
/// }
43+
/// ```
44+
#[clippy::version = "1.72.0"]
45+
pub NEEDLESS_PASS_BY_REF_MUT,
46+
suspicious,
47+
"using a `&mut` argument when it's not mutated"
48+
}
49+
50+
#[derive(Copy, Clone)]
51+
pub struct NeedlessPassByRefMut {
52+
avoid_breaking_exported_api: bool,
53+
}
54+
55+
impl NeedlessPassByRefMut {
56+
pub fn new(avoid_breaking_exported_api: bool) -> Self {
57+
Self {
58+
avoid_breaking_exported_api,
59+
}
60+
}
61+
}
62+
63+
impl_lint_pass!(NeedlessPassByRefMut => [NEEDLESS_PASS_BY_REF_MUT]);
64+
65+
fn should_skip<'tcx>(
66+
cx: &LateContext<'tcx>,
67+
input: rustc_hir::Ty<'tcx>,
68+
ty: Ty<'_>,
69+
arg: &rustc_hir::Param<'_>,
70+
) -> bool {
71+
// We check if this a `&mut`. `ref_mutability` returns `None` if it's not a reference.
72+
if !matches!(ty.ref_mutability(), Some(Mutability::Mut)) {
73+
return true;
74+
}
75+
76+
if is_self(arg) {
77+
return true;
78+
}
79+
80+
if let PatKind::Binding(.., name, _) = arg.pat.kind {
81+
// If it's a potentially unused variable, we don't check it.
82+
if name.name == kw::Underscore || name.as_str().starts_with('_') {
83+
return true;
84+
}
85+
}
86+
87+
// All spans generated from a proc-macro invocation are the same...
88+
is_from_proc_macro(cx, &input)
89+
}
90+
91+
impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
92+
fn check_fn(
93+
&mut self,
94+
cx: &LateContext<'tcx>,
95+
kind: FnKind<'tcx>,
96+
decl: &'tcx FnDecl<'_>,
97+
body: &'tcx Body<'_>,
98+
span: Span,
99+
fn_def_id: LocalDefId,
100+
) {
101+
if span.from_expansion() {
102+
return;
103+
}
104+
105+
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id);
106+
107+
match kind {
108+
FnKind::ItemFn(.., header) => {
109+
let attrs = cx.tcx.hir().attrs(hir_id);
110+
if header.abi != Abi::Rust || requires_exact_signature(attrs) {
111+
return;
112+
}
113+
},
114+
FnKind::Method(..) => (),
115+
FnKind::Closure => return,
116+
}
117+
118+
// Exclude non-inherent impls
119+
if let Some(Node::Item(item)) = cx.tcx.hir().find_parent(hir_id) {
120+
if matches!(
121+
item.kind,
122+
ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
123+
) {
124+
return;
125+
}
126+
}
127+
128+
let fn_sig = cx.tcx.fn_sig(fn_def_id).subst_identity();
129+
let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig);
130+
131+
// If there are no `&mut` argument, no need to go any further.
132+
if !decl
133+
.inputs
134+
.iter()
135+
.zip(fn_sig.inputs())
136+
.zip(body.params)
137+
.any(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
138+
{
139+
return;
140+
}
141+
142+
// Collect variables mutably used and spans which will need dereferencings from the
143+
// function body.
144+
let MutablyUsedVariablesCtxt { mutably_used_vars, .. } = {
145+
let mut ctx = MutablyUsedVariablesCtxt::default();
146+
let infcx = cx.tcx.infer_ctxt().build();
147+
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
148+
ctx
149+
};
150+
151+
let mut it = decl
152+
.inputs
153+
.iter()
154+
.zip(fn_sig.inputs())
155+
.zip(body.params)
156+
.filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
157+
.peekable();
158+
if it.peek().is_none() {
159+
return;
160+
}
161+
let show_semver_warning = self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id);
162+
for ((&input, &_), arg) in it {
163+
// Only take `&mut` arguments.
164+
if_chain! {
165+
if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind;
166+
if !mutably_used_vars.contains(&canonical_id);
167+
if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind;
168+
then {
169+
// If the argument is never used mutably, we emit the warning.
170+
let sp = input.span;
171+
span_lint_and_then(
172+
cx,
173+
NEEDLESS_PASS_BY_REF_MUT,
174+
sp,
175+
"this argument is a mutable reference, but not used mutably",
176+
|diag| {
177+
diag.span_suggestion(
178+
sp,
179+
"consider changing to".to_string(),
180+
format!(
181+
"&{}",
182+
snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),
183+
),
184+
Applicability::Unspecified,
185+
);
186+
if show_semver_warning {
187+
diag.warn("changing this function will impact semver compatibility");
188+
}
189+
},
190+
);
191+
}
192+
}
193+
}
194+
}
195+
}
196+
197+
#[derive(Default)]
198+
struct MutablyUsedVariablesCtxt {
199+
mutably_used_vars: HirIdSet,
200+
prev_bind: Option<HirId>,
201+
aliases: HirIdMap<HirId>,
202+
}
203+
204+
impl MutablyUsedVariablesCtxt {
205+
fn add_mutably_used_var(&mut self, mut used_id: HirId) {
206+
while let Some(id) = self.aliases.get(&used_id) {
207+
self.mutably_used_vars.insert(used_id);
208+
used_id = *id;
209+
}
210+
self.mutably_used_vars.insert(used_id);
211+
}
212+
}
213+
214+
impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
215+
fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
216+
if let euv::Place {
217+
base: euv::PlaceBase::Local(vid),
218+
base_ty,
219+
..
220+
} = &cmt.place
221+
{
222+
if let Some(bind_id) = self.prev_bind.take() {
223+
self.aliases.insert(bind_id, *vid);
224+
} else if matches!(base_ty.ref_mutability(), Some(Mutability::Mut)) {
225+
self.add_mutably_used_var(*vid);
226+
}
227+
}
228+
}
229+
230+
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId, borrow: ty::BorrowKind) {
231+
self.prev_bind = None;
232+
if let euv::Place {
233+
base: euv::PlaceBase::Local(vid),
234+
base_ty,
235+
..
236+
} = &cmt.place
237+
{
238+
// If this is a mutable borrow, it was obviously used mutably so we add it. However
239+
// for `UniqueImmBorrow`, it's interesting because if you do: `array[0] = value` inside
240+
// a closure, it'll return this variant whereas if you have just an index access, it'll
241+
// return `ImmBorrow`. So if there is "Unique" and it's a mutable reference, we add it
242+
// to the mutably used variables set.
243+
if borrow == ty::BorrowKind::MutBorrow
244+
|| (borrow == ty::BorrowKind::UniqueImmBorrow && base_ty.ref_mutability() == Some(Mutability::Mut))
245+
{
246+
self.add_mutably_used_var(*vid);
247+
}
248+
}
249+
}
250+
251+
fn mutate(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
252+
self.prev_bind = None;
253+
if let euv::Place {
254+
projections,
255+
base: euv::PlaceBase::Local(vid),
256+
..
257+
} = &cmt.place
258+
{
259+
if !projections.is_empty() {
260+
self.add_mutably_used_var(*vid);
261+
}
262+
}
263+
}
264+
265+
fn copy(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
266+
self.prev_bind = None;
267+
}
268+
269+
fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
270+
271+
fn bind(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
272+
self.prev_bind = Some(id);
273+
}
274+
}

clippy_lints/src/needless_pass_by_value.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
289289
}
290290

291291
/// Functions marked with these attributes must have the exact signature.
292-
fn requires_exact_signature(attrs: &[Attribute]) -> bool {
292+
pub(crate) fn requires_exact_signature(attrs: &[Attribute]) -> bool {
293293
attrs.iter().any(|attr| {
294294
[sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
295295
.iter()

clippy_lints/src/non_expressive_names.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ struct ExistingName {
9191
struct SimilarNamesLocalVisitor<'a, 'tcx> {
9292
names: Vec<ExistingName>,
9393
cx: &'a EarlyContext<'tcx>,
94-
lint: &'a NonExpressiveNames,
94+
lint: NonExpressiveNames,
9595

9696
/// A stack of scopes containing the single-character bindings in each scope.
9797
single_char_names: Vec<Vec<Ident>>,
@@ -365,7 +365,7 @@ impl EarlyLintPass for NonExpressiveNames {
365365
..
366366
}) = item.kind
367367
{
368-
do_check(self, cx, &item.attrs, &sig.decl, blk);
368+
do_check(*self, cx, &item.attrs, &sig.decl, blk);
369369
}
370370
}
371371

@@ -380,12 +380,12 @@ impl EarlyLintPass for NonExpressiveNames {
380380
..
381381
}) = item.kind
382382
{
383-
do_check(self, cx, &item.attrs, &sig.decl, blk);
383+
do_check(*self, cx, &item.attrs, &sig.decl, blk);
384384
}
385385
}
386386
}
387387

388-
fn do_check(lint: &mut NonExpressiveNames, cx: &EarlyContext<'_>, attrs: &[Attribute], decl: &FnDecl, blk: &Block) {
388+
fn do_check(lint: NonExpressiveNames, cx: &EarlyContext<'_>, attrs: &[Attribute], decl: &FnDecl, blk: &Block) {
389389
if !attrs.iter().any(|attr| attr.has_name(sym::test)) {
390390
let mut visitor = SimilarNamesLocalVisitor {
391391
names: Vec::new(),

tests/ui-toml/toml_trivially_copy/test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//@normalize-stderr-test: "\(limit: \d+ byte\)" -> "(limit: N byte)"
33

44
#![warn(clippy::trivially_copy_pass_by_ref)]
5+
#![allow(clippy::needless_pass_by_ref_mut)]
56

67
#[derive(Copy, Clone)]
78
struct Foo(u8);

tests/ui-toml/toml_trivially_copy/test.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
2-
--> $DIR/test.rs:14:11
2+
--> $DIR/test.rs:15:11
33
|
44
LL | fn bad(x: &u16, y: &Foo) {}
55
| ^^^^ help: consider passing by value instead: `u16`
66
|
77
= note: `-D clippy::trivially-copy-pass-by-ref` implied by `-D warnings`
88

99
error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
10-
--> $DIR/test.rs:14:20
10+
--> $DIR/test.rs:15:20
1111
|
1212
LL | fn bad(x: &u16, y: &Foo) {}
1313
| ^^^^ help: consider passing by value instead: `Foo`

0 commit comments

Comments
 (0)