Skip to content
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

Merged
merged 2 commits into from
Aug 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,7 @@ Released 2018-09-13
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
Expand Down
5 changes: 5 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ mod serde_api;
mod shadow;
mod single_component_path_imports;
mod slow_vector_initialization;
mod stable_sort_primitive;
mod strings;
mod suspicious_trait_impl;
mod swap;
Expand Down Expand Up @@ -776,6 +777,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&shadow::SHADOW_UNRELATED,
&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
&stable_sort_primitive::STABLE_SORT_PRIMITIVE,
&strings::STRING_ADD,
&strings::STRING_ADD_ASSIGN,
&strings::STRING_LIT_AS_BYTES,
Expand Down Expand Up @@ -1078,6 +1080,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box macro_use::MacroUseImports::default());
store.register_late_pass(|| box map_identity::MapIdentity);
store.register_late_pass(|| box pattern_type_mismatch::PatternTypeMismatch);
store.register_late_pass(|| box stable_sort_primitive::StableSortPrimitive);
store.register_late_pass(|| box repeat_once::RepeatOnce);

store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
Expand Down Expand Up @@ -1408,6 +1411,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&serde_api::SERDE_API_MISUSE),
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE),
LintId::of(&strings::STRING_LIT_AS_BYTES),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
Expand Down Expand Up @@ -1723,6 +1727,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&mutex_atomic::MUTEX_ATOMIC),
LintId::of(&redundant_clone::REDUNDANT_CLONE),
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE),
LintId::of(&types::BOX_VEC),
LintId::of(&types::REDUNDANT_ALLOCATION),
LintId::of(&vec::USELESS_VEC),
Expand Down
130 changes: 130 additions & 0 deletions clippy_lints/src/stable_sort_primitive.rs
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
Copy link
Member

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.

Copy link
Contributor Author

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.

* 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,
);
}
}
}
30 changes: 30 additions & 0 deletions clippy_lints/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,36 @@ pub fn run_lints(cx: &LateContext<'_>, lints: &[&'static Lint], id: HirId) -> bo
})
}

/// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point
/// number type, a str, or an array, slice, or tuple of those types).
pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
match ty.kind {
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
ty::Ref(_, inner, _) if inner.kind == ty::Str => true,
ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type),
ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type),
_ => false,
}
}

/// Returns true iff the given expression is a slice of primitives (as defined in the
/// `is_recursively_primitive_type` function).
pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
match expr_type.kind {
ty::Slice(ref element_type)
| ty::Ref(
_,
ty::TyS {
kind: ty::Slice(ref element_type),
..
},
_,
) => is_recursively_primitive_type(element_type),
_ => false,
}
}

#[macro_export]
macro_rules! unwrap_cargo_metadata {
($cx: ident, $lint: ident, $deps: expr) => {{
Expand Down
7 changes: 7 additions & 0 deletions src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2026,6 +2026,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
deprecation: None,
module: "slow_vector_initialization",
},
Lint {
name: "stable_sort_primitive",
group: "perf",
desc: "use of sort() when sort_unstable() is equivalent",
deprecation: None,
module: "stable_sort_primitive",
},
Lint {
name: "string_add",
group: "restriction",
Expand Down
32 changes: 32 additions & 0 deletions tests/ui/stable_sort_primitive.fixed
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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a test for chars as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test cases for types that deref to a slice of primitives.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe Vec derefs to a slice, so I think the tests already have that last bit covered, unless I don't understand what you're getting at.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you are correct. My bad.

Copy link
Contributor

Choose a reason for hiding this comment

The 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: vec![(2, 1), (1, 3), (3, 2)]

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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();
}
32 changes: 32 additions & 0 deletions tests/ui/stable_sort_primitive.rs
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();
}
46 changes: 46 additions & 0 deletions tests/ui/stable_sort_primitive.stderr
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

2 changes: 2 additions & 0 deletions tests/ui/unnecessary_sort_by.fixed
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() {
Expand Down
2 changes: 2 additions & 0 deletions tests/ui/unnecessary_sort_by.rs
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() {
Expand Down
Loading