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

Unused trait imports (formerly anonymous trait import) #13322

Merged
merged 2 commits into from
Sep 22, 2024

Conversation

RuairidhWilliamson
Copy link
Contributor

@RuairidhWilliamson RuairidhWilliamson commented Aug 29, 2024

For #11969

I'm looking for help and feedback on implementing a new lint for suggesting use ... as _ for traits where possible.

I have had a go at implementing this but I don't know if this is the best way to do it as I am new to clippy.

There are some edge cases I can think of where this doesn't work but have aired on the side of false negatives instead of false positives.

An example of a false negative. I couldn't figure out the best way to resolve an import from within clippy. The sub module imports MyAny so that cannot be anonymized but use std::any::Any could be. In this case it is not caught because Any and MyAny have the same DefId.

mod nested_mod_used_bad1 {
    use std::any::Any;
    use std::any::Any as MyAny;
    mod foo {
        use crate::nested_mod_used_bad1::MyAny;
        fn foo() {
            println!("{:?}", MyAny::type_id("foo"));
        }
    }
}

Any feedback is much appreciated.


changelog: new lint: unused_trait_names

@rustbot
Copy link
Collaborator

rustbot commented Aug 29, 2024

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @y21 (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Aug 29, 2024
@y21
Copy link
Member

y21 commented Aug 30, 2024

I've not properly looked at the changes here in full detail but some initial comments:

There are some edge cases I can think of where this doesn't work but have aired on the side of false negatives instead of false positives.

I'd say that this is fine and even preferred (false negatives over false positives), especially for a lint that's going to be as trigger-heavy as this one.

I wonder though, instead of reimplementing a part of resolution, could this use tcx.maybe_unused_trait_imports().contains(use_def_id)? If I remember right, that set is created in early name resolution for imports that aren't referred to by path but may be used for method resolution in typeck, which sounds like it might be of use here?
The current way of walking the whole module and all its contents for every single import seems like it could get expensive, but that might just be premature

@RuairidhWilliamson
Copy link
Contributor Author

Thank you for the response.

You are right. maybe_unused_trait_imports solves exactly this problem and I was able to remove the import resolution. I am not sure how I missed that query but it has made the code much cleaner.

I had to change some of the tests but I think they are correct now and fixed the false negatives.

@nyurik
Copy link
Contributor

nyurik commented Sep 1, 2024

Awesome lint, thanks!!! I would even suggest removing the wip from the subject.

You used Applicability::MaybeIncorrect - are there any such known bad false positive cases? If so, you may want to add them, at least as a disabled code block, to the tests - so that in the future they can be handled. Also, do add all the false negatives as tests - even though they will be ignored for now, others may improve their handling.

Lastly, a tiny minor nit - you may want to move the main() {} to the top of the test file - this way new test cases will be added at the end, and the line numbering will stay intact in the blessed results.

@RuairidhWilliamson
Copy link
Contributor Author

Thanks @nyurik :)

Yes I agree it shouldn't be wip anymore.

You used Applicability::MaybeIncorrect - are there any such known bad false positive cases? If so, you may want to add them, at least as a disabled code block, to the tests - so that in the future they can be handled. Also, do add all the false negatives as tests - even though they will be ignored for now, others may improve their handling.

Yes, good point. The change to using maybe_unused_trait_imports() has cleared up the edge cases I can think of so I will make it MachineApplicable.

Lastly, a tiny minor nit - you may want to move the main() {} to the top of the test file - this way new test cases will be added at the end, and the line numbering will stay intact in the blessed results.

Ah that is a good point. I have moved main to the top.

@RuairidhWilliamson RuairidhWilliamson changed the title wip: Anonymous Trait Import Anonymous Trait Import Sep 1, 2024
Copy link
Member

@y21 y21 left a comment

Choose a reason for hiding this comment

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

A lot of the comments that I had before when I looked at this first are no longer relevant and fixed now with the extra visitor removed so that's nice :)

clippy_lints/src/anon_trait_import.rs Outdated Show resolved Hide resolved
clippy_lints/src/anon_trait_import.rs Outdated Show resolved Hide resolved
&& let module = cx.tcx.parent_module_from_def_id(item.owner_id.def_id)
&& cx.tcx.visibility(item.owner_id.def_id) == Visibility::Restricted(module.to_def_id())
&& let Some(last_segment) = path.segments.last()
&& let Some(snip) = snippet_opt(cx, last_segment.ident.span)
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to have some macro checks to prevent cases where the lint is coming from an external macro.
For example the first 100 results here are almost all from within a bitflags! macro expansion where the user can't change anything: https://github.com/rust-lang/rust-clippy/actions/runs/10657161173

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason using is_from_proc_macro(cx, item) wouldn't trigger when testing with with_span!. I managed to get it working by using is_from_proc_macro(cx, &last_segment.ident) but I don't understand why that worked and the former didn't.

tests/ui/anon_trait_import.rs Outdated Show resolved Hide resolved
tests/ui/anon_trait_import.rs Outdated Show resolved Hide resolved
clippy_lints/src/anon_trait_import.rs Outdated Show resolved Hide resolved
clippy_lints/src/anon_trait_import.rs Outdated Show resolved Hide resolved
@y21
Copy link
Member

y21 commented Sep 1, 2024

You used Applicability::MaybeIncorrect - are there any such known bad false positive cases?

FWIW, something like use path::to::Trait::{self}; currently has a wrong/breaking suggestion (suggests use path::to::Trait as _::{self}), but it seems like a weird thing to write, considering that trait-relative items can't be imported, so {self} would always be on its own. So I'd say it's fine to ignore that and not block machine-applicability on it

@RuairidhWilliamson
Copy link
Contributor Author

Thanks for the review.

You used Applicability::MaybeIncorrect - are there any such known bad false positive cases?

FWIW, something like use path::to::Trait::{self}; currently has a wrong/breaking suggestion (suggests use path::to::Trait as _::{self}), but it seems like a weird thing to write, considering that trait-relative items can't be imported, so {self} would always be on its own. So I'd say it's fine to ignore that and not block machine-applicability on it

Ah yes that is an interesting edge case. I have had a look at the HIR to see if it is easy to ignore this pattern but it seems to be hard to distinguish at that level.

use std::any::Any::{self};
use ::{};
use std::any::Any;

I guess maybe I could use the span to check the source manually but that feels messy.

If you are happy that it is not an issue that needs to be addressed then I am too :)

@bors
Copy link
Collaborator

bors commented Sep 3, 2024

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

@bors
Copy link
Collaborator

bors commented Sep 5, 2024

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

Copy link
Member

@y21 y21 left a comment

Choose a reason for hiding this comment

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

Looks good to me aside from a few small things

/// }
/// ```
#[clippy::version = "1.82.0"]
pub ANON_TRAIT_IMPORT,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pub ANON_TRAIT_IMPORT,
pub ANON_TRAIT_IMPORTS,

Lint names should generally be in plural so it makes sense when read as "allow anon trait imports"

Comment on lines 20 to 21
/// Traits imported that aren't used directly can be imported anonymously `use Trait as _`.
/// It is more explicit, avoids polutting the current scope with unused names and can be useful to show which imports are required for traits.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Traits imported that aren't used directly can be imported anonymously `use Trait as _`.
/// It is more explicit, avoids polutting the current scope with unused names and can be useful to show which imports are required for traits.
/// Traits imported that aren't used directly can be imported anonymously with `use Trait as _`.
/// It is more explicit, avoids polluting the current scope with unused names and can be useful to show which imports are required for traits.

/// ```
#[clippy::version = "1.82.0"]
pub ANON_TRAIT_IMPORT,
nursery,
Copy link
Member

Choose a reason for hiding this comment

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

Do you intend to move this to a different category before the merge or keep it in the nursery for now?

Usually new lints start out in a more defined category. Nursery is typically for lints that are known to be buggy

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I have moved it to pedantic.

@y21
Copy link
Member

y21 commented Sep 8, 2024

This might be more or less tangential to this lint but we might also want to adjust rustc's suggestion for importing traits for methods. I imagine it would be somewhat annoying to apply the compiler's suggestion to import a trait only to immediately trigger a clippy lint.

@nyurik
Copy link
Contributor

nyurik commented Sep 9, 2024

@y21 i agree, rustc should suggest use Foo as _; rather than use Foo when the trait method is being used. This would both prevent inconsistencies with this lint, as well as teach users about namespace pollution. Would you like to create an issue for that?

@RuairidhWilliamson
Copy link
Contributor Author

I have found another sub optimal scenario but don't think it is bad enough to prevent merge.

It would be nice if multiple renames that import the same trait gets suggested to combine into a single import. Currently this produces a suggestion for each import meaning running --fix produces the second output.

use std::any::{Any, Any as MyAny};

After running --fix:

use std::any::{Any as _, Any as _};

Rustc will lint one of the imports as unused so should highlight the issue.

@bors
Copy link
Collaborator

bors commented Sep 11, 2024

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

@y21
Copy link
Member

y21 commented Sep 11, 2024

It would be nice if multiple renames that import the same trait gets suggested to combine into a single import. Currently this produces a suggestion for each import meaning running --fix produces the second output.

I think that's fine. Or at least, I don't think this situation would be common enough to be an actual problem. It seems odd to import the same trait twice to begin with IMO.
We have a few other lints that could theoretically run into this, where the suggestion would trigger more warnings in specific edge cases, which is ok as long as there's no contradictory warnings.

Copy link
Member

Choose a reason for hiding this comment

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

Please also add a test for making sure that #[allow(clippy::anon_trait_imports)] on the use item works. You might have to include anon_trait_imports here to avoid a false positive on the useless_attribute lint:

matches!(
symbol.as_str(),
"wildcard_imports"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added 👍

Copy link
Member

Choose a reason for hiding this comment

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

There's tests for #[allow] on the enclosing module and function now, but not one for the use item directly as far as I can tell. I mean something like

#[allow(clippy::anon_trait_imports)]
use std::any::Any;

"".type_id();

Can you also add one for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, added.

There is a false positive for useless_attribute in this case

Copy link
Member

Choose a reason for hiding this comment

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

Being able to allow the lint on the item that it warns on is something that should really work for all lints (without triggering other lints), so it would be good if you could fix that false positive for this lint. It should be fairly straightforward, you just need to add it to the matches! macro (see initial comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, yeah I forgot that you explained how to fix that. Fixed 👍

@y21
Copy link
Member

y21 commented Sep 13, 2024

Opened a thread on zulip for the FCP process that new lints need to go through: https://rust-lang.zulipchat.com/#narrow/stream/257328-clippy/topic/FCP.3A.20anon_trait_imports

Implementation looks good to me aside from the test change that I commented on, but that shouldn't involve any major changes in a way that would block the FCP.

@kpreid
Copy link
Contributor

kpreid commented Sep 13, 2024

Isn't this lint named backwards? Lints are named for what they report, not what they suggest you do instead, and this lint is objecting to unused names and suggesting an anonymous import.

How about naming it unused_trait_names?

@RuairidhWilliamson
Copy link
Contributor Author

Based on feedback in zulip I've renamed the lint from anon_trait_imports -> unused_trait_names and moved the lint from pedantic to restriction (which means it doesn't get run as dogfood).

@nyurik
Copy link
Contributor

nyurik commented Sep 19, 2024

we could probably add it to Cargo.toml to be part of dogfood? I really think this is a good thing ™️ to have ... so if we make this more visible, and if we make the compiler suggest code in this format, eventually it could make it to pedantic and then to style...

@RuairidhWilliamson
Copy link
Contributor Author

we could probably add it to Cargo.toml to be part of dogfood? I really think this is a good thing ™️ to have ... so if we make this more visible, and if we make the compiler suggest code in this format, eventually it could make it to pedantic and then to style...

yeah I agree but maybe it is best to do it in a follow up PR as it adds a lot of changes across clippy :)

Copy link
Member

@y21 y21 left a comment

Choose a reason for hiding this comment

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

Looks good to me now aside from two small things. Can you also squash the commits here a bit?

I also agree that if we're going to apply this style to the code base and enable the lint it should be done in a separate PR since it'll probably involve a discussion, which doesn't really need to block the lint itself

clippy_lints/src/unused_trait_names.rs Show resolved Hide resolved
clippy_lints/src/unused_trait_names.rs Outdated Show resolved Hide resolved
@RuairidhWilliamson RuairidhWilliamson changed the title Anonymous Trait Import Unused trait imports (formerly anonymous trait import) Sep 21, 2024
@RuairidhWilliamson
Copy link
Contributor Author

I've addressed your review and squashed everything into two commits :)

@y21
Copy link
Member

y21 commented Sep 22, 2024

Great, thank you for implementing this lint!

@bors r+

@bors
Copy link
Collaborator

bors commented Sep 22, 2024

📌 Commit 739ef7b has been approved by y21

It is now in the queue for this repository.

@bors
Copy link
Collaborator

bors commented Sep 22, 2024

⌛ Testing commit 739ef7b with merge 7951e02...

bors added a commit that referenced this pull request Sep 22, 2024
Unused trait imports (formerly anonymous trait import)

For #11969

I'm looking for help and feedback on implementing a new lint for suggesting `use ... as _` for traits where possible.

I have had a go at implementing this but I don't know if this is the best way to do it as I am new to clippy.

There are some edge cases I can think of where this doesn't work but have aired on the side of false negatives instead of false positives.

An example of a false negative. I couldn't figure out the best way to resolve an import from within clippy. The sub module imports MyAny so that cannot be anonymized but `use std::any::Any` could be. In this case it is not caught because `Any` and `MyAny` have the same DefId.
```rust
mod nested_mod_used_bad1 {
    use std::any::Any;
    use std::any::Any as MyAny;
    mod foo {
        use crate::nested_mod_used_bad1::MyAny;
        fn foo() {
            println!("{:?}", MyAny::type_id("foo"));
        }
    }
}
```

Any feedback is much appreciated.
@bors
Copy link
Collaborator

bors commented Sep 22, 2024

💔 Test failed - checks-action_test

@y21
Copy link
Member

y21 commented Sep 22, 2024

PR description was missing a changelog: line, I edited it in.

@bors retry

@bors
Copy link
Collaborator

bors commented Sep 22, 2024

⌛ Testing commit 739ef7b with merge abdf173...

@bors
Copy link
Collaborator

bors commented Sep 22, 2024

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

@RuairidhWilliamson
Copy link
Contributor Author

@y21 Great thank you for all your help, I'm glad we got this added!

@RuairidhWilliamson RuairidhWilliamson deleted the anon-trait-import branch September 22, 2024 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-final-comment-period Status: final comment period it will be merged unless new objections are raised (~1 week)
Projects
None yet
6 participants