Skip to content
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

unnecessary call to min function #11924

Open
FelixMaetzler opened this issue Dec 4, 2023 · 8 comments
Open

unnecessary call to min function #11924

FelixMaetzler opened this issue Dec 4, 2023 · 8 comments
Assignees
Labels
A-lint Area: New lints good-first-issue These issues are a good way to get started with Clippy

Comments

@FelixMaetzler
Copy link

FelixMaetzler commented Dec 4, 2023

What it does

A few days ago i opened #11901
You could do this for the min() method too.
So 0.min(<anything>) is always zero if <anything> is an unsigned integer.
Also this method is symmetric, that means same applies to <anything>.min(0).

You could go even further and generalize this to T::MIN.min(<anything>) (where T is any type of integer like i.e. i64) is always T::MIN.
And you could do this with the upper bound too: T::MAX.min(<anything>) is for any integer type T the same as <anything>.

Advantage

  • Remove of unnecessary function call
  • Reduction of Code Complexity

Drawbacks

No response

Example

// example with zero and unsigned Type
let a = 0.min(42_usize);

// example with MIN and signed Type
let a = 42.min(i64::MIN);

// example with MAX and signed Type
let a = i32::MAX.min(9);

Could be written as:

// example with zero and unsigned Type
let a = 0;

// example with MIN and signed Type
let a = i64::MIN;

// example with MAX and signed Type
let a = 9;
@FelixMaetzler FelixMaetzler added the A-lint Area: New lints label Dec 4, 2023
@FelixMaetzler
Copy link
Author

I have never done a contribution to such a project, so i think this is a perfect opportunity for that.
I would like to implement this lint (or at least try it).

@FelixMaetzler
Copy link
Author

@rustbot claim

@Centri3
Copy link
Member

Centri3 commented Dec 4, 2023

This is a good idea. You'll want to match on an Expr method call, the receiver is the 0 and the argument is the 42_usize. It'd be easiest to just match on the function name itself rather than using diagnostic items or similar since we'd need one for every type.

Getting the value of the constant is tricky, versus just matching on the name. I'd go with the former for the same reason as matching on the name. See the manual_is_finite and manual_is_infinite lint combo for an example on how to do this: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/manual_float_methods.rs#L55, most notably, use the Constant struct in clippy_utils.

Not sure how you'd easily check if it's gt/lt if it's a float.

I think we could extend this to min on two constants, not just 0, correct? (Note that the logic for 0, T::MIN, etc. would ideally be different - anything is unnecessary there, so we don't need to parse the argument which can't be done in some cases and thus wouldn't lint normally)

The same applies to the max lint as well, they should share the same code ideally

@FelixMaetzler
Copy link
Author

This is a good idea. You'll want to match on an Expr method call, the receiver is the 0 and the argument is the 42_usize. It'd be easiest to just match on the function name itself rather than using diagnostic items or similar since we'd need one for every type.

Yes i thought the same.
I would add my 'min' method in this match: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/methods/mod.rs#L4098 and just calling my own check function.

and here is a bit of pseudo code of what i would do in my check function:

// i have something like this:
let _ = a.min(b);

// Check function:
let ty = cx.typeck_results().expr_ty(expr);
match ty {
    types_i_want_to_support => {},
    _ => return,
}
if a == 0 | T::MIN | T::MAX {
    lint()
}
if b == 0 | T::MIN | T::MAX {
    lint()
}

Technically i don't want to check if it is a 0, because this is just a "special case" of T::MIN. In other words: If 0 is not T::MIN i don't want to lint.
But i don't know right now if and how this is possible. I have to look into that.

I think we could extend this to min on two constants, not just 0, correct? (Note that the logic for 0, T::MIN, etc. would ideally be different - anything is unnecessary there, so we don't need to parse the argument which can't be done in some cases and thus wouldn't lint normally)

I think i don't quite understand what you're saying.
The two constants are T::MIN and T::MAX (with 0 as "special case" for T::MIN)?
Or do you mean constant expressions (things with the const keyword) that evaluate to that?

Getting the value of the constant is tricky, versus just matching on the name. I'd go with the former for the same reason as matching on the name. See the manual_is_finite and manual_is_infinite lint combo for an example on how to do this: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/manual_float_methods.rs#L55, most notably, use the Constant struct in clippy_utils.

I will look into that. Thank you!

The same applies to the max lint as well, they should share the same code ideally

That's a good point. The logic for checking on these special values could been shared.

@Centri3
Copy link
Member

Centri3 commented Dec 4, 2023

I think i don't quite understand what you're saying.

It's 4am. On the first section I mean that, rather than only linting when the receiver is T::MIN, we always lint it if they're both constants and recv < arg and they're both constants (or literals).

The second section is how constant turns MIR (external)/HIR (same crate) into, well, a Constant. It doesn't look into any (many?) function calls, so for example, 0u32.min(a()) wouldn't lint since it (a()) wouldn't evaluate to a constant. This isn't good, so if it's T::MIN (and T::MAX for max), the lint shouldn't check the argument at all. If it's not T::MIN it falls back to checking the argument.

@FelixMaetzler
Copy link
Author

It's 4am.

My bad. I'm from europe and i didn't think about other timezones.

On the first section I mean that, rather than only linting when the receiver is T::MIN, we always lint it if they're both constants and recv < arg and they're both constants (or literals).

Of course. Thats a good point. I didnt think of that at all. That happens if you are so focused on a detail, that you don't see the bigger picture.

The second section is how constant turns MIR (external)/HIR (same crate) into, well, a Constant.

I definitely have to look into what different Types there are and what i can do with it.

It doesn't look into any (many?) function calls, so for example, 0u32.min(a()) wouldn't lint since it (a()) wouldn't evaluate to a constant. This isn't good, so if it's T::MIN (and T::MAX for max), the lint shouldn't check the argument at all. If it's not T::MIN it falls back to checking the argument.

I think it does work. Because i see the 0u32 and then i can lint without the need of checking a().
This should be the first if in my pseudo code from above.
I can't simplify it with a constant (because then i need to know what a() evaluates to like you said) but i can simplify 0u32.min(a()) to just a().

@Centri3
Copy link
Member

Centri3 commented Dec 4, 2023

My bad.

Was just explaining why it was worded improperly 😅 You're fine :)

@FelixMaetzler
Copy link
Author

FelixMaetzler commented Dec 8, 2023

So i have implemented the first draft. Because i'm new to colloboration on github, what is the next step?
How can i show my code so that other people can have a look at it and give feedback and check it for bugs etc?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lint Area: New lints good-first-issue These issues are a good way to get started with Clippy
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants