diff --git a/clippy_lints/src/floating_point_arithmetic/custom_abs.rs b/clippy_lints/src/floating_point_arithmetic/custom_abs.rs new file mode 100644 index 000000000000..d12a32e15881 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/custom_abs.rs @@ -0,0 +1,100 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64, Int}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eq_expr_value, higher, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_span::SyntaxContext; +use rustc_span::source_map::Spanned; + +use super::SUBOPTIMAL_FLOPS; + +/// Returns true iff expr is an expression which tests whether or not +/// test is positive or an expression which tests whether or not test +/// is nonnegative. +/// Used for check-custom-abs function below +fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { + if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { + match op { + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), + _ => false, + } + } else { + false + } +} + +/// See [`is_testing_positive`] +fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { + if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { + match op { + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), + _ => false, + } + } else { + false + } +} + +/// Returns true iff expr is some zero literal +fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { + match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { + Some(Int(i)) => i == 0, + Some(F32(f)) => f == 0.0, + Some(F64(f)) => f == 0.0, + _ => false, + } +} + +/// If the two expressions are negations of each other, then it returns +/// a tuple, in which the first element is true iff expr1 is the +/// positive expressions, and the second element is the positive +/// one of the two expressions +/// If the two expressions are not negations of each other, then it +/// returns None. +fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> { + if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind + && eq_expr_value(cx, expr1_negated, expr2) + { + return Some((false, expr2)); + } + if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind + && eq_expr_value(cx, expr1, expr2_negated) + { + return Some((true, expr1)); + } + None +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let Some(higher::If { + cond, + then, + r#else: Some(r#else), + }) = higher::If::hir(expr) + && let if_body_expr = peel_blocks(then) + && let else_body_expr = peel_blocks(r#else) + && let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr) + { + let sugg_positive_abs = if is_testing_positive(cx, cond, body) { + if_expr_positive + } else if is_testing_negative(cx, cond, body) { + !if_expr_positive + } else { + return; + }; + let mut app = Applicability::MachineApplicable; + let body = Sugg::hir_with_applicability(cx, body, "_", &mut app).maybe_paren(); + let sugg = if sugg_positive_abs { + ("manual implementation of `abs` method", format!("{body}.abs()")) + } else { + #[rustfmt::skip] + ("manual implementation of negation of `abs` method", format!("-{body}.abs()")) + }; + span_lint_and_sugg(cx, SUBOPTIMAL_FLOPS, expr.span, sugg.0, "try", sugg.1, app); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/expm1.rs b/clippy_lints/src/floating_point_arithmetic/expm1.rs new file mode 100644 index 000000000000..9a4c97569308 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/expm1.rs @@ -0,0 +1,42 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::IMPRECISE_FLOPS; + +// TODO: Lint expressions of the form `x.exp() - y` where y > 1 +// and suggest usage of `x.exp_m1() - (y - 1)` instead +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, .. + }, + lhs, + rhs, + ) = expr.kind + && let ExprKind::MethodCall(path, self_arg, [], _) = lhs.kind + && path.ident.name == sym::exp + && cx.typeck_results().expr_ty(lhs).is_floating_point() + && let Some(value) = ConstEvalCtxt::new(cx).eval(rhs) + && (F32(1.0) == value || F64(1.0) == value) + && cx.typeck_results().expr_ty(self_arg).is_floating_point() + { + span_lint_and_then( + cx, + IMPRECISE_FLOPS, + expr.span, + "(e.pow(x) - 1) can be computed more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, self_arg, "_", &mut app).maybe_paren(); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.exp_m1()"), app); + }, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/hypot.rs b/clippy_lints/src/floating_point_arithmetic/hypot.rs new file mode 100644 index 000000000000..49f8ba4bf825 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/hypot.rs @@ -0,0 +1,82 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::Int; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eq_expr_value, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::IMPRECISE_FLOPS; + +pub(super) fn detect(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applicability) -> Option { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + add_lhs, + add_rhs, + ) = receiver.kind + { + // check if expression of the form x * x + y * y + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + lmul_lhs, + lmul_rhs, + ) = add_lhs.kind + && let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + rmul_lhs, + rmul_rhs, + ) = add_rhs.kind + && eq_expr_value(cx, lmul_lhs, lmul_rhs) + && eq_expr_value(cx, rmul_lhs, rmul_rhs) + { + return Some(format!( + "{}.hypot({})", + Sugg::hir_with_applicability(cx, lmul_lhs, "_", app).maybe_paren(), + Sugg::hir_with_applicability(cx, rmul_lhs, "_", app) + )); + } + + // check if expression of the form x.powi(2) + y.powi(2) + if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = add_lhs.kind + && let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = add_rhs.kind + && lmethod.name == sym::powi + && rmethod.name == sym::powi + && let ecx = ConstEvalCtxt::new(cx) + && let Some(lvalue) = ecx.eval(largs_1) + && let Some(rvalue) = ecx.eval(rargs_1) + && Int(2) == lvalue + && Int(2) == rvalue + { + return Some(format!( + "{}.hypot({})", + Sugg::hir_with_applicability(cx, largs_0, "_", app).maybe_paren(), + Sugg::hir_with_applicability(cx, rargs_0, "_", app) + )); + } + } + + None +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { + let mut app = Applicability::MachineApplicable; + if let Some(message) = detect(cx, receiver, &mut app) { + span_lint_and_sugg( + cx, + IMPRECISE_FLOPS, + expr.span, + "hypotenuse can be computed more accurately", + "consider using", + message, + app, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/lib.rs b/clippy_lints/src/floating_point_arithmetic/lib.rs new file mode 100644 index 000000000000..3fa041f97802 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/lib.rs @@ -0,0 +1,42 @@ +use clippy_utils::sugg::Sugg; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +// Adds type suffixes and parenthesis to method receivers if necessary +pub(super) fn prepare_receiver_sugg<'a>( + cx: &LateContext<'_>, + mut expr: &'a Expr<'a>, + app: &mut Applicability, +) -> Sugg<'a> { + let mut suggestion = Sugg::hir_with_applicability(cx, expr, "_", app); + + if let ExprKind::Unary(UnOp::Neg, inner_expr) = expr.kind { + expr = inner_expr; + } + + if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind() + // if the expression is a float literal and it is unsuffixed then + // add a suffix so the suggestion is valid and unambiguous + && let ExprKind::Lit(lit) = expr.kind + && let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node + { + let op = format!( + "{suggestion}{}{}", + // Check for float literals without numbers following the decimal + // separator such as `2.` and adds a trailing zero + if sym.as_str().ends_with('.') { "0" } else { "" }, + float_ty.name_str() + ) + .into(); + + suggestion = match suggestion { + Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op), + _ => Sugg::NonParen(op), + }; + } + + suggestion.maybe_paren() +} diff --git a/clippy_lints/src/floating_point_arithmetic/ln1p.rs b/clippy_lints/src/floating_point_arithmetic/ln1p.rs new file mode 100644 index 000000000000..4c9aa96b5042 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/ln1p.rs @@ -0,0 +1,41 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::IMPRECISE_FLOPS; + +// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and +// suggest usage of `(x + (y - 1)).ln_1p()` instead +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + lhs, + rhs, + ) = receiver.kind + { + let ecx = ConstEvalCtxt::new(cx); + let recv = match (ecx.eval(lhs), ecx.eval(rhs)) { + (Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs, + (_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs, + _ => return, + }; + + span_lint_and_then( + cx, + IMPRECISE_FLOPS, + expr.span, + "ln(1 + x) can be computed more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = super::lib::prepare_receiver_sugg(cx, recv, &mut app); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.ln_1p()"), app); + }, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/log_base.rs b/clippy_lints/src/floating_point_arithmetic/log_base.rs new file mode 100644 index 000000000000..4ccc784655ed --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/log_base.rs @@ -0,0 +1,44 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::SyntaxContext; +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; + +use super::SUBOPTIMAL_FLOPS; + +// Returns the specialized log method for a given base if base is constant +// and is one of 2, 10 and e +fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> { + if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) { + if F32(2.0) == value || F64(2.0) == value { + return Some("log2"); + } else if F32(10.0) == value || F64(10.0) == value { + return Some("log10"); + } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value { + return Some("ln"); + } + } + + None +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { + if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "logarithm for bases 2, 10 and e can be computed more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); + }, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/log_division.rs b/clippy_lints/src/floating_point_arithmetic/log_division.rs new file mode 100644 index 000000000000..e3419ffad72a --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/log_division.rs @@ -0,0 +1,52 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eq_expr_value, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::SUBOPTIMAL_FLOPS; + +fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool { + if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind + && let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind + { + return method_a.name == method_b.name + && args_a.len() == args_b.len() + && (matches!(method_a.name, sym::ln | sym::log2 | sym::log10) + || method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0])); + } + + false +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + // check if expression of the form x.logN() / y.logN() + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Div, .. + }, + lhs, + rhs, + ) = expr.kind + && are_same_base_logs(cx, lhs, rhs) + && let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind + && let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind + { + let mut app = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "log base can be expressed more clearly", + "consider using", + format!( + "{}.log({})", + Sugg::hir_with_applicability(cx, largs_self, "_", &mut app).maybe_paren(), + Sugg::hir_with_applicability(cx, rargs_self, "_", &mut app), + ), + app, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/mod.rs b/clippy_lints/src/floating_point_arithmetic/mod.rs index 64a2af38a108..edc638c96bbf 100644 --- a/clippy_lints/src/floating_point_arithmetic/mod.rs +++ b/clippy_lints/src/floating_point_arithmetic/mod.rs @@ -1,22 +1,20 @@ -use clippy_utils::consts::Constant::{F32, F64, Int}; -use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; -use clippy_utils::{ - eq_expr_value, get_parent_expr, has_ambiguous_literal_in_expr, higher, is_in_const_context, is_no_std_crate, - numeric_literal, peel_blocks, sugg, sym, -}; -use rustc_ast::ast; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; +use clippy_utils::{is_in_const_context, is_no_std_crate, sym}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::SyntaxContext; -use rustc_span::source_map::Spanned; -use std::f32::consts as f32_consts; -use std::f64::consts as f64_consts; -use sugg::Sugg; + +mod custom_abs; +mod expm1; +mod hypot; +mod lib; +mod ln1p; +mod log_base; +mod log_division; +mod mul_add; +mod powf; +mod powi; +mod radians; declare_clippy_lint! { /// ### What it does @@ -110,634 +108,6 @@ SUBOPTIMAL_FLOPS ]); -// Returns the specialized log method for a given base if base is constant -// and is one of 2, 10 and e -fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> { - if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) { - if F32(2.0) == value || F64(2.0) == value { - return Some("log2"); - } else if F32(10.0) == value || F64(10.0) == value { - return Some("log10"); - } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value { - return Some("ln"); - } - } - - None -} - -// Adds type suffixes and parenthesis to method receivers if necessary -fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>, app: &mut Applicability) -> Sugg<'a> { - let mut suggestion = Sugg::hir_with_applicability(cx, expr, "_", app); - - if let ExprKind::Unary(UnOp::Neg, inner_expr) = expr.kind { - expr = inner_expr; - } - - if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind() - // if the expression is a float literal and it is unsuffixed then - // add a suffix so the suggestion is valid and unambiguous - && let ExprKind::Lit(lit) = expr.kind - && let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node - { - let op = format!( - "{suggestion}{}{}", - // Check for float literals without numbers following the decimal - // separator such as `2.` and adds a trailing zero - if sym.as_str().ends_with('.') { "0" } else { "" }, - float_ty.name_str() - ) - .into(); - - suggestion = match suggestion { - Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op), - _ => Sugg::NonParen(op), - }; - } - - suggestion.maybe_paren() -} - -fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "logarithm for bases 2, 10 and e can be computed more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); - diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); - }, - ); - } -} - -// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and -// suggest usage of `(x + (y - 1)).ln_1p()` instead -fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Add, .. - }, - lhs, - rhs, - ) = receiver.kind - { - let ecx = ConstEvalCtxt::new(cx); - let recv = match (ecx.eval(lhs), ecx.eval(rhs)) { - (Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs, - (_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs, - _ => return, - }; - - span_lint_and_then( - cx, - IMPRECISE_FLOPS, - expr.span, - "ln(1 + x) can be computed more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = prepare_receiver_sugg(cx, recv, &mut app); - diag.span_suggestion(expr.span, "consider using", format!("{recv}.ln_1p()"), app); - }, - ); - } -} - -// Returns an integer if the float constant is a whole number and it can be -// converted to an integer without loss of precision. For now we only check -// ranges [-16777215, 16777216) for type f32 as whole number floats outside -// this range are lossy and ambiguous. -#[expect(clippy::cast_possible_truncation)] -fn get_integer_from_float_constant(value: &Constant) -> Option { - match value { - F32(num) if num.fract() == 0.0 => { - if (-16_777_215.0..16_777_216.0).contains(num) { - Some(num.round() as i32) - } else { - None - } - }, - F64(num) if num.fract() == 0.0 => { - if (-2_147_483_648.0..2_147_483_648.0).contains(num) { - Some(num.round() as i32) - } else { - None - } - }, - _ => None, - } -} - -fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - // Check receiver - if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) - && let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { - Some("exp") - } else if F32(2.0) == value || F64(2.0) == value { - Some("exp2") - } else { - None - } - { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "exponent for bases 2 and e can be computed more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = prepare_receiver_sugg(cx, &args[0], &mut app); - diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); - }, - ); - } - - // Check argument - if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); - let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { - ( - SUBOPTIMAL_FLOPS, - "square-root of a number can be computed more efficiently and accurately", - format!("{recv}.sqrt()"), - ) - } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { - ( - IMPRECISE_FLOPS, - "cube-root of a number can be computed more accurately", - format!("{recv}.cbrt()"), - ) - } else if let Some(exponent) = get_integer_from_float_constant(&value) { - ( - SUBOPTIMAL_FLOPS, - "exponentiation with integer powers can be computed more efficiently", - format!( - "{recv}.powi({})", - numeric_literal::format(&exponent.to_string(), None, false) - ), - ) - } else { - return; - }; - - span_lint_and_sugg(cx, lint, expr.span, help, "consider using", suggestion, app); - } -} - -fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) - && value == Int(2) - && let Some(parent) = get_parent_expr(cx, expr) - { - if let Some(grandparent) = get_parent_expr(cx, parent) - && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = grandparent.kind - && method.name == sym::sqrt - // we don't care about the applicability as this is an early-return condition - && detect_hypot(cx, receiver, &mut Applicability::Unspecified).is_some() - { - return; - } - - if let ExprKind::Binary( - Spanned { - node: op @ (BinOpKind::Add | BinOpKind::Sub), - .. - }, - lhs, - rhs, - ) = parent.kind - { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - parent.span, - "multiply and add expressions can be calculated more efficiently and accurately", - |diag| { - let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; - - // Negate expr if original code has subtraction and expr is on the right side - let maybe_neg_sugg = |expr, hir_id, app: &mut _| { - let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); - if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { - -sugg - } else { - sugg - } - }; - - let mut app = Applicability::MachineApplicable; - diag.span_suggestion( - parent.span, - "consider using", - format!( - "{}.mul_add({}, {})", - Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(), - maybe_neg_sugg(receiver, expr.hir_id, &mut app), - maybe_neg_sugg(other_addend, other_addend.hir_id, &mut app), - ), - app, - ); - }, - ); - } - } -} - -fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applicability) -> Option { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Add, .. - }, - add_lhs, - add_rhs, - ) = receiver.kind - { - // check if expression of the form x * x + y * y - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Mul, .. - }, - lmul_lhs, - lmul_rhs, - ) = add_lhs.kind - && let ExprKind::Binary( - Spanned { - node: BinOpKind::Mul, .. - }, - rmul_lhs, - rmul_rhs, - ) = add_rhs.kind - && eq_expr_value(cx, lmul_lhs, lmul_rhs) - && eq_expr_value(cx, rmul_lhs, rmul_rhs) - { - return Some(format!( - "{}.hypot({})", - Sugg::hir_with_applicability(cx, lmul_lhs, "_", app).maybe_paren(), - Sugg::hir_with_applicability(cx, rmul_lhs, "_", app) - )); - } - - // check if expression of the form x.powi(2) + y.powi(2) - if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = add_lhs.kind - && let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = add_rhs.kind - && lmethod.name == sym::powi - && rmethod.name == sym::powi - && let ecx = ConstEvalCtxt::new(cx) - && let Some(lvalue) = ecx.eval(largs_1) - && let Some(rvalue) = ecx.eval(rargs_1) - && Int(2) == lvalue - && Int(2) == rvalue - { - return Some(format!( - "{}.hypot({})", - Sugg::hir_with_applicability(cx, largs_0, "_", app).maybe_paren(), - Sugg::hir_with_applicability(cx, rargs_0, "_", app) - )); - } - } - - None -} - -fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { - let mut app = Applicability::MachineApplicable; - if let Some(message) = detect_hypot(cx, receiver, &mut app) { - span_lint_and_sugg( - cx, - IMPRECISE_FLOPS, - expr.span, - "hypotenuse can be computed more accurately", - "consider using", - message, - app, - ); - } -} - -// TODO: Lint expressions of the form `x.exp() - y` where y > 1 -// and suggest usage of `x.exp_m1() - (y - 1)` instead -fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Sub, .. - }, - lhs, - rhs, - ) = expr.kind - && let ExprKind::MethodCall(path, self_arg, [], _) = lhs.kind - && path.ident.name == sym::exp - && cx.typeck_results().expr_ty(lhs).is_floating_point() - && let Some(value) = ConstEvalCtxt::new(cx).eval(rhs) - && (F32(1.0) == value || F64(1.0) == value) - && cx.typeck_results().expr_ty(self_arg).is_floating_point() - { - span_lint_and_then( - cx, - IMPRECISE_FLOPS, - expr.span, - "(e.pow(x) - 1) can be computed more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, self_arg, "_", &mut app).maybe_paren(); - diag.span_suggestion(expr.span, "consider using", format!("{recv}.exp_m1()"), app); - }, - ); - } -} - -fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Mul, .. - }, - lhs, - rhs, - ) = expr.kind - && cx.typeck_results().expr_ty(lhs).is_floating_point() - && cx.typeck_results().expr_ty(rhs).is_floating_point() - { - return Some((lhs, rhs)); - } - - None -} - -fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: op @ (BinOpKind::Add | BinOpKind::Sub), - .. - }, - lhs, - rhs, - ) = &expr.kind - { - if let Some(parent) = get_parent_expr(cx, expr) - && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind - && method.name == sym::sqrt - // we don't care about the applicability as this is an early-return condition - && detect_hypot(cx, receiver, &mut Applicability::Unspecified).is_some() - { - return; - } - - let maybe_neg_sugg = |expr, app: &mut _| { - let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); - if let BinOpKind::Sub = op { -sugg } else { sugg } - }; - - let mut app = Applicability::MachineApplicable; - let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) - && cx.typeck_results().expr_ty(rhs).is_floating_point() - { - ( - inner_lhs, - Sugg::hir_with_applicability(cx, inner_rhs, "_", &mut app), - maybe_neg_sugg(rhs, &mut app), - ) - } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) - && cx.typeck_results().expr_ty(lhs).is_floating_point() - { - ( - inner_lhs, - maybe_neg_sugg(inner_rhs, &mut app), - Sugg::hir_with_applicability(cx, lhs, "_", &mut app), - ) - } else { - return; - }; - - // Check if any variable in the expression has an ambiguous type (could be f32 or f64) - // see: https://github.com/rust-lang/rust-clippy/issues/14897 - if (matches!(recv.kind, ExprKind::Path(_)) || matches!(recv.kind, ExprKind::Call(_, _))) - && has_ambiguous_literal_in_expr(cx, recv) - { - return; - } - - span_lint_and_sugg( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "multiply and add expressions can be calculated more efficiently and accurately", - "consider using", - format!("{}.mul_add({arg1}, {arg2})", prepare_receiver_sugg(cx, recv, &mut app)), - app, - ); - } -} - -/// Returns true iff expr is an expression which tests whether or not -/// test is positive or an expression which tests whether or not test -/// is nonnegative. -/// Used for check-custom-abs function below -fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { - if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { - match op { - BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), - BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), - _ => false, - } - } else { - false - } -} - -/// See [`is_testing_positive`] -fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { - if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { - match op { - BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), - BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), - _ => false, - } - } else { - false - } -} - -/// Returns true iff expr is some zero literal -fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { - match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { - Some(Int(i)) => i == 0, - Some(F32(f)) => f == 0.0, - Some(F64(f)) => f == 0.0, - _ => false, - } -} - -/// If the two expressions are negations of each other, then it returns -/// a tuple, in which the first element is true iff expr1 is the -/// positive expressions, and the second element is the positive -/// one of the two expressions -/// If the two expressions are not negations of each other, then it -/// returns None. -fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> { - if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind - && eq_expr_value(cx, expr1_negated, expr2) - { - return Some((false, expr2)); - } - if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind - && eq_expr_value(cx, expr1, expr2_negated) - { - return Some((true, expr1)); - } - None -} - -fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let Some(higher::If { - cond, - then, - r#else: Some(r#else), - }) = higher::If::hir(expr) - && let if_body_expr = peel_blocks(then) - && let else_body_expr = peel_blocks(r#else) - && let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr) - { - let sugg_positive_abs = if is_testing_positive(cx, cond, body) { - if_expr_positive - } else if is_testing_negative(cx, cond, body) { - !if_expr_positive - } else { - return; - }; - let mut app = Applicability::MachineApplicable; - let body = Sugg::hir_with_applicability(cx, body, "_", &mut app).maybe_paren(); - let sugg = if sugg_positive_abs { - ("manual implementation of `abs` method", format!("{body}.abs()")) - } else { - #[rustfmt::skip] - ("manual implementation of negation of `abs` method", format!("-{body}.abs()")) - }; - span_lint_and_sugg(cx, SUBOPTIMAL_FLOPS, expr.span, sugg.0, "try", sugg.1, app); - } -} - -fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool { - if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind - && let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind - { - return method_a.name == method_b.name - && args_a.len() == args_b.len() - && (matches!(method_a.name, sym::ln | sym::log2 | sym::log10) - || method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0])); - } - - false -} - -fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { - // check if expression of the form x.logN() / y.logN() - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Div, .. - }, - lhs, - rhs, - ) = expr.kind - && are_same_base_logs(cx, lhs, rhs) - && let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind - && let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind - { - let mut app = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "log base can be expressed more clearly", - "consider using", - format!( - "{}.log({})", - Sugg::hir_with_applicability(cx, largs_self, "_", &mut app).maybe_paren(), - Sugg::hir_with_applicability(cx, rargs_self, "_", &mut app), - ), - app, - ); - } -} - -fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Div, .. - }, - div_lhs, - div_rhs, - ) = expr.kind - && let ExprKind::Binary( - Spanned { - node: BinOpKind::Mul, .. - }, - mul_lhs, - mul_rhs, - ) = div_lhs.kind - && let ecx = ConstEvalCtxt::new(cx) - && let Some(rvalue) = ecx.eval(div_rhs) - && let Some(lvalue) = ecx.eval(mul_rhs) - { - // TODO: also check for constant values near PI/180 or 180/PI - if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) - && (F32(180_f32) == lvalue || F64(180_f64) == lvalue) - { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "conversion to degrees can be done more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); - let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind - && let ast::LitKind::Float(ref value, float_type) = literal.node - && float_type == ast::LitFloatType::Unsuffixed - { - if value.as_str().ends_with('.') { - format!("{recv}0_f64.to_degrees()") - } else { - format!("{recv}_f64.to_degrees()") - } - } else { - format!("{}.to_degrees()", recv.maybe_paren()) - }; - diag.span_suggestion(expr.span, "consider using", proposal, app); - }, - ); - } else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue) - && (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) - { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "conversion to radians can be done more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); - let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind - && let ast::LitKind::Float(ref value, float_type) = literal.node - && float_type == ast::LitFloatType::Unsuffixed - { - if value.as_str().ends_with('.') { - format!("{recv}0_f64.to_radians()") - } else { - format!("{recv}_f64.to_radians()") - } - } else { - format!("{}.to_radians()", recv.maybe_paren()) - }; - diag.span_suggestion(expr.span, "consider using", proposal, app); - }, - ); - } - } -} - impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // All of these operations are currently not const and are in std. @@ -750,22 +120,22 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if recv_ty.is_floating_point() && !is_no_std_crate(cx) && cx.ty_based_def(expr).opt_parent(cx).is_impl(cx) { match path.ident.name { - sym::ln => check_ln1p(cx, expr, receiver), - sym::log => check_log_base(cx, expr, receiver, args), - sym::powf => check_powf(cx, expr, receiver, args), - sym::powi => check_powi(cx, expr, receiver, args), - sym::sqrt => check_hypot(cx, expr, receiver), + sym::ln => ln1p::check(cx, expr, receiver), + sym::log => log_base::check(cx, expr, receiver, args), + sym::powf => powf::check(cx, expr, receiver, args), + sym::powi => powi::check(cx, expr, receiver, args), + sym::sqrt => hypot::check(cx, expr, receiver), _ => {}, } } } else { if !is_no_std_crate(cx) { - check_expm1(cx, expr); - check_mul_add(cx, expr); - check_custom_abs(cx, expr); - check_log_division(cx, expr); + expm1::check(cx, expr); + mul_add::check(cx, expr); + custom_abs::check(cx, expr); + log_division::check(cx, expr); } - check_radians(cx, expr); + radians::check(cx, expr); } } } diff --git a/clippy_lints/src/floating_point_arithmetic/mul_add.rs b/clippy_lints/src/floating_point_arithmetic/mul_add.rs new file mode 100644 index 000000000000..03a9d3b05f88 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/mul_add.rs @@ -0,0 +1,94 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{get_parent_expr, has_ambiguous_literal_in_expr, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::SUBOPTIMAL_FLOPS; + +fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + lhs, + rhs, + ) = expr.kind + && cx.typeck_results().expr_ty(lhs).is_floating_point() + && cx.typeck_results().expr_ty(rhs).is_floating_point() + { + return Some((lhs, rhs)); + } + + None +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: op @ (BinOpKind::Add | BinOpKind::Sub), + .. + }, + lhs, + rhs, + ) = &expr.kind + { + if let Some(parent) = get_parent_expr(cx, expr) + && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind + && method.name == sym::sqrt + // we don't care about the applicability as this is an early-return condition + && super::hypot::detect(cx, receiver, &mut Applicability::Unspecified).is_some() + { + return; + } + + let maybe_neg_sugg = |expr, app: &mut _| { + let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); + if let BinOpKind::Sub = op { -sugg } else { sugg } + }; + + let mut app = Applicability::MachineApplicable; + let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) + && cx.typeck_results().expr_ty(rhs).is_floating_point() + { + ( + inner_lhs, + Sugg::hir_with_applicability(cx, inner_rhs, "_", &mut app), + maybe_neg_sugg(rhs, &mut app), + ) + } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) + && cx.typeck_results().expr_ty(lhs).is_floating_point() + { + ( + inner_lhs, + maybe_neg_sugg(inner_rhs, &mut app), + Sugg::hir_with_applicability(cx, lhs, "_", &mut app), + ) + } else { + return; + }; + + // Check if any variable in the expression has an ambiguous type (could be f32 or f64) + // see: https://github.com/rust-lang/rust-clippy/issues/14897 + if (matches!(recv.kind, ExprKind::Path(_)) || matches!(recv.kind, ExprKind::Call(_, _))) + && has_ambiguous_literal_in_expr(cx, recv) + { + return; + } + + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "multiply and add expressions can be calculated more efficiently and accurately", + "consider using", + format!( + "{}.mul_add({arg1}, {arg2})", + super::lib::prepare_receiver_sugg(cx, recv, &mut app) + ), + app, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/powf.rs b/clippy_lints/src/floating_point_arithmetic/powf.rs new file mode 100644 index 000000000000..8e4a7388e784 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/powf.rs @@ -0,0 +1,94 @@ +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::numeric_literal; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; + +use super::{IMPRECISE_FLOPS, SUBOPTIMAL_FLOPS}; + +// Returns an integer if the float constant is a whole number and it can be +// converted to an integer without loss of precision. For now we only check +// ranges [-16777215, 16777216) for type f32 as whole number floats outside +// this range are lossy and ambiguous. +#[expect(clippy::cast_possible_truncation)] +fn get_integer_from_float_constant(value: &Constant) -> Option { + match value { + F32(num) if num.fract() == 0.0 => { + if (-16_777_215.0..16_777_216.0).contains(num) { + Some(num.round() as i32) + } else { + None + } + }, + F64(num) if num.fract() == 0.0 => { + if (-2_147_483_648.0..2_147_483_648.0).contains(num) { + Some(num.round() as i32) + } else { + None + } + }, + _ => None, + } +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { + // Check receiver + if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) + && let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { + Some("exp") + } else if F32(2.0) == value || F64(2.0) == value { + Some("exp2") + } else { + None + } + { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "exponent for bases 2 and e can be computed more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = super::lib::prepare_receiver_sugg(cx, &args[0], &mut app); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); + }, + ); + } + + // Check argument + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); + let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { + ( + SUBOPTIMAL_FLOPS, + "square-root of a number can be computed more efficiently and accurately", + format!("{recv}.sqrt()"), + ) + } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { + ( + IMPRECISE_FLOPS, + "cube-root of a number can be computed more accurately", + format!("{recv}.cbrt()"), + ) + } else if let Some(exponent) = get_integer_from_float_constant(&value) { + ( + SUBOPTIMAL_FLOPS, + "exponentiation with integer powers can be computed more efficiently", + format!( + "{recv}.powi({})", + numeric_literal::format(&exponent.to_string(), None, false) + ), + ) + } else { + return; + }; + + span_lint_and_sugg(cx, lint, expr.span, help, "consider using", suggestion, app); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/powi.rs b/clippy_lints/src/floating_point_arithmetic/powi.rs new file mode 100644 index 000000000000..a61a2a82c023 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/powi.rs @@ -0,0 +1,70 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::Int; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use clippy_utils::{get_parent_expr, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::SUBOPTIMAL_FLOPS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) + && value == Int(2) + && let Some(parent) = get_parent_expr(cx, expr) + { + if let Some(grandparent) = get_parent_expr(cx, parent) + && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = grandparent.kind + && method.name == sym::sqrt + // we don't care about the applicability as this is an early-return condition + && super::hypot::detect(cx, receiver, &mut Applicability::Unspecified).is_some() + { + return; + } + + if let ExprKind::Binary( + Spanned { + node: op @ (BinOpKind::Add | BinOpKind::Sub), + .. + }, + lhs, + rhs, + ) = parent.kind + { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + parent.span, + "multiply and add expressions can be calculated more efficiently and accurately", + |diag| { + let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; + + // Negate expr if original code has subtraction and expr is on the right side + let maybe_neg_sugg = |expr, hir_id, app: &mut _| { + let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); + if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { + -sugg + } else { + sugg + } + }; + + let mut app = Applicability::MachineApplicable; + diag.span_suggestion( + parent.span, + "consider using", + format!( + "{}.mul_add({}, {})", + Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(), + maybe_neg_sugg(receiver, expr.hir_id, &mut app), + maybe_neg_sugg(other_addend, other_addend.hir_id, &mut app), + ), + app, + ); + }, + ); + } + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/radians.rs b/clippy_lints/src/floating_point_arithmetic/radians.rs new file mode 100644 index 000000000000..2021f00a97e8 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/radians.rs @@ -0,0 +1,89 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; + +use super::SUBOPTIMAL_FLOPS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Div, .. + }, + div_lhs, + div_rhs, + ) = expr.kind + && let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + mul_lhs, + mul_rhs, + ) = div_lhs.kind + && let ecx = ConstEvalCtxt::new(cx) + && let Some(rvalue) = ecx.eval(div_rhs) + && let Some(lvalue) = ecx.eval(mul_rhs) + { + // TODO: also check for constant values near PI/180 or 180/PI + if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) + && (F32(180_f32) == lvalue || F64(180_f64) == lvalue) + { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "conversion to degrees can be done more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); + let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind + && let ast::LitKind::Float(ref value, float_type) = literal.node + && float_type == ast::LitFloatType::Unsuffixed + { + if value.as_str().ends_with('.') { + format!("{recv}0_f64.to_degrees()") + } else { + format!("{recv}_f64.to_degrees()") + } + } else { + format!("{}.to_degrees()", recv.maybe_paren()) + }; + diag.span_suggestion(expr.span, "consider using", proposal, app); + }, + ); + } else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue) + && (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) + { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "conversion to radians can be done more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); + let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind + && let ast::LitKind::Float(ref value, float_type) = literal.node + && float_type == ast::LitFloatType::Unsuffixed + { + if value.as_str().ends_with('.') { + format!("{recv}0_f64.to_radians()") + } else { + format!("{recv}_f64.to_radians()") + } + } else { + format!("{}.to_radians()", recv.maybe_paren()) + }; + diag.span_suggestion(expr.span, "consider using", proposal, app); + }, + ); + } + } +}