Repository: rust-lang/rust-clippy
Pull request #13979: Add new `confusing_method_to_numeric_cast` lint
Author: login: GuillaumeGomez
Labels: name: S-waiting-on-author
description: Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)
Created: 2025-01-10T16:46:16Z | Merged: 2025-05-12T15:34:27Z

Fixes https://github.com/rust-lang/rust-clippy/issues/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 


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b62c9a59aa5..6ab4cffb0cd3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5594,6 +5594,7 @@ Released 2018-09-13
 [`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read
 [`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
 [`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
+[`confusing_method_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#confusing_method_to_numeric_cast
 [`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
 [`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
 [`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
diff --git a/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs b/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
new file mode 100644
index 000000000000..31cdd078f45a
--- /dev/null
+++ b/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
@@ -0,0 +1,82 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, GenericArg, Ty};
+use rustc_span::def_id::DefId;
+use rustc_span::{Symbol, sym};
+
+use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
+
+fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
+    match ty.kind() {
+        ty::Char => Some("char"),
+        ty::Int(int) => Some(int.name_str()),
+        ty::Uint(uint) => Some(uint.name_str()),
+        ty::Float(float) => Some(float.name_str()),
+        _ => None,
+    }
+}
+
+fn get_const_name_and_ty_name(
+    cx: &LateContext<'_>,
+    method_name: Symbol,
+    method_def_id: DefId,
+    generics: &[GenericArg<'_>],
+) -> Option<(&'static str, &'static str)> {
+    let method_name = method_name.as_str();
+    let diagnostic_name = cx.tcx.get_diagnostic_name(method_def_id);
+
+    let ty_name = if diagnostic_name.is_some_and(|diag| diag == sym::cmp_ord_min || diag == sym::cmp_ord_max) {
+        // We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
+        if let [ty] = generics
+            && let Some(ty) = ty.as_type()
+        {
+            get_primitive_ty_name(ty)?
+        } else {
+            return None;
+        }
+    } else if let Some(impl_id) = cx.tcx.impl_of_method(method_def_id)
+        && let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity())
+        && ["min", "max", "minimum", "maximum", "min_value", "max_value"].contains(&method_name)
+    {
+        ty_name
+    } else {
+        return None;
+    };
+
+    let const_name = if method_name.starts_with("max") { "MAX" } else { "MIN" };
+    Some((const_name, ty_name))
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+    // We allow casts from any function type to any function type.
+    match cast_to.kind() {
+        ty::FnDef(..) | ty::FnPtr(..) => return,
+        _ => { /* continue to checks */ },
+    }
+
+    if let ty::FnDef(def_id, generics) = cast_from.kind()
+        && let Some(method_name) = cx.tcx.opt_item_name(*def_id)
+        && let Some((const_name, ty_name)) = get_const_name_and_ty_name(cx, method_name, *def_id, generics.as_slice())
+    {
+        let mut applicability = Applicability::MaybeIncorrect;
+        let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
+
+        span_lint_and_then(
+            cx,
+            CONFUSING_METHOD_TO_NUMERIC_CAST,
+            expr.span,
+            format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
+            |diag| {
+                diag.span_suggestion_verbose(
+                    expr.span,
+                    "did you mean to use the associated constant?",
+                    format!("{ty_name}::{const_name} as {cast_to}"),
+                    applicability,
+                );
+            },
+        );
+    }
+}
diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs
index 76931fce209e..16a1552c5eea 100644
--- a/clippy_lints/src/casts/mod.rs
+++ b/clippy_lints/src/casts/mod.rs
@@ -14,6 +14,7 @@ mod cast_sign_loss;
 mod cast_slice_different_sizes;
 mod cast_slice_from_raw_parts;
 mod char_lit_as_u8;
+mod confusing_method_to_numeric_cast;
 mod fn_to_numeric_cast;
 mod fn_to_numeric_cast_any;
 mod fn_to_numeric_cast_with_truncation;
@@ -786,6 +787,32 @@ declare_clippy_lint! {
     "casting small constant literals to pointers to create dangling pointers"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type.
+    ///
+    /// ### Why restrict this?
+    /// Casting a function pointer to an integer can have surprising results and can occur
+    /// accidentally if parentheses are omitted from a function call. If you aren't doing anything
+    /// low-level with function pointers then you can opt out of casting functions to integers in
+    /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
+    /// pointer casts in your code.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// let _ = u16::max as usize;
+    /// ```
+    ///
+    /// Use instead:
+    /// ```no_run
+    /// let _ = u16::MAX as usize;
+    /// ```
+    #[clippy::version = "1.86.0"]
+    pub CONFUSING_METHOD_TO_NUMERIC_CAST,
+    suspicious,
+    "casting a primitive method pointer to any integer type"
+}
+
 pub struct Casts {
     msrv: Msrv,
 }
@@ -823,6 +850,7 @@ impl_lint_pass!(Casts => [
     REF_AS_PTR,
     AS_POINTER_UNDERSCORE,
     MANUAL_DANGLING_PTR,
+    CONFUSING_METHOD_TO_NUMERIC_CAST,
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for Casts {
@@ -847,6 +875,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
             ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
             as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
             fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
+            confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
             fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
             fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
             zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 2cccd6ba2702..472d0f732ac0 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -64,6 +64,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
     crate::casts::CAST_SLICE_DIFFERENT_SIZES_INFO,
     crate::casts::CAST_SLICE_FROM_RAW_PARTS_INFO,
     crate::casts::CHAR_LIT_AS_U8_INFO,
+    crate::casts::CONFUSING_METHOD_TO_NUMERIC_CAST_INFO,
     crate::casts::FN_TO_NUMERIC_CAST_INFO,
     crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
     crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
diff --git a/tests/ui/confusing_method_to_numeric_cast.fixed b/tests/ui/confusing_method_to_numeric_cast.fixed
new file mode 100644
index 000000000000..e698b99edd5c
--- /dev/null
+++ b/tests/ui/confusing_method_to_numeric_cast.fixed
@@ -0,0 +1,14 @@
+#![feature(float_minimum_maximum)]
+#![warn(clippy::confusing_method_to_numeric_cast)]
+
+fn main() {
+    let _ = u16::MAX as usize; //~ confusing_method_to_numeric_cast
+    let _ = u16::MIN as usize; //~ confusing_method_to_numeric_cast
+    let _ = u16::MAX as usize; //~ confusing_method_to_numeric_cast
+    let _ = u16::MIN as usize; //~ confusing_method_to_numeric_cast
+
+    let _ = f32::MAX as usize; //~ confusing_method_to_numeric_cast
+    let _ = f32::MAX as usize; //~ confusing_method_to_numeric_cast
+    let _ = f32::MIN as usize; //~ confusing_method_to_numeric_cast
+    let _ = f32::MIN as usize; //~ confusing_method_to_numeric_cast
+}
diff --git a/tests/ui/confusing_method_to_numeric_cast.rs b/tests/ui/confusing_method_to_numeric_cast.rs
new file mode 100644
index 000000000000..ef65c21563d9
--- /dev/null
+++ b/tests/ui/confusing_method_to_numeric_cast.rs
@@ -0,0 +1,14 @@
+#![feature(float_minimum_maximum)]
+#![warn(clippy::confusing_method_to_numeric_cast)]
+
+fn main() {
+    let _ = u16::max as usize; //~ confusing_method_to_numeric_cast
+    let _ = u16::min as usize; //~ confusing_method_to_numeric_cast
+    let _ = u16::max_value as usize; //~ confusing_method_to_numeric_cast
+    let _ = u16::min_value as usize; //~ confusing_method_to_numeric_cast
+
+    let _ = f32::maximum as usize; //~ confusing_method_to_numeric_cast
+    let _ = f32::max as usize; //~ confusing_method_to_numeric_cast
+    let _ = f32::minimum as usize; //~ confusing_method_to_numeric_cast
+    let _ = f32::min as usize; //~ confusing_method_to_numeric_cast
+}
diff --git a/tests/ui/confusing_method_to_numeric_cast.stderr b/tests/ui/confusing_method_to_numeric_cast.stderr
new file mode 100644
index 000000000000..ba90df2059af
--- /dev/null
+++ b/tests/ui/confusing_method_to_numeric_cast.stderr
@@ -0,0 +1,100 @@
+error: casting function pointer `u16::max` to `usize`
+  --> tests/ui/confusing_method_to_numeric_cast.rs:5:13
+   |
+LL |     let _ = u16::max as usize;
+   |             ^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::confusing-method-to-numeric-cast` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::confusing_method_to_numeric_cast)]`
+help: did you mean to use the associated constant?
+   |
+LL -     let _ = u16::max as usize;
+LL +     let _ = u16::MAX as usize;
+   |
+
+error: casting function pointer `u16::min` to `usize`
+  --> tests/ui/confusing_method_to_numeric_cast.rs:6:13
+   |
+LL |     let _ = u16::min as usize;
+   |             ^^^^^^^^^^^^^^^^^
+   |
+help: did you mean to use the associated constant?
+   |
+LL -     let _ = u16::min as usize;
+LL +     let _ = u16::MIN as usize;
+   |
+
+error: casting function pointer `u16::max_value` to `usize`
+  --> tests/ui/confusing_method_to_numeric_cast.rs:7:13
+   |
+LL |     let _ = u16::max_value as usize;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: did you mean to use the associated constant?
+   |
+LL -     let _ = u16::max_value as usize;
+LL +     let _ = u16::MAX as usize;
+   |
+
+error: casting function pointer `u16::min_value` to `usize`
+  --> tests/ui/confusing_method_to_numeric_cast.rs:8:13
+   |
+LL |     let _ = u16::min_value as usize;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: did you mean to use the associated constant?
+   |
+LL -     let _ = u16::min_value as usize;
+LL +     let _ = u16::MIN as usize;
+   |
+
+error: casting function pointer `f32::maximum` to `usize`
+  --> tests/ui/confusing_method_to_numeric_cast.rs:10:13
+   |
+LL |     let _ = f32::maximum as usize;
+   |             ^^^^^^^^^^^^^^^^^^^^^
+   |
+help: did you mean to use the associated constant?
+   |
+LL -     let _ = f32::maximum as usize;
+LL +     let _ = f32::MAX as usize;
+   |
+
+error: casting function pointer `f32::max` to `usize`
+  --> tests/ui/confusing_method_to_numeric_cast.rs:11:13
+   |
+LL |     let _ = f32::max as usize;
+   |             ^^^^^^^^^^^^^^^^^
+   |
+help: did you mean to use the associated constant?
+   |
+LL -     let _ = f32::max as usize;
+LL +     let _ = f32::MAX as usize;
+   |
+
+error: casting function pointer `f32::minimum` to `usize`
+  --> tests/ui/confusing_method_to_numeric_cast.rs:12:13
+   |
+LL |     let _ = f32::minimum as usize;
+   |             ^^^^^^^^^^^^^^^^^^^^^
+   |
+help: did you mean to use the associated constant?
+   |
+LL -     let _ = f32::minimum as usize;
+LL +     let _ = f32::MIN as usize;
+   |
+
+error: casting function pointer `f32::min` to `usize`
+  --> tests/ui/confusing_method_to_numeric_cast.rs:13:13
+   |
+LL |     let _ = f32::min as usize;
+   |             ^^^^^^^^^^^^^^^^^
+   |
+help: did you mean to use the associated constant?
+   |
+LL -     let _ = f32::min as usize;
+LL +     let _ = f32::MIN as usize;
+   |
+
+error: aborting due to 8 previous errors
+


Reviews:
user: login: alex-semenyuk
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-10T17:32:39Z
user: login: alex-semenyuk
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-10T17:36:05Z
user: login: samueltardieu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-10T17:38:21Z
user: login: GuillaumeGomez
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-10T23:20:17Z
user: login: samueltardieu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-10T23:25:52Z
user: login: samueltardieu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-10T23:47:32Z
user: login: samueltardieu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-10T23:57:36Z
user: login: samueltardieu
body: I think this would deserve more tests, with the various types, as well as some other types implementing `Ord` not eligible to this lint.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-11T00:12:15Z
user: login: theemathas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-11T06:52:53Z
user: login: theemathas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-11T07:04:01Z
user: login: theemathas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-11T07:05:58Z
user: login: GuillaumeGomez
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T16:37:15Z
user: login: GuillaumeGomez
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T19:42:37Z
user: login: theemathas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T15:39:29Z
user: login: meltyness
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-12T15:41:52Z

Review comments:
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::PRIMITIVE_METHOD_TO_NUMERIC_CAST;
+
+fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
+    match ty.kind() {
+        ty::Char => Some("char"),
+        ty::Int(int) => Some(int.name_str()),
+        ty::Uint(uint) => Some(uint.name_str()),
+        ty::Float(float) => Some(float.name_str()),
+        _ => None,
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+    // We allow casts from any function type to any function type.
+    match cast_to.kind() {
+        ty::FnDef(..) | ty::FnPtr(..) => return,
+        _ => { /* continue to checks */ },
+    }
+
+    if let ty::FnDef(def_id, generics) = cast_from.kind()
+        && let Some(method_name) = cx.tcx.opt_item_name(*def_id)
+        && let method_name = method_name.as_str()
+        && (method_name == "min" || method_name == "max")
+        // We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
+        && let [ty] = generics.as_slice()
+        && let Some(ty) = ty.as_type()
+        // We get its name in case it's a primitive with an associated MIN/MAx constant.
path: clippy_lints/src/casts/primitive_method_to_numeric_cast.rs
user: login: alex-semenyuk
body: typo
```suggestion
        // We get its name in case it's a primitive with an associated MIN/MAX constant.
```
created_at: 2025-01-10T17:32:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::PRIMITIVE_METHOD_TO_NUMERIC_CAST;
path: clippy_lints/src/casts/primitive_method_to_numeric_cast.rs
user: login: alex-semenyuk
body: Are there primitive methods only max, min 
If so might make sense to specify them at lint name   
created_at: 2025-01-10T17:36:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::PRIMITIVE_METHOD_TO_NUMERIC_CAST;
path: clippy_lints/src/casts/primitive_method_to_numeric_cast.rs
user: login: samueltardieu
body: Or `confusing_method_to_numeric_cast`?
created_at: 2025-01-10T17:38:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::PRIMITIVE_METHOD_TO_NUMERIC_CAST;
path: clippy_lints/src/casts/primitive_method_to_numeric_cast.rs
user: login: GuillaumeGomez
body: I'm very bad at naming so I'll take your suggestion. :3
created_at: 2025-01-10T23:20:17Z
author_association: MEMBER
diff_hunk: @@ -754,6 +755,32 @@ declare_clippy_lint! {
     "detects `as *mut _` and `as *const _` conversion"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for casts of a primitive method pointer to any integer type.
+    ///
+    /// ### Why restrict this?
+    /// Casting a function pointer to an integer can have surprising results and can occur
+    /// accidentally if parentheses are omitted from a function call. If you aren't doing anything
+    /// low-level with function pointers then you can opt out of casting functions to integers in
+    /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
+    /// pointer casts in your code.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// let _ = u16::max as usize;
+    /// ```
+    ///
+    /// Use instead:
+    /// ```no_run
+    /// let _ = u16::MAX as usize;
+    /// ```
+    #[clippy::version = "1.85.0"]
path: clippy_lints/src/casts/mod.rs
user: login: samueltardieu
body: Nit: 1.86.0
created_at: 2025-01-10T23:25:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
+
+fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
+    match ty.kind() {
+        ty::Char => Some("char"),
+        ty::Int(int) => Some(int.name_str()),
+        ty::Uint(uint) => Some(uint.name_str()),
+        ty::Float(float) => Some(float.name_str()),
+        _ => None,
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+    // We allow casts from any function type to any function type.
+    match cast_to.kind() {
+        ty::FnDef(..) | ty::FnPtr(..) => return,
+        _ => { /* continue to checks */ },
+    }
+
+    if let ty::FnDef(def_id, generics) = cast_from.kind()
+        && let Some(method_name) = cx.tcx.opt_item_name(*def_id)
+        && let method_name = method_name.as_str()
+        && (method_name == "min" || method_name == "max")
+        // We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
+        && let [ty] = generics.as_slice()
+        && let Some(ty) = ty.as_type()
+        // We get its name in case it's a primitive with an associated MIN/MAX constant.
+        && let Some(ty_name) = get_primitive_ty_name(ty)
+        && match_def_path(cx, *def_id, &["core", "cmp", "Ord", method_name])
path: clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
user: login: samueltardieu
body: I think you can use the diag item here.

```suggestion
        && is_diag_trait_item(cx, *def_id, sym::Ord)
```
created_at: 2025-01-10T23:47:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
+
+fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
+    match ty.kind() {
+        ty::Char => Some("char"),
+        ty::Int(int) => Some(int.name_str()),
+        ty::Uint(uint) => Some(uint.name_str()),
+        ty::Float(float) => Some(float.name_str()),
+        _ => None,
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+    // We allow casts from any function type to any function type.
+    match cast_to.kind() {
+        ty::FnDef(..) | ty::FnPtr(..) => return,
+        _ => { /* continue to checks */ },
+    }
+
+    if let ty::FnDef(def_id, generics) = cast_from.kind()
+        && let Some(method_name) = cx.tcx.opt_item_name(*def_id)
+        && let method_name = method_name.as_str()
+        && (method_name == "min" || method_name == "max")
+        // We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
+        && let [ty] = generics.as_slice()
+        && let Some(ty) = ty.as_type()
+        // We get its name in case it's a primitive with an associated MIN/MAX constant.
+        && let Some(ty_name) = get_primitive_ty_name(ty)
path: clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
user: login: samueltardieu
body: ```suggestion
        && matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_))
```

(floating point types don't implement `::MIN`/`::MAX`) 

and then you can use directly `ty` in your diagnostic, it will print as the type name, no need for the `get_primitive_ty_name()` function.
created_at: 2025-01-10T23:57:36Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
+
+fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
+    match ty.kind() {
+        ty::Char => Some("char"),
+        ty::Int(int) => Some(int.name_str()),
+        ty::Uint(uint) => Some(uint.name_str()),
+        ty::Float(float) => Some(float.name_str()),
+        _ => None,
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+    // We allow casts from any function type to any function type.
+    match cast_to.kind() {
+        ty::FnDef(..) | ty::FnPtr(..) => return,
+        _ => { /* continue to checks */ },
+    }
+
+    if let ty::FnDef(def_id, generics) = cast_from.kind()
+        && let Some(method_name) = cx.tcx.opt_item_name(*def_id)
+        && let method_name = method_name.as_str()
+        && (method_name == "min" || method_name == "max")
+        // We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
+        && let [ty] = generics.as_slice()
+        && let Some(ty) = ty.as_type()
+        // We get its name in case it's a primitive with an associated MIN/MAX constant.
+        && let Some(ty_name) = get_primitive_ty_name(ty)
+        && match_def_path(cx, *def_id, &["core", "cmp", "Ord", method_name])
path: clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
user: login: samueltardieu
body: In fact, you can even go simpler and test against the right method directly, both `Ord::min` and `Ord::max` are diagnostic items. This way you can use `.item_name()` directly as you know the method has a name.

```rust
    if let ty::FnDef(def_id, generics) = cast_from.kind()
        && cx.tcx.get_diagnostic_name(*def_id).is_some_and(|diag| diag == sym::cmp_ord_min || diag == sym::cmp_ord_max)
        // We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
        && let [ty] = generics.as_slice()
        && let Some(ty) = ty.as_type()
        // We get its name in case it's a primitive with an associated MIN/MAX constant.
        && matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_))
    {
        let mut applicability = Applicability::MaybeIncorrect;
        let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);

        span_lint_and_then(
            cx,
            PRIMITIVE_METHOD_TO_NUMERIC_CAST,
            expr.span,
            format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
            |diag| {
                diag.span_suggestion_verbose(
                    expr.span,
                    "did you mean to use the associated constant?",
                    format!(
                        "{ty}::{} as {cast_to}",
                        cx.tcx.item_name(*def_id).as_str().to_ascii_uppercase()
                    ),
                    applicability,
                );
            },
        );
    }
```
created_at: 2025-01-11T00:11:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,24 @@
+error: unknown lint: `clippy::primitive_method_to_numeric_cast`
path: tests/ui/confusing_method_to_numeric_cast.stderr
user: login: theemathas
body: Is this supposed to happen?
created_at: 2025-01-11T06:52:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -754,6 +755,32 @@ declare_clippy_lint! {
     "detects `as *mut _` and `as *const _` conversion"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for casts of a primitive method pointer to any integer type.
path: clippy_lints/src/casts/mod.rs
user: login: theemathas
body: This communicates that casting *any* method on any integer type will cause this lint to fire, even though it checks for only min and max.
created_at: 2025-01-11T07:04:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
+
+fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
+    match ty.kind() {
+        ty::Char => Some("char"),
+        ty::Int(int) => Some(int.name_str()),
+        ty::Uint(uint) => Some(uint.name_str()),
+        ty::Float(float) => Some(float.name_str()),
+        _ => None,
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+    // We allow casts from any function type to any function type.
+    match cast_to.kind() {
+        ty::FnDef(..) | ty::FnPtr(..) => return,
+        _ => { /* continue to checks */ },
+    }
+
+    if let ty::FnDef(def_id, generics) = cast_from.kind()
+        && let Some(method_name) = cx.tcx.opt_item_name(*def_id)
+        && let method_name = method_name.as_str()
+        && (method_name == "min" || method_name == "max")
+        // We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
+        && let [ty] = generics.as_slice()
+        && let Some(ty) = ty.as_type()
+        // We get its name in case it's a primitive with an associated MIN/MAX constant.
+        && let Some(ty_name) = get_primitive_ty_name(ty)
path: clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
user: login: theemathas
body: Floating point types do have MIN/MAX https://doc.rust-lang.org/std/primitive.f32.html#associatedconstant.MAX
created_at: 2025-01-11T07:05:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
+
+fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
+    match ty.kind() {
+        ty::Char => Some("char"),
+        ty::Int(int) => Some(int.name_str()),
+        ty::Uint(uint) => Some(uint.name_str()),
+        ty::Float(float) => Some(float.name_str()),
+        _ => None,
+    }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+    // We allow casts from any function type to any function type.
+    match cast_to.kind() {
+        ty::FnDef(..) | ty::FnPtr(..) => return,
+        _ => { /* continue to checks */ },
+    }
+
+    if let ty::FnDef(def_id, generics) = cast_from.kind()
+        && let Some(method_name) = cx.tcx.opt_item_name(*def_id)
+        && let method_name = method_name.as_str()
+        && (method_name == "min" || method_name == "max")
+        // We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
+        && let [ty] = generics.as_slice()
+        && let Some(ty) = ty.as_type()
+        // We get its name in case it's a primitive with an associated MIN/MAX constant.
+        && let Some(ty_name) = get_primitive_ty_name(ty)
path: clippy_lints/src/casts/confusing_method_to_numeric_cast.rs
user: login: GuillaumeGomez
body: I'll add a test for floats then.
created_at: 2025-05-07T16:37:14Z
author_association: MEMBER
diff_hunk: @@ -754,6 +755,32 @@ declare_clippy_lint! {
     "detects `as *mut _` and `as *const _` conversion"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for casts of a primitive method pointer to any integer type.
path: clippy_lints/src/casts/mod.rs
user: login: GuillaumeGomez
body: Good point, clarified it.
created_at: 2025-05-07T19:42:37Z
author_association: MEMBER
diff_hunk: @@ -786,6 +787,32 @@ declare_clippy_lint! {
     "casting small constant literals to pointers to create dangling pointers"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type.
+    ///
+    /// ### Why restrict this?
+    /// Casting a function pointer to an integer can have surprising results and can occur
+    /// accidentally if parentheses are omitted from a function call. If you aren't doing anything
path: clippy_lints/src/casts/mod.rs
user: login: theemathas
body: Is this comment incorrect? I don't think omitted parentheses are in the cases caught by this lint.
created_at: 2025-05-12T15:39:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -786,6 +787,32 @@ declare_clippy_lint! {
     "casting small constant literals to pointers to create dangling pointers"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type.
+    ///
+    /// ### Why restrict this?
+    /// Casting a function pointer to an integer can have surprising results and can occur
+    /// accidentally if parentheses are omitted from a function call. If you aren't doing anything
path: clippy_lints/src/casts/mod.rs
user: login: meltyness
body: Yes that's correct; the expression, `i32::max`:
- omits parenthesis
- may be cast as an FP's (abstractly) location to, an `i64`, for example.

where the most likely intended effect would have been to obtain `2_147_483_647i32`
created_at: 2025-05-12T15:41:52Z
author_association: NONE

PR comments:
user: login: rustbot
created_at: 2025-01-10T16:46:22Z
body: r? @llogiq

rustbot has assigned @llogiq.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use `r?` to explicitly pick a reviewer
author_association: COLLABORATOR
user: login: GuillaumeGomez
created_at: 2025-01-10T23:45:03Z
body: Renamed and fixed the typos/nits.
author_association: MEMBER
user: login: theemathas
created_at: 2025-01-11T06:54:18Z
body: Could this be extended to also cover conversions using from/into/try_from/try_into?
author_association: CONTRIBUTOR
user: login: theemathas
created_at: 2025-01-11T06:58:30Z
body: Floating point numbers also have inherent min/max methods even though they don't implement Ord https://doc.rust-lang.org/std/primitive.f32.html#method.max
author_association: CONTRIBUTOR
user: login: samueltardieu
created_at: 2025-01-11T07:57:36Z
body: > Floating point numbers also have inherent min/max methods even though they don't implement Ord https://doc.rust-lang.org/std/primitive.f32.html#method.max

Good point. I wonder whether:

- the lint must cover them (probably)
- the lint should cover any situation where we have a `min`/`max` method as well as `MIN`/`MAX` associated constants
- the lint should cover any situation where we have a `<x>` method and an associated `X` constant (whatever `x` is) – I think this would be overkill because I couldn't find existing cases other than `min` and `max` on primitive types


author_association: MEMBER
user: login: llogiq
created_at: 2025-01-26T11:48:55Z
body: > the lint should cover any situation where we have a `min`/`max` method as well as `MIN`/`MAX` associated constants

I don't think we need this. People can name their functions and constants however they like, so we should not guess intent based on names here. If we cover the standard library, we'll be fine. If we really want, we can add a configuration entry for people to add their own items to the lint.
author_association: CONTRIBUTOR
user: login: GuillaumeGomez
created_at: 2025-01-29T19:00:35Z
body: Someone commented on [the issue](https://github.com/rust-lang/rust-clippy/issues/13973#issuecomment-2622493811) that `min_value` and `max_value` are also confusing and I agree so I plan to add them when I'll update this PR.
author_association: MEMBER
user: login: meltyness
created_at: 2025-01-29T21:20:58Z
body: > Someone commented on [the issue](https://github.com/rust-lang/rust-clippy/issues/13973#issuecomment-2622493811) that `min_value` and `max_value` are also confusing and I agree so I plan to add them when I'll update this PR.

I took a whack at it, it seems like `generics` available to discern the base type available for the trait methods is not available for these deprecated methods, I made a workaround. I'm not sure it's the best way, but it seems to work with some cursory testing.

```rust
 else if let ty::FnDef(def_id, _generics) = cast_from.kind()
        && let Some(method_name) = cx.tcx.opt_item_name(*def_id)
        && let method_name = method_name.as_str()
        && (method_name == "min_value" || method_name == "max_value")
        && ["core", "num"].iter().map(|x| Symbol::intern(x)).zip(cx.get_def_path(*def_id).iter().copied()).all(|(a,b)| a == b)
    {
        let mut applicability = Applicability::MaybeIncorrect;
        let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);

        let method_name = if method_name == "min_value" { "min" } else { "max" };
        let inferred_type = cx.get_def_path(*def_id).iter().copied().skip(2).next().unwrap();
        let inferred_type = inferred_type.as_str().split_whitespace().skip(1).next().unwrap();
        let inferred_type = inferred_type.chars().filter(|arg0: &char| char::is_alphanumeric(*arg0)).collect::<String>();
        span_lint_and_then(
            cx,
            CONFUSING_METHOD_TO_NUMERIC_CAST,
            expr.span,
            format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
            |diag| {
                diag.span_suggestion_verbose(
                    expr.span,
                    "did you mean to use the associated constant?",
                    format!("{inferred_type:#?}::{} as {cast_to}", method_name.to_ascii_uppercase()),
                    applicability,
                );
            },
        );
    }
 ```
author_association: NONE
user: login: meltyness
created_at: 2025-03-12T17:02:35Z
body: There's also `f32::minimum` and `f32::maximum` i've discovered
author_association: NONE
user: login: rustbot
created_at: 2025-03-31T23:43:03Z
body: :umbrella: The latest upstream changes (possibly d28d2344d000aa96bef729cf408731f952f71fb0) made this pull request unmergeable. Please [resolve the merge conflicts](https://rustc-dev-guide.rust-lang.org/git.html#rebasing-and-conflicts).
author_association: COLLABORATOR
user: login: GuillaumeGomez
created_at: 2025-05-07T19:43:44Z
body: Finally took time to update this PR and to apply suggestions. I still don't think we should generalize this check between any methods and any existing associated constant so I didn't implement it but I suppose it can always be revisited at a later time.
author_association: MEMBER
user: login: llogiq
created_at: 2025-05-12T15:27:30Z
body: Thank you. Sorry this was waiting on me for so long.
author_association: CONTRIBUTOR