Skip to content

Commit 6b48c3a

Browse files
committed
Add lint ref_mut_iter_method_chain
1 parent 8b84a76 commit 6b48c3a

9 files changed

+163
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3107,6 +3107,7 @@ Released 2018-09-13
31073107
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
31083108
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
31093109
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
3110+
[`ref_mut_iter_method_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_mut_iter_method_chain
31103111
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
31113112
[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
31123113
[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
165165
LintId::of(methods::OPTION_FILTER_MAP),
166166
LintId::of(methods::OPTION_MAP_OR_NONE),
167167
LintId::of(methods::OR_FUN_CALL),
168+
LintId::of(methods::REF_MUT_ITER_METHOD_CHAIN),
168169
LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
169170
LintId::of(methods::SEARCH_IS_SOME),
170171
LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ store.register_lints(&[
299299
methods::OPTION_FILTER_MAP,
300300
methods::OPTION_MAP_OR_NONE,
301301
methods::OR_FUN_CALL,
302+
methods::REF_MUT_ITER_METHOD_CHAIN,
302303
methods::RESULT_MAP_OR_INTO_OPTION,
303304
methods::SEARCH_IS_SOME,
304305
methods::SHOULD_IMPLEMENT_TRAIT,

clippy_lints/src/lib.register_style.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
6464
LintId::of(methods::NEW_RET_NO_SELF),
6565
LintId::of(methods::OK_EXPECT),
6666
LintId::of(methods::OPTION_MAP_OR_NONE),
67+
LintId::of(methods::REF_MUT_ITER_METHOD_CHAIN),
6768
LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
6869
LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
6970
LintId::of(methods::SINGLE_CHAR_ADD_STR),

clippy_lints/src/methods/mod.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mod option_as_ref_deref;
4444
mod option_map_or_none;
4545
mod option_map_unwrap_or;
4646
mod or_fun_call;
47+
mod ref_mut_iter_method_chain;
4748
mod search_is_some;
4849
mod single_char_add_str;
4950
mod single_char_insert_string;
@@ -1861,6 +1862,30 @@ declare_clippy_lint! {
18611862
"replace `.splitn(2, pat)` with `.split_once(pat)`"
18621863
}
18631864

1865+
declare_clippy_lint! {
1866+
/// ### What it does
1867+
/// Check for `&mut iter` followed by a method call.
1868+
///
1869+
/// ### Why is this bad?
1870+
/// This requires using parenthesis to signify precedence.
1871+
///
1872+
/// ### Example
1873+
/// ```rust
1874+
/// let mut iter = ['a', 'b', '.', 'd'].iter();
1875+
/// let before_dot = (&mut iter).take_while(|&&c| c != '.').collect::<Vec<_>>();
1876+
/// let after_dot = iter.collect::<Vec<_>>();
1877+
/// ```
1878+
/// Use instead:
1879+
/// ```rust
1880+
/// let mut iter = ['a', 'b', '.', 'd'].iter();
1881+
/// let before_dot = iter.by_ref().take_while(|&&c| c != '.').collect::<Vec<_>>();
1882+
/// let after_dot = iter.collect::<Vec<_>>();
1883+
/// ```
1884+
pub REF_MUT_ITER_METHOD_CHAIN,
1885+
style,
1886+
"`&mut iter` used in a method chain"
1887+
}
1888+
18641889
pub struct Methods {
18651890
avoid_breaking_exported_api: bool,
18661891
msrv: Option<RustcVersion>,
@@ -1939,7 +1964,8 @@ impl_lint_pass!(Methods => [
19391964
SUSPICIOUS_SPLITN,
19401965
MANUAL_STR_REPEAT,
19411966
EXTEND_WITH_DRAIN,
1942-
MANUAL_SPLIT_ONCE
1967+
MANUAL_SPLIT_ONCE,
1968+
REF_MUT_ITER_METHOD_CHAIN
19431969
]);
19441970

19451971
/// Extracts a method call name, args, and `Span` of the method name.
@@ -1973,7 +1999,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
19731999
hir::ExprKind::Call(func, args) => {
19742000
from_iter_instead_of_collect::check(cx, expr, args, func);
19752001
},
1976-
hir::ExprKind::MethodCall(method_call, ref method_span, args, _) => {
2002+
hir::ExprKind::MethodCall(method_call, ref method_span, args @ [self_arg, ..], _) => {
19772003
or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
19782004
expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
19792005
clone_on_copy::check(cx, expr, method_call.ident.name, args);
@@ -1982,6 +2008,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
19822008
single_char_add_str::check(cx, expr, args);
19832009
into_iter_on_ref::check(cx, expr, *method_span, method_call.ident.name, args);
19842010
single_char_pattern::check(cx, expr, method_call.ident.name, args);
2011+
ref_mut_iter_method_chain::check(cx, self_arg);
19852012
},
19862013
hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
19872014
let mut info = BinaryExprInfo {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::in_macro;
3+
use clippy_utils::source::snippet_with_context;
4+
use clippy_utils::ty::implements_trait;
5+
use if_chain::if_chain;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, UnOp};
8+
use rustc_lint::LateContext;
9+
use rustc_span::sym;
10+
11+
use super::REF_MUT_ITER_METHOD_CHAIN;
12+
13+
pub(crate) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>) {
14+
if_chain! {
15+
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, base_expr) = self_arg.kind;
16+
if !in_macro(self_arg.span);
17+
if let Some(&iter_trait) = cx.tcx.all_diagnostic_items(()).get(&sym::Iterator);
18+
if implements_trait(cx, cx.typeck_results().expr_ty(base_expr).peel_refs(), iter_trait, &[]);
19+
then {
20+
let snip_span = match base_expr.kind {
21+
ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() && !in_macro(base_expr.span)
22+
=> e.span,
23+
_ => base_expr.span,
24+
};
25+
let mut app = Applicability::MachineApplicable;
26+
span_lint_and_sugg(
27+
cx,
28+
REF_MUT_ITER_METHOD_CHAIN,
29+
self_arg.span,
30+
"use of `&mut` on an iterator in a method chain",
31+
"try",
32+
format!(
33+
"{}.by_ref()",
34+
snippet_with_context(cx, snip_span, self_arg.span.ctxt(), "..", &mut app).0,
35+
),
36+
app,
37+
);
38+
}
39+
}
40+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// run-rustfix
2+
#![warn(clippy::ref_mut_iter_method_chain)]
3+
4+
macro_rules! m {
5+
($i:ident) => {
6+
$i
7+
};
8+
(&mut $i:ident) => {
9+
&mut $i
10+
};
11+
(($i:expr).$m:ident($arg:expr)) => {
12+
($i).$m($arg)
13+
};
14+
}
15+
16+
fn main() {
17+
let mut iter = [0, 1, 2].iter();
18+
let _ = iter.by_ref().find(|&&x| x == 1);
19+
let _ = m!(iter).by_ref().find(|&&x| x == 1);
20+
21+
// Don't lint. `&mut` comes from macro expansion.
22+
let _ = m!(&mut iter).find(|&&x| x == 1);
23+
24+
// Don't lint. Method call from expansion
25+
let _ = m!((&mut iter).find(|&&x| x == 1));
26+
27+
// Don't lint. No method chain.
28+
for &x in &mut iter {
29+
print!("{}", x)
30+
}
31+
32+
let iter = &mut iter;
33+
iter.by_ref().find(|&&x| x == 1);
34+
}

tests/ui/ref_mut_iter_method_chain.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// run-rustfix
2+
#![warn(clippy::ref_mut_iter_method_chain)]
3+
4+
macro_rules! m {
5+
($i:ident) => {
6+
$i
7+
};
8+
(&mut $i:ident) => {
9+
&mut $i
10+
};
11+
(($i:expr).$m:ident($arg:expr)) => {
12+
($i).$m($arg)
13+
};
14+
}
15+
16+
fn main() {
17+
let mut iter = [0, 1, 2].iter();
18+
let _ = (&mut iter).find(|&&x| x == 1);
19+
let _ = (&mut m!(iter)).find(|&&x| x == 1);
20+
21+
// Don't lint. `&mut` comes from macro expansion.
22+
let _ = m!(&mut iter).find(|&&x| x == 1);
23+
24+
// Don't lint. Method call from expansion
25+
let _ = m!((&mut iter).find(|&&x| x == 1));
26+
27+
// Don't lint. No method chain.
28+
for &x in &mut iter {
29+
print!("{}", x)
30+
}
31+
32+
let iter = &mut iter;
33+
(&mut *iter).find(|&&x| x == 1);
34+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: use of `&mut` on an iterator in a method chain
2+
--> $DIR/ref_mut_iter_method_chain.rs:18:13
3+
|
4+
LL | let _ = (&mut iter).find(|&&x| x == 1);
5+
| ^^^^^^^^^^^ help: try: `iter.by_ref()`
6+
|
7+
= note: `-D clippy::ref-mut-iter-method-chain` implied by `-D warnings`
8+
9+
error: use of `&mut` on an iterator in a method chain
10+
--> $DIR/ref_mut_iter_method_chain.rs:19:13
11+
|
12+
LL | let _ = (&mut m!(iter)).find(|&&x| x == 1);
13+
| ^^^^^^^^^^^^^^^ help: try: `m!(iter).by_ref()`
14+
15+
error: use of `&mut` on an iterator in a method chain
16+
--> $DIR/ref_mut_iter_method_chain.rs:33:5
17+
|
18+
LL | (&mut *iter).find(|&&x| x == 1);
19+
| ^^^^^^^^^^^^ help: try: `iter.by_ref()`
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)