-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add lint ref_mut_iter_method_chain
#7688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6b48c3a
6aa878c
c959d5e
453c07b
31dc4ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,40 @@ | ||||||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||||||
use clippy_utils::source::{snippet_expr, ExprPosition}; | ||||||
use clippy_utils::ty::implements_trait; | ||||||
use if_chain::if_chain; | ||||||
use rustc_errors::Applicability; | ||||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, UnOp}; | ||||||
use rustc_lint::LateContext; | ||||||
use rustc_span::sym; | ||||||
|
||||||
use super::REF_MUT_ITER_METHOD_CHAIN; | ||||||
|
||||||
pub(crate) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>) { | ||||||
if_chain! { | ||||||
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, base_expr) = self_arg.kind; | ||||||
if !self_arg.span.from_expansion(); | ||||||
if let Some(&iter_trait) = cx.tcx.all_diagnostic_items(()).name_to_id.get(&sym::Iterator); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
should work here. |
||||||
if implements_trait(cx, cx.typeck_results().expr_ty(base_expr).peel_refs(), iter_trait, &[]); | ||||||
then { | ||||||
let snip_expr = match base_expr.kind { | ||||||
ExprKind::Unary(UnOp::Deref, e) | ||||||
if cx.typeck_results().expr_ty(e).is_ref() && !base_expr.span.from_expansion() | ||||||
=> e, | ||||||
_ => base_expr, | ||||||
}; | ||||||
let mut app = Applicability::MachineApplicable; | ||||||
span_lint_and_sugg( | ||||||
cx, | ||||||
REF_MUT_ITER_METHOD_CHAIN, | ||||||
self_arg.span, | ||||||
"use of `&mut` on an iterator in a method chain", | ||||||
"try", | ||||||
format!( | ||||||
"{}.by_ref()", | ||||||
snippet_expr(cx, snip_expr, ExprPosition::Postfix, self_arg.span.ctxt(), &mut app), | ||||||
), | ||||||
app, | ||||||
); | ||||||
} | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ | |
|
||
use crate::line_span; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Expr, ExprKind}; | ||
use rustc_hir::{BinOpKind, Expr, ExprKind}; | ||
use rustc_lint::{LateContext, LintContext}; | ||
use rustc_span::hygiene; | ||
use rustc_span::{BytePos, Pos, Span, SyntaxContext}; | ||
|
@@ -306,6 +306,147 @@ pub fn snippet_with_context( | |
) | ||
} | ||
|
||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] | ||
pub enum ExprPosition { | ||
// Also includes `return`, `yield`, `break` and closures | ||
Paren, | ||
AssignmentRhs, | ||
AssignmentLhs, | ||
RangeLhs, | ||
RangeRhs, | ||
OrLhs, | ||
OrRhs, | ||
AndLhs, | ||
AndRhs, | ||
Let, | ||
EqLhs, | ||
EqRhs, | ||
BitOrLhs, | ||
BitOrRhs, | ||
BitXorLhs, | ||
BitXorRhs, | ||
BitAndLhs, | ||
BitAndRhs, | ||
ShiftLhs, | ||
ShiftRhs, | ||
AddLhs, | ||
AddRhs, | ||
MulLhs, | ||
MulRhs, | ||
// Also includes type ascription | ||
Cast, | ||
Prefix, | ||
Postfix, | ||
} | ||
|
||
/// Extracts a snippet of the given expression taking into account the `SyntaxContext` the snippet | ||
/// needs to be taken from. Parenthesis will be added if needed to place the snippet in the target | ||
/// precedence level. Returns a placeholder (`(..)`) if a snippet can't be extracted (e.g. an | ||
/// invalid span). | ||
/// | ||
/// The `SyntaxContext` of the expression will be walked up to the given target context (usually | ||
/// from the parent expression) before extracting a snippet. This allows getting the call to a macro | ||
/// rather than the expression from expanding the macro. e.g. In the expression `&vec![]` taking a | ||
/// snippet of the chile of the borrow expression will get a snippet of what `vec![]` expands in to. | ||
/// With the target context set to the same as the borrow expression, this will get a snippet of the | ||
/// call to the macro. | ||
/// | ||
/// The applicability will be modified in two ways: | ||
/// * If a snippet can't be extracted it will be changed from `MachineApplicable` or | ||
/// `MaybeIncorrect` to `HasPlaceholders`. | ||
/// * If the snippet is taken from a macro expansion then it will be changed from | ||
/// `MachineApplicable` to `MaybeIncorrect`. | ||
pub fn snippet_expr( | ||
cx: &LateContext<'_>, | ||
expr: &Expr<'_>, | ||
target_position: ExprPosition, | ||
ctxt: SyntaxContext, | ||
app: &mut Applicability, | ||
) -> String { | ||
let (snip, is_mac_call) = snippet_with_context(cx, expr.span, ctxt, "(..)", app); | ||
|
||
match snip { | ||
Cow::Borrowed(snip) => snip.to_owned(), | ||
Cow::Owned(snip) if is_mac_call => snip, | ||
Cow::Owned(mut snip) => { | ||
let ctxt = expr.span.ctxt(); | ||
|
||
// Attempt to determine if parenthesis are needed base on the target position. The snippet may have | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this the same as what Would using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding to the left or right of an operator needs to do different things. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, I prefer reusing Especially when you're working on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Working on #7986 which should be a nicer solution. We can hold off merging this until that's done. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, let's do this. 👍 |
||
// parenthesis already, so attempt to find those. | ||
// TODO: Remove parenthesis if they aren't needed at the target position. | ||
let needs_paren = match expr.peel_drop_temps().kind { | ||
ExprKind::Binary(_, lhs, rhs) | ||
if (ctxt == lhs.span.ctxt() && expr.span.lo() != lhs.span.lo()) | ||
|| (ctxt == rhs.span.ctxt() && expr.span.hi() != rhs.span.hi()) => | ||
{ | ||
false | ||
}, | ||
ExprKind::Binary(op, ..) => match op.node { | ||
BinOpKind::Add | BinOpKind::Sub => target_position > ExprPosition::AddLhs, | ||
BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem => target_position > ExprPosition::MulLhs, | ||
BinOpKind::And => target_position > ExprPosition::AndLhs, | ||
BinOpKind::Or => target_position > ExprPosition::OrLhs, | ||
BinOpKind::BitXor => target_position > ExprPosition::BitXorLhs, | ||
BinOpKind::BitAnd => target_position > ExprPosition::BitAndLhs, | ||
BinOpKind::BitOr => target_position > ExprPosition::BitOrLhs, | ||
BinOpKind::Shl | BinOpKind::Shr => target_position > ExprPosition::ShiftLhs, | ||
BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Gt | BinOpKind::Ge => { | ||
target_position > ExprPosition::EqLhs | ||
}, | ||
}, | ||
ExprKind::Box(..) | ExprKind::Unary(..) | ExprKind::AddrOf(..) if snip.starts_with('(') => false, | ||
ExprKind::Box(..) | ExprKind::Unary(..) | ExprKind::AddrOf(..) => { | ||
target_position > ExprPosition::Prefix | ||
}, | ||
ExprKind::Let(..) if snip.starts_with('(') => false, | ||
ExprKind::Let(..) => target_position > ExprPosition::Let, | ||
ExprKind::Cast(lhs, rhs) | ||
if (ctxt == lhs.span.ctxt() && expr.span.lo() != lhs.span.lo()) | ||
|| (ctxt == rhs.span.ctxt() && expr.span.hi() != rhs.span.hi()) => | ||
{ | ||
false | ||
}, | ||
ExprKind::Cast(..) | ExprKind::Type(..) => target_position > ExprPosition::Cast, | ||
|
||
ExprKind::Closure(..) | ||
| ExprKind::Break(..) | ||
| ExprKind::Ret(..) | ||
| ExprKind::Yield(..) | ||
| ExprKind::Assign(..) | ||
| ExprKind::AssignOp(..) => target_position > ExprPosition::AssignmentRhs, | ||
|
||
// Postfix operators, or expression with braces of some form | ||
ExprKind::Array(_) | ||
| ExprKind::Call(..) | ||
| ExprKind::ConstBlock(_) | ||
| ExprKind::MethodCall(..) | ||
| ExprKind::Tup(..) | ||
| ExprKind::Lit(..) | ||
| ExprKind::DropTemps(_) | ||
| ExprKind::If(..) | ||
| ExprKind::Loop(..) | ||
| ExprKind::Match(..) | ||
| ExprKind::Block(..) | ||
| ExprKind::Field(..) | ||
| ExprKind::Index(..) | ||
| ExprKind::Path(_) | ||
| ExprKind::Continue(_) | ||
| ExprKind::InlineAsm(_) | ||
| ExprKind::LlvmInlineAsm(_) | ||
| ExprKind::Struct(..) | ||
| ExprKind::Repeat(..) | ||
| ExprKind::Err => false, | ||
}; | ||
|
||
if needs_paren { | ||
snip.insert(0, '('); | ||
snip.push(')'); | ||
} | ||
snip | ||
}, | ||
} | ||
} | ||
|
||
/// Walks the span up to the target context, thereby returning the macro call site if the span is | ||
/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the | ||
/// case of the span being in a macro expansion, but the target context is from expanding a macro | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// run-rustfix | ||
#![warn(clippy::ref_mut_iter_method_chain)] | ||
|
||
macro_rules! m { | ||
($i:ident) => { | ||
$i | ||
}; | ||
(&mut $i:ident) => { | ||
&mut $i | ||
}; | ||
(($i:expr).$m:ident($arg:expr)) => { | ||
($i).$m($arg) | ||
}; | ||
} | ||
|
||
fn main() { | ||
let mut iter = [0, 1, 2].iter(); | ||
let _ = iter.by_ref().find(|&&x| x == 1); | ||
let _ = m!(iter).by_ref().find(|&&x| x == 1); | ||
|
||
// Don't lint. `&mut` comes from macro expansion. | ||
let _ = m!(&mut iter).find(|&&x| x == 1); | ||
|
||
// Don't lint. Method call from expansion | ||
let _ = m!((&mut iter).find(|&&x| x == 1)); | ||
|
||
// Don't lint. No method chain. | ||
for &x in &mut iter { | ||
print!("{}", x); | ||
} | ||
|
||
for &x in iter.by_ref().filter(|&&x| x % 2 == 0) { | ||
print!("{}", x); | ||
} | ||
|
||
let iter = &mut iter; | ||
iter.by_ref().find(|&&x| x == 1); | ||
let mut iter = iter; | ||
let iter = &mut iter; | ||
(*iter).by_ref().find(|&&x| x == 1); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// run-rustfix | ||
#![warn(clippy::ref_mut_iter_method_chain)] | ||
|
||
macro_rules! m { | ||
($i:ident) => { | ||
$i | ||
}; | ||
(&mut $i:ident) => { | ||
&mut $i | ||
}; | ||
(($i:expr).$m:ident($arg:expr)) => { | ||
($i).$m($arg) | ||
}; | ||
} | ||
|
||
fn main() { | ||
let mut iter = [0, 1, 2].iter(); | ||
let _ = (&mut iter).find(|&&x| x == 1); | ||
let _ = (&mut m!(iter)).find(|&&x| x == 1); | ||
|
||
// Don't lint. `&mut` comes from macro expansion. | ||
let _ = m!(&mut iter).find(|&&x| x == 1); | ||
|
||
// Don't lint. Method call from expansion | ||
let _ = m!((&mut iter).find(|&&x| x == 1)); | ||
|
||
// Don't lint. No method chain. | ||
for &x in &mut iter { | ||
Jarcho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
print!("{}", x); | ||
} | ||
|
||
for &x in (&mut iter).filter(|&&x| x % 2 == 0) { | ||
print!("{}", x); | ||
} | ||
|
||
let iter = &mut iter; | ||
(&mut *iter).find(|&&x| x == 1); | ||
let mut iter = iter; | ||
let iter = &mut iter; | ||
(&mut **iter).find(|&&x| x == 1); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.