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

Add needless_pass_by_ref_mut lint #10900

Merged
merged 6 commits into from
Jul 9, 2023

Conversation

GuillaumeGomez
Copy link
Member

@GuillaumeGomez GuillaumeGomez commented Jun 6, 2023

changelog: [needless_pass_by_ref_mut]: This PR add a new lint needless_pass_by_ref_mut which emits a warning in case a &mut function argument isn't used mutably. It doesn't warn on trait and trait impls functions.

Fixes #8863.

@rustbot
Copy link
Collaborator

rustbot commented Jun 6, 2023

r? @Jarcho

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Jun 6, 2023
@GuillaumeGomez
Copy link
Member Author

I found two cases where it doesn't work:

fn foo2(s: &mut Vec<u32>) {
    s.push(8);
}

fn foo3(s: &mut Vec<u32>) -> &mut Vec<u32> {
    s
}

@GuillaumeGomez
Copy link
Member Author

I fixed the two cases. If people could please check if I didn't miss other cases? :)

@Centri3
Copy link
Member

Centri3 commented Jun 13, 2023

@GuillaumeGomez
Copy link
Member Author

Thanks a lot for the cases! For NonNull, I'll check what's going on.

Copy link
Member

@Centri3 Centri3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't done yet, but I thought I'd give a small review anyway ^^

clippy_lints/src/needless_pass_by_ref_mut.rs Outdated Show resolved Hide resolved
clippy_lints/src/needless_pass_by_ref_mut.rs Show resolved Hide resolved
clippy_lints/src/needless_pass_by_ref_mut.rs Outdated Show resolved Hide resolved
@GuillaumeGomez
Copy link
Member Author

I know this isn't done yet, but I thought I'd give a small review anyway ^^

Very much appreciated, thanks! I'll fix them when I resume this work tomorrow.

@emilk
Copy link

emilk commented Jun 14, 2023

I'd like to make sure these are also caught:

impl Bar {
    // Should warn about `vec`
    pub fn mushroom(&self, vec: &mut Vec<i32>) -> usize {
        vec.len()
    }

    // Should warn about `vec`
    pub fn badger(&mut self, vec: &mut Vec<i32>) -> usize {
        vec.len()
    }
}

Additionally, shouldn't we warn about &mut self too? e.g.

struct Foo {
    x: i32
}

impl Foo {
    pub fn get(&mut self) -> i32 {
        self.x
    }
}

I have never worked in this repository before - how do I easily test a single lint like this? cargo test has a bunch of unrelated failures on my machine (M1 Mac)

@Centri3
Copy link
Member

Centri3 commented Jun 14, 2023

I have never worked in this repository before - how do I easily test a single lint like this? cargo test has a bunch of unrelated failures on my machine (M1 Mac)

TESTNAME=<name> cargo uitest. The name just has to be a substring of any test's name, in this case needless_pass_by_ref_mut should work.

@GuillaumeGomez
Copy link
Member Author

@emilk: I'll add them to the tests within this PR. The more I have, the better.

@GuillaumeGomez
Copy link
Member Author

GuillaumeGomez commented Jun 14, 2023

Additionally, shouldn't we warn about &mut self too? e.g.

This is tricky because you might want to actually force your object instance to be mutably borrowed even if you don't use it mutably internally. However, we can always add another lint (allow by default) which could check that too.

@GuillaumeGomez GuillaumeGomez force-pushed the needless-pass-by-ref branch 3 times, most recently from fac1b21 to 317ac5b Compare June 14, 2023 16:07
@GuillaumeGomez
Copy link
Member Author

@Centri3 I was able to fix two of the four problems you linked (the alias and the NonNull::from). For the last two, I need to be able to make a small code to reproduce the issue. So to do that: how can I run my own clippy onto rayon for example? I didn't find explanations for that in the clippy book unfortunately...

@Centri3
Copy link
Member

Centri3 commented Jun 14, 2023

The easiest way is lintcheck; cargo lintcheck, if you want you can add more crates in lintcheck/lintcheck_crates.toml (though rayon should be there by default)

@GuillaumeGomez GuillaumeGomez changed the title Add needless_pass_by_ref lint Add needless_pass_by_ref_mut lint Jun 14, 2023
@GuillaumeGomez
Copy link
Member Author

Thanks! That was quite useful and indeed, rayon was there. Allowed me to discover new corner cases. Continuing to move on this slowly but surely.

@bors
Copy link
Contributor

bors commented Jun 15, 2023

☔ The latest upstream changes (presumably #10934) made this pull request unmergeable. Please resolve the merge conflicts.

@GuillaumeGomez GuillaumeGomez force-pushed the needless-pass-by-ref branch 2 times, most recently from 6bb17f7 to 83d8728 Compare June 15, 2023 13:13
@GuillaumeGomez
Copy link
Member Author

From lintcheck, the remaining warnings seem legit. I'll add allow(needless_pass_by_ref_mut) in the failing clippy UI tests.

@GuillaumeGomez GuillaumeGomez force-pushed the needless-pass-by-ref branch 2 times, most recently from 3bd0481 to 3c85849 Compare June 15, 2023 18:55
@GuillaumeGomez
Copy link
Member Author

Just realized that avoid-breaking-exported-api is true by default. I don't think skipping this lint by default for public items is a good idea so I'll go for the first proposed solution instead.

Just in case, here's the diff to have the avoid-breaking-exported-api change:

From cdfb83030b2f78d672d9f6b08911cf8123edf338 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Mon, 3 Jul 2023 22:19:32 +0200
Subject: [PATCH] Make NEEDLESS_PASS_BY_REF_MUT not emitted if
 `avoid-breaking-exported-api` option is enabled

---
 clippy_lints/src/lib.rs                       |  6 ++++-
 clippy_lints/src/needless_pass_by_ref_mut.rs  | 22 +++++++++++++++++--
 .../ui/should_impl_trait/method_list_2.stderr | 10 +--------
 3 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index e1e4a83b0..040ed1a86 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -1058,7 +1058,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     let stack_size_threshold = conf.stack_size_threshold;
     store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
     store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
-    store.register_late_pass(|_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut));
+    store.register_late_pass(move |_| {
+        Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(
+            avoid_breaking_exported_api,
+        ))
+    });
     store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
     store.register_late_pass(move |_| {
         Box::new(single_call_fn::SingleCallFn {
diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs
index c5dc87cdf..614b1f965 100644
--- a/clippy_lints/src/needless_pass_by_ref_mut.rs
+++ b/clippy_lints/src/needless_pass_by_ref_mut.rs
@@ -12,7 +12,7 @@ use rustc_infer::infer::TyCtxtInferExt;
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::mir::FakeReadCause;
 use rustc_middle::ty::{self, Ty};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::def_id::LocalDefId;
 use rustc_span::symbol::kw;
 use rustc_span::Span;
@@ -43,7 +43,21 @@ declare_clippy_lint! {
     suspicious,
     "using a `&mut` argument when it's not mutated"
 }
-declare_lint_pass!(NeedlessPassByRefMut => [NEEDLESS_PASS_BY_REF_MUT]);
+
+#[derive(Copy, Clone)]
+pub struct NeedlessPassByRefMut {
+    avoid_breaking_exported_api: bool,
+}
+
+impl NeedlessPassByRefMut {
+    pub fn new(avoid_breaking_exported_api: bool) -> Self {
+        Self {
+            avoid_breaking_exported_api,
+        }
+    }
+}
+
+impl_lint_pass!(NeedlessPassByRefMut => [NEEDLESS_PASS_BY_REF_MUT]);
 
 fn should_skip<'tcx>(
     cx: &LateContext<'tcx>,
@@ -81,6 +95,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
         span: Span,
         fn_def_id: LocalDefId,
     ) {
+        if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id) {
+            return;
+        }
+
         if span.from_expansion() {
             return;
         }
diff --git a/tests/ui/should_impl_trait/method_list_2.stderr b/tests/ui/should_impl_trait/method_list_2.stderr
index e47cb209b..10bfea68f 100644
--- a/tests/ui/should_impl_trait/method_list_2.stderr
+++ b/tests/ui/should_impl_trait/method_list_2.stderr
@@ -39,14 +39,6 @@ LL | |     }
    |
    = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name
 
-error: this argument is a mutable reference, but not used mutably
-  --> $DIR/method_list_2.rs:38:31
-   |
-LL |     pub fn hash(&self, state: &mut T) {
-   |                               ^^^^^^ help: consider changing to: `&T`
-   |
-   = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings`
-
 error: method `index` can be confused for the standard trait method `std::ops::Index::index`
   --> $DIR/method_list_2.rs:42:5
    |
@@ -157,5 +149,5 @@ LL | |     }
    |
    = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name
 
-error: aborting due to 16 previous errors
+error: aborting due to 15 previous errors
 
-- 
2.34.1

@GuillaumeGomez GuillaumeGomez force-pushed the needless-pass-by-ref branch from cdfb830 to 33adfcd Compare July 3, 2023 20:48
@llogiq
Copy link
Contributor

llogiq commented Jul 3, 2023

Why do you think avoiding API breakage by default is a bad idea?

@Centri3
Copy link
Member

Centri3 commented Jul 3, 2023

Probably because it doesn't cause API breakage, just a clippy lint.

@emilk
Copy link

emilk commented Jul 3, 2023

I just learned about avoid-breaking-exported-api now, and now I'm turning it off for my projects so I get more clippy lints :)

Whether or not avoid-breaking-exported-api should be on by default seems like a discussion outside the scope of this PR.

For consistency with how other lints work, it seems this lint should heed it too (just like similar lints do, e.g. unnecessary_wraps and unused_self)

@Centri3
Copy link
Member

Centri3 commented Jul 3, 2023

I disagree as changing &mut to & doesn't cause API breakage, you can pass &mut to a function expecting & just fine

@emilk
Copy link

emilk commented Jul 3, 2023

I disagree as changing &mut to & doesn't cause API breakage, you can pass &mut to a function expecting & just fine

It's a good point, however:

If you have pub fn foo(bar: &mut Baz) { bar.non_mut_method() } you reserve the right to mutate bar in the future. If you follow the lint suggestion and change it to fn foo(bar: &Baz) you are not breaking the API, but you are changing the API so that you cannot mutate the argument in the future. Thus when you next release realize you want to mutate the argument, you have lost the ability to do so, unless you now introduce a breaking change by re-adding the mut.

@Centri3
Copy link
Member

Centri3 commented Jul 3, 2023

Those are the cases where you should probably allow the lint, though it isn't particularly vocal about this unfortunately (something should be added to the description noting this)

@llogiq
Copy link
Contributor

llogiq commented Jul 4, 2023

Probably because it doesn't cause API breakage, just a clippy lint.

Would it be possible to shadow a trait method following the lint's suggestion? If so, it could break the build.

@llogiq
Copy link
Contributor

llogiq commented Jul 4, 2023

That said, there is an argument to be made that we should make the default false for projects before 1.0 and true for those who have reached it 😀

@GuillaumeGomez
Copy link
Member Author

That said, there is an argument to be made that we should make the default false for projects before 1.0 and true for those who have reached it grinning

I disagree with this position: clippy is supposed to tell me what it potential issues it sees, and then it's up to me to decide whether or not I apply them.

So something I can suggest is the following: if an item is publicly accessible, to not enable the possibility of automatically fixing it. What do you think about it?

@GuillaumeGomez
Copy link
Member Author

GuillaumeGomez commented Jul 4, 2023

So I did the following changes:

  • Emit a warning in case it would change semver compatibility
  • Making the lint not machine applicable ever: the reason why is simply as said previously: some code might not work after this change so better let users do this refactoring "by hand" to be sure they didn't miss something.

@Centri3
Copy link
Member

Centri3 commented Jul 6, 2023

Probably because it doesn't cause API breakage, just a clippy lint.

Would it be possible to shadow a trait method following the lint's suggestion? If so, it could break the build.

Most likely so, but I wouldn't consider this a "breaking change"; afaik that would be up to the library author to refactor (which, since it now isn't machine applicable, wouldn't be a big deal), as the only way I can think of for that to happen is the usage within the function itself, which changing shouldn't cause issues downstream

Perhaps something like &mut IterMut vs &IterMut as an argument (is that even used, though?)? Just first thing that came to mind. That might be able to break but I'm not sure if something like that is common enough to be an issue

@llogiq
Copy link
Contributor

llogiq commented Jul 9, 2023

The reason for the avoid-breaking-exported-api is that we consider a warning that can only be fixed by allow or breaking code as a false positive, and we really want to avoid those. Actually breaking code indirectly like this is considered even worse, because the user may not be aware they're breaking their users' code. Now I agree that in this case the potential for breakage isn't that high (probably mostly due to traits implemented on &mut T, but not &T plus inference), and if the user gets a fair warning on public items, this is OK in my book.

Sorry I'm being pedantic here, but we've had some bad experience with users ditching clippy because of too many false positives, and we want this to be useful.

@bors r+

@bors
Copy link
Contributor

bors commented Jul 9, 2023

📌 Commit f048f73 has been approved by llogiq

It is now in the queue for this repository.

@bors
Copy link
Contributor

bors commented Jul 9, 2023

⌛ Testing commit f048f73 with merge b46033e...

@bors
Copy link
Contributor

bors commented Jul 9, 2023

☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test
Approved by: llogiq
Pushing b46033e to master...

@bors bors merged commit b46033e into rust-lang:master Jul 9, 2023
@GuillaumeGomez GuillaumeGomez deleted the needless-pass-by-ref branch July 9, 2023 17:25
bors added a commit that referenced this pull request Jul 9, 2023
…arth

Fix typo in `needless_pass_by_ref_mut` lint description

Someone nicely showed me that I made a small typo in #10900.
bors added a commit that referenced this pull request Jul 10, 2023
…arth

Fix typo in `needless_pass_by_ref_mut` lint description

Someone nicely showed me that I made a small typo in #10900.
bors added a commit that referenced this pull request Jul 10, 2023
…arth

Fix typo in `needless_pass_by_ref_mut` lint description

Someone nicely showed me that I made a small typo in #10900.

changelog: none
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unecessary mut reference in parameter
8 participants