Skip to content

Commit c0b9c74

Browse files
Add needless_pass_by_ref lint
1 parent 8a1f0cd commit c0b9c74

File tree

5 files changed

+194
-1
lines changed

5 files changed

+194
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5017,6 +5017,7 @@ Released 2018-09-13
50175017
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
50185018
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
50195019
[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals
5020+
[`needless_pass_by_ref_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_ref_mut
50205021
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
50215022
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
50225023
[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
465465
crate::needless_if::NEEDLESS_IF_INFO,
466466
crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
467467
crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
468+
crate::needless_pass_by_ref_mut::NEEDLESS_PASS_BY_REF_MUT_INFO,
468469
crate::needless_pass_by_value::NEEDLESS_PASS_BY_VALUE_INFO,
469470
crate::needless_question_mark::NEEDLESS_QUESTION_MARK_INFO,
470471
crate::needless_update::NEEDLESS_UPDATE_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ mod needless_for_each;
228228
mod needless_if;
229229
mod needless_late_init;
230230
mod needless_parens_on_range_literals;
231+
mod needless_pass_by_ref_mut;
231232
mod needless_pass_by_value;
232233
mod needless_question_mark;
233234
mod needless_update;
@@ -1045,6 +1046,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10451046
});
10461047
let stack_size_threshold = conf.stack_size_threshold;
10471048
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
1049+
store.register_late_pass(|_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut));
10481050
// add lints here, do not remove this comment, it's used in `new_lint`
10491051
}
10501052

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
use super::needless_pass_by_value::requires_exact_signature;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::is_self;
4+
use clippy_utils::source::snippet;
5+
use if_chain::if_chain;
6+
use rustc_errors::{Applicability, Diagnostic};
7+
use rustc_hir::intravisit::FnKind;
8+
use rustc_hir::HirIdSet;
9+
use rustc_hir::{Body, FnDecl, HirId, Impl, ItemKind, Mutability, Node, PatKind};
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_lint_pass, declare_tool_lint};
16+
use rustc_span::def_id::LocalDefId;
17+
use rustc_span::Span;
18+
use rustc_target::spec::abi::Abi;
19+
20+
declare_clippy_lint! {
21+
/// ### What it does
22+
/// Check if a `&mut` function argument is actually used mutably.
23+
///
24+
/// ### Why is this bad?
25+
/// Less `mut` means less fights with the borrow checker. It can also lead to more
26+
/// opportunities for parallelization.
27+
///
28+
/// ### Example
29+
/// ```rust
30+
/// fn foo(&mut self, y: &mut i32) -> i32 {
31+
/// self.x + *y
32+
/// }
33+
/// ```
34+
/// Use instead:
35+
/// ```rust
36+
/// fn foo(&self, y: &i32) -> i32 {
37+
/// self.x + *y
38+
/// }
39+
/// ```
40+
#[clippy::version = "1.72.0"]
41+
pub NEEDLESS_PASS_BY_REF_MUT,
42+
suspicious,
43+
"default lint description"
44+
}
45+
declare_lint_pass!(NeedlessPassByRefMut => [NEEDLESS_PASS_BY_REF_MUT]);
46+
47+
fn should_skip(span: Span, input: rustc_hir::Ty<'_>, ty: Ty<'_>, arg: &rustc_hir::Param<'_>) -> bool {
48+
// All spans generated from a proc-macro invocation are the same...
49+
if span == input.span {
50+
return true;
51+
}
52+
53+
if is_self(arg) {
54+
return true;
55+
}
56+
57+
!matches!(ty.ref_mutability(), Some(Mutability::Mut))
58+
}
59+
60+
impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
61+
fn check_fn(
62+
&mut self,
63+
cx: &LateContext<'tcx>,
64+
kind: FnKind<'tcx>,
65+
decl: &'tcx FnDecl<'_>,
66+
body: &'tcx Body<'_>,
67+
span: Span,
68+
fn_def_id: LocalDefId,
69+
) {
70+
if span.from_expansion() {
71+
return;
72+
}
73+
74+
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id);
75+
76+
match kind {
77+
FnKind::ItemFn(.., header) => {
78+
let attrs = cx.tcx.hir().attrs(hir_id);
79+
if header.abi != Abi::Rust || requires_exact_signature(attrs) {
80+
return;
81+
}
82+
},
83+
FnKind::Method(..) => (),
84+
FnKind::Closure => return,
85+
}
86+
87+
// Exclude non-inherent impls
88+
if let Some(Node::Item(item)) = cx.tcx.hir().find_parent(hir_id) {
89+
if matches!(
90+
item.kind,
91+
ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
92+
) {
93+
return;
94+
}
95+
}
96+
97+
let fn_sig = cx.tcx.fn_sig(fn_def_id).subst_identity();
98+
let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig);
99+
100+
// If there are no `&mut` argument, no need to go any further.
101+
if !decl
102+
.inputs
103+
.iter()
104+
.zip(fn_sig.inputs())
105+
.zip(body.params)
106+
.any(|((&input, &ty), arg)| !should_skip(span, input, ty, arg))
107+
{
108+
return;
109+
}
110+
111+
// Collect variables mutably used and spans which will need dereferencings from the
112+
// function body.
113+
let MutablyUsedVariablesCtxt { mutably_used_vars, .. } = {
114+
let mut ctx = MutablyUsedVariablesCtxt::default();
115+
let infcx = cx.tcx.infer_ctxt().build();
116+
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
117+
ctx
118+
};
119+
120+
for ((&input, &ty), arg) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params) {
121+
if should_skip(span, input, ty, arg) {
122+
continue;
123+
}
124+
125+
// Only take `&mut` arguments.
126+
if_chain! {
127+
if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind;
128+
if !mutably_used_vars.contains(&canonical_id);
129+
if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind;
130+
then {
131+
// If the argument is never used mutably, we emit the error.
132+
span_lint_and_then(
133+
cx,
134+
NEEDLESS_PASS_BY_REF_MUT,
135+
input.span,
136+
"this argument is a mutable reference, but not used mutably",
137+
|diag: &mut Diagnostic| {
138+
diag.span_suggestion(
139+
input.span,
140+
format!(
141+
"consider changing to &{}",
142+
snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),
143+
),
144+
"",
145+
Applicability::Unspecified,
146+
);
147+
},
148+
);
149+
}
150+
}
151+
}
152+
}
153+
}
154+
155+
#[derive(Default)]
156+
struct MutablyUsedVariablesCtxt {
157+
mutably_used_vars: HirIdSet,
158+
}
159+
160+
impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
161+
fn consume(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {}
162+
163+
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId, borrow: ty::BorrowKind) {
164+
if borrow == ty::BorrowKind::MutBorrow {
165+
if let euv::Place {
166+
base: euv::PlaceBase::Local(vid),
167+
..
168+
} = &cmt.place
169+
{
170+
self.mutably_used_vars.insert(*vid);
171+
}
172+
}
173+
}
174+
175+
fn mutate(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
176+
if let euv::Place {
177+
projections,
178+
base: euv::PlaceBase::Local(vid),
179+
..
180+
} = &cmt.place
181+
{
182+
if !projections.is_empty() {
183+
self.mutably_used_vars.insert(*vid);
184+
}
185+
}
186+
}
187+
188+
fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
189+
}

clippy_lints/src/needless_pass_by_value.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
309309
}
310310

311311
/// Functions marked with these attributes must have the exact signature.
312-
fn requires_exact_signature(attrs: &[Attribute]) -> bool {
312+
pub(crate) fn requires_exact_signature(attrs: &[Attribute]) -> bool {
313313
attrs.iter().any(|attr| {
314314
[sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
315315
.iter()

0 commit comments

Comments
 (0)