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