1 //! lint on manually implemented checked conversions that could be transformed into `try_from`
3 use clippy_config
::msrvs
::{self, Msrv}
;
4 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
5 use clippy_utils
::source
::snippet_with_applicability
;
6 use clippy_utils
::{in_constant, is_integer_literal, SpanlessEq}
;
7 use rustc_errors
::Applicability
;
8 use rustc_hir
::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind}
;
9 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
10 use rustc_middle
::lint
::in_external_macro
;
11 use rustc_session
::impl_lint_pass
;
13 declare_clippy_lint
! {
15 /// Checks for explicit bounds checking when casting.
17 /// ### Why is this bad?
18 /// Reduces the readability of statements & is error prone.
22 /// # let foo: u32 = 5;
23 /// foo <= i32::MAX as u32;
29 /// # #[allow(unused)]
30 /// i32::try_from(foo).is_ok();
32 #[clippy::version = "1.37.0"]
33 pub CHECKED_CONVERSIONS
,
35 "`try_from` could replace manual bounds checking when casting"
38 pub struct CheckedConversions
{
42 impl CheckedConversions
{
44 pub fn new(msrv
: Msrv
) -> Self {
49 impl_lint_pass
!(CheckedConversions
=> [CHECKED_CONVERSIONS
]);
51 impl<'tcx
> LateLintPass
<'tcx
> for CheckedConversions
{
52 fn check_expr(&mut self, cx
: &LateContext
<'_
>, item
: &Expr
<'_
>) {
53 if !self.msrv
.meets(msrvs
::TRY_FROM
) {
57 let result
= if !in_constant(cx
, item
.hir_id
)
58 && !in_external_macro(cx
.sess(), item
.span
)
59 && let ExprKind
::Binary(op
, left
, right
) = &item
.kind
62 BinOpKind
::Ge
| BinOpKind
::Le
=> single_check(item
),
63 BinOpKind
::And
=> double_check(cx
, left
, right
),
70 if let Some(cv
) = result
{
71 if let Some(to_type
) = cv
.to_type
{
72 let mut applicability
= Applicability
::MachineApplicable
;
73 let snippet
= snippet_with_applicability(cx
, cv
.expr_to_cast
.span
, "_", &mut applicability
);
78 "checked cast can be simplified",
80 format
!("{to_type}::try_from({snippet}).is_ok()"),
87 extract_msrv_attr
!(LateContext
);
90 /// Searches for a single check from unsigned to _ is done
91 /// todo: check for case signed -> larger unsigned == only x >= 0
92 fn single_check
<'tcx
>(expr
: &'tcx Expr
<'tcx
>) -> Option
<Conversion
<'tcx
>> {
93 check_upper_bound(expr
).filter(|cv
| cv
.cvt
== ConversionType
::FromUnsigned
)
96 /// Searches for a combination of upper & lower bound checks
97 fn double_check
<'a
>(cx
: &LateContext
<'_
>, left
: &'a Expr
<'_
>, right
: &'a Expr
<'_
>) -> Option
<Conversion
<'a
>> {
98 let upper_lower
= |l
, r
| {
99 let upper
= check_upper_bound(l
);
100 let lower
= check_lower_bound(r
);
102 upper
.zip(lower
).and_then(|(l
, r
)| l
.combine(r
, cx
))
105 upper_lower(left
, right
).or_else(|| upper_lower(right
, left
))
108 /// Contains the result of a tried conversion check
109 #[derive(Clone, Debug)]
110 struct Conversion
<'a
> {
112 expr_to_cast
: &'a Expr
<'a
>,
113 to_type
: Option
<&'a
str>,
116 /// The kind of conversion that is checked
117 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
118 enum ConversionType
{
124 impl<'a
> Conversion
<'a
> {
125 /// Combine multiple conversions if the are compatible
126 pub fn combine(self, other
: Self, cx
: &LateContext
<'_
>) -> Option
<Conversion
<'a
>> {
127 if self.is_compatible(&other
, cx
) {
128 // Prefer a Conversion that contains a type-constraint
129 Some(if self.to_type
.is_some() { self }
else { other }
)
135 /// Checks if two conversions are compatible
136 /// same type of conversion, same 'castee' and same 'to type'
137 pub fn is_compatible(&self, other
: &Self, cx
: &LateContext
<'_
>) -> bool
{
138 (self.cvt
== other
.cvt
)
139 && (SpanlessEq
::new(cx
).eq_expr(self.expr_to_cast
, other
.expr_to_cast
))
140 && (self.has_compatible_to_type(other
))
143 /// Checks if the to-type is the same (if there is a type constraint)
144 fn has_compatible_to_type(&self, other
: &Self) -> bool
{
145 match (self.to_type
, other
.to_type
) {
146 (Some(l
), Some(r
)) => l
== r
,
151 /// Try to construct a new conversion if the conversion type is valid
152 fn try_new(expr_to_cast
: &'a Expr
<'_
>, from_type
: &str, to_type
: &'a
str) -> Option
<Conversion
<'a
>> {
153 ConversionType
::try_new(from_type
, to_type
).map(|cvt
| Conversion
{
156 to_type
: Some(to_type
),
160 /// Construct a new conversion without type constraint
161 fn new_any(expr_to_cast
: &'a Expr
<'_
>) -> Conversion
<'a
> {
163 cvt
: ConversionType
::SignedToUnsigned
,
170 impl ConversionType
{
171 /// Creates a conversion type if the type is allowed & conversion is valid
173 fn try_new(from
: &str, to
: &str) -> Option
<Self> {
174 if UINTS
.contains(&from
) {
175 Some(Self::FromUnsigned
)
176 } else if SINTS
.contains(&from
) {
177 if UINTS
.contains(&to
) {
178 Some(Self::SignedToUnsigned
)
179 } else if SINTS
.contains(&to
) {
180 Some(Self::SignedToSigned
)
190 /// Check for `expr <= (to_type::MAX as from_type)`
191 fn check_upper_bound
<'tcx
>(expr
: &'tcx Expr
<'tcx
>) -> Option
<Conversion
<'tcx
>> {
192 if let ExprKind
::Binary(ref op
, left
, right
) = &expr
.kind
193 && let Some((candidate
, check
)) = normalize_le_ge(op
, left
, right
)
194 && let Some((from
, to
)) = get_types_from_cast(check
, INTS
, "max_value", "MAX")
196 Conversion
::try_new(candidate
, from
, to
)
202 /// Check for `expr >= 0|(to_type::MIN as from_type)`
203 fn check_lower_bound
<'tcx
>(expr
: &'tcx Expr
<'tcx
>) -> Option
<Conversion
<'tcx
>> {
204 fn check_function
<'a
>(candidate
: &'a Expr
<'a
>, check
: &'a Expr
<'a
>) -> Option
<Conversion
<'a
>> {
205 (check_lower_bound_zero(candidate
, check
)).or_else(|| (check_lower_bound_min(candidate
, check
)))
208 // First of we need a binary containing the expression & the cast
209 if let ExprKind
::Binary(ref op
, left
, right
) = &expr
.kind
{
210 normalize_le_ge(op
, right
, left
).and_then(|(l
, r
)| check_function(l
, r
))
216 /// Check for `expr >= 0`
217 fn check_lower_bound_zero
<'a
>(candidate
: &'a Expr
<'_
>, check
: &'a Expr
<'_
>) -> Option
<Conversion
<'a
>> {
218 is_integer_literal(check
, 0).then(|| Conversion
::new_any(candidate
))
221 /// Check for `expr >= (to_type::MIN as from_type)`
222 fn check_lower_bound_min
<'a
>(candidate
: &'a Expr
<'_
>, check
: &'a Expr
<'_
>) -> Option
<Conversion
<'a
>> {
223 if let Some((from
, to
)) = get_types_from_cast(check
, SINTS
, "min_value", "MIN") {
224 Conversion
::try_new(candidate
, from
, to
)
230 /// Tries to extract the from- and to-type from a cast expression
231 fn get_types_from_cast
<'a
>(
235 assoc_const
: &'a
str,
236 ) -> Option
<(&'a
str, &'a
str)> {
237 // `to_type::max_value() as from_type`
238 // or `to_type::MAX as from_type`
239 let call_from_cast
: Option
<(&Expr
<'_
>, &str)> = if let ExprKind
::Cast(limit
, from_type
) = &expr
.kind
240 // to_type::max_value(), from_type
241 && let TyKind
::Path(ref from_type_path
) = &from_type
.kind
242 && let Some(from_sym
) = int_ty_to_sym(from_type_path
)
244 Some((limit
, from_sym
))
249 // `from_type::from(to_type::max_value())`
250 let limit_from
: Option
<(&Expr
<'_
>, &str)> = call_from_cast
.or_else(|| {
251 if let ExprKind
::Call(from_func
, [limit
]) = &expr
.kind
252 // `from_type::from, to_type::max_value()`
254 && let ExprKind
::Path(ref path
) = &from_func
.kind
255 && let Some(from_sym
) = get_implementing_type(path
, INTS
, "from")
257 Some((limit
, from_sym
))
263 if let Some((limit
, from_type
)) = limit_from
{
265 // `from_type::from(_)`
266 ExprKind
::Call(path
, _
) => {
267 if let ExprKind
::Path(ref path
) = path
.kind
{
269 if let Some(to_type
) = get_implementing_type(path
, types
, func
) {
270 return Some((from_type
, to_type
));
275 ExprKind
::Path(ref path
) => {
276 if let Some(to_type
) = get_implementing_type(path
, types
, assoc_const
) {
277 return Some((from_type
, to_type
));
286 /// Gets the type which implements the called function
287 fn get_implementing_type
<'a
>(path
: &QPath
<'_
>, candidates
: &'a
[&str], function
: &str) -> Option
<&'a
str> {
288 if let QPath
::TypeRelative(ty
, path
) = &path
289 && path
.ident
.name
.as_str() == function
290 && let TyKind
::Path(QPath
::Resolved(None
, tp
)) = &ty
.kind
291 && let [int
] = tp
.segments
293 let name
= int
.ident
.name
.as_str();
294 candidates
.iter().find(|c
| &name
== *c
).copied()
300 /// Gets the type as a string, if it is a supported integer
301 fn int_ty_to_sym
<'tcx
>(path
: &QPath
<'_
>) -> Option
<&'tcx
str> {
302 if let QPath
::Resolved(_
, path
) = *path
303 && let [ty
] = path
.segments
305 let name
= ty
.ident
.name
.as_str();
306 INTS
.iter().find(|c
| &name
== *c
).copied()
312 /// Will return the expressions as if they were expr1 <= expr2
313 fn normalize_le_ge
<'a
>(op
: &BinOp
, left
: &'a Expr
<'a
>, right
: &'a Expr
<'a
>) -> Option
<(&'a Expr
<'a
>, &'a Expr
<'a
>)> {
315 BinOpKind
::Le
=> Some((left
, right
)),
316 BinOpKind
::Ge
=> Some((right
, left
)),
322 const UINTS
: &[&str] = &["u8", "u16", "u32", "u64", "usize"];
323 const SINTS
: &[&str] = &["i8", "i16", "i32", "i64", "isize"];
324 const INTS
: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"];