Fix pow() to return more known signs
This commit is contained in:
parent
47339d01f8
commit
c5e8487d63
@ -1,9 +1,9 @@
|
|||||||
use clippy_utils::consts::{constant, Constant};
|
use clippy_utils::consts::{constant, Constant};
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use clippy_utils::diagnostics::span_lint;
|
||||||
use clippy_utils::{clip, method_chain_args, sext};
|
use clippy_utils::{method_chain_args, sext};
|
||||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_middle::ty::{self, Ty, UintTy};
|
use rustc_middle::ty::{self, Ty};
|
||||||
|
|
||||||
use super::CAST_SIGN_LOSS;
|
use super::CAST_SIGN_LOSS;
|
||||||
|
|
||||||
@ -22,10 +22,7 @@ const METHODS_RET_POSITIVE: &[&str] = &[
|
|||||||
"wrapping_rem_euclid",
|
"wrapping_rem_euclid",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// A list of methods that act like `pow()`, and can never return:
|
/// A list of methods that act like `pow()`. See `pow_call_result_sign()` for details.
|
||||||
/// - a negative value from a non-negative base
|
|
||||||
/// - a negative value from a negative base and even exponent
|
|
||||||
/// - a non-negative value from a negative base and odd exponent
|
|
||||||
///
|
///
|
||||||
/// Methods that can overflow and return a negative value must not be included in this list,
|
/// Methods that can overflow and return a negative value must not be included in this list,
|
||||||
/// because casting their return values can still result in sign loss.
|
/// because casting their return values can still result in sign loss.
|
||||||
@ -34,7 +31,13 @@ const METHODS_POW: &[&str] = &["pow", "saturating_pow", "checked_pow"];
|
|||||||
/// A list of methods that act like `unwrap()`, and don't change the sign of the inner value.
|
/// A list of methods that act like `unwrap()`, and don't change the sign of the inner value.
|
||||||
const METHODS_UNWRAP: &[&str] = &["unwrap", "unwrap_unchecked", "expect", "into_ok"];
|
const METHODS_UNWRAP: &[&str] = &["unwrap", "unwrap_unchecked", "expect", "into_ok"];
|
||||||
|
|
||||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
|
pub(super) fn check<'cx>(
|
||||||
|
cx: &LateContext<'cx>,
|
||||||
|
expr: &Expr<'_>,
|
||||||
|
cast_op: &Expr<'_>,
|
||||||
|
cast_from: Ty<'cx>,
|
||||||
|
cast_to: Ty<'_>,
|
||||||
|
) {
|
||||||
if should_lint(cx, cast_op, cast_from, cast_to) {
|
if should_lint(cx, cast_op, cast_from, cast_to) {
|
||||||
span_lint(
|
span_lint(
|
||||||
cx,
|
cx,
|
||||||
@ -45,7 +48,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool {
|
fn should_lint<'cx>(cx: &LateContext<'cx>, cast_op: &Expr<'_>, cast_from: Ty<'cx>, cast_to: Ty<'_>) -> bool {
|
||||||
match (cast_from.is_integral(), cast_to.is_integral()) {
|
match (cast_from.is_integral(), cast_to.is_integral()) {
|
||||||
(true, true) => {
|
(true, true) => {
|
||||||
if !cast_from.is_signed() || cast_to.is_signed() {
|
if !cast_from.is_signed() || cast_to.is_signed() {
|
||||||
@ -82,7 +85,9 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Option<i128> {
|
fn get_const_int_eval<'cx>(cx: &LateContext<'cx>, expr: &Expr<'_>, ty: impl Into<Option<Ty<'cx>>>) -> Option<i128> {
|
||||||
|
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
|
||||||
|
|
||||||
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
|
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
|
||||||
&& let ty::Int(ity) = *ty.kind()
|
&& let ty::Int(ity) = *ty.kind()
|
||||||
{
|
{
|
||||||
@ -97,7 +102,7 @@ enum Sign {
|
|||||||
Uncertain,
|
Uncertain,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign {
|
fn expr_sign<'cx>(cx: &LateContext<'cx>, expr: &Expr<'_>, ty: impl Into<Option<Ty<'cx>>>) -> Sign {
|
||||||
// Try evaluate this expr first to see if it's positive
|
// Try evaluate this expr first to see if it's positive
|
||||||
if let Some(val) = get_const_int_eval(cx, expr, ty) {
|
if let Some(val) = get_const_int_eval(cx, expr, ty) {
|
||||||
return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative };
|
return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative };
|
||||||
@ -112,6 +117,8 @@ fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign {
|
|||||||
&& let Some(arglist) = method_chain_args(expr, &[found_name])
|
&& let Some(arglist) = method_chain_args(expr, &[found_name])
|
||||||
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
|
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
|
||||||
{
|
{
|
||||||
|
// The original type has changed, but we can't use `ty` here anyway, because it has been
|
||||||
|
// moved.
|
||||||
method_name = inner_path.ident.name.as_str();
|
method_name = inner_path.ident.name.as_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,31 +137,31 @@ fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign {
|
|||||||
/// Return the sign of the `pow` call's result, ignoring overflow.
|
/// Return the sign of the `pow` call's result, ignoring overflow.
|
||||||
///
|
///
|
||||||
/// If the base is positive, the result is always positive.
|
/// If the base is positive, the result is always positive.
|
||||||
/// If the base is negative, and the exponent is a even number, the result is always positive,
|
/// If the exponent is a even number, the result is always positive,
|
||||||
/// otherwise if the exponent is an odd number, the result is always negative.
|
/// Otherwise, if the base is negative, and the exponent is an odd number, the result is always
|
||||||
|
/// negative.
|
||||||
///
|
///
|
||||||
/// If either value can't be evaluated, [`Sign::Uncertain`] will be returned.
|
/// Otherwise, returns [`Sign::Uncertain`].
|
||||||
fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'_>) -> Sign {
|
fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'_>) -> Sign {
|
||||||
let base_ty = cx.typeck_results().expr_ty(base);
|
let base_sign = expr_sign(cx, base, None);
|
||||||
let Some(base_val) = get_const_int_eval(cx, base, base_ty) else {
|
let exponent_val = get_const_int_eval(cx, exponent, None);
|
||||||
return Sign::Uncertain;
|
let exponent_is_even = exponent_val.map(|val| val % 2 == 0);
|
||||||
};
|
|
||||||
// Non-negative bases raised to non-negative exponents are always non-negative, ignoring overflow.
|
|
||||||
// (Rust's integer pow() function takes an unsigned exponent.)
|
|
||||||
if base_val >= 0 {
|
|
||||||
return Sign::ZeroOrPositive;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), exponent) else {
|
match (base_sign, exponent_is_even) {
|
||||||
return Sign::Uncertain;
|
// Non-negative bases always return non-negative results, ignoring overflow.
|
||||||
};
|
// This is because Rust's integer pow() functions take an unsigned exponent.
|
||||||
|
(Sign::ZeroOrPositive, _) => Sign::ZeroOrPositive,
|
||||||
|
|
||||||
// A negative value raised to an even exponent is non-negative, and an odd exponent
|
// Any base raised to an even exponent is non-negative.
|
||||||
// is negative, ignoring overflow.
|
// This is true even if we don't know the value of the base.
|
||||||
if clip(cx.tcx, n, UintTy::U32) % 2 == 0 {
|
(_, Some(true)) => Sign::ZeroOrPositive,
|
||||||
return Sign::ZeroOrPositive;
|
|
||||||
} else {
|
// A negative base raised to an odd exponent is non-negative.
|
||||||
return Sign::Negative;
|
(Sign::Negative, Some(false)) => Sign::Negative,
|
||||||
|
|
||||||
|
// Negative or unknown base to an unknown exponent, or an unknown base to an odd exponent.
|
||||||
|
(Sign::Negative | Sign::Uncertain, None) => Sign::Uncertain,
|
||||||
|
(Sign::Uncertain, Some(false)) => Sign::Uncertain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user