Skip to content

Commit b363499

Browse files
authored
Add new confusing_method_to_numeric_cast lint (#13979)
Fixes #13973. I don't think we can make `fn_to_numeric_cast_any` to be emitted in some special cases. Its category cannot be changed at runtime. I think in this case, the best might be a specialized new lint so we can target exactly what we want. ---- changelog: Add new `confusing_method_to_numeric_cast` lint
2 parents 2ce5451 + a94abae commit b363499

7 files changed

+241
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5692,6 +5692,7 @@ Released 2018-09-13
56925692
[`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read
56935693
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
56945694
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
5695+
[`confusing_method_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#confusing_method_to_numeric_cast
56955696
[`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
56965697
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
56975698
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::Expr;
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty::{self, GenericArg, Ty};
7+
use rustc_span::def_id::DefId;
8+
use rustc_span::{Symbol, sym};
9+
10+
use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
11+
12+
fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
13+
match ty.kind() {
14+
ty::Char => Some("char"),
15+
ty::Int(int) => Some(int.name_str()),
16+
ty::Uint(uint) => Some(uint.name_str()),
17+
ty::Float(float) => Some(float.name_str()),
18+
_ => None,
19+
}
20+
}
21+
22+
fn get_const_name_and_ty_name(
23+
cx: &LateContext<'_>,
24+
method_name: Symbol,
25+
method_def_id: DefId,
26+
generics: &[GenericArg<'_>],
27+
) -> Option<(&'static str, &'static str)> {
28+
let method_name = method_name.as_str();
29+
let diagnostic_name = cx.tcx.get_diagnostic_name(method_def_id);
30+
31+
let ty_name = if diagnostic_name.is_some_and(|diag| diag == sym::cmp_ord_min || diag == sym::cmp_ord_max) {
32+
// We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
33+
if let [ty] = generics
34+
&& let Some(ty) = ty.as_type()
35+
{
36+
get_primitive_ty_name(ty)?
37+
} else {
38+
return None;
39+
}
40+
} else if let Some(impl_id) = cx.tcx.impl_of_method(method_def_id)
41+
&& let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity())
42+
&& ["min", "max", "minimum", "maximum", "min_value", "max_value"].contains(&method_name)
43+
{
44+
ty_name
45+
} else {
46+
return None;
47+
};
48+
49+
let const_name = if method_name.starts_with("max") { "MAX" } else { "MIN" };
50+
Some((const_name, ty_name))
51+
}
52+
53+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
54+
// We allow casts from any function type to any function type.
55+
match cast_to.kind() {
56+
ty::FnDef(..) | ty::FnPtr(..) => return,
57+
_ => { /* continue to checks */ },
58+
}
59+
60+
if let ty::FnDef(def_id, generics) = cast_from.kind()
61+
&& let Some(method_name) = cx.tcx.opt_item_name(*def_id)
62+
&& let Some((const_name, ty_name)) = get_const_name_and_ty_name(cx, method_name, *def_id, generics.as_slice())
63+
{
64+
let mut applicability = Applicability::MaybeIncorrect;
65+
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
66+
67+
span_lint_and_then(
68+
cx,
69+
CONFUSING_METHOD_TO_NUMERIC_CAST,
70+
expr.span,
71+
format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
72+
|diag| {
73+
diag.span_suggestion_verbose(
74+
expr.span,
75+
"did you mean to use the associated constant?",
76+
format!("{ty_name}::{const_name} as {cast_to}"),
77+
applicability,
78+
);
79+
},
80+
);
81+
}
82+
}

clippy_lints/src/casts/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod cast_sign_loss;
1414
mod cast_slice_different_sizes;
1515
mod cast_slice_from_raw_parts;
1616
mod char_lit_as_u8;
17+
mod confusing_method_to_numeric_cast;
1718
mod fn_to_numeric_cast;
1819
mod fn_to_numeric_cast_any;
1920
mod fn_to_numeric_cast_with_truncation;
@@ -786,6 +787,32 @@ declare_clippy_lint! {
786787
"casting small constant literals to pointers to create dangling pointers"
787788
}
788789

790+
declare_clippy_lint! {
791+
/// ### What it does
792+
/// Checks for casts of a primitive method pointer like `max`/`min` to any integer type.
793+
///
794+
/// ### Why restrict this?
795+
/// Casting a function pointer to an integer can have surprising results and can occur
796+
/// accidentally if parentheses are omitted from a function call. If you aren't doing anything
797+
/// low-level with function pointers then you can opt out of casting functions to integers in
798+
/// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
799+
/// pointer casts in your code.
800+
///
801+
/// ### Example
802+
/// ```no_run
803+
/// let _ = u16::max as usize;
804+
/// ```
805+
///
806+
/// Use instead:
807+
/// ```no_run
808+
/// let _ = u16::MAX as usize;
809+
/// ```
810+
#[clippy::version = "1.86.0"]
811+
pub CONFUSING_METHOD_TO_NUMERIC_CAST,
812+
suspicious,
813+
"casting a primitive method pointer to any integer type"
814+
}
815+
789816
pub struct Casts {
790817
msrv: Msrv,
791818
}
@@ -823,6 +850,7 @@ impl_lint_pass!(Casts => [
823850
REF_AS_PTR,
824851
AS_POINTER_UNDERSCORE,
825852
MANUAL_DANGLING_PTR,
853+
CONFUSING_METHOD_TO_NUMERIC_CAST,
826854
]);
827855

828856
impl<'tcx> LateLintPass<'tcx> for Casts {
@@ -847,6 +875,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
847875
ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
848876
as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
849877
fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
878+
confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
850879
fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
851880
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
852881
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir);

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
6464
crate::casts::CAST_SLICE_DIFFERENT_SIZES_INFO,
6565
crate::casts::CAST_SLICE_FROM_RAW_PARTS_INFO,
6666
crate::casts::CHAR_LIT_AS_U8_INFO,
67+
crate::casts::CONFUSING_METHOD_TO_NUMERIC_CAST_INFO,
6768
crate::casts::FN_TO_NUMERIC_CAST_INFO,
6869
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
6970
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![feature(float_minimum_maximum)]
2+
#![warn(clippy::confusing_method_to_numeric_cast)]
3+
4+
fn main() {
5+
let _ = u16::MAX as usize; //~ confusing_method_to_numeric_cast
6+
let _ = u16::MIN as usize; //~ confusing_method_to_numeric_cast
7+
let _ = u16::MAX as usize; //~ confusing_method_to_numeric_cast
8+
let _ = u16::MIN as usize; //~ confusing_method_to_numeric_cast
9+
10+
let _ = f32::MAX as usize; //~ confusing_method_to_numeric_cast
11+
let _ = f32::MAX as usize; //~ confusing_method_to_numeric_cast
12+
let _ = f32::MIN as usize; //~ confusing_method_to_numeric_cast
13+
let _ = f32::MIN as usize; //~ confusing_method_to_numeric_cast
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![feature(float_minimum_maximum)]
2+
#![warn(clippy::confusing_method_to_numeric_cast)]
3+
4+
fn main() {
5+
let _ = u16::max as usize; //~ confusing_method_to_numeric_cast
6+
let _ = u16::min as usize; //~ confusing_method_to_numeric_cast
7+
let _ = u16::max_value as usize; //~ confusing_method_to_numeric_cast
8+
let _ = u16::min_value as usize; //~ confusing_method_to_numeric_cast
9+
10+
let _ = f32::maximum as usize; //~ confusing_method_to_numeric_cast
11+
let _ = f32::max as usize; //~ confusing_method_to_numeric_cast
12+
let _ = f32::minimum as usize; //~ confusing_method_to_numeric_cast
13+
let _ = f32::min as usize; //~ confusing_method_to_numeric_cast
14+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
error: casting function pointer `u16::max` to `usize`
2+
--> tests/ui/confusing_method_to_numeric_cast.rs:5:13
3+
|
4+
LL | let _ = u16::max as usize;
5+
| ^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::confusing-method-to-numeric-cast` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::confusing_method_to_numeric_cast)]`
9+
help: did you mean to use the associated constant?
10+
|
11+
LL - let _ = u16::max as usize;
12+
LL + let _ = u16::MAX as usize;
13+
|
14+
15+
error: casting function pointer `u16::min` to `usize`
16+
--> tests/ui/confusing_method_to_numeric_cast.rs:6:13
17+
|
18+
LL | let _ = u16::min as usize;
19+
| ^^^^^^^^^^^^^^^^^
20+
|
21+
help: did you mean to use the associated constant?
22+
|
23+
LL - let _ = u16::min as usize;
24+
LL + let _ = u16::MIN as usize;
25+
|
26+
27+
error: casting function pointer `u16::max_value` to `usize`
28+
--> tests/ui/confusing_method_to_numeric_cast.rs:7:13
29+
|
30+
LL | let _ = u16::max_value as usize;
31+
| ^^^^^^^^^^^^^^^^^^^^^^^
32+
|
33+
help: did you mean to use the associated constant?
34+
|
35+
LL - let _ = u16::max_value as usize;
36+
LL + let _ = u16::MAX as usize;
37+
|
38+
39+
error: casting function pointer `u16::min_value` to `usize`
40+
--> tests/ui/confusing_method_to_numeric_cast.rs:8:13
41+
|
42+
LL | let _ = u16::min_value as usize;
43+
| ^^^^^^^^^^^^^^^^^^^^^^^
44+
|
45+
help: did you mean to use the associated constant?
46+
|
47+
LL - let _ = u16::min_value as usize;
48+
LL + let _ = u16::MIN as usize;
49+
|
50+
51+
error: casting function pointer `f32::maximum` to `usize`
52+
--> tests/ui/confusing_method_to_numeric_cast.rs:10:13
53+
|
54+
LL | let _ = f32::maximum as usize;
55+
| ^^^^^^^^^^^^^^^^^^^^^
56+
|
57+
help: did you mean to use the associated constant?
58+
|
59+
LL - let _ = f32::maximum as usize;
60+
LL + let _ = f32::MAX as usize;
61+
|
62+
63+
error: casting function pointer `f32::max` to `usize`
64+
--> tests/ui/confusing_method_to_numeric_cast.rs:11:13
65+
|
66+
LL | let _ = f32::max as usize;
67+
| ^^^^^^^^^^^^^^^^^
68+
|
69+
help: did you mean to use the associated constant?
70+
|
71+
LL - let _ = f32::max as usize;
72+
LL + let _ = f32::MAX as usize;
73+
|
74+
75+
error: casting function pointer `f32::minimum` to `usize`
76+
--> tests/ui/confusing_method_to_numeric_cast.rs:12:13
77+
|
78+
LL | let _ = f32::minimum as usize;
79+
| ^^^^^^^^^^^^^^^^^^^^^
80+
|
81+
help: did you mean to use the associated constant?
82+
|
83+
LL - let _ = f32::minimum as usize;
84+
LL + let _ = f32::MIN as usize;
85+
|
86+
87+
error: casting function pointer `f32::min` to `usize`
88+
--> tests/ui/confusing_method_to_numeric_cast.rs:13:13
89+
|
90+
LL | let _ = f32::min as usize;
91+
| ^^^^^^^^^^^^^^^^^
92+
|
93+
help: did you mean to use the associated constant?
94+
|
95+
LL - let _ = f32::min as usize;
96+
LL + let _ = f32::MIN as usize;
97+
|
98+
99+
error: aborting due to 8 previous errors
100+

0 commit comments

Comments
 (0)