blob: 73347e7141eb448538bcca8111303270d74895f9 [file] [log] [blame]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sym;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArg, Ty};
use rustc_span::Symbol;
use rustc_span::def_id::DefId;
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 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_assoc(method_def_id)
&& let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity())
&& matches!(
method_name,
sym::min | sym::max | sym::minimum | sym::maximum | sym::min_value | sym::max_value
)
{
ty_name
} else {
return None;
};
let const_name = if matches!(method_name, sym::max | sym::maximum | sym::max_value) {
"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.
if cast_to.is_fn() {
return;
}
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,
);
},
);
}
}