| use clippy_config::msrvs::{self, Msrv}; |
| use clippy_utils::consts::{constant, Constant}; |
| use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; |
| use clippy_utils::higher::If; |
| use clippy_utils::sugg::Sugg; |
| use clippy_utils::ty::implements_trait; |
| use clippy_utils::visitors::is_const_evaluatable; |
| use clippy_utils::{ |
| eq_expr_value, in_constant, is_diag_trait_item, is_trait_method, path_res, path_to_local_id, peel_blocks, |
| peel_blocks_with_stmt, MaybePath, |
| }; |
| use itertools::Itertools; |
| use rustc_errors::{Applicability, Diag}; |
| use rustc_hir::def::Res; |
| use rustc_hir::{Arm, BinOpKind, Block, Expr, ExprKind, HirId, PatKind, PathSegment, PrimTy, QPath, StmtKind}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_middle::ty::Ty; |
| use rustc_session::impl_lint_pass; |
| use rustc_span::symbol::sym; |
| use rustc_span::Span; |
| use std::cmp::Ordering; |
| use std::ops::Deref; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Identifies good opportunities for a clamp function from std or core, and suggests using it. |
| /// |
| /// ### Why is this bad? |
| /// clamp is much shorter, easier to read, and doesn't use any control flow. |
| /// |
| /// ### Limitations |
| /// |
| /// This lint will only trigger if max and min are known at compile time, and max is |
| /// greater than min. |
| /// |
| /// ### Known issue(s) |
| /// If the clamped variable is NaN this suggestion will cause the code to propagate NaN |
| /// rather than returning either `max` or `min`. |
| /// |
| /// `clamp` functions will panic if `max < min`, `max.is_nan()`, or `min.is_nan()`. |
| /// Some may consider panicking in these situations to be desirable, but it also may |
| /// introduce panicking where there wasn't any before. |
| /// |
| /// See also [the discussion in the |
| /// PR](https://github.com/rust-lang/rust-clippy/pull/9484#issuecomment-1278922613). |
| /// |
| /// ### Examples |
| /// ```no_run |
| /// # let (input, min, max) = (0, -2, 1); |
| /// if input > max { |
| /// max |
| /// } else if input < min { |
| /// min |
| /// } else { |
| /// input |
| /// } |
| /// # ; |
| /// ``` |
| /// |
| /// ```no_run |
| /// # let (input, min, max) = (0, -2, 1); |
| /// input.max(min).min(max) |
| /// # ; |
| /// ``` |
| /// |
| /// ```no_run |
| /// # let (input, min, max) = (0, -2, 1); |
| /// match input { |
| /// x if x > max => max, |
| /// x if x < min => min, |
| /// x => x, |
| /// } |
| /// # ; |
| /// ``` |
| /// |
| /// ```no_run |
| /// # let (input, min, max) = (0, -2, 1); |
| /// let mut x = input; |
| /// if x < min { x = min; } |
| /// if x > max { x = max; } |
| /// ``` |
| /// Use instead: |
| /// ```no_run |
| /// # let (input, min, max) = (0, -2, 1); |
| /// input.clamp(min, max) |
| /// # ; |
| /// ``` |
| #[clippy::version = "1.66.0"] |
| pub MANUAL_CLAMP, |
| complexity, |
| "using a clamp pattern instead of the clamp function" |
| } |
| impl_lint_pass!(ManualClamp => [MANUAL_CLAMP]); |
| |
| pub struct ManualClamp { |
| msrv: Msrv, |
| } |
| |
| impl ManualClamp { |
| pub fn new(msrv: Msrv) -> Self { |
| Self { msrv } |
| } |
| } |
| |
| #[derive(Debug)] |
| struct ClampSuggestion<'tcx> { |
| params: InputMinMax<'tcx>, |
| span: Span, |
| make_assignment: Option<&'tcx Expr<'tcx>>, |
| hir_with_ignore_attr: Option<HirId>, |
| } |
| |
| impl<'tcx> ClampSuggestion<'tcx> { |
| /// This function will return true if and only if you can demonstrate at compile time that min |
| /// is less than max. |
| fn min_less_than_max(&self, cx: &LateContext<'tcx>) -> bool { |
| let max_type = cx.typeck_results().expr_ty(self.params.max); |
| let min_type = cx.typeck_results().expr_ty(self.params.min); |
| if max_type != min_type { |
| return false; |
| } |
| if let Some(max) = constant(cx, cx.typeck_results(), self.params.max) |
| && let Some(min) = constant(cx, cx.typeck_results(), self.params.min) |
| && let Some(ord) = Constant::partial_cmp(cx.tcx, max_type, &min, &max) |
| { |
| ord != Ordering::Greater |
| } else { |
| false |
| } |
| } |
| } |
| |
| #[derive(Debug)] |
| struct InputMinMax<'tcx> { |
| input: &'tcx Expr<'tcx>, |
| min: &'tcx Expr<'tcx>, |
| max: &'tcx Expr<'tcx>, |
| is_float: bool, |
| } |
| |
| impl<'tcx> LateLintPass<'tcx> for ManualClamp { |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
| if !self.msrv.meets(msrvs::CLAMP) { |
| return; |
| } |
| if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) { |
| let suggestion = is_if_elseif_else_pattern(cx, expr) |
| .or_else(|| is_max_min_pattern(cx, expr)) |
| .or_else(|| is_call_max_min_pattern(cx, expr)) |
| .or_else(|| is_match_pattern(cx, expr)) |
| .or_else(|| is_if_elseif_pattern(cx, expr)); |
| if let Some(suggestion) = suggestion { |
| maybe_emit_suggestion(cx, &suggestion); |
| } |
| } |
| } |
| |
| fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) { |
| if !self.msrv.meets(msrvs::CLAMP) || in_constant(cx, block.hir_id) { |
| return; |
| } |
| for suggestion in is_two_if_pattern(cx, block) { |
| maybe_emit_suggestion(cx, &suggestion); |
| } |
| } |
| extract_msrv_attr!(LateContext); |
| } |
| |
| fn maybe_emit_suggestion<'tcx>(cx: &LateContext<'tcx>, suggestion: &ClampSuggestion<'tcx>) { |
| if !suggestion.min_less_than_max(cx) { |
| return; |
| } |
| let ClampSuggestion { |
| params: InputMinMax { |
| input, |
| min, |
| max, |
| is_float, |
| }, |
| span, |
| make_assignment, |
| hir_with_ignore_attr, |
| } = suggestion; |
| let input = Sugg::hir(cx, input, "..").maybe_par(); |
| let min = Sugg::hir(cx, min, ".."); |
| let max = Sugg::hir(cx, max, ".."); |
| let semicolon = if make_assignment.is_some() { ";" } else { "" }; |
| let assignment = if let Some(assignment) = make_assignment { |
| let assignment = Sugg::hir(cx, assignment, ".."); |
| format!("{assignment} = ") |
| } else { |
| String::new() |
| }; |
| let suggestion = format!("{assignment}{input}.clamp({min}, {max}){semicolon}"); |
| let msg = "clamp-like pattern without using clamp function"; |
| let lint_builder = |d: &mut Diag<'_, ()>| { |
| d.span_suggestion(*span, "replace with clamp", suggestion, Applicability::MaybeIncorrect); |
| if *is_float { |
| d.note("clamp will panic if max < min, min.is_nan(), or max.is_nan()") |
| .note("clamp returns NaN if the input is NaN"); |
| } else { |
| d.note("clamp will panic if max < min"); |
| } |
| }; |
| if let Some(hir_id) = hir_with_ignore_attr { |
| span_lint_hir_and_then(cx, MANUAL_CLAMP, *hir_id, *span, msg, lint_builder); |
| } else { |
| span_lint_and_then(cx, MANUAL_CLAMP, *span, msg, lint_builder); |
| } |
| } |
| |
| #[derive(Debug, Copy, Clone, Eq, PartialEq)] |
| enum TypeClampability { |
| Float, |
| Ord, |
| } |
| |
| impl TypeClampability { |
| fn is_clampable<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<TypeClampability> { |
| if ty.is_floating_point() { |
| Some(TypeClampability::Float) |
| } else if cx |
| .tcx |
| .get_diagnostic_item(sym::Ord) |
| .map_or(false, |id| implements_trait(cx, ty, id, &[])) |
| { |
| Some(TypeClampability::Ord) |
| } else { |
| None |
| } |
| } |
| |
| fn is_float(self) -> bool { |
| matches!(self, TypeClampability::Float) |
| } |
| } |
| |
| /// Targets patterns like |
| /// |
| /// ```no_run |
| /// # let (input, min, max) = (0, -3, 12); |
| /// |
| /// if input < min { |
| /// min |
| /// } else if input > max { |
| /// max |
| /// } else { |
| /// input |
| /// } |
| /// # ; |
| /// ``` |
| fn is_if_elseif_else_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> { |
| if let Some(If { |
| cond, |
| then, |
| r#else: Some(else_if), |
| }) = If::hir(expr) |
| && let Some(If { |
| cond: else_if_cond, |
| then: else_if_then, |
| r#else: Some(else_body), |
| }) = If::hir(peel_blocks(else_if)) |
| { |
| let params = is_clamp_meta_pattern( |
| cx, |
| &BinaryOp::new(peel_blocks(cond))?, |
| &BinaryOp::new(peel_blocks(else_if_cond))?, |
| peel_blocks(then), |
| peel_blocks(else_if_then), |
| None, |
| )?; |
| // Contents of the else should be the resolved input. |
| if !eq_expr_value(cx, params.input, peel_blocks(else_body)) { |
| return None; |
| } |
| Some(ClampSuggestion { |
| params, |
| span: expr.span, |
| make_assignment: None, |
| hir_with_ignore_attr: None, |
| }) |
| } else { |
| None |
| } |
| } |
| |
| /// Targets patterns like |
| /// |
| /// ```no_run |
| /// # let (input, min_value, max_value) = (0, -3, 12); |
| /// |
| /// input.max(min_value).min(max_value) |
| /// # ; |
| /// ``` |
| fn is_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> { |
| if let ExprKind::MethodCall(seg_second, receiver, [arg_second], _) = &expr.kind |
| && (cx.typeck_results().expr_ty_adjusted(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord)) |
| && let ExprKind::MethodCall(seg_first, input, [arg_first], _) = &receiver.kind |
| && (cx.typeck_results().expr_ty_adjusted(input).is_floating_point() || is_trait_method(cx, receiver, sym::Ord)) |
| { |
| let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point(); |
| let (min, max) = match (seg_first.ident.as_str(), seg_second.ident.as_str()) { |
| ("min", "max") => (arg_second, arg_first), |
| ("max", "min") => (arg_first, arg_second), |
| _ => return None, |
| }; |
| Some(ClampSuggestion { |
| params: InputMinMax { |
| input, |
| min, |
| max, |
| is_float, |
| }, |
| span: expr.span, |
| make_assignment: None, |
| hir_with_ignore_attr: None, |
| }) |
| } else { |
| None |
| } |
| } |
| |
| /// Targets patterns like |
| /// |
| /// ```no_run |
| /// # let (input, min_value, max_value) = (0, -3, 12); |
| /// # use std::cmp::{max, min}; |
| /// min(max(input, min_value), max_value) |
| /// # ; |
| /// ``` |
| fn is_call_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> { |
| fn segment<'tcx>(cx: &LateContext<'_>, func: &Expr<'tcx>) -> Option<FunctionType<'tcx>> { |
| match func.kind { |
| ExprKind::Path(QPath::Resolved(None, path)) => { |
| let id = path.res.opt_def_id()?; |
| match cx.tcx.get_diagnostic_name(id) { |
| Some(sym::cmp_min) => Some(FunctionType::CmpMin), |
| Some(sym::cmp_max) => Some(FunctionType::CmpMax), |
| _ if is_diag_trait_item(cx, id, sym::Ord) => { |
| Some(FunctionType::OrdOrFloat(path.segments.last().expect("infallible"))) |
| }, |
| _ => None, |
| } |
| }, |
| ExprKind::Path(QPath::TypeRelative(ty, seg)) => { |
| matches!(path_res(cx, ty), Res::PrimTy(PrimTy::Float(_))).then(|| FunctionType::OrdOrFloat(seg)) |
| }, |
| _ => None, |
| } |
| } |
| |
| enum FunctionType<'tcx> { |
| CmpMin, |
| CmpMax, |
| OrdOrFloat(&'tcx PathSegment<'tcx>), |
| } |
| |
| fn check<'tcx>( |
| cx: &LateContext<'tcx>, |
| outer_fn: &'tcx Expr<'tcx>, |
| inner_call: &'tcx Expr<'tcx>, |
| outer_arg: &'tcx Expr<'tcx>, |
| span: Span, |
| ) -> Option<ClampSuggestion<'tcx>> { |
| if let ExprKind::Call(inner_fn, [first, second]) = &inner_call.kind |
| && let Some(inner_seg) = segment(cx, inner_fn) |
| && let Some(outer_seg) = segment(cx, outer_fn) |
| { |
| let (input, inner_arg) = match (is_const_evaluatable(cx, first), is_const_evaluatable(cx, second)) { |
| (true, false) => (second, first), |
| (false, true) => (first, second), |
| _ => return None, |
| }; |
| let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point(); |
| let (min, max) = match (inner_seg, outer_seg) { |
| (FunctionType::CmpMin, FunctionType::CmpMax) => (outer_arg, inner_arg), |
| (FunctionType::CmpMax, FunctionType::CmpMin) => (inner_arg, outer_arg), |
| (FunctionType::OrdOrFloat(first_segment), FunctionType::OrdOrFloat(second_segment)) => { |
| match (first_segment.ident.as_str(), second_segment.ident.as_str()) { |
| ("min", "max") => (outer_arg, inner_arg), |
| ("max", "min") => (inner_arg, outer_arg), |
| _ => return None, |
| } |
| }, |
| _ => return None, |
| }; |
| Some(ClampSuggestion { |
| params: InputMinMax { |
| input, |
| min, |
| max, |
| is_float, |
| }, |
| span, |
| make_assignment: None, |
| hir_with_ignore_attr: None, |
| }) |
| } else { |
| None |
| } |
| } |
| |
| if let ExprKind::Call(outer_fn, [first, second]) = &expr.kind { |
| check(cx, outer_fn, first, second, expr.span).or_else(|| check(cx, outer_fn, second, first, expr.span)) |
| } else { |
| None |
| } |
| } |
| |
| /// Targets patterns like |
| /// |
| /// ```no_run |
| /// # let (input, min, max) = (0, -3, 12); |
| /// |
| /// match input { |
| /// input if input > max => max, |
| /// input if input < min => min, |
| /// input => input, |
| /// } |
| /// # ; |
| /// ``` |
| fn is_match_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> { |
| if let ExprKind::Match(value, [first_arm, second_arm, last_arm], rustc_hir::MatchSource::Normal) = &expr.kind { |
| // Find possible min/max branches |
| let minmax_values = |a: &'tcx Arm<'tcx>| { |
| if let PatKind::Binding(_, var_hir_id, _, None) = &a.pat.kind |
| && let Some(e) = a.guard |
| { |
| Some((e, var_hir_id, a.body)) |
| } else { |
| None |
| } |
| }; |
| let (first, first_hir_id, first_expr) = minmax_values(first_arm)?; |
| let (second, second_hir_id, second_expr) = minmax_values(second_arm)?; |
| let first = BinaryOp::new(first)?; |
| let second = BinaryOp::new(second)?; |
| if let PatKind::Binding(_, binding, _, None) = &last_arm.pat.kind |
| && path_to_local_id(peel_blocks_with_stmt(last_arm.body), *binding) |
| && last_arm.guard.is_none() |
| { |
| // Proceed as normal |
| } else { |
| return None; |
| } |
| if let Some(params) = is_clamp_meta_pattern( |
| cx, |
| &first, |
| &second, |
| first_expr, |
| second_expr, |
| Some((*first_hir_id, *second_hir_id)), |
| ) { |
| return Some(ClampSuggestion { |
| params: InputMinMax { |
| input: value, |
| min: params.min, |
| max: params.max, |
| is_float: params.is_float, |
| }, |
| span: expr.span, |
| make_assignment: None, |
| hir_with_ignore_attr: None, |
| }); |
| } |
| } |
| None |
| } |
| |
| /// Targets patterns like |
| /// |
| /// ```no_run |
| /// # let (input, min, max) = (0, -3, 12); |
| /// |
| /// let mut x = input; |
| /// if x < min { x = min; } |
| /// if x > max { x = max; } |
| /// ``` |
| fn is_two_if_pattern<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Vec<ClampSuggestion<'tcx>> { |
| block_stmt_with_last(block) |
| .tuple_windows() |
| .filter_map(|(maybe_set_first, maybe_set_second)| { |
| if let StmtKind::Expr(first_expr) = *maybe_set_first |
| && let StmtKind::Expr(second_expr) = *maybe_set_second |
| && let Some(If { |
| cond: first_cond, |
| then: first_then, |
| r#else: None, |
| }) = If::hir(first_expr) |
| && let Some(If { |
| cond: second_cond, |
| then: second_then, |
| r#else: None, |
| }) = If::hir(second_expr) |
| && let ExprKind::Assign(maybe_input_first_path, maybe_min_max_first, _) = |
| peel_blocks_with_stmt(first_then).kind |
| && let ExprKind::Assign(maybe_input_second_path, maybe_min_max_second, _) = |
| peel_blocks_with_stmt(second_then).kind |
| && eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path) |
| && let Some(first_bin) = BinaryOp::new(first_cond) |
| && let Some(second_bin) = BinaryOp::new(second_cond) |
| && let Some(input_min_max) = is_clamp_meta_pattern( |
| cx, |
| &first_bin, |
| &second_bin, |
| maybe_min_max_first, |
| maybe_min_max_second, |
| None, |
| ) |
| { |
| Some(ClampSuggestion { |
| params: InputMinMax { |
| input: maybe_input_first_path, |
| min: input_min_max.min, |
| max: input_min_max.max, |
| is_float: input_min_max.is_float, |
| }, |
| span: first_expr.span.to(second_expr.span), |
| make_assignment: Some(maybe_input_first_path), |
| hir_with_ignore_attr: Some(first_expr.hir_id()), |
| }) |
| } else { |
| None |
| } |
| }) |
| .collect() |
| } |
| |
| /// Targets patterns like |
| /// |
| /// ```no_run |
| /// # let (mut input, min, max) = (0, -3, 12); |
| /// |
| /// if input < min { |
| /// input = min; |
| /// } else if input > max { |
| /// input = max; |
| /// } |
| /// ``` |
| fn is_if_elseif_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> { |
| if let Some(If { |
| cond, |
| then, |
| r#else: Some(else_if), |
| }) = If::hir(expr) |
| && let Some(If { |
| cond: else_if_cond, |
| then: else_if_then, |
| r#else: None, |
| }) = If::hir(peel_blocks(else_if)) |
| && let ExprKind::Assign(maybe_input_first_path, maybe_min_max_first, _) = peel_blocks_with_stmt(then).kind |
| && let ExprKind::Assign(maybe_input_second_path, maybe_min_max_second, _) = |
| peel_blocks_with_stmt(else_if_then).kind |
| { |
| let params = is_clamp_meta_pattern( |
| cx, |
| &BinaryOp::new(peel_blocks(cond))?, |
| &BinaryOp::new(peel_blocks(else_if_cond))?, |
| peel_blocks(maybe_min_max_first), |
| peel_blocks(maybe_min_max_second), |
| None, |
| )?; |
| if !eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path) { |
| return None; |
| } |
| Some(ClampSuggestion { |
| params, |
| span: expr.span, |
| make_assignment: Some(maybe_input_first_path), |
| hir_with_ignore_attr: None, |
| }) |
| } else { |
| None |
| } |
| } |
| |
| /// `ExprKind::Binary` but more narrowly typed |
| #[derive(Debug, Clone, Copy)] |
| struct BinaryOp<'tcx> { |
| op: BinOpKind, |
| left: &'tcx Expr<'tcx>, |
| right: &'tcx Expr<'tcx>, |
| } |
| |
| impl<'tcx> BinaryOp<'tcx> { |
| fn new(e: &'tcx Expr<'tcx>) -> Option<BinaryOp<'tcx>> { |
| match &e.kind { |
| ExprKind::Binary(op, left, right) => Some(BinaryOp { |
| op: op.node, |
| left, |
| right, |
| }), |
| _ => None, |
| } |
| } |
| |
| fn flip(&self) -> Self { |
| Self { |
| op: match self.op { |
| BinOpKind::Le => BinOpKind::Ge, |
| BinOpKind::Lt => BinOpKind::Gt, |
| BinOpKind::Ge => BinOpKind::Le, |
| BinOpKind::Gt => BinOpKind::Lt, |
| other => other, |
| }, |
| left: self.right, |
| right: self.left, |
| } |
| } |
| } |
| |
| /// The clamp meta pattern is a pattern shared between many (but not all) patterns. |
| /// In summary, this pattern consists of two if statements that meet many criteria, |
| /// |
| /// - binary operators that are one of [`>`, `<`, `>=`, `<=`]. |
| /// |
| /// - Both binary statements must have a shared argument |
| /// |
| /// - Which can appear on the left or right side of either statement |
| /// |
| /// - The binary operators must define a finite range for the shared argument. To put this in |
| /// the terms of Rust `std` library, the following ranges are acceptable |
| /// |
| /// - `Range` |
| /// - `RangeInclusive` |
| /// |
| /// And all other range types are not accepted. For the purposes of `clamp` it's irrelevant |
| /// whether the range is inclusive or not, the output is the same. |
| /// |
| /// - The result of each if statement must be equal to the argument unique to that if statement. The |
| /// result can not be the shared argument in either case. |
| fn is_clamp_meta_pattern<'tcx>( |
| cx: &LateContext<'tcx>, |
| first_bin: &BinaryOp<'tcx>, |
| second_bin: &BinaryOp<'tcx>, |
| first_expr: &'tcx Expr<'tcx>, |
| second_expr: &'tcx Expr<'tcx>, |
| // This parameters is exclusively for the match pattern. |
| // It exists because the variable bindings used in that pattern |
| // refer to the variable bound in the match arm, not the variable |
| // bound outside of it. Fortunately due to context we know this has to |
| // be the input variable, not the min or max. |
| input_hir_ids: Option<(HirId, HirId)>, |
| ) -> Option<InputMinMax<'tcx>> { |
| fn check<'tcx>( |
| cx: &LateContext<'tcx>, |
| first_bin: &BinaryOp<'tcx>, |
| second_bin: &BinaryOp<'tcx>, |
| first_expr: &'tcx Expr<'tcx>, |
| second_expr: &'tcx Expr<'tcx>, |
| input_hir_ids: Option<(HirId, HirId)>, |
| is_float: bool, |
| ) -> Option<InputMinMax<'tcx>> { |
| match (&first_bin.op, &second_bin.op) { |
| (BinOpKind::Ge | BinOpKind::Gt, BinOpKind::Le | BinOpKind::Lt) => { |
| let (min, max) = (second_expr, first_expr); |
| let refers_to_input = match input_hir_ids { |
| Some((first_hir_id, second_hir_id)) => { |
| path_to_local_id(peel_blocks(first_bin.left), first_hir_id) |
| && path_to_local_id(peel_blocks(second_bin.left), second_hir_id) |
| }, |
| None => eq_expr_value(cx, first_bin.left, second_bin.left), |
| }; |
| (refers_to_input |
| && eq_expr_value(cx, first_bin.right, first_expr) |
| && eq_expr_value(cx, second_bin.right, second_expr)) |
| .then_some(InputMinMax { |
| input: first_bin.left, |
| min, |
| max, |
| is_float, |
| }) |
| }, |
| _ => None, |
| } |
| } |
| // First filter out any expressions with side effects |
| let exprs = [ |
| first_bin.left, |
| first_bin.right, |
| second_bin.left, |
| second_bin.right, |
| first_expr, |
| second_expr, |
| ]; |
| let clampability = TypeClampability::is_clampable(cx, cx.typeck_results().expr_ty(first_expr))?; |
| let is_float = clampability.is_float(); |
| if exprs.iter().any(|e| peel_blocks(e).can_have_side_effects()) { |
| return None; |
| } |
| if !(is_ord_op(first_bin.op) && is_ord_op(second_bin.op)) { |
| return None; |
| } |
| let cases = [ |
| (*first_bin, *second_bin), |
| (first_bin.flip(), second_bin.flip()), |
| (first_bin.flip(), *second_bin), |
| (*first_bin, second_bin.flip()), |
| ]; |
| |
| cases.into_iter().find_map(|(first, second)| { |
| check(cx, &first, &second, first_expr, second_expr, input_hir_ids, is_float).or_else(|| { |
| check( |
| cx, |
| &second, |
| &first, |
| second_expr, |
| first_expr, |
| input_hir_ids.map(|(l, r)| (r, l)), |
| is_float, |
| ) |
| }) |
| }) |
| } |
| |
| fn block_stmt_with_last<'tcx>(block: &'tcx Block<'tcx>) -> impl Iterator<Item = MaybeBorrowedStmtKind<'tcx>> { |
| block |
| .stmts |
| .iter() |
| .map(|s| MaybeBorrowedStmtKind::Borrowed(&s.kind)) |
| .chain( |
| block |
| .expr |
| .as_ref() |
| .map(|e| MaybeBorrowedStmtKind::Owned(StmtKind::Expr(e))), |
| ) |
| } |
| |
| fn is_ord_op(op: BinOpKind) -> bool { |
| matches!(op, BinOpKind::Ge | BinOpKind::Gt | BinOpKind::Le | BinOpKind::Lt) |
| } |
| |
| /// Really similar to Cow, but doesn't have a `Clone` requirement. |
| #[derive(Debug)] |
| enum MaybeBorrowedStmtKind<'a> { |
| Borrowed(&'a StmtKind<'a>), |
| Owned(StmtKind<'a>), |
| } |
| |
| impl<'a> Clone for MaybeBorrowedStmtKind<'a> { |
| fn clone(&self) -> Self { |
| match self { |
| Self::Borrowed(t) => Self::Borrowed(t), |
| Self::Owned(StmtKind::Expr(e)) => Self::Owned(StmtKind::Expr(e)), |
| Self::Owned(_) => unreachable!("Owned should only ever contain a StmtKind::Expr."), |
| } |
| } |
| } |
| |
| impl<'a> Deref for MaybeBorrowedStmtKind<'a> { |
| type Target = StmtKind<'a>; |
| |
| fn deref(&self) -> &Self::Target { |
| match self { |
| Self::Borrowed(t) => t, |
| Self::Owned(t) => t, |
| } |
| } |
| } |