Skip to content

Conversation

paulmialane
Copy link

@paulmialane paulmialane commented Oct 8, 2025

Add a config option inherent-impl-lint-scope to the lint multiple_inherent_impl to target a different scope according to people's needs. It can take three values: module, file, and crate (default).

  • module is the weakest option. It lints if there are two or more impls in the same module.
  • file is a bit stronger, since it lints if there are two or more impls in the same file. So, this triggers the lint (where it did not with module):
  • crate is the strongest of them; it triggers as soon as there are two or more impls anywhere in the crate. It is the current behaviour of the lint, so it's the default option.

changelog: [multiple_inherent_impl] : Add config option (module, file or crate) to target specific scope

fixes #14867

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Oct 8, 2025
@rustbot
Copy link
Collaborator

rustbot commented Oct 8, 2025

r? @samueltardieu

rustbot has assigned @samueltardieu.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Copy link
Member

@samueltardieu samueltardieu left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution. There are some things that need to be changed in the way the configuration option is parsed (and output in messages) so that lower-case versions "crate", "file", and "module" can be used instead of a mixed-case one.

View changes since this review

///
/// The config option controls the scope at which multiple inherent impl blocks for the same
/// struct are linted, allowing values of "module" (only within the same module), "file"
/// (within the same file), or "crate" (anywhere in the crate, default).
Copy link
Member

Choose a reason for hiding this comment

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

Those lowercase values are not currently recognized (yet), see other comment.

struct T;

impl S {
fn fisrt() {}
Copy link
Member

Choose a reason for hiding this comment

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

Typo? (several occurrences)

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Oct 8, 2025
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Oct 8, 2025
Copy link
Member

@samueltardieu samueltardieu left a comment

Choose a reason for hiding this comment

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

Thanks for the update. Here are a new round of comments, the PR is starting to look good.

View changes since this review

/// A list of paths to types that should be treated as if they do not contain interior mutability
#[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
/// Sets the scope at which duplicate impl blocks for the same type are linted.
Copy link
Member

Choose a reason for hiding this comment

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

Nit

Suggested change
/// Sets the scope at which duplicate impl blocks for the same type are linted.
/// Sets the scope ("crate", "file", or "module") in which duplicate inherent `impl` blocks for the same type are linted.

Entry::Vacant(e) => {
// Store the id for the first impl block of this type. The span is retrieved lazily.
e.insert(IdOrSpan::Id(impl_id));
e.insert((IdOrSpan::Id(impl_id), criterion.clone()));
Copy link
Member

Choose a reason for hiding this comment

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

There seems to be no need to insert the criterion as part of the value (while it is useful as part of the key). Can't you just keep inserting the IdOrSpan as was done before? Also, this would let you remove the .clone() and write match type_map.entry((impl_ty, criterion)) { above.

Entry::Occupied(mut e) => {
if let Some(span) = get_impl_span(cx, impl_id) {
let first_span = match *e.get() {
let first_span = match e.get().0 {
Copy link
Member

Choose a reason for hiding this comment

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

You can also restore the dereference once you remove the criterion.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Oct 8, 2025
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Oct 9, 2025
Copy link
Member

@samueltardieu samueltardieu left a comment

Choose a reason for hiding this comment

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

Could you also please squash the commits?

View changes since this review

Comment on lines 98 to 109
if let Node::Item(&Item {
kind: ItemKind::Impl(_impl_id),
span,
..
}) = cx.tcx.hir_node(hir_id)
{
Criterion::File(cx.tcx.sess.source_map().lookup_source_file(span.lo()).name.clone())
} else {
// We know we are working on an impl, so the pattern matching can
// not fail
unreachable!()
}
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
if let Node::Item(&Item {
kind: ItemKind::Impl(_impl_id),
span,
..
}) = cx.tcx.hir_node(hir_id)
{
Criterion::File(cx.tcx.sess.source_map().lookup_source_file(span.lo()).name.clone())
} else {
// We know we are working on an impl, so the pattern matching can
// not fail
unreachable!()
}
let span = cx.tcx.hir_span(hir_id);
Criterion::File(cx.tcx.sess.source_map().lookup_source_file(span.lo()).name.clone())

Comment on lines 84 to 87
|| is_lint_allowed(
cx,
MULTIPLE_INHERENT_IMPL,
cx.tcx.local_def_id_to_hir_id(id),
Copy link
Member

Choose a reason for hiding this comment

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

I think this will not work if #[expect] is used on the type definition, as the expectation will not be fulfilled. It would probably be better to call clippy_utils::fulfill_or_allowed(…). This must be done only when you know for sure that the lint would be emitted if not allowed or expected.

Could you add some tests with #[expect] to check that this works as expected (ah ah)?

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Oct 9, 2025
@paulmialane
Copy link
Author

I need help to understand what's wrong with my test file.

I looked at how #[expect(...)] works, and as suggested I tried using fulfill_or_allowed(). I think I have the desired behaviour, as when I test on my own crate using cargo dev lint ... I have the expected errors.

But, when I use cargo uibless on my new test file:

error: diagnostic code `clippy::unfulfilled_lint_expectations` not found on line 80
  --> tests/ui/impl.rs:81:6
   |
81 | //~^ unfulfilled_lint_expectations
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected because of this pattern
   |

error: there were 1 unmatched diagnostics
  --> tests/ui/impl.rs:80:10
   |
80 | #[expect(clippy::multiple_inherent_impl)]
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Error[unfulfilled_lint_expectations]: this lint expectation is unfulfilled

I don't understand why I get this... The way I wrote it is probably not the intended way, but then how?

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lint suggestion: duplicate_impl_blocks
3 participants