-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Stable sort primitive #5809
Stable sort primitive #5809
Changes from all commits
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,130 @@ | ||
use crate::utils::{is_slice_of_primitives, span_lint_and_sugg, sugg::Sugg}; | ||
|
||
use if_chain::if_chain; | ||
|
||
use rustc_errors::Applicability; | ||
use rustc_hir::{Expr, ExprKind}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
|
||
declare_clippy_lint! { | ||
/// **What it does:** | ||
/// When sorting primitive values (integers, bools, chars, as well | ||
/// as arrays, slices, and tuples of such items), it is better to | ||
/// use an unstable sort than a stable sort. | ||
/// | ||
/// **Why is this bad?** | ||
/// Using a stable sort consumes more memory and cpu cycles. Because | ||
/// values which compare equal are identical, preserving their | ||
/// relative order (the guarantee that a stable sort provides) means | ||
/// nothing, while the extra costs still apply. | ||
/// | ||
/// **Known problems:** | ||
/// None | ||
/// | ||
/// **Example:** | ||
/// | ||
/// ```rust | ||
/// let mut vec = vec![2, 1, 3]; | ||
/// vec.sort(); | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// let mut vec = vec![2, 1, 3]; | ||
/// vec.sort_unstable(); | ||
/// ``` | ||
pub STABLE_SORT_PRIMITIVE, | ||
perf, | ||
"use of sort() when sort_unstable() is equivalent" | ||
} | ||
|
||
declare_lint_pass!(StableSortPrimitive => [STABLE_SORT_PRIMITIVE]); | ||
|
||
/// The three "kinds" of sorts | ||
enum SortingKind { | ||
Vanilla, | ||
/* The other kinds of lint are currently commented out because they | ||
* can map distinct values to equal ones. If the key function is | ||
* provably one-to-one, or if the Cmp function conserves equality, | ||
* then they could be linted on, but I don't know if we can check | ||
* for that. */ | ||
|
||
/* ByKey, | ||
* ByCmp, */ | ||
} | ||
impl SortingKind { | ||
/// The name of the stable version of this kind of sort | ||
fn stable_name(&self) -> &str { | ||
match self { | ||
SortingKind::Vanilla => "sort", | ||
/* SortingKind::ByKey => "sort_by_key", | ||
* SortingKind::ByCmp => "sort_by", */ | ||
} | ||
} | ||
/// The name of the unstable version of this kind of sort | ||
fn unstable_name(&self) -> &str { | ||
match self { | ||
SortingKind::Vanilla => "sort_unstable", | ||
/* SortingKind::ByKey => "sort_unstable_by_key", | ||
* SortingKind::ByCmp => "sort_unstable_by", */ | ||
} | ||
} | ||
/// Takes the name of a function call and returns the kind of sort | ||
/// that corresponds to that function name (or None if it isn't) | ||
fn from_stable_name(name: &str) -> Option<SortingKind> { | ||
match name { | ||
"sort" => Some(SortingKind::Vanilla), | ||
// "sort_by" => Some(SortingKind::ByCmp), | ||
// "sort_by_key" => Some(SortingKind::ByKey), | ||
_ => None, | ||
} | ||
} | ||
} | ||
|
||
/// A detected instance of this lint | ||
struct LintDetection { | ||
slice_name: String, | ||
method: SortingKind, | ||
method_args: String, | ||
} | ||
|
||
fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintDetection> { | ||
if_chain! { | ||
if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind; | ||
if let Some(slice) = &args.get(0); | ||
if let Some(method) = SortingKind::from_stable_name(&method_name.ident.name.as_str()); | ||
if is_slice_of_primitives(cx, slice); | ||
then { | ||
let args_str = args.iter().skip(1).map(|arg| Sugg::hir(cx, arg, "..").to_string()).collect::<Vec<String>>().join(", "); | ||
Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str }) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
impl LateLintPass<'_> for StableSortPrimitive { | ||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { | ||
if let Some(detection) = detect_stable_sort_primitive(cx, expr) { | ||
span_lint_and_sugg( | ||
cx, | ||
STABLE_SORT_PRIMITIVE, | ||
expr.span, | ||
format!( | ||
"Use {} instead of {}", | ||
detection.method.unstable_name(), | ||
detection.method.stable_name() | ||
) | ||
.as_str(), | ||
"try", | ||
format!( | ||
"{}.{}({})", | ||
detection.slice_name, | ||
detection.method.unstable_name(), | ||
detection.method_args | ||
), | ||
Applicability::MachineApplicable, | ||
); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// run-rustfix | ||
#![warn(clippy::stable_sort_primitive)] | ||
|
||
fn main() { | ||
// positive examples | ||
let mut vec = vec![1, 3, 2]; | ||
vec.sort_unstable(); | ||
let mut vec = vec![false, false, true]; | ||
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. Should we add a test for chars as well? 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. Also test cases for types that deref to a slice of primitives. 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. I believe 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. No, you are correct. My bad. 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. @JarredAllen Hey should we add test cases for list of tuples such as: 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. @krishna-veerareddy I added test cases for a list of tuples and also for one of arrays. |
||
vec.sort_unstable(); | ||
let mut vec = vec!['a', 'A', 'c']; | ||
vec.sort_unstable(); | ||
let mut vec = vec!["ab", "cd", "ab", "bc"]; | ||
vec.sort_unstable(); | ||
let mut vec = vec![(2, 1), (1, 2), (2, 5)]; | ||
vec.sort_unstable(); | ||
let mut vec = vec![[2, 1], [1, 2], [2, 5]]; | ||
vec.sort_unstable(); | ||
let mut arr = [1, 3, 2]; | ||
arr.sort_unstable(); | ||
// Negative examples: behavior changes if made unstable | ||
let mut vec = vec![1, 3, 2]; | ||
vec.sort_by_key(|i| i / 2); | ||
vec.sort_by(|a, b| (a + b).cmp(&b)); | ||
// negative examples - Not of a primitive type | ||
let mut vec_of_complex = vec![String::from("hello"), String::from("world!")]; | ||
vec_of_complex.sort(); | ||
vec_of_complex.sort_by_key(String::len); | ||
let mut vec = vec![(String::from("hello"), String::from("world"))]; | ||
vec.sort(); | ||
let mut vec = vec![[String::from("hello"), String::from("world")]]; | ||
vec.sort(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// run-rustfix | ||
#![warn(clippy::stable_sort_primitive)] | ||
|
||
fn main() { | ||
// positive examples | ||
let mut vec = vec![1, 3, 2]; | ||
vec.sort(); | ||
let mut vec = vec![false, false, true]; | ||
vec.sort(); | ||
let mut vec = vec!['a', 'A', 'c']; | ||
vec.sort(); | ||
let mut vec = vec!["ab", "cd", "ab", "bc"]; | ||
vec.sort(); | ||
let mut vec = vec![(2, 1), (1, 2), (2, 5)]; | ||
vec.sort(); | ||
let mut vec = vec![[2, 1], [1, 2], [2, 5]]; | ||
vec.sort(); | ||
let mut arr = [1, 3, 2]; | ||
arr.sort(); | ||
// Negative examples: behavior changes if made unstable | ||
let mut vec = vec![1, 3, 2]; | ||
vec.sort_by_key(|i| i / 2); | ||
vec.sort_by(|a, b| (a + b).cmp(&b)); | ||
// negative examples - Not of a primitive type | ||
let mut vec_of_complex = vec![String::from("hello"), String::from("world!")]; | ||
vec_of_complex.sort(); | ||
vec_of_complex.sort_by_key(String::len); | ||
let mut vec = vec![(String::from("hello"), String::from("world"))]; | ||
vec.sort(); | ||
let mut vec = vec![[String::from("hello"), String::from("world")]]; | ||
vec.sort(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
error: Use sort_unstable instead of sort | ||
--> $DIR/stable_sort_primitive.rs:7:5 | ||
| | ||
LL | vec.sort(); | ||
| ^^^^^^^^^^ help: try: `vec.sort_unstable()` | ||
| | ||
= note: `-D clippy::stable-sort-primitive` implied by `-D warnings` | ||
|
||
error: Use sort_unstable instead of sort | ||
--> $DIR/stable_sort_primitive.rs:9:5 | ||
| | ||
LL | vec.sort(); | ||
| ^^^^^^^^^^ help: try: `vec.sort_unstable()` | ||
|
||
error: Use sort_unstable instead of sort | ||
--> $DIR/stable_sort_primitive.rs:11:5 | ||
| | ||
LL | vec.sort(); | ||
| ^^^^^^^^^^ help: try: `vec.sort_unstable()` | ||
|
||
error: Use sort_unstable instead of sort | ||
--> $DIR/stable_sort_primitive.rs:13:5 | ||
| | ||
LL | vec.sort(); | ||
| ^^^^^^^^^^ help: try: `vec.sort_unstable()` | ||
|
||
error: Use sort_unstable instead of sort | ||
--> $DIR/stable_sort_primitive.rs:15:5 | ||
| | ||
LL | vec.sort(); | ||
| ^^^^^^^^^^ help: try: `vec.sort_unstable()` | ||
|
||
error: Use sort_unstable instead of sort | ||
--> $DIR/stable_sort_primitive.rs:17:5 | ||
| | ||
LL | vec.sort(); | ||
| ^^^^^^^^^^ help: try: `vec.sort_unstable()` | ||
|
||
error: Use sort_unstable instead of sort | ||
--> $DIR/stable_sort_primitive.rs:19:5 | ||
| | ||
LL | arr.sort(); | ||
| ^^^^^^^^^^ help: try: `arr.sort_unstable()` | ||
|
||
error: aborting due to 7 previous errors | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
// run-rustfix | ||
|
||
#![allow(clippy::stable_sort_primitive)] | ||
|
||
use std::cmp::Reverse; | ||
|
||
fn unnecessary_sort_by() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
// run-rustfix | ||
|
||
#![allow(clippy::stable_sort_primitive)] | ||
|
||
use std::cmp::Reverse; | ||
|
||
fn unnecessary_sort_by() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would avoid using multiline comments and instead just use lots of
//
, it's pretty standard style in Rust or at least in this codebase.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason,
cargo dev fmt
seems to insist on changing those comments to be multiline ones.