-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new confusing_method_to_numeric_cast
lint
#13979
base: master
Are you sure you want to change the base?
Add new confusing_method_to_numeric_cast
lint
#13979
Conversation
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
// We get its name in case it's a primitive with an associated MIN/MAx constant. | |
// We get its name in case it's a primitive with an associated MIN/MAX constant. |
use rustc_lint::LateContext; | ||
use rustc_middle::ty::{self, Ty}; | ||
|
||
use super::PRIMITIVE_METHOD_TO_NUMERIC_CAST; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there primitive methods only max, min
If so might make sense to specify them at lint name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or confusing_method_to_numeric_cast
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm very bad at naming so I'll take your suggestion. :3
clippy_lints/src/casts/mod.rs
Outdated
/// ```no_run | ||
/// let _ = u16::MAX as usize; | ||
/// ``` | ||
#[clippy::version = "1.85.0"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: 1.86.0
359e815
to
042556c
Compare
primitive_method_to_numeric_cast
lintconfusing_method_to_numeric_cast
lint
Renamed and fixed the typos/nits. |
&& 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]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can use the diag item here.
&& match_def_path(cx, *def_id, &["core", "cmp", "Ord", method_name]) | |
&& is_diag_trait_item(cx, *def_id, sym::Ord) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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,
);
},
);
}
&& 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
&& 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) | |
&& 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Floating point types do have MIN/MAX https://doc.rust-lang.org/std/primitive.f32.html#associatedconstant.MAX
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would deserve more tests, with the various types, as well as some other types implementing Ord
not eligible to this lint.
&& 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]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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,
);
},
);
}
@@ -0,0 +1,24 @@ | |||
error: unknown lint: `clippy::primitive_method_to_numeric_cast` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this supposed to happen?
Could this be extended to also cover conversions using from/into/try_from/try_into? |
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 |
@@ -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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Good point. I wonder whether:
|
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. |
Someone commented on the issue that |
I took a whack at it, it seems like 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,
);
},
);
} |
There's also |
☔ The latest upstream changes (possibly d28d234) made this pull request unmergeable. Please resolve the merge conflicts. |
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