Skip to content

Commit 4705c9b

Browse files
committed
Add parenthesis when needed for suggestion in ref_mut_iter_method_chain
1 parent 6b48c3a commit 4705c9b

File tree

5 files changed

+105
-11
lines changed

5 files changed

+105
-11
lines changed

clippy_lints/src/methods/ref_mut_iter_method_chain.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::in_macro;
3-
use clippy_utils::source::snippet_with_context;
2+
use clippy_utils::source::{snippet_expr, TargetPrecedence};
43
use clippy_utils::ty::implements_trait;
54
use if_chain::if_chain;
65
use rustc_errors::Applicability;
@@ -13,14 +12,14 @@ use super::REF_MUT_ITER_METHOD_CHAIN;
1312
pub(crate) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>) {
1413
if_chain! {
1514
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);
15+
if !self_arg.span.from_expansion();
16+
if let Some(&iter_trait) = cx.tcx.all_diagnostic_items(()).name_to_id.get(&sym::Iterator);
1817
if implements_trait(cx, cx.typeck_results().expr_ty(base_expr).peel_refs(), iter_trait, &[]);
1918
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,
19+
let snip_expr = match base_expr.kind {
20+
ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() && !base_expr.span.from_expansion()
21+
=> e,
22+
_ => base_expr,
2423
};
2524
let mut app = Applicability::MachineApplicable;
2625
span_lint_and_sugg(
@@ -31,7 +30,7 @@ pub(crate) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>) {
3130
"try",
3231
format!(
3332
"{}.by_ref()",
34-
snippet_with_context(cx, snip_span, self_arg.span.ctxt(), "..", &mut app).0,
33+
snippet_expr(cx, snip_expr, TargetPrecedence::Postfix, self_arg.span.ctxt(), &mut app),
3534
),
3635
app,
3736
);

clippy_utils/src/source.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use crate::line_span;
66
use rustc_errors::Applicability;
7-
use rustc_hir::{Expr, ExprKind};
7+
use rustc_hir::{BinOpKind, Expr, ExprKind};
88
use rustc_lint::{LateContext, LintContext};
99
use rustc_span::hygiene;
1010
use rustc_span::{BytePos, Pos, Span, SyntaxContext};
@@ -306,6 +306,89 @@ pub fn snippet_with_context(
306306
)
307307
}
308308

309+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
310+
pub enum TargetPrecedence {
311+
Closure,
312+
Assignment,
313+
Range,
314+
Or,
315+
And,
316+
Eq,
317+
BitOr,
318+
BitAnd,
319+
Shift,
320+
BitXor,
321+
Add,
322+
Mul,
323+
As,
324+
Prefix,
325+
Postfix,
326+
}
327+
328+
/// Extracts a snippet of the given expression taking into account the `SyntaxContext` the snippet
329+
/// needs to be taken from. Parenthesis will be added if needed to place the snippet in the target
330+
/// precedence level. Returns a placeholder (`(..)`) if a snippet can't be extracted (e.g. an
331+
/// invalid span).
332+
///
333+
/// The `SyntaxContext` of the expression will be walked up to the given target context (usually
334+
/// from the parent expression) before extracting a snippet. This allows getting the call to a macro
335+
/// rather than the expression from expanding the macro. e.g. In the expression `&vec![]` taking a
336+
/// snippet of the chile of the borrow expression will get a snippet of what `vec![]` expands in to.
337+
/// With the target context set to the same as the borrow expression, this will get a snippet of the
338+
/// call to the macro.
339+
///
340+
/// The applicability will be modified in two ways:
341+
/// * If a snippet can't be extracted it will be changed from `MachineApplicable` or
342+
/// `MaybeIncorrect` to `HasPlaceholders`.
343+
/// * If the snippet is taken from a macro expansion then it will be changed from
344+
/// `MachineApplicable` to `MaybeIncorrect`.
345+
pub fn snippet_expr(
346+
cx: &LateContext<'_>,
347+
expr: &Expr<'_>,
348+
target_precedence: TargetPrecedence,
349+
ctxt: SyntaxContext,
350+
app: &mut Applicability,
351+
) -> String {
352+
let (snip, is_mac_call) = snippet_with_context(cx, expr.span, ctxt, "(..)", app);
353+
354+
match snip {
355+
Cow::Borrowed(snip) => snip.to_owned(),
356+
Cow::Owned(snip) if is_mac_call => snip,
357+
Cow::Owned(mut snip) => {
358+
let needs_paren = match expr.kind {
359+
ExprKind::Binary(op, ..) => match op.node {
360+
BinOpKind::Add | BinOpKind::Sub => target_precedence > TargetPrecedence::Add,
361+
BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem => target_precedence > TargetPrecedence::Mul,
362+
BinOpKind::And => target_precedence > TargetPrecedence::And,
363+
BinOpKind::Or => target_precedence > TargetPrecedence::Or,
364+
BinOpKind::BitXor => target_precedence > TargetPrecedence::BitXor,
365+
BinOpKind::BitAnd => target_precedence > TargetPrecedence::BitAnd,
366+
BinOpKind::BitOr => target_precedence > TargetPrecedence::BitOr,
367+
BinOpKind::Shl | BinOpKind::Shr => target_precedence > TargetPrecedence::Shift,
368+
BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Gt | BinOpKind::Ge => {
369+
target_precedence > TargetPrecedence::Eq
370+
},
371+
},
372+
ExprKind::Unary(..) | ExprKind::AddrOf(..) => target_precedence > TargetPrecedence::Prefix,
373+
ExprKind::Cast(..) => target_precedence > TargetPrecedence::As,
374+
ExprKind::Box(..)
375+
| ExprKind::Closure(..)
376+
| ExprKind::Break(..)
377+
| ExprKind::Ret(..)
378+
| ExprKind::Yield(..) => target_precedence > TargetPrecedence::Closure,
379+
ExprKind::Assign(..) | ExprKind::AssignOp(..) => target_precedence > TargetPrecedence::Assignment,
380+
_ => false,
381+
};
382+
383+
if needs_paren {
384+
snip.insert(0, '(');
385+
snip.push(')');
386+
}
387+
snip
388+
},
389+
}
390+
}
391+
309392
/// Walks the span up to the target context, thereby returning the macro call site if the span is
310393
/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
311394
/// case of the span being in a macro expansion, but the target context is from expanding a macro

tests/ui/ref_mut_iter_method_chain.fixed

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,7 @@ fn main() {
3131

3232
let iter = &mut iter;
3333
iter.by_ref().find(|&&x| x == 1);
34+
let mut iter = iter;
35+
let iter = &mut iter;
36+
(*iter).by_ref().find(|&&x| x == 1);
3437
}

tests/ui/ref_mut_iter_method_chain.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,7 @@ fn main() {
3131

3232
let iter = &mut iter;
3333
(&mut *iter).find(|&&x| x == 1);
34+
let mut iter = iter;
35+
let iter = &mut iter;
36+
(&mut **iter).find(|&&x| x == 1);
3437
}

tests/ui/ref_mut_iter_method_chain.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,11 @@ error: use of `&mut` on an iterator in a method chain
1818
LL | (&mut *iter).find(|&&x| x == 1);
1919
| ^^^^^^^^^^^^ help: try: `iter.by_ref()`
2020

21-
error: aborting due to 3 previous errors
21+
error: use of `&mut` on an iterator in a method chain
22+
--> $DIR/ref_mut_iter_method_chain.rs:36:5
23+
|
24+
LL | (&mut **iter).find(|&&x| x == 1);
25+
| ^^^^^^^^^^^^^ help: try: `(*iter).by_ref()`
26+
27+
error: aborting due to 4 previous errors
2228

0 commit comments

Comments
 (0)