Skip to content

Commit 0813a12

Browse files
committed
Add lint ref_mut_iter_method_chain
1 parent 59cd777 commit 0813a12

7 files changed

+163
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2895,6 +2895,7 @@ Released 2018-09-13
28952895
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
28962896
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
28972897
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
2898+
[`ref_mut_iter_method_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_mut_iter_method_chain
28982899
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
28992900
[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
29002901
[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
792792
methods::OPTION_FILTER_MAP,
793793
methods::OPTION_MAP_OR_NONE,
794794
methods::OR_FUN_CALL,
795+
methods::REF_MUT_ITER_METHOD_CHAIN,
795796
methods::RESULT_MAP_OR_INTO_OPTION,
796797
methods::SEARCH_IS_SOME,
797798
methods::SHOULD_IMPLEMENT_TRAIT,
@@ -1345,6 +1346,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13451346
LintId::of(methods::OPTION_FILTER_MAP),
13461347
LintId::of(methods::OPTION_MAP_OR_NONE),
13471348
LintId::of(methods::OR_FUN_CALL),
1349+
LintId::of(methods::REF_MUT_ITER_METHOD_CHAIN),
13481350
LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
13491351
LintId::of(methods::SEARCH_IS_SOME),
13501352
LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
@@ -1547,6 +1549,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15471549
LintId::of(methods::NEW_RET_NO_SELF),
15481550
LintId::of(methods::OK_EXPECT),
15491551
LintId::of(methods::OPTION_MAP_OR_NONE),
1552+
LintId::of(methods::REF_MUT_ITER_METHOD_CHAIN),
15501553
LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
15511554
LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
15521555
LintId::of(methods::SINGLE_CHAR_ADD_STR),

clippy_lints/src/methods/mod.rs

+29-2
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;
@@ -1796,6 +1797,30 @@ declare_clippy_lint! {
17961797
"replace `.splitn(2, pat)` with `.split_once(pat)`"
17971798
}
17981799

1800+
declare_clippy_lint! {
1801+
/// ### What it does
1802+
/// Check for `&mut iter` followed by a method call.
1803+
///
1804+
/// ### Why is this bad?
1805+
/// This requires using parenthesis to signify precedence.
1806+
///
1807+
/// ### Example
1808+
/// ```rust
1809+
/// let mut iter = ['a', 'b', '.', 'd'].iter();
1810+
/// let before_dot = (&mut iter).take_while(|&&c| c != '.').collect::<Vec<_>>();
1811+
/// let after_dot = iter.collect::<Vec<_>>();
1812+
/// ```
1813+
/// Use instead:
1814+
/// ```rust
1815+
/// let mut iter = ['a', 'b', '.', 'd'].iter();
1816+
/// let before_dot = iter.by_ref().take_while(|&&c| c != '.').collect::<Vec<_>>();
1817+
/// let after_dot = iter.collect::<Vec<_>>();
1818+
/// ```
1819+
pub REF_MUT_ITER_METHOD_CHAIN,
1820+
style,
1821+
"`&mut iter` used in a method chain"
1822+
}
1823+
17991824
pub struct Methods {
18001825
avoid_breaking_exported_api: bool,
18011826
msrv: Option<RustcVersion>,
@@ -1874,7 +1899,8 @@ impl_lint_pass!(Methods => [
18741899
SUSPICIOUS_SPLITN,
18751900
MANUAL_STR_REPEAT,
18761901
EXTEND_WITH_DRAIN,
1877-
MANUAL_SPLIT_ONCE
1902+
MANUAL_SPLIT_ONCE,
1903+
REF_MUT_ITER_METHOD_CHAIN
18781904
]);
18791905

18801906
/// Extracts a method call name, args, and `Span` of the method name.
@@ -1908,7 +1934,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
19081934
hir::ExprKind::Call(func, args) => {
19091935
from_iter_instead_of_collect::check(cx, expr, args, func);
19101936
},
1911-
hir::ExprKind::MethodCall(method_call, ref method_span, args, _) => {
1937+
hir::ExprKind::MethodCall(method_call, ref method_span, args @ [self_arg, ..], _) => {
19121938
or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
19131939
expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
19141940
clone_on_copy::check(cx, expr, method_call.ident.name, args);
@@ -1917,6 +1943,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
19171943
single_char_add_str::check(cx, expr, args);
19181944
into_iter_on_ref::check(cx, expr, *method_span, method_call.ident.name, args);
19191945
single_char_pattern::check(cx, expr, method_call.ident.name, args);
1946+
ref_mut_iter_method_chain::check(cx, self_arg);
19201947
},
19211948
hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
19221949
let mut info = BinaryExprInfo {
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+
}
+34
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

+34
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+
}
+22
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)