-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(multiple_inherent_impl): Add config option to target specific scope #15843
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
base: master
Are you sure you want to change the base?
Changes from 2 commits
b2b0a44
fa88945
f287b42
21e0f33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,23 @@ | ||
use clippy_config::Conf; | ||
use clippy_config::types::InherentImplLintScope; | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::is_lint_allowed; | ||
use rustc_data_structures::fx::FxHashMap; | ||
use rustc_hir::def_id::LocalDefId; | ||
use rustc_hir::def_id::{LocalDefId, LocalModDefId}; | ||
use rustc_hir::{Item, ItemKind, Node}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_session::declare_lint_pass; | ||
use rustc_span::Span; | ||
use rustc_session::impl_lint_pass; | ||
use rustc_span::{FileName, Span}; | ||
use std::collections::hash_map::Entry; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Checks for multiple inherent implementations of a struct | ||
/// | ||
/// 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). | ||
/// | ||
/// ### Why restrict this? | ||
/// Splitting the implementation of a type makes the code harder to navigate. | ||
/// | ||
|
@@ -41,7 +47,26 @@ declare_clippy_lint! { | |
"Multiple inherent impl that could be grouped" | ||
} | ||
|
||
declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); | ||
impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); | ||
|
||
pub struct MultipleInherentImpl { | ||
scope: InherentImplLintScope, | ||
} | ||
|
||
impl MultipleInherentImpl { | ||
pub fn new(conf: &'static Conf) -> Self { | ||
Self { | ||
scope: conf.inherent_impl_lint_scope, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Hash, Eq, PartialEq, Clone)] | ||
enum Criterion { | ||
Module(LocalModDefId), | ||
File(FileName), | ||
Crate, | ||
} | ||
|
||
impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { | ||
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { | ||
|
@@ -66,24 +91,43 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { | |
|
||
for impl_id in impl_ids.iter().map(|id| id.expect_local()) { | ||
let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity(); | ||
match type_map.entry(impl_ty) { | ||
let hir_id = cx.tcx.local_def_id_to_hir_id(impl_id); | ||
let criterion = match self.scope { | ||
InherentImplLintScope::Module => Criterion::Module(cx.tcx.parent_module(hir_id)), | ||
InherentImplLintScope::File => { | ||
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 the pattern matching can | ||
// not fail | ||
unreachable!() | ||
} | ||
}, | ||
InherentImplLintScope::Crate => Criterion::Crate, | ||
}; | ||
match type_map.entry((impl_ty, criterion.clone())) { | ||
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())); | ||
|
||
}, | ||
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 { | ||
|
||
IdOrSpan::Span(s) => s, | ||
IdOrSpan::Id(id) => { | ||
if let Some(s) = get_impl_span(cx, id) { | ||
// Remember the span of the first block. | ||
*e.get_mut() = IdOrSpan::Span(s); | ||
*e.get_mut() = (IdOrSpan::Span(s), criterion.clone()); | ||
s | ||
} else { | ||
// The first impl block isn't considered by the lint. Replace it with the | ||
// current one. | ||
*e.get_mut() = IdOrSpan::Span(span); | ||
*e.get_mut() = (IdOrSpan::Span(span), criterion.clone()); | ||
continue; | ||
} | ||
}, | ||
|
@@ -97,7 +141,6 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { | |
// Switching to the next type definition, no need to keep the current entries around. | ||
type_map.clear(); | ||
} | ||
|
||
// `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first. | ||
lint_spans.sort_by_key(|x| x.0.lo()); | ||
for (span, first_span) in lint_spans { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
error: error reading Clippy's configuration file: unknown variant `FooBar`, expected one of `crate`, `file`, `module` | ||
--> $DIR/tests/ui-cargo/multiple_inherent_impl/config_fail/clippy.toml:1:28 | ||
| | ||
1 | inherent-impl-lint-scope = "FooBar" | ||
| ^^^^^^^^ | ||
|
||
error: could not compile `config_fail` (bin "config_fail") due to 1 previous error |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "config_fail" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
inherent-impl-lint-scope = "FooBar" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#![allow(dead_code)] | ||
#![deny(clippy::multiple_inherent_impl)] | ||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
error: multiple implementations of this structure | ||
--> src/main.rs:11:1 | ||
| | ||
11 | / impl S { | ||
12 | | //^ Must trigger | ||
13 | | fn second() {} | ||
14 | | } | ||
| |_^ | ||
| | ||
note: first implementation here | ||
--> src/main.rs:7:1 | ||
| | ||
7 | / impl S { | ||
8 | | fn first() {} | ||
9 | | } | ||
| |_^ | ||
note: the lint level is defined here | ||
--> src/main.rs:2:9 | ||
| | ||
2 | #![deny(clippy::multiple_inherent_impl)] | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: multiple implementations of this structure | ||
--> src/main.rs:22:5 | ||
| | ||
22 | / impl T { | ||
23 | | //^ Must trigger | ||
24 | | fn second() {} | ||
25 | | } | ||
| |_____^ | ||
| | ||
note: first implementation here | ||
--> src/main.rs:16:1 | ||
| | ||
16 | / impl T { | ||
17 | | fn first() {} | ||
18 | | } | ||
| |_^ | ||
|
||
error: multiple implementations of this structure | ||
--> src/main.rs:36:1 | ||
| | ||
36 | / impl b::T { | ||
37 | | //^ Must trigger | ||
38 | | fn second() {} | ||
39 | | } | ||
| |_^ | ||
| | ||
note: first implementation here | ||
--> src/b.rs:4:1 | ||
| | ||
4 | / impl T { | ||
5 | | fn first() {} | ||
6 | | } | ||
| |_^ | ||
|
||
error: could not compile `crate_fail` (bin "crate_fail") due to 3 previous errors |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "crate_fail" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
inherent-impl-lint-scope = "crate" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pub struct S; | ||
pub struct T; | ||
|
||
impl T { | ||
fn first() {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#![allow(dead_code)] | ||
#![deny(clippy::multiple_inherent_impl)] | ||
|
||
struct S; | ||
struct T; | ||
|
||
impl S { | ||
fn first() {} | ||
} | ||
|
||
impl S { | ||
//^ Must trigger | ||
fn second() {} | ||
} | ||
|
||
impl T { | ||
fn first() {} | ||
} | ||
|
||
mod a { | ||
use super::T; | ||
impl T { | ||
//^ Must trigger | ||
fn second() {} | ||
} | ||
} | ||
|
||
mod b; | ||
|
||
impl b::S { | ||
//^ Must NOT trigger | ||
fn first() {} | ||
fn second() {} | ||
} | ||
|
||
impl b::T { | ||
//^ Must trigger | ||
fn second() {} | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
error: multiple implementations of this structure | ||
--> src/main.rs:13:5 | ||
| | ||
13 | / impl S { | ||
14 | | //^ Must trigger | ||
15 | | fn second() {} | ||
16 | | } | ||
| |_____^ | ||
| | ||
note: first implementation here | ||
--> src/main.rs:6:1 | ||
| | ||
6 | / impl S { | ||
7 | | fn first() {} | ||
8 | | } | ||
| |_^ | ||
note: the lint level is defined here | ||
--> src/main.rs:2:9 | ||
| | ||
2 | #![deny(clippy::multiple_inherent_impl)] | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: multiple implementations of this structure | ||
--> src/main.rs:26:5 | ||
| | ||
26 | / impl S { | ||
27 | | //^ Must trigger | ||
28 | | | ||
29 | | fn second() {} | ||
30 | | } | ||
| |_____^ | ||
| | ||
note: first implementation here | ||
--> src/main.rs:22:5 | ||
| | ||
22 | / impl S { | ||
23 | | fn first() {} | ||
24 | | } | ||
| |_____^ | ||
|
||
error: multiple implementations of this structure | ||
--> src/c.rs:17:5 | ||
| | ||
17 | / impl T { | ||
18 | | //^ Must trigger | ||
19 | | fn second() {} | ||
20 | | } | ||
| |_____^ | ||
| | ||
note: first implementation here | ||
--> src/c.rs:10:5 | ||
| | ||
10 | / impl T { | ||
11 | | fn first() {} | ||
12 | | } | ||
| |_____^ | ||
|
||
error: could not compile `file_fail` (bin "file_fail") due to 3 previous errors |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "file_fail" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
inherent-impl-lint-scope = "file" |
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