Skip to content

Commit c057abd

Browse files
authored
Add suggestion to cast_sign_loss and cast_possible_wrap using the cast_{un,}signed() methods (#15384)
changelog: [`cast_sign_loss`, `cast_possible_wrap`]: add suggestion using `cast_{un,}signed()` methods Fixes #14150 This PR adds a suggestion to `cast_sign_loss` and `cast_possible_wrap`, which fixes reported warnings using the `cast_{un,}signed()` methods, as proposed in #14150. Suggestions are only made if MSRV is >=1.87. Also, I'm not 100% sure about the suggestion message yet. I'm open for suggestions.
2 parents 467efe0 + 4e286bb commit c057abd

File tree

7 files changed

+140
-49
lines changed

7 files changed

+140
-49
lines changed

clippy_lints/src/casts/cast_possible_wrap.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::msrvs::{self, Msrv};
3+
use clippy_utils::sugg::Sugg;
4+
use rustc_errors::Applicability;
25
use rustc_hir::Expr;
36
use rustc_lint::LateContext;
47
use rustc_middle::ty::Ty;
@@ -16,7 +19,14 @@ enum EmitState {
1619
LintOnPtrSize(u64),
1720
}
1821

19-
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
22+
pub(super) fn check(
23+
cx: &LateContext<'_>,
24+
expr: &Expr<'_>,
25+
cast_op: &Expr<'_>,
26+
cast_from: Ty<'_>,
27+
cast_to: Ty<'_>,
28+
msrv: Msrv,
29+
) {
2030
let (Some(from_nbits), Some(to_nbits)) = (
2131
utils::int_ty_to_nbits(cx.tcx, cast_from),
2232
utils::int_ty_to_nbits(cx.tcx, cast_to),
@@ -85,5 +95,23 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
8595
.note("`usize` and `isize` may be as small as 16 bits on some platforms")
8696
.note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types");
8797
}
98+
99+
if msrv.meets(cx, msrvs::INTEGER_SIGN_CAST)
100+
&& let Some(cast) = utils::is_signedness_cast(cast_from, cast_to)
101+
{
102+
let method = match cast {
103+
utils::CastTo::Signed => "cast_signed()",
104+
utils::CastTo::Unsigned => "cast_unsigned()",
105+
};
106+
let mut app = Applicability::MaybeIncorrect;
107+
let sugg = Sugg::hir_with_context(cx, cast_op, expr.span.ctxt(), "..", &mut app);
108+
109+
diag.span_suggestion(
110+
expr.span,
111+
format!("if this is intentional, use `{method}` instead"),
112+
format!("{}.{method}", sugg.maybe_paren()),
113+
app,
114+
);
115+
}
88116
});
89117
}

clippy_lints/src/casts/cast_sign_loss.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ use std::convert::Infallible;
22
use std::ops::ControlFlow;
33

44
use clippy_utils::consts::{ConstEvalCtxt, Constant};
5-
use clippy_utils::diagnostics::span_lint;
5+
use clippy_utils::diagnostics::span_lint_and_then;
6+
use clippy_utils::msrvs::{self, Msrv};
7+
use clippy_utils::sugg::Sugg;
68
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
79
use clippy_utils::{method_chain_args, sext, sym};
10+
use rustc_errors::Applicability;
811
use rustc_hir::{BinOpKind, Expr, ExprKind};
912
use rustc_lint::LateContext;
1013
use rustc_middle::ty::{self, Ty};
1114
use rustc_span::Symbol;
1215

13-
use super::CAST_SIGN_LOSS;
16+
use super::{CAST_SIGN_LOSS, utils};
1417

1518
/// A list of methods that can never return a negative value.
1619
/// Includes methods that panic rather than returning a negative value.
@@ -42,13 +45,33 @@ pub(super) fn check<'cx>(
4245
cast_op: &Expr<'_>,
4346
cast_from: Ty<'cx>,
4447
cast_to: Ty<'_>,
48+
msrv: Msrv,
4549
) {
4650
if should_lint(cx, cast_op, cast_from, cast_to) {
47-
span_lint(
51+
span_lint_and_then(
4852
cx,
4953
CAST_SIGN_LOSS,
5054
expr.span,
5155
format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"),
56+
|diag| {
57+
if msrv.meets(cx, msrvs::INTEGER_SIGN_CAST)
58+
&& let Some(cast) = utils::is_signedness_cast(cast_from, cast_to)
59+
{
60+
let method = match cast {
61+
utils::CastTo::Signed => "cast_signed()",
62+
utils::CastTo::Unsigned => "cast_unsigned()",
63+
};
64+
let mut app = Applicability::MaybeIncorrect;
65+
let sugg = Sugg::hir_with_context(cx, cast_op, expr.span.ctxt(), "..", &mut app);
66+
67+
diag.span_suggestion(
68+
expr.span,
69+
format!("if this is intentional, use `{method}` instead"),
70+
format!("{}.{method}", sugg.maybe_paren()),
71+
app,
72+
);
73+
}
74+
},
5275
);
5376
}
5477
}

clippy_lints/src/casts/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,9 +890,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
890890
if cast_to.is_numeric() {
891891
cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span);
892892
if cast_from.is_numeric() {
893-
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
893+
cast_possible_wrap::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
894894
cast_precision_loss::check(cx, expr, cast_from, cast_to);
895-
cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to);
895+
cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
896896
cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
897897
cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to);
898898
}

clippy_lints/src/casts/utils.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,18 @@ pub(super) fn enum_ty_to_nbits(adt: AdtDef<'_>, tcx: TyCtxt<'_>) -> u64 {
6060
neg_bits.max(pos_bits).into()
6161
}
6262
}
63+
64+
pub(super) enum CastTo {
65+
Signed,
66+
Unsigned,
67+
}
68+
/// Returns `Some` if the type cast is between 2 integral types that differ
69+
/// only in signedness, otherwise `None`. The value of `Some` is which
70+
/// signedness is casted to.
71+
pub(super) fn is_signedness_cast(cast_from: Ty<'_>, cast_to: Ty<'_>) -> Option<CastTo> {
72+
match (cast_from.kind(), cast_to.kind()) {
73+
(ty::Int(from), ty::Uint(to)) if from.to_unsigned() == *to => Some(CastTo::Unsigned),
74+
(ty::Uint(from), ty::Int(to)) if *from == to.to_unsigned() => Some(CastTo::Signed),
75+
_ => None,
76+
}
77+
}

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ macro_rules! msrv_aliases {
2424
// names may refer to stabilized feature flags or library items
2525
msrv_aliases! {
2626
1,88,0 { LET_CHAINS }
27-
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF }
27+
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST }
2828
1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL }
2929
1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
3030
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }

tests/ui/cast.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,3 +569,16 @@ fn issue12721() {
569569
(255 % 999999u64) as u8;
570570
//~^ cast_possible_truncation
571571
}
572+
573+
mod issue14150 {
574+
#[clippy::msrv = "1.87"]
575+
fn msrv_supports_cast_signed() {
576+
_ = 1u8 as i8;
577+
//~^ cast_possible_wrap
578+
}
579+
#[clippy::msrv = "1.86"]
580+
fn msrv_doesnt_supports_cast_signed() {
581+
_ = 1u8 as i8;
582+
//~^ cast_possible_wrap
583+
}
584+
}

0 commit comments

Comments
 (0)