From 8590ab4d46de4eb43e7ebd42cb2f13b0064573e6 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Mon, 18 May 2020 21:48:35 -0700 Subject: [PATCH] More progress towards sort_by_key_reverse lint --- clippy_lints/src/lib.rs | 1 + clippy_lints/src/sort_by_key_reverse.rs | 131 ++++++++++++++++++++---- tests/ui/sort_by_key_reverse.fixed | 9 ++ tests/ui/sort_by_key_reverse.rs | 5 +- tests/ui/sort_by_key_reverse.stderr | 22 ++++ 5 files changed, 149 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index f51855badff..e7a4c1ecaa9 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -998,6 +998,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box ptr_offset_with_cast::PtrOffsetWithCast); store.register_late_pass(|| box redundant_clone::RedundantClone); store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit); + store.register_late_pass(|| box sort_by_key_reverse::SortByKeyReverse); store.register_late_pass(|| box types::RefToMut); store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants); store.register_late_pass(|| box missing_const_for_fn::MissingConstForFn); diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key_reverse.rs index 7d7097a8125..d70391999a0 100644 --- a/clippy_lints/src/sort_by_key_reverse.rs +++ b/clippy_lints/src/sort_by_key_reverse.rs @@ -1,11 +1,12 @@ -use crate::utils::{match_type, span_lint_and_sugg}; +use crate::utils; use crate::utils::paths; use crate::utils::sugg::Sugg; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_lint::{LateLintPass, LateContext}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_hir::*; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Ident; declare_clippy_lint! { /// **What it does:** @@ -40,18 +41,122 @@ struct LintTrigger { unstable: bool, } +/// Detect if the two expressions are mirrored (identical, except one +/// contains a and the other replaces it with b) +fn mirrored_exprs(cx: &LateContext<'_, '_>, a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool { + match (&a_expr.kind, &b_expr.kind) { + // Two boxes with mirrored contents + (ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + // Two arrays with mirrored contents + (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) + => left_exprs.iter().zip(right_exprs.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // The two exprs are function calls. + // Check to see that the function itself and its arguments are mirrored + (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) + => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + && left_args.iter().zip(right_args.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // The two exprs are method calls. + // Check to see that the function is the same and the arguments are mirrored + // This is enough because the receiver of the method is listed in the arguments + (ExprKind::MethodCall(left_segment, _, left_args), ExprKind::MethodCall(right_segment, _, right_args)) + => left_segment.ident == right_segment.ident + && left_args.iter().zip(right_args.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // Two tuples with mirrored contents + (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) + => left_exprs.iter().zip(right_exprs.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // Two binary ops, which are the same operation and which have mirrored arguments + (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) + => left_op.node == right_op.node + && mirrored_exprs(cx, left_left, a_ident, right_left, b_ident) + && mirrored_exprs(cx, left_right, a_ident, right_right, b_ident), + // Two unary ops, which are the same operation and which have the same argument + (ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) + => left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + // The two exprs are literals of some kind + (ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node, + (ExprKind::Cast(left_expr, _), ExprKind::Cast(right_expr, _)) + => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (ExprKind::DropTemps(left), ExprKind::DropTemps(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), + (ExprKind::Block(left, _), ExprKind::Block(right, _)) => mirrored_blocks(cx, left, a_ident, right, b_ident), + (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) + => left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident), + // The two exprs are `a` and `b`, directly + (ExprKind::Path(QPath::Resolved(_, Path { segments: &[PathSegment { ident: left_ident, .. }], .. },)), + ExprKind::Path(QPath::Resolved(_, Path { segments: &[PathSegment { ident: right_ident, .. }], .. },)), + ) => &left_ident == a_ident && &right_ident == b_ident, + // The two exprs are Paths to the same name (which is neither a nor b) + (ExprKind::Path(QPath::Resolved(_, Path { segments: left_segments, .. })), + ExprKind::Path(QPath::Resolved(_, Path { segments: right_segments, .. }))) + => left_segments.iter().zip(right_segments.iter()).all(|(left, right)| left.ident == right.ident) + && left_segments.iter().all(|seg| &seg.ident != a_ident && &seg.ident != b_ident), + // Matching expressions, but one or both is borrowed + (ExprKind::AddrOf(left_kind, Mutability::Not, left_expr), ExprKind::AddrOf(right_kind, Mutability::Not, right_expr)) + => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) + => mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident), + (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) + => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident), + // _ => false, + (left, right) => { + println!("{:?}\n{:?}", left, right); + false + }, + } +} + +/// Detect if the two blocks are mirrored (identical, except one +/// contains a and the other replaces it with b) +fn mirrored_blocks(cx: &LateContext<'_, '_>, a_block: &Block<'_>, a_ident: &Ident, b_block: &Block<'_>, b_ident: &Ident) -> bool { + match (a_block, b_block) { + (Block { stmts: left_stmts, expr: left_expr, .. }, + Block { stmts: right_stmts, expr: right_expr, .. }) + => left_stmts.iter().zip(right_stmts.iter()).all(|(left, right)| match (&left.kind, &right.kind) { + (StmtKind::Expr(left_expr), StmtKind::Expr(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (StmtKind::Semi(left_expr), StmtKind::Semi(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (StmtKind::Item(left_item), StmtKind::Item(right_item)) => left_item.id == right_item.id, + (StmtKind::Local(left), StmtKind::Local(right)) => mirrored_locals(cx, left, a_ident, right, b_ident), + _ => false, + }) && match (left_expr, right_expr) { + (None, None) => true, + (Some(left), Some(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), + _ => false, + }, + } +} + +/// Check that the two "Local"s (let statements) are equal +fn mirrored_locals(cx: &LateContext<'_, '_>, a_local: &Local<'_>, a_ident: &Ident, b_local: &Local<'_>, b_ident: &Ident) -> bool { + match (a_local, b_local) { + (Local { pat: left_pat, init: left_expr, .. }, Local { pat: right_pat, init: right_expr, .. }) + => match (left_expr, right_expr) { + (None, None) => true, + (Some(left), Some(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), + _ => false, + }, + } +} + fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option { if_chain! { if let ExprKind::MethodCall(name_ident, _, args) = &expr.kind; if let name = name_ident.ident.name.to_ident_string(); if name == "sort_by" || name == "sort_unstable_by"; - if let [vec, Expr { kind: ExprKind::Closure(_, closure_decl, closure_body_id, _, _), .. }] = args; - if closure_decl.inputs.len() == 2; - if match_type(cx, &cx.tables.expr_ty(vec), &paths::VEC); + if let [vec, Expr { kind: ExprKind::Closure(_, _, closure_body_id, _, _), .. }] = args; + if utils::match_type(cx, &cx.tables.expr_ty(vec), &paths::VEC); + if let closure_body = cx.tcx.hir().body(*closure_body_id); + if let &[ + Param { pat: Pat { kind: PatKind::Binding(_, _, a_ident, _), .. }, ..}, + Param { pat: Pat { kind: PatKind::Binding(_, _, b_ident, _), .. }, .. } + ] = &closure_body.params; + if let ExprKind::MethodCall(method_path, _, [ref b_expr, ref a_expr]) = &closure_body.value.kind; + if method_path.ident.name.to_ident_string() == "cmp"; + if mirrored_exprs(&cx, &a_expr, &a_ident, &b_expr, &b_ident); then { let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); let unstable = name == "sort_unstable_by"; - Some(LintTrigger { vec_name, unstable, closure_arg: "e".to_string(), closure_reverse_body: "e".to_string() }) + let closure_arg = a_ident.name.to_ident_string(); + let closure_reverse_body = Sugg::hir(cx, &a_expr, "..").to_string(); + Some(LintTrigger { vec_name, unstable, closure_arg, closure_reverse_body }) } else { None } @@ -60,18 +165,8 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option impl LateLintPass<'_, '_> for SortByKeyReverse { fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { - println!("{:?}", expr); - span_lint_and_sugg( - cx, - SORT_BY_KEY_REVERSE, - expr.span, - "use Vec::sort_by_key here instead", - "try", - String::from("being a better person"), - Applicability::MachineApplicable, - ); if let Some(trigger) = detect_lint(cx, expr) { - span_lint_and_sugg( + utils::span_lint_and_sugg( cx, SORT_BY_KEY_REVERSE, expr.span, diff --git a/tests/ui/sort_by_key_reverse.fixed b/tests/ui/sort_by_key_reverse.fixed index e69de29bb2d..4b18a073e1a 100644 --- a/tests/ui/sort_by_key_reverse.fixed +++ b/tests/ui/sort_by_key_reverse.fixed @@ -0,0 +1,9 @@ +// run-rustfix +#![warn(clippy::sort_by_key_reverse)] + +fn main() { + let mut vec: Vec = vec![3, 6, 1, 2, 5]; + vec.sort_by_key(|a| Reverse(a)); + vec.sort_by_key(|a| Reverse(&(a+5).abs())); + vec.sort_by_key(|a| Reverse(&-a)); +} diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key_reverse.rs index c0350f243c7..f4fb70b7b1d 100644 --- a/tests/ui/sort_by_key_reverse.rs +++ b/tests/ui/sort_by_key_reverse.rs @@ -1,6 +1,9 @@ +// run-rustfix #![warn(clippy::sort_by_key_reverse)] fn main() { - let mut vec = vec![3, 6, 1, 2, 5]; + let mut vec: Vec = vec![3, 6, 1, 2, 5]; vec.sort_by(|a, b| b.cmp(a)); + vec.sort_by(|a, b| (b + 5).abs().cmp(&(a+5).abs())); + vec.sort_by(|a, b| (-b).cmp(&-a)); } diff --git a/tests/ui/sort_by_key_reverse.stderr b/tests/ui/sort_by_key_reverse.stderr index e69de29bb2d..36a28c04b1c 100644 --- a/tests/ui/sort_by_key_reverse.stderr +++ b/tests/ui/sort_by_key_reverse.stderr @@ -0,0 +1,22 @@ +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key_reverse.rs:6:5 + | +LL | vec.sort_by(|a, b| b.cmp(a)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(a))` + | + = note: `-D clippy::sort-by-key-reverse` implied by `-D warnings` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key_reverse.rs:7:5 + | +LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a+5).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(&(a+5).abs()))` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key_reverse.rs:8:5 + | +LL | vec.sort_by(|a, b| (-b).cmp(&-a)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(&-a))` + +error: aborting due to 3 previous errors +