Skip to content

Commit 1b67c0b

Browse files
committed
Fix redundant_slicing when the result is used as the reciever in a method call.
1 parent f51fb34 commit 1b67c0b

File tree

5 files changed

+282
-15
lines changed

5 files changed

+282
-15
lines changed

clippy_lints/src/redundant_slicing.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::get_parent_expr;
3-
use clippy_utils::source::snippet_with_context;
3+
use clippy_utils::source::{position_of_expr, snippet_expr, ExprPosition};
44
use clippy_utils::ty::is_type_lang_item;
55
use if_chain::if_chain;
66
use rustc_errors::Applicability;
77
use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability};
88
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
910
use rustc_middle::ty::TyS;
1011
use rustc_session::{declare_lint_pass, declare_tool_lint};
1112

@@ -57,34 +58,47 @@ impl LateLintPass<'_> for RedundantSlicing {
5758
if TyS::same_type(cx.typeck_results().expr_ty(expr), cx.typeck_results().expr_ty(indexed));
5859
then {
5960
let mut app = Applicability::MachineApplicable;
60-
let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;
61+
let position = position_of_expr(cx, expr);
6162

62-
let (reborrow_str, help_str) = if mutability == Mutability::Mut {
63+
let (reborrow_str, help_str, snip_position) = if mutability == Mutability::Mut {
6364
// The slice was used to reborrow the mutable reference.
64-
("&mut *", "reborrow the original value instead")
65+
("&mut *", "reborrow the original value instead", ExprPosition::Prefix)
6566
} else if matches!(
6667
get_parent_expr(cx, expr),
6768
Some(Expr {
6869
kind: ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _),
6970
..
7071
})
72+
) || matches!(
73+
cx.typeck_results().expr_adjustments(expr),
74+
[Adjustment {
75+
kind: Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Mut { .. })),
76+
..
77+
}]
7178
) {
7279
// The slice was used to make a temporary reference.
73-
("&*", "reborrow the original value instead")
80+
("&*", "reborrow the original value instead", ExprPosition::Prefix)
7481
} else {
75-
("", "use the original value instead")
82+
("", "use the original value instead", position)
7683
};
7784

85+
let snip = snippet_expr(cx, indexed, snip_position, ctxt, &mut app);
86+
7887
span_lint_and_sugg(
7988
cx,
8089
REDUNDANT_SLICING,
8190
expr.span,
8291
"redundant slicing of the whole range",
8392
help_str,
84-
format!("{}{}", reborrow_str, snip),
93+
if reborrow_str != "" && position > ExprPosition::Prefix {
94+
format!("({}{})", reborrow_str, snip)
95+
} else {
96+
format!("{}{}", reborrow_str, snip)
97+
},
8598
app,
8699
);
87100
}
101+
88102
}
89103
}
90104
}

clippy_utils/src/source.rs

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
33
#![allow(clippy::module_name_repetitions)]
44

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

309+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
310+
pub enum ExprPosition {
311+
// Also includes `return`, `yield`, `break` and closures
312+
Paren,
313+
AssignmentRhs,
314+
AssignmentLhs,
315+
RangeLhs,
316+
RangeRhs,
317+
OrLhs,
318+
OrRhs,
319+
AndLhs,
320+
AndRhs,
321+
Let,
322+
EqLhs,
323+
EqRhs,
324+
BitOrLhs,
325+
BitOrRhs,
326+
BitXorLhs,
327+
BitXorRhs,
328+
BitAndLhs,
329+
BitAndRhs,
330+
ShiftLhs,
331+
ShiftRhs,
332+
AddLhs,
333+
AddRhs,
334+
MulLhs,
335+
MulRhs,
336+
// Also includes type ascription
337+
Cast,
338+
Prefix,
339+
Postfix,
340+
}
341+
342+
/// Extracts a snippet of the given expression taking into account the `SyntaxContext` the snippet
343+
/// needs to be taken from. Parenthesis will be added if needed to place the snippet in the target
344+
/// precedence level. Returns a placeholder (`(..)`) if a snippet can't be extracted (e.g. an
345+
/// invalid span).
346+
///
347+
/// The `SyntaxContext` of the expression will be walked up to the given target context (usually
348+
/// from the parent expression) before extracting a snippet. This allows getting the call to a macro
349+
/// rather than the expression from expanding the macro. e.g. In the expression `&vec![]` taking a
350+
/// snippet of the chile of the borrow expression will get a snippet of what `vec![]` expands in to.
351+
/// With the target context set to the same as the borrow expression, this will get a snippet of the
352+
/// call to the macro.
353+
///
354+
/// The applicability will be modified in two ways:
355+
/// * If a snippet can't be extracted it will be changed from `MachineApplicable` or
356+
/// `MaybeIncorrect` to `HasPlaceholders`.
357+
/// * If the snippet is taken from a macro expansion then it will be changed from
358+
/// `MachineApplicable` to `MaybeIncorrect`.
359+
pub fn snippet_expr(
360+
cx: &LateContext<'_>,
361+
expr: &Expr<'_>,
362+
target_position: ExprPosition,
363+
ctxt: SyntaxContext,
364+
app: &mut Applicability,
365+
) -> String {
366+
let (snip, is_mac_call) = snippet_with_context(cx, expr.span, ctxt, "(..)", app);
367+
368+
match snip {
369+
Cow::Borrowed(snip) => snip.to_owned(),
370+
Cow::Owned(snip) if is_mac_call => snip,
371+
Cow::Owned(mut snip) => {
372+
let ctxt = expr.span.ctxt();
373+
374+
// Attempt to determine if parenthesis are needed base on the target position. The snippet may have
375+
// parenthesis already, so attempt to find those.
376+
// TODO: Remove parenthesis if they aren't needed at the target position.
377+
let needs_paren = match expr.peel_drop_temps().kind {
378+
ExprKind::Binary(_, lhs, rhs)
379+
if (ctxt == lhs.span.ctxt() && expr.span.lo() != lhs.span.lo())
380+
|| (ctxt == rhs.span.ctxt() && expr.span.hi() != rhs.span.hi()) =>
381+
{
382+
false
383+
},
384+
ExprKind::Binary(op, ..) => match op.node {
385+
BinOpKind::Add | BinOpKind::Sub => target_position > ExprPosition::AddLhs,
386+
BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem => target_position > ExprPosition::MulLhs,
387+
BinOpKind::And => target_position > ExprPosition::AndLhs,
388+
BinOpKind::Or => target_position > ExprPosition::OrLhs,
389+
BinOpKind::BitXor => target_position > ExprPosition::BitXorLhs,
390+
BinOpKind::BitAnd => target_position > ExprPosition::BitAndLhs,
391+
BinOpKind::BitOr => target_position > ExprPosition::BitOrLhs,
392+
BinOpKind::Shl | BinOpKind::Shr => target_position > ExprPosition::ShiftLhs,
393+
BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Gt | BinOpKind::Ge => {
394+
target_position > ExprPosition::EqLhs
395+
},
396+
},
397+
ExprKind::Box(..) | ExprKind::Unary(..) | ExprKind::AddrOf(..) if snip.starts_with('(') => false,
398+
ExprKind::Box(..) | ExprKind::Unary(..) | ExprKind::AddrOf(..) => {
399+
target_position > ExprPosition::Prefix
400+
},
401+
ExprKind::Let(..) if snip.starts_with('(') => false,
402+
ExprKind::Let(..) => target_position > ExprPosition::Let,
403+
ExprKind::Cast(lhs, rhs)
404+
if (ctxt == lhs.span.ctxt() && expr.span.lo() != lhs.span.lo())
405+
|| (ctxt == rhs.span.ctxt() && expr.span.hi() != rhs.span.hi()) =>
406+
{
407+
false
408+
},
409+
ExprKind::Cast(..) | ExprKind::Type(..) => target_position > ExprPosition::Cast,
410+
411+
ExprKind::Closure(..)
412+
| ExprKind::Break(..)
413+
| ExprKind::Ret(..)
414+
| ExprKind::Yield(..)
415+
| ExprKind::Assign(..)
416+
| ExprKind::AssignOp(..) => target_position > ExprPosition::AssignmentRhs,
417+
418+
// Postfix operators, or expression with braces of some form
419+
ExprKind::Array(_)
420+
| ExprKind::Call(..)
421+
| ExprKind::ConstBlock(_)
422+
| ExprKind::MethodCall(..)
423+
| ExprKind::Tup(..)
424+
| ExprKind::Lit(..)
425+
| ExprKind::DropTemps(_)
426+
| ExprKind::If(..)
427+
| ExprKind::Loop(..)
428+
| ExprKind::Match(..)
429+
| ExprKind::Block(..)
430+
| ExprKind::Field(..)
431+
| ExprKind::Index(..)
432+
| ExprKind::Path(_)
433+
| ExprKind::Continue(_)
434+
| ExprKind::InlineAsm(_)
435+
| ExprKind::LlvmInlineAsm(_)
436+
| ExprKind::Struct(..)
437+
| ExprKind::Repeat(..)
438+
| ExprKind::Err => false,
439+
};
440+
441+
if needs_paren {
442+
snip.insert(0, '(');
443+
snip.push(')');
444+
}
445+
snip
446+
},
447+
}
448+
}
449+
450+
/// Gets which position the expression is in relative to it's parent. Defaults to `Paren` if the
451+
/// parent node is not an expression.
452+
pub fn position_of_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> ExprPosition {
453+
match get_parent_expr(cx, expr) {
454+
None => ExprPosition::Paren,
455+
Some(parent) => match parent.kind {
456+
ExprKind::DropTemps(_) => position_of_expr(cx, parent),
457+
ExprKind::Binary(op, lhs, _) => match (op.node, expr.hir_id == lhs.hir_id) {
458+
(BinOpKind::Add | BinOpKind::Sub, true) => ExprPosition::AddLhs,
459+
(BinOpKind::Add | BinOpKind::Sub, false) => ExprPosition::AddRhs,
460+
(BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem, true) => ExprPosition::MulLhs,
461+
(BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem, false) => ExprPosition::MulRhs,
462+
(BinOpKind::And, true) => ExprPosition::AndLhs,
463+
(BinOpKind::And, false) => ExprPosition::AndRhs,
464+
(BinOpKind::Or, true) => ExprPosition::OrLhs,
465+
(BinOpKind::Or, false) => ExprPosition::OrRhs,
466+
(BinOpKind::BitXor, true) => ExprPosition::BitXorLhs,
467+
(BinOpKind::BitXor, false) => ExprPosition::BitXorRhs,
468+
(BinOpKind::BitAnd, true) => ExprPosition::BitAndLhs,
469+
(BinOpKind::BitAnd, false) => ExprPosition::BitAndRhs,
470+
(BinOpKind::BitOr, true) => ExprPosition::BitOrLhs,
471+
(BinOpKind::BitOr, false) => ExprPosition::BitOrRhs,
472+
(BinOpKind::Shl | BinOpKind::Shr, true) => ExprPosition::ShiftLhs,
473+
(BinOpKind::Shl | BinOpKind::Shr, false) => ExprPosition::ShiftRhs,
474+
(
475+
BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Gt | BinOpKind::Ge,
476+
true,
477+
) => ExprPosition::EqLhs,
478+
(
479+
BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Gt | BinOpKind::Ge,
480+
false,
481+
) => ExprPosition::EqRhs,
482+
},
483+
ExprKind::Unary(..) | ExprKind::AddrOf(..) => ExprPosition::Prefix,
484+
ExprKind::Cast(..) | ExprKind::Type(..) => ExprPosition::Cast,
485+
ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == expr.hir_id => {
486+
ExprPosition::AssignmentLhs
487+
},
488+
ExprKind::Assign(..) | ExprKind::AssignOp(..) => ExprPosition::AssignmentRhs,
489+
ExprKind::Call(e, _) | ExprKind::MethodCall(_, _, [e, ..], _) | ExprKind::Index(e, _)
490+
if expr.hir_id == e.hir_id =>
491+
{
492+
ExprPosition::Postfix
493+
},
494+
ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) | ExprKind::Field(..) => {
495+
ExprPosition::Postfix
496+
},
497+
_ => ExprPosition::Paren,
498+
},
499+
}
500+
}
501+
309502
/// Walks the span up to the target context, thereby returning the macro call site if the span is
310503
/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
311504
/// case of the span being in a macro expansion, but the target context is from expanding a macro

tests/ui/redundant_slicing.fixed

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
#![warn(clippy::redundant_slicing)]
4+
5+
fn main() {
6+
let slice: &[u32] = &[0];
7+
let _ = slice;
8+
9+
let v = vec![0];
10+
let _ = &v[..]; // Changes the type
11+
let _ = (&v[..]); // Outer borrow is redundant
12+
13+
static S: &[u8] = &[0, 1, 2];
14+
let err = &mut &*S; // Should reborrow instead of slice
15+
16+
let mut vec = vec![0];
17+
let mut_slice = &mut *vec;
18+
let _ = &mut *mut_slice; // Should reborrow instead of slice
19+
20+
macro_rules! m {
21+
($e:expr) => {
22+
$e
23+
};
24+
}
25+
let _ = slice;
26+
27+
macro_rules! m2 {
28+
($e:expr) => {
29+
&$e[..]
30+
};
31+
}
32+
let _ = m2!(slice); // Don't lint in a macro
33+
34+
trait T {
35+
fn f(&mut self);
36+
}
37+
impl T for &'_ [u32] {
38+
fn f(&mut self) {}
39+
}
40+
41+
// Reborrow, `f` takes a mutable reference to the slice
42+
(&*slice).f();
43+
}

tests/ui/redundant_slicing.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// run-rustfix
12
#![allow(unused)]
23
#![warn(clippy::redundant_slicing)]
34

@@ -29,4 +30,14 @@ fn main() {
2930
};
3031
}
3132
let _ = m2!(slice); // Don't lint in a macro
33+
34+
trait T {
35+
fn f(&mut self);
36+
}
37+
impl T for &'_ [u32] {
38+
fn f(&mut self) {}
39+
}
40+
41+
// Reborrow, `f` takes a mutable reference to the slice
42+
(&slice[..]).f();
3243
}

tests/ui/redundant_slicing.stderr

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,40 @@
11
error: redundant slicing of the whole range
2-
--> $DIR/redundant_slicing.rs:6:13
2+
--> $DIR/redundant_slicing.rs:7:13
33
|
44
LL | let _ = &slice[..];
55
| ^^^^^^^^^^ help: use the original value instead: `slice`
66
|
77
= note: `-D clippy::redundant-slicing` implied by `-D warnings`
88

99
error: redundant slicing of the whole range
10-
--> $DIR/redundant_slicing.rs:10:13
10+
--> $DIR/redundant_slicing.rs:11:13
1111
|
1212
LL | let _ = &(&v[..])[..]; // Outer borrow is redundant
1313
| ^^^^^^^^^^^^^ help: use the original value instead: `(&v[..])`
1414

1515
error: redundant slicing of the whole range
16-
--> $DIR/redundant_slicing.rs:13:20
16+
--> $DIR/redundant_slicing.rs:14:20
1717
|
1818
LL | let err = &mut &S[..]; // Should reborrow instead of slice
1919
| ^^^^^^ help: reborrow the original value instead: `&*S`
2020

2121
error: redundant slicing of the whole range
22-
--> $DIR/redundant_slicing.rs:17:13
22+
--> $DIR/redundant_slicing.rs:18:13
2323
|
2424
LL | let _ = &mut mut_slice[..]; // Should reborrow instead of slice
2525
| ^^^^^^^^^^^^^^^^^^ help: reborrow the original value instead: `&mut *mut_slice`
2626

2727
error: redundant slicing of the whole range
28-
--> $DIR/redundant_slicing.rs:24:13
28+
--> $DIR/redundant_slicing.rs:25:13
2929
|
3030
LL | let _ = &m!(slice)[..];
3131
| ^^^^^^^^^^^^^^ help: use the original value instead: `slice`
3232

33-
error: aborting due to 5 previous errors
33+
error: redundant slicing of the whole range
34+
--> $DIR/redundant_slicing.rs:42:5
35+
|
36+
LL | (&slice[..]).f();
37+
| ^^^^^^^^^^^^ help: reborrow the original value instead: `(&*slice)`
38+
39+
error: aborting due to 6 previous errors
3440

0 commit comments

Comments
 (0)