]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/manual_clamp.rs
Rollup merge of #103531 - chenyukang:yukang/fix-103474, r=estebank
[rust.git] / src / tools / clippy / clippy_lints / src / manual_clamp.rs
1 use itertools::Itertools;
2 use rustc_errors::Diagnostic;
3 use rustc_hir::{
4     def::Res, Arm, BinOpKind, Block, Expr, ExprKind, Guard, HirId, PatKind, PathSegment, PrimTy, QPath, StmtKind,
5 };
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_middle::ty::Ty;
8 use rustc_semver::RustcVersion;
9 use rustc_session::{declare_tool_lint, impl_lint_pass};
10 use rustc_span::{symbol::sym, Span};
11 use std::ops::Deref;
12
13 use clippy_utils::{
14     diagnostics::{span_lint_and_then, span_lint_hir_and_then},
15     eq_expr_value,
16     higher::If,
17     is_diag_trait_item, is_trait_method, meets_msrv, msrvs, path_res, path_to_local_id, peel_blocks,
18     peel_blocks_with_stmt,
19     sugg::Sugg,
20     ty::implements_trait,
21     visitors::is_const_evaluatable,
22     MaybePath,
23 };
24 use rustc_errors::Applicability;
25
26 declare_clippy_lint! {
27     /// ### What it does
28     /// Identifies good opportunities for a clamp function from std or core, and suggests using it.
29     ///
30     /// ### Why is this bad?
31     /// clamp is much shorter, easier to read, and doesn't use any control flow.
32     ///
33     /// ### Known issue(s)
34     /// If the clamped variable is NaN this suggestion will cause the code to propagate NaN
35     /// rather than returning either `max` or `min`.
36     ///
37     /// `clamp` functions will panic if `max < min`, `max.is_nan()`, or `min.is_nan()`.
38     /// Some may consider panicking in these situations to be desirable, but it also may
39     /// introduce panicking where there wasn't any before.
40     ///
41     /// ### Examples
42     /// ```rust
43     /// # let (input, min, max) = (0, -2, 1);
44     /// if input > max {
45     ///     max
46     /// } else if input < min {
47     ///     min
48     /// } else {
49     ///     input
50     /// }
51     /// # ;
52     /// ```
53     ///
54     /// ```rust
55     /// # let (input, min, max) = (0, -2, 1);
56     /// input.max(min).min(max)
57     /// # ;
58     /// ```
59     ///
60     /// ```rust
61     /// # let (input, min, max) = (0, -2, 1);
62     /// match input {
63     ///     x if x > max => max,
64     ///     x if x < min => min,
65     ///     x => x,
66     /// }
67     /// # ;
68     /// ```
69     ///
70     /// ```rust
71     /// # let (input, min, max) = (0, -2, 1);
72     /// let mut x = input;
73     /// if x < min { x = min; }
74     /// if x > max { x = max; }
75     /// ```
76     /// Use instead:
77     /// ```rust
78     /// # let (input, min, max) = (0, -2, 1);
79     /// input.clamp(min, max)
80     /// # ;
81     /// ```
82     #[clippy::version = "1.66.0"]
83     pub MANUAL_CLAMP,
84     complexity,
85     "using a clamp pattern instead of the clamp function"
86 }
87 impl_lint_pass!(ManualClamp => [MANUAL_CLAMP]);
88
89 pub struct ManualClamp {
90     msrv: Option<RustcVersion>,
91 }
92
93 impl ManualClamp {
94     pub fn new(msrv: Option<RustcVersion>) -> Self {
95         Self { msrv }
96     }
97 }
98
99 #[derive(Debug)]
100 struct ClampSuggestion<'tcx> {
101     params: InputMinMax<'tcx>,
102     span: Span,
103     make_assignment: Option<&'tcx Expr<'tcx>>,
104     hir_with_ignore_attr: Option<HirId>,
105 }
106
107 #[derive(Debug)]
108 struct InputMinMax<'tcx> {
109     input: &'tcx Expr<'tcx>,
110     min: &'tcx Expr<'tcx>,
111     max: &'tcx Expr<'tcx>,
112     is_float: bool,
113 }
114
115 impl<'tcx> LateLintPass<'tcx> for ManualClamp {
116     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
117         if !meets_msrv(self.msrv, msrvs::CLAMP) {
118             return;
119         }
120         if !expr.span.from_expansion() {
121             let suggestion = is_if_elseif_else_pattern(cx, expr)
122                 .or_else(|| is_max_min_pattern(cx, expr))
123                 .or_else(|| is_call_max_min_pattern(cx, expr))
124                 .or_else(|| is_match_pattern(cx, expr))
125                 .or_else(|| is_if_elseif_pattern(cx, expr));
126             if let Some(suggestion) = suggestion {
127                 emit_suggestion(cx, &suggestion);
128             }
129         }
130     }
131
132     fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
133         if !meets_msrv(self.msrv, msrvs::CLAMP) {
134             return;
135         }
136         for suggestion in is_two_if_pattern(cx, block) {
137             emit_suggestion(cx, &suggestion);
138         }
139     }
140     extract_msrv_attr!(LateContext);
141 }
142
143 fn emit_suggestion<'tcx>(cx: &LateContext<'tcx>, suggestion: &ClampSuggestion<'tcx>) {
144     let ClampSuggestion {
145         params: InputMinMax {
146             input,
147             min,
148             max,
149             is_float,
150         },
151         span,
152         make_assignment,
153         hir_with_ignore_attr,
154     } = suggestion;
155     let input = Sugg::hir(cx, input, "..").maybe_par();
156     let min = Sugg::hir(cx, min, "..");
157     let max = Sugg::hir(cx, max, "..");
158     let semicolon = if make_assignment.is_some() { ";" } else { "" };
159     let assignment = if let Some(assignment) = make_assignment {
160         let assignment = Sugg::hir(cx, assignment, "..");
161         format!("{assignment} = ")
162     } else {
163         String::new()
164     };
165     let suggestion = format!("{assignment}{input}.clamp({min}, {max}){semicolon}");
166     let msg = "clamp-like pattern without using clamp function";
167     let lint_builder = |d: &mut Diagnostic| {
168         d.span_suggestion(*span, "replace with clamp", suggestion, Applicability::MaybeIncorrect);
169         if *is_float {
170             d.note("clamp will panic if max < min, min.is_nan(), or max.is_nan()")
171                 .note("clamp returns NaN if the input is NaN");
172         } else {
173             d.note("clamp will panic if max < min");
174         }
175     };
176     if let Some(hir_id) = hir_with_ignore_attr {
177         span_lint_hir_and_then(cx, MANUAL_CLAMP, *hir_id, *span, msg, lint_builder);
178     } else {
179         span_lint_and_then(cx, MANUAL_CLAMP, *span, msg, lint_builder);
180     }
181 }
182
183 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
184 enum TypeClampability {
185     Float,
186     Ord,
187 }
188
189 impl TypeClampability {
190     fn is_clampable<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<TypeClampability> {
191         if ty.is_floating_point() {
192             Some(TypeClampability::Float)
193         } else if cx
194             .tcx
195             .get_diagnostic_item(sym::Ord)
196             .map_or(false, |id| implements_trait(cx, ty, id, &[]))
197         {
198             Some(TypeClampability::Ord)
199         } else {
200             None
201         }
202     }
203
204     fn is_float(self) -> bool {
205         matches!(self, TypeClampability::Float)
206     }
207 }
208
209 /// Targets patterns like
210 ///
211 /// ```
212 /// # let (input, min, max) = (0, -3, 12);
213 ///
214 /// if input < min {
215 ///     min
216 /// } else if input > max {
217 ///     max
218 /// } else {
219 ///     input
220 /// }
221 /// # ;
222 /// ```
223 fn is_if_elseif_else_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
224     if let Some(If {
225         cond,
226         then,
227         r#else: Some(else_if),
228     }) = If::hir(expr)
229     && let Some(If {
230         cond: else_if_cond,
231         then: else_if_then,
232         r#else: Some(else_body),
233     }) = If::hir(peel_blocks(else_if))
234     {
235         let params = is_clamp_meta_pattern(
236             cx,
237             &BinaryOp::new(peel_blocks(cond))?,
238             &BinaryOp::new(peel_blocks(else_if_cond))?,
239             peel_blocks(then),
240             peel_blocks(else_if_then),
241             None,
242         )?;
243         // Contents of the else should be the resolved input.
244         if !eq_expr_value(cx, params.input, peel_blocks(else_body)) {
245             return None;
246         }
247         Some(ClampSuggestion {
248             params,
249             span: expr.span,
250             make_assignment: None,
251             hir_with_ignore_attr: None,
252         })
253     } else {
254         None
255     }
256 }
257
258 /// Targets patterns like
259 ///
260 /// ```
261 /// # let (input, min_value, max_value) = (0, -3, 12);
262 ///
263 /// input.max(min_value).min(max_value)
264 /// # ;
265 /// ```
266 fn is_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
267     if let ExprKind::MethodCall(seg_second, receiver, [arg_second], _) = &expr.kind
268         && (cx.typeck_results().expr_ty_adjusted(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord))
269         && let ExprKind::MethodCall(seg_first, input, [arg_first], _) = &receiver.kind
270         && (cx.typeck_results().expr_ty_adjusted(input).is_floating_point() || is_trait_method(cx, receiver, sym::Ord))
271     {
272         let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point();
273         let (min, max) = match (seg_first.ident.as_str(), seg_second.ident.as_str()) {
274             ("min", "max") => (arg_second, arg_first),
275             ("max", "min") => (arg_first, arg_second),
276             _ => return None,
277         };
278         Some(ClampSuggestion {
279             params: InputMinMax { input, min, max, is_float },
280             span: expr.span,
281             make_assignment: None,
282             hir_with_ignore_attr: None,
283         })
284     } else {
285         None
286     }
287 }
288
289 /// Targets patterns like
290 ///
291 /// ```
292 /// # let (input, min_value, max_value) = (0, -3, 12);
293 /// # use std::cmp::{max, min};
294 /// min(max(input, min_value), max_value)
295 /// # ;
296 /// ```
297 fn is_call_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
298     fn segment<'tcx>(cx: &LateContext<'_>, func: &Expr<'tcx>) -> Option<FunctionType<'tcx>> {
299         match func.kind {
300             ExprKind::Path(QPath::Resolved(None, path)) => {
301                 let id = path.res.opt_def_id()?;
302                 match cx.tcx.get_diagnostic_name(id) {
303                     Some(sym::cmp_min) => Some(FunctionType::CmpMin),
304                     Some(sym::cmp_max) => Some(FunctionType::CmpMax),
305                     _ if is_diag_trait_item(cx, id, sym::Ord) => {
306                         Some(FunctionType::OrdOrFloat(path.segments.last().expect("infallible")))
307                     },
308                     _ => None,
309                 }
310             },
311             ExprKind::Path(QPath::TypeRelative(ty, seg)) => {
312                 matches!(path_res(cx, ty), Res::PrimTy(PrimTy::Float(_))).then(|| FunctionType::OrdOrFloat(seg))
313             },
314             _ => None,
315         }
316     }
317
318     enum FunctionType<'tcx> {
319         CmpMin,
320         CmpMax,
321         OrdOrFloat(&'tcx PathSegment<'tcx>),
322     }
323
324     fn check<'tcx>(
325         cx: &LateContext<'tcx>,
326         outer_fn: &'tcx Expr<'tcx>,
327         inner_call: &'tcx Expr<'tcx>,
328         outer_arg: &'tcx Expr<'tcx>,
329         span: Span,
330     ) -> Option<ClampSuggestion<'tcx>> {
331         if let ExprKind::Call(inner_fn, [first, second]) = &inner_call.kind
332             && let Some(inner_seg) = segment(cx, inner_fn)
333             && let Some(outer_seg) = segment(cx, outer_fn)
334         {
335             let (input, inner_arg) = match (is_const_evaluatable(cx, first), is_const_evaluatable(cx, second)) {
336                 (true, false) => (second, first),
337                 (false, true) => (first, second),
338                 _ => return None,
339             };
340             let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point();
341             let (min, max) = match (inner_seg, outer_seg) {
342                 (FunctionType::CmpMin, FunctionType::CmpMax) => (outer_arg, inner_arg),
343                 (FunctionType::CmpMax, FunctionType::CmpMin) => (inner_arg, outer_arg),
344                 (FunctionType::OrdOrFloat(first_segment), FunctionType::OrdOrFloat(second_segment)) => {
345                     match (first_segment.ident.as_str(), second_segment.ident.as_str()) {
346                         ("min", "max") => (outer_arg, inner_arg),
347                         ("max", "min") => (inner_arg, outer_arg),
348                         _ => return None,
349                     }
350                 }
351                 _ => return None,
352             };
353             Some(ClampSuggestion {
354                 params: InputMinMax { input, min, max, is_float },
355                 span,
356                 make_assignment: None,
357                 hir_with_ignore_attr: None,
358             })
359         } else {
360             None
361         }
362     }
363
364     if let ExprKind::Call(outer_fn, [first, second]) = &expr.kind {
365         check(cx, outer_fn, first, second, expr.span).or_else(|| check(cx, outer_fn, second, first, expr.span))
366     } else {
367         None
368     }
369 }
370
371 /// Targets patterns like
372 ///
373 /// ```
374 /// # let (input, min, max) = (0, -3, 12);
375 ///
376 /// match input {
377 ///     input if input > max => max,
378 ///     input if input < min => min,
379 ///     input => input,
380 /// }
381 /// # ;
382 /// ```
383 fn is_match_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
384     if let ExprKind::Match(value, [first_arm, second_arm, last_arm], rustc_hir::MatchSource::Normal) = &expr.kind {
385         // Find possible min/max branches
386         let minmax_values = |a: &'tcx Arm<'tcx>| {
387             if let PatKind::Binding(_, var_hir_id, _, None) = &a.pat.kind
388             && let Some(Guard::If(e)) = a.guard {
389                 Some((e, var_hir_id, a.body))
390             } else {
391                 None
392             }
393         };
394         let (first, first_hir_id, first_expr) = minmax_values(first_arm)?;
395         let (second, second_hir_id, second_expr) = minmax_values(second_arm)?;
396         let first = BinaryOp::new(first)?;
397         let second = BinaryOp::new(second)?;
398         if let PatKind::Binding(_, binding, _, None) = &last_arm.pat.kind
399             && path_to_local_id(peel_blocks_with_stmt(last_arm.body), *binding)
400             && last_arm.guard.is_none()
401         {
402             // Proceed as normal
403         } else {
404             return None;
405         }
406         if let Some(params) = is_clamp_meta_pattern(
407             cx,
408             &first,
409             &second,
410             first_expr,
411             second_expr,
412             Some((*first_hir_id, *second_hir_id)),
413         ) {
414             return Some(ClampSuggestion {
415                 params: InputMinMax {
416                     input: value,
417                     min: params.min,
418                     max: params.max,
419                     is_float: params.is_float,
420                 },
421                 span: expr.span,
422                 make_assignment: None,
423                 hir_with_ignore_attr: None,
424             });
425         }
426     }
427     None
428 }
429
430 /// Targets patterns like
431 ///
432 /// ```
433 /// # let (input, min, max) = (0, -3, 12);
434 ///
435 /// let mut x = input;
436 /// if x < min { x = min; }
437 /// if x > max { x = max; }
438 /// ```
439 fn is_two_if_pattern<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Vec<ClampSuggestion<'tcx>> {
440     block_stmt_with_last(block)
441         .tuple_windows()
442         .filter_map(|(maybe_set_first, maybe_set_second)| {
443             if let StmtKind::Expr(first_expr) = *maybe_set_first
444                 && let StmtKind::Expr(second_expr) = *maybe_set_second
445                 && let Some(If { cond: first_cond, then: first_then, r#else: None }) = If::hir(first_expr)
446                 && let Some(If { cond: second_cond, then: second_then, r#else: None }) = If::hir(second_expr)
447                 && let ExprKind::Assign(
448                     maybe_input_first_path,
449                     maybe_min_max_first,
450                     _
451                 ) = peel_blocks_with_stmt(first_then).kind
452                 && let ExprKind::Assign(
453                     maybe_input_second_path,
454                     maybe_min_max_second,
455                     _
456                 ) = peel_blocks_with_stmt(second_then).kind
457                 && eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path)
458                 && let Some(first_bin) = BinaryOp::new(first_cond)
459                 && let Some(second_bin) = BinaryOp::new(second_cond)
460                 && let Some(input_min_max) = is_clamp_meta_pattern(
461                     cx,
462                     &first_bin,
463                     &second_bin,
464                     maybe_min_max_first,
465                     maybe_min_max_second,
466                     None
467                 )
468             {
469                 Some(ClampSuggestion {
470                     params: InputMinMax {
471                         input: maybe_input_first_path,
472                         min: input_min_max.min,
473                         max: input_min_max.max,
474                         is_float: input_min_max.is_float,
475                     },
476                     span: first_expr.span.to(second_expr.span),
477                     make_assignment: Some(maybe_input_first_path),
478                     hir_with_ignore_attr: Some(first_expr.hir_id()),
479                 })
480             } else {
481                 None
482             }
483         })
484         .collect()
485 }
486
487 /// Targets patterns like
488 ///
489 /// ```
490 /// # let (mut input, min, max) = (0, -3, 12);
491 ///
492 /// if input < min {
493 ///     input = min;
494 /// } else if input > max {
495 ///     input = max;
496 /// }
497 /// ```
498 fn is_if_elseif_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
499     if let Some(If {
500         cond,
501         then,
502         r#else: Some(else_if),
503     }) = If::hir(expr)
504         && let Some(If {
505             cond: else_if_cond,
506             then: else_if_then,
507             r#else: None,
508         }) = If::hir(peel_blocks(else_if))
509         && let ExprKind::Assign(
510             maybe_input_first_path,
511             maybe_min_max_first,
512             _
513         ) = peel_blocks_with_stmt(then).kind
514         && let ExprKind::Assign(
515             maybe_input_second_path,
516             maybe_min_max_second,
517             _
518         ) = peel_blocks_with_stmt(else_if_then).kind
519     {
520         let params = is_clamp_meta_pattern(
521             cx,
522             &BinaryOp::new(peel_blocks(cond))?,
523             &BinaryOp::new(peel_blocks(else_if_cond))?,
524             peel_blocks(maybe_min_max_first),
525             peel_blocks(maybe_min_max_second),
526             None,
527         )?;
528         if !eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path) {
529             return None;
530         }
531         Some(ClampSuggestion {
532             params,
533             span: expr.span,
534             make_assignment: Some(maybe_input_first_path),
535             hir_with_ignore_attr: None,
536         })
537     } else {
538         None
539     }
540 }
541
542 /// `ExprKind::Binary` but more narrowly typed
543 #[derive(Debug, Clone, Copy)]
544 struct BinaryOp<'tcx> {
545     op: BinOpKind,
546     left: &'tcx Expr<'tcx>,
547     right: &'tcx Expr<'tcx>,
548 }
549
550 impl<'tcx> BinaryOp<'tcx> {
551     fn new(e: &'tcx Expr<'tcx>) -> Option<BinaryOp<'tcx>> {
552         match &e.kind {
553             ExprKind::Binary(op, left, right) => Some(BinaryOp {
554                 op: op.node,
555                 left,
556                 right,
557             }),
558             _ => None,
559         }
560     }
561
562     fn flip(&self) -> Self {
563         Self {
564             op: match self.op {
565                 BinOpKind::Le => BinOpKind::Ge,
566                 BinOpKind::Lt => BinOpKind::Gt,
567                 BinOpKind::Ge => BinOpKind::Le,
568                 BinOpKind::Gt => BinOpKind::Lt,
569                 other => other,
570             },
571             left: self.right,
572             right: self.left,
573         }
574     }
575 }
576
577 /// The clamp meta pattern is a pattern shared between many (but not all) patterns.
578 /// In summary, this pattern consists of two if statements that meet many criteria,
579 /// - binary operators that are one of [`>`, `<`, `>=`, `<=`].
580 /// - Both binary statements must have a shared argument
581 ///     - Which can appear on the left or right side of either statement
582 ///     - The binary operators must define a finite range for the shared argument. To put this in
583 ///       the terms of Rust `std` library, the following ranges are acceptable
584 ///         - `Range`
585 ///         - `RangeInclusive`
586 ///       And all other range types are not accepted. For the purposes of `clamp` it's irrelevant
587 ///       whether the range is inclusive or not, the output is the same.
588 /// - The result of each if statement must be equal to the argument unique to that if statement. The
589 ///   result can not be the shared argument in either case.
590 fn is_clamp_meta_pattern<'tcx>(
591     cx: &LateContext<'tcx>,
592     first_bin: &BinaryOp<'tcx>,
593     second_bin: &BinaryOp<'tcx>,
594     first_expr: &'tcx Expr<'tcx>,
595     second_expr: &'tcx Expr<'tcx>,
596     // This parameters is exclusively for the match pattern.
597     // It exists because the variable bindings used in that pattern
598     // refer to the variable bound in the match arm, not the variable
599     // bound outside of it. Fortunately due to context we know this has to
600     // be the input variable, not the min or max.
601     input_hir_ids: Option<(HirId, HirId)>,
602 ) -> Option<InputMinMax<'tcx>> {
603     fn check<'tcx>(
604         cx: &LateContext<'tcx>,
605         first_bin: &BinaryOp<'tcx>,
606         second_bin: &BinaryOp<'tcx>,
607         first_expr: &'tcx Expr<'tcx>,
608         second_expr: &'tcx Expr<'tcx>,
609         input_hir_ids: Option<(HirId, HirId)>,
610         is_float: bool,
611     ) -> Option<InputMinMax<'tcx>> {
612         match (&first_bin.op, &second_bin.op) {
613             (BinOpKind::Ge | BinOpKind::Gt, BinOpKind::Le | BinOpKind::Lt) => {
614                 let (min, max) = (second_expr, first_expr);
615                 let refers_to_input = match input_hir_ids {
616                     Some((first_hir_id, second_hir_id)) => {
617                         path_to_local_id(peel_blocks(first_bin.left), first_hir_id)
618                             && path_to_local_id(peel_blocks(second_bin.left), second_hir_id)
619                     },
620                     None => eq_expr_value(cx, first_bin.left, second_bin.left),
621                 };
622                 (refers_to_input
623                     && eq_expr_value(cx, first_bin.right, first_expr)
624                     && eq_expr_value(cx, second_bin.right, second_expr))
625                 .then_some(InputMinMax {
626                     input: first_bin.left,
627                     min,
628                     max,
629                     is_float,
630                 })
631             },
632             _ => None,
633         }
634     }
635     // First filter out any expressions with side effects
636     let exprs = [
637         first_bin.left,
638         first_bin.right,
639         second_bin.left,
640         second_bin.right,
641         first_expr,
642         second_expr,
643     ];
644     let clampability = TypeClampability::is_clampable(cx, cx.typeck_results().expr_ty(first_expr))?;
645     let is_float = clampability.is_float();
646     if exprs.iter().any(|e| peel_blocks(e).can_have_side_effects()) {
647         return None;
648     }
649     if !(is_ord_op(first_bin.op) && is_ord_op(second_bin.op)) {
650         return None;
651     }
652     let cases = [
653         (*first_bin, *second_bin),
654         (first_bin.flip(), second_bin.flip()),
655         (first_bin.flip(), *second_bin),
656         (*first_bin, second_bin.flip()),
657     ];
658
659     cases.into_iter().find_map(|(first, second)| {
660         check(cx, &first, &second, first_expr, second_expr, input_hir_ids, is_float).or_else(|| {
661             check(
662                 cx,
663                 &second,
664                 &first,
665                 second_expr,
666                 first_expr,
667                 input_hir_ids.map(|(l, r)| (r, l)),
668                 is_float,
669             )
670         })
671     })
672 }
673
674 fn block_stmt_with_last<'tcx>(block: &'tcx Block<'tcx>) -> impl Iterator<Item = MaybeBorrowedStmtKind<'tcx>> {
675     block
676         .stmts
677         .iter()
678         .map(|s| MaybeBorrowedStmtKind::Borrowed(&s.kind))
679         .chain(
680             block
681                 .expr
682                 .as_ref()
683                 .map(|e| MaybeBorrowedStmtKind::Owned(StmtKind::Expr(e))),
684         )
685 }
686
687 fn is_ord_op(op: BinOpKind) -> bool {
688     matches!(op, BinOpKind::Ge | BinOpKind::Gt | BinOpKind::Le | BinOpKind::Lt)
689 }
690
691 /// Really similar to Cow, but doesn't have a `Clone` requirement.
692 #[derive(Debug)]
693 enum MaybeBorrowedStmtKind<'a> {
694     Borrowed(&'a StmtKind<'a>),
695     Owned(StmtKind<'a>),
696 }
697
698 impl<'a> Clone for MaybeBorrowedStmtKind<'a> {
699     fn clone(&self) -> Self {
700         match self {
701             Self::Borrowed(t) => Self::Borrowed(t),
702             Self::Owned(StmtKind::Expr(e)) => Self::Owned(StmtKind::Expr(e)),
703             Self::Owned(_) => unreachable!("Owned should only ever contain a StmtKind::Expr."),
704         }
705     }
706 }
707
708 impl<'a> Deref for MaybeBorrowedStmtKind<'a> {
709     type Target = StmtKind<'a>;
710
711     fn deref(&self) -> &Self::Target {
712         match self {
713             Self::Borrowed(t) => t,
714             Self::Owned(t) => t,
715         }
716     }
717 }