]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/stable_sort_primitive.rs
Auto merge of #8100 - c410-f3r:blah-blah-blah, r=giraffate
[rust.git] / clippy_lints / src / stable_sort_primitive.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::{is_slice_of_primitives, sugg::Sugg};
3 use if_chain::if_chain;
4 use rustc_errors::Applicability;
5 use rustc_hir::{Expr, ExprKind};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8
9 declare_clippy_lint! {
10     /// ### What it does
11     /// When sorting primitive values (integers, bools, chars, as well
12     /// as arrays, slices, and tuples of such items), it is better to
13     /// use an unstable sort than a stable sort.
14     ///
15     /// ### Why is this bad?
16     /// Using a stable sort consumes more memory and cpu cycles. Because
17     /// values which compare equal are identical, preserving their
18     /// relative order (the guarantee that a stable sort provides) means
19     /// nothing, while the extra costs still apply.
20     ///
21     /// ### Example
22     /// ```rust
23     /// let mut vec = vec![2, 1, 3];
24     /// vec.sort();
25     /// ```
26     /// Use instead:
27     /// ```rust
28     /// let mut vec = vec![2, 1, 3];
29     /// vec.sort_unstable();
30     /// ```
31     #[clippy::version = "1.47.0"]
32     pub STABLE_SORT_PRIMITIVE,
33     perf,
34     "use of sort() when sort_unstable() is equivalent"
35 }
36
37 declare_lint_pass!(StableSortPrimitive => [STABLE_SORT_PRIMITIVE]);
38
39 /// The three "kinds" of sorts
40 enum SortingKind {
41     Vanilla,
42     /* The other kinds of lint are currently commented out because they
43      * can map distinct values to equal ones. If the key function is
44      * provably one-to-one, or if the Cmp function conserves equality,
45      * then they could be linted on, but I don't know if we can check
46      * for that. */
47
48     /* ByKey,
49      * ByCmp, */
50 }
51 impl SortingKind {
52     /// The name of the stable version of this kind of sort
53     fn stable_name(&self) -> &str {
54         match self {
55             SortingKind::Vanilla => "sort",
56             /* SortingKind::ByKey => "sort_by_key",
57              * SortingKind::ByCmp => "sort_by", */
58         }
59     }
60     /// The name of the unstable version of this kind of sort
61     fn unstable_name(&self) -> &str {
62         match self {
63             SortingKind::Vanilla => "sort_unstable",
64             /* SortingKind::ByKey => "sort_unstable_by_key",
65              * SortingKind::ByCmp => "sort_unstable_by", */
66         }
67     }
68     /// Takes the name of a function call and returns the kind of sort
69     /// that corresponds to that function name (or None if it isn't)
70     fn from_stable_name(name: &str) -> Option<SortingKind> {
71         match name {
72             "sort" => Some(SortingKind::Vanilla),
73             // "sort_by" => Some(SortingKind::ByCmp),
74             // "sort_by_key" => Some(SortingKind::ByKey),
75             _ => None,
76         }
77     }
78 }
79
80 /// A detected instance of this lint
81 struct LintDetection {
82     slice_name: String,
83     method: SortingKind,
84     method_args: String,
85     slice_type: String,
86 }
87
88 fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintDetection> {
89     if_chain! {
90         if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
91         if let Some(slice) = &args.get(0);
92         if let Some(method) = SortingKind::from_stable_name(&method_name.ident.name.as_str());
93         if let Some(slice_type) = is_slice_of_primitives(cx, slice);
94         then {
95             let args_str = args.iter().skip(1).map(|arg| Sugg::hir(cx, arg, "..").to_string()).collect::<Vec<String>>().join(", ");
96             Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str, slice_type })
97         } else {
98             None
99         }
100     }
101 }
102
103 impl LateLintPass<'_> for StableSortPrimitive {
104     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
105         if let Some(detection) = detect_stable_sort_primitive(cx, expr) {
106             span_lint_and_then(
107                 cx,
108                 STABLE_SORT_PRIMITIVE,
109                 expr.span,
110                 format!(
111                     "used `{}` on primitive type `{}`",
112                     detection.method.stable_name(),
113                     detection.slice_type,
114                 )
115                 .as_str(),
116                 |diag| {
117                     diag.span_suggestion(
118                         expr.span,
119                         "try",
120                         format!(
121                             "{}.{}({})",
122                             detection.slice_name,
123                             detection.method.unstable_name(),
124                             detection.method_args,
125                         ),
126                         Applicability::MachineApplicable,
127                     );
128                     diag.note(
129                         "an unstable sort would perform faster without any observable difference for this data type",
130                     );
131                 },
132             );
133         }
134     }
135 }