Skip to content

Commit ac10c03

Browse files
Add needless_pass_by_ref lint
1 parent 8a1f0cd commit ac10c03

File tree

5 files changed

+221
-1
lines changed

5 files changed

+221
-1
lines changed

CHANGELOG.md

+1
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

+1
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

+2
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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
use super::needless_pass_by_value::requires_exact_signature;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
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_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<'tcx>(
48+
cx: &LateContext<'tcx>,
49+
input: rustc_hir::Ty<'tcx>,
50+
ty: Ty<'_>,
51+
arg: &rustc_hir::Param<'_>,
52+
) -> bool {
53+
if is_self(arg) {
54+
return true;
55+
}
56+
57+
// We check if this a `&mut`. `ref_mutability` returns `None` if it's not a reference.
58+
if !matches!(ty.ref_mutability(), Some(Mutability::Mut)) {
59+
return true;
60+
}
61+
62+
// All spans generated from a proc-macro invocation are the same...
63+
is_from_proc_macro(cx, &input)
64+
}
65+
66+
impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
67+
fn check_fn(
68+
&mut self,
69+
cx: &LateContext<'tcx>,
70+
kind: FnKind<'tcx>,
71+
decl: &'tcx FnDecl<'_>,
72+
body: &'tcx Body<'_>,
73+
span: Span,
74+
fn_def_id: LocalDefId,
75+
) {
76+
if span.from_expansion() {
77+
return;
78+
}
79+
80+
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id);
81+
82+
match kind {
83+
FnKind::ItemFn(.., header) => {
84+
let attrs = cx.tcx.hir().attrs(hir_id);
85+
if header.abi != Abi::Rust || requires_exact_signature(attrs) {
86+
return;
87+
}
88+
},
89+
FnKind::Method(..) => (),
90+
FnKind::Closure => return,
91+
}
92+
93+
// Exclude non-inherent impls
94+
if let Some(Node::Item(item)) = cx.tcx.hir().find_parent(hir_id) {
95+
if matches!(
96+
item.kind,
97+
ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
98+
) {
99+
return;
100+
}
101+
}
102+
103+
let fn_sig = cx.tcx.fn_sig(fn_def_id).subst_identity();
104+
let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig);
105+
106+
// If there are no `&mut` argument, no need to go any further.
107+
if !decl
108+
.inputs
109+
.iter()
110+
.zip(fn_sig.inputs())
111+
.zip(body.params)
112+
.any(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg))
113+
{
114+
return;
115+
}
116+
117+
// Collect variables mutably used and spans which will need dereferencings from the
118+
// function body.
119+
let MutablyUsedVariablesCtxt { mutably_used_vars, .. } = {
120+
let mut ctx = MutablyUsedVariablesCtxt::default();
121+
let infcx = cx.tcx.infer_ctxt().build();
122+
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
123+
ctx
124+
};
125+
126+
for ((&input, &ty), arg) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params) {
127+
if should_skip(cx, input, ty, arg) {
128+
continue;
129+
}
130+
131+
// Only take `&mut` arguments.
132+
if_chain! {
133+
if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind;
134+
if !mutably_used_vars.contains(&canonical_id);
135+
if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind;
136+
then {
137+
// If the argument is never used mutably, we emit the error.
138+
span_lint_and_sugg(
139+
cx,
140+
NEEDLESS_PASS_BY_REF_MUT,
141+
input.span,
142+
"this argument is a mutable reference, but not used mutably",
143+
"consider changing to",
144+
format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_")),
145+
Applicability::Unspecified,
146+
);
147+
}
148+
}
149+
}
150+
}
151+
}
152+
153+
#[derive(Default)]
154+
struct MutablyUsedVariablesCtxt {
155+
mutably_used_vars: HirIdSet,
156+
prev_bind: Option<HirId>,
157+
aliases: HirIdMap<HirId>,
158+
}
159+
160+
impl MutablyUsedVariablesCtxt {
161+
fn add_mutably_used_var(&mut self, mut used_id: HirId) {
162+
while let Some(id) = self.aliases.get(&used_id) {
163+
self.mutably_used_vars.insert(used_id);
164+
used_id = *id;
165+
}
166+
self.mutably_used_vars.insert(used_id);
167+
}
168+
}
169+
170+
impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
171+
fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
172+
// eprintln!("CONSUME>> {_id:?} {cmt:?}", );
173+
if let Some(bind_id) = self.prev_bind.take() {
174+
if let euv::Place {
175+
base: euv::PlaceBase::Local(vid),
176+
..
177+
} = &cmt.place
178+
{
179+
self.aliases.insert(bind_id, *vid);
180+
}
181+
}
182+
}
183+
184+
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId, borrow: ty::BorrowKind) {
185+
self.prev_bind = None;
186+
if borrow == ty::BorrowKind::MutBorrow {
187+
if let euv::Place {
188+
base: euv::PlaceBase::Local(vid),
189+
..
190+
} = &cmt.place
191+
{
192+
self.add_mutably_used_var(*vid);
193+
}
194+
}
195+
}
196+
197+
fn mutate(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
198+
if let euv::Place {
199+
projections,
200+
base: euv::PlaceBase::Local(vid),
201+
..
202+
} = &cmt.place
203+
{
204+
self.prev_bind = None;
205+
if !projections.is_empty() {
206+
self.add_mutably_used_var(*vid);
207+
}
208+
}
209+
}
210+
211+
fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
212+
213+
fn bind(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
214+
self.prev_bind = Some(id);
215+
}
216+
}

clippy_lints/src/needless_pass_by_value.rs

+1-1
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)