]>
Commit | Line | Data |
---|---|---|
17df50a5 | 1 | use clippy_utils::consts::{constant_simple, Constant}; |
cdc7bbd5 | 2 | use clippy_utils::diagnostics::span_lint; |
2b03887a | 3 | use clippy_utils::is_trait_method; |
f20569fa XL |
4 | use rustc_hir::{Expr, ExprKind}; |
5 | use rustc_lint::{LateContext, LateLintPass}; | |
6 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
5e7ed085 | 7 | use rustc_span::sym; |
f20569fa XL |
8 | use std::cmp::Ordering; |
9 | ||
10 | declare_clippy_lint! { | |
94222f64 XL |
11 | /// ### What it does |
12 | /// Checks for expressions where `std::cmp::min` and `max` are | |
f20569fa XL |
13 | /// used to clamp values, but switched so that the result is constant. |
14 | /// | |
94222f64 XL |
15 | /// ### Why is this bad? |
16 | /// This is in all probability not the intended outcome. At | |
f20569fa XL |
17 | /// the least it hurts readability of the code. |
18 | /// | |
94222f64 | 19 | /// ### Example |
923072b8 | 20 | /// ```rust,ignore |
f20569fa | 21 | /// min(0, max(100, x)) |
923072b8 FG |
22 | /// |
23 | /// // or | |
24 | /// | |
f20569fa XL |
25 | /// x.max(100).min(0) |
26 | /// ``` | |
27 | /// It will always be equal to `0`. Probably the author meant to clamp the value | |
28 | /// between 0 and 100, but has erroneously swapped `min` and `max`. | |
a2a8927a | 29 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
30 | pub MIN_MAX, |
31 | correctness, | |
32 | "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant" | |
33 | } | |
34 | ||
35 | declare_lint_pass!(MinMaxPass => [MIN_MAX]); | |
36 | ||
37 | impl<'tcx> LateLintPass<'tcx> for MinMaxPass { | |
38 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
39 | if let Some((outer_max, outer_c, oe)) = min_max(cx, expr) { | |
40 | if let Some((inner_max, inner_c, ie)) = min_max(cx, oe) { | |
41 | if outer_max == inner_max { | |
42 | return; | |
43 | } | |
44 | match ( | |
45 | outer_max, | |
46 | Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c), | |
47 | ) { | |
48 | (_, None) | (MinMax::Max, Some(Ordering::Less)) | (MinMax::Min, Some(Ordering::Greater)) => (), | |
49 | _ => { | |
50 | span_lint( | |
51 | cx, | |
52 | MIN_MAX, | |
53 | expr.span, | |
54 | "this `min`/`max` combination leads to constant result", | |
55 | ); | |
56 | }, | |
57 | } | |
58 | } | |
59 | } | |
60 | } | |
61 | } | |
62 | ||
63 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] | |
64 | enum MinMax { | |
65 | Min, | |
66 | Max, | |
67 | } | |
68 | ||
fe692bf9 | 69 | fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> { |
f20569fa | 70 | match expr.kind { |
cdc7bbd5 | 71 | ExprKind::Call(path, args) => { |
f20569fa XL |
72 | if let ExprKind::Path(ref qpath) = path.kind { |
73 | cx.typeck_results() | |
74 | .qpath_res(qpath, path.hir_id) | |
75 | .opt_def_id() | |
5e7ed085 | 76 | .and_then(|def_id| match cx.tcx.get_diagnostic_name(def_id) { |
f2b60f7d FG |
77 | Some(sym::cmp_min) => fetch_const(cx, None, args, MinMax::Min), |
78 | Some(sym::cmp_max) => fetch_const(cx, None, args, MinMax::Max), | |
5e7ed085 | 79 | _ => None, |
f20569fa XL |
80 | }) |
81 | } else { | |
82 | None | |
83 | } | |
84 | }, | |
f2b60f7d | 85 | ExprKind::MethodCall(path, receiver, args @ [_], _) => { |
2b03887a | 86 | if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) { |
f2b60f7d FG |
87 | if path.ident.name == sym!(max) { |
88 | fetch_const(cx, Some(receiver), args, MinMax::Max) | |
89 | } else if path.ident.name == sym!(min) { | |
90 | fetch_const(cx, Some(receiver), args, MinMax::Min) | |
f20569fa XL |
91 | } else { |
92 | None | |
93 | } | |
f2b60f7d FG |
94 | } else { |
95 | None | |
f20569fa XL |
96 | } |
97 | }, | |
98 | _ => None, | |
99 | } | |
100 | } | |
101 | ||
fe692bf9 FG |
102 | fn fetch_const<'a, 'tcx>( |
103 | cx: &LateContext<'tcx>, | |
f2b60f7d FG |
104 | receiver: Option<&'a Expr<'a>>, |
105 | args: &'a [Expr<'a>], | |
106 | m: MinMax, | |
fe692bf9 | 107 | ) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> { |
f2b60f7d FG |
108 | let mut args = receiver.into_iter().chain(args); |
109 | let first_arg = args.next()?; | |
110 | let second_arg = args.next()?; | |
111 | if args.next().is_some() { | |
f20569fa XL |
112 | return None; |
113 | } | |
f2b60f7d FG |
114 | constant_simple(cx, cx.typeck_results(), first_arg).map_or_else( |
115 | || constant_simple(cx, cx.typeck_results(), second_arg).map(|c| (m, c, first_arg)), | |
f20569fa | 116 | |c| { |
f2b60f7d | 117 | if constant_simple(cx, cx.typeck_results(), second_arg).is_none() { |
f20569fa | 118 | // otherwise ignore |
f2b60f7d | 119 | Some((m, c, second_arg)) |
f20569fa XL |
120 | } else { |
121 | None | |
122 | } | |
123 | }, | |
124 | ) | |
125 | } |