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