diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ad1a7177eb4..02754f0f40c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6269,6 +6269,7 @@ Released 2018-09-13 [`set_contains_or_insert`]: https://rust-lang.github.io/rust-clippy/master/index.html#set_contains_or_insert [`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse [`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same +[`shadow_type_generic`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_type_generic [`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated [`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement [`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c3f8e02b4c06..ad7e1009e366 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -671,6 +671,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::shadow::SHADOW_REUSE_INFO, crate::shadow::SHADOW_SAME_INFO, crate::shadow::SHADOW_UNRELATED_INFO, + crate::shadow_type::SHADOW_TYPE_GENERIC_INFO, crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO, crate::single_call_fn::SINGLE_CALL_FN_INFO, crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 96a6dee58852..105e1d6fccc7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -337,6 +337,7 @@ mod semicolon_if_nothing_returned; mod serde_api; mod set_contains_or_insert; mod shadow; +mod shadow_type; mod significant_drop_tightening; mod single_call_fn; mod single_char_lifetime_names; @@ -830,5 +831,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))); store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); + store.register_late_pass(|_| Box::::default()); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/shadow_type.rs b/clippy_lints/src/shadow_type.rs new file mode 100644 index 000000000000..f3a87b6fdda3 --- /dev/null +++ b/clippy_lints/src/shadow_type.rs @@ -0,0 +1,105 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{GenericParamKind, Generics, HirId, Item, ItemKind, Mod}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::impl_lint_pass; +use rustc_span::{Ident, Span, Symbol}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for generic parameters that shadow types in scope. + /// + /// ### Why restrict this? + /// To avoid confusion and potential bugs, as it can lead to + /// misunderstandings about which type is being referred to. + /// + /// ### Example + /// ```no_run + /// struct Foo; + /// struct Bar { f: Foo } + /// ``` + /// + /// Use instead: + /// ```no_run + /// struct Foo; + /// struct Bar { f: F } // use different generic parameter name + /// ``` + #[clippy::version = "1.89.0"] + pub SHADOW_TYPE_GENERIC, + restriction, + "shadowing of type in scope by generic parameter" +} + +#[derive(Default)] +pub(crate) struct ShadowTypeGeneric { + module_types: FxHashMap, +} + +impl_lint_pass!(ShadowTypeGeneric => [SHADOW_TYPE_GENERIC]); + +impl<'tcx> LateLintPass<'tcx> for ShadowTypeGeneric { + fn check_mod(&mut self, cx: &LateContext<'tcx>, module: &'tcx Mod<'tcx>, _: HirId) { + self.module_types.clear(); + let items = module.item_ids.iter().map(|&id| cx.tcx.hir_item(id)); + for item in items { + if item.span.in_external_macro(cx.sess().source_map()) || item.span.from_expansion() { + continue; + } + + if let ItemKind::Enum(ident, _, _) | ItemKind::Struct(ident, _, _) = item.kind { + self.module_types.insert(ident.name, ident.span); + } + } + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if item.span.in_external_macro(cx.sess().source_map()) || item.span.from_expansion() { + return; + } + if let ItemKind::Enum(ident, generics, _) | ItemKind::Struct(ident, generics, _) = item.kind { + self.check(cx, ident, generics); + } + } +} + +impl ShadowTypeGeneric { + fn check(&self, cx: &LateContext<'_>, ident: Ident, generics: &Generics<'_>) { + // Look for generic parameters such as `T`. + let generic_params = generics + .params + .iter() + .filter(|gen_param| matches!(gen_param.kind, GenericParamKind::Type { .. })); + + // Match generic parameters with module types and split into spans lists. + let (gen_param_spans, type_spans): (Vec<_>, Vec<_>) = generic_params + .filter_map(|gen_param| { + self.module_types + .get(&gen_param.name.ident().name) + .map(|type_span| (gen_param.span, type_span)) + }) + .unzip(); + + let (msg, help) = match gen_param_spans.len() { + 0 => { + // No generic parameters shadowing types in scope + return; + }, + 1 => ( + format!("generic parameter in `{ident}` shadows type in scope"), + "consider using a different name for the generic parameter", + ), + _ => ( + format!("generic parameters in `{ident}` shadow types in scope"), + "consider using different names for the generic parameters", + ), + }; + + span_lint_and_then(cx, SHADOW_TYPE_GENERIC, gen_param_spans, msg, |diag| { + diag.span_labels( + type_spans, + &format!("this type is being shadowed by a generic parameter in `{ident}`"), + ); + diag.help(help); + }); + } +} diff --git a/tests/ui/shadow_type.rs b/tests/ui/shadow_type.rs new file mode 100644 index 000000000000..8c22869f854a --- /dev/null +++ b/tests/ui/shadow_type.rs @@ -0,0 +1,65 @@ +#![warn(clippy::shadow_type_generic)] + +pub mod structs { + struct Foo; + enum Bar {} + + //~v shadow_type_generic + struct Struct1 { + foo: Foo, + } + //~v shadow_type_generic + struct Struct2 { + bar: Bar, + } + //~v shadow_type_generic + struct Struct3 { + foo: Foo, + bar: Bar, + } + //~v shadow_type_generic + struct Struct4 { + foo: Foo, + b: B, + bar: Bar, + } + struct Struct5 { + foo: Foo, + } + struct Struct6 { + bar: Bar, + } +} + +pub mod enums { + struct Foo; + enum Bar {} + + //~v shadow_type_generic + enum Enum1 { + Foo(Foo), + } + //~v shadow_type_generic + enum Enum2 { + Bar(Bar), + } + //~v shadow_type_generic + enum Enum3 { + Foo(Foo), + Bar(Bar), + } + //~v shadow_type_generic + enum Enum4 { + Foo(Foo), + B(B), + Bar(Bar), + } + enum Enum5 { + Foo(Foo), + } + enum Enum6 { + Bar(Bar), + } +} + +fn main() {} diff --git a/tests/ui/shadow_type.stderr b/tests/ui/shadow_type.stderr new file mode 100644 index 000000000000..ba6121b5eb28 --- /dev/null +++ b/tests/ui/shadow_type.stderr @@ -0,0 +1,100 @@ +error: generic parameter in `Struct1` shadows type in scope + --> tests/ui/shadow_type.rs:8:20 + | +LL | struct Foo; + | --- this type is being shadowed by a generic parameter in `Struct1` +... +LL | struct Struct1 { + | ^^^ + | + = help: consider using a different name for the generic parameter + = note: `-D clippy::shadow-type-generic` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::shadow_type_generic)]` + +error: generic parameter in `Struct2` shadows type in scope + --> tests/ui/shadow_type.rs:12:20 + | +LL | enum Bar {} + | --- this type is being shadowed by a generic parameter in `Struct2` +... +LL | struct Struct2 { + | ^^^ + | + = help: consider using a different name for the generic parameter + +error: generic parameters in `Struct3` shadow types in scope + --> tests/ui/shadow_type.rs:16:20 + | +LL | struct Foo; + | --- this type is being shadowed by a generic parameter in `Struct3` +LL | enum Bar {} + | --- this type is being shadowed by a generic parameter in `Struct3` +... +LL | struct Struct3 { + | ^^^ ^^^ + | + = help: consider using different names for the generic parameters + +error: generic parameters in `Struct4` shadow types in scope + --> tests/ui/shadow_type.rs:21:20 + | +LL | struct Foo; + | --- this type is being shadowed by a generic parameter in `Struct4` +LL | enum Bar {} + | --- this type is being shadowed by a generic parameter in `Struct4` +... +LL | struct Struct4 { + | ^^^ ^^^ + | + = help: consider using different names for the generic parameters + +error: generic parameter in `Enum1` shadows type in scope + --> tests/ui/shadow_type.rs:39:16 + | +LL | struct Foo; + | --- this type is being shadowed by a generic parameter in `Enum1` +... +LL | enum Enum1 { + | ^^^ + | + = help: consider using a different name for the generic parameter + +error: generic parameter in `Enum2` shadows type in scope + --> tests/ui/shadow_type.rs:43:16 + | +LL | enum Bar {} + | --- this type is being shadowed by a generic parameter in `Enum2` +... +LL | enum Enum2 { + | ^^^ + | + = help: consider using a different name for the generic parameter + +error: generic parameters in `Enum3` shadow types in scope + --> tests/ui/shadow_type.rs:47:16 + | +LL | struct Foo; + | --- this type is being shadowed by a generic parameter in `Enum3` +LL | enum Bar {} + | --- this type is being shadowed by a generic parameter in `Enum3` +... +LL | enum Enum3 { + | ^^^ ^^^ + | + = help: consider using different names for the generic parameters + +error: generic parameters in `Enum4` shadow types in scope + --> tests/ui/shadow_type.rs:52:16 + | +LL | struct Foo; + | --- this type is being shadowed by a generic parameter in `Enum4` +LL | enum Bar {} + | --- this type is being shadowed by a generic parameter in `Enum4` +... +LL | enum Enum4 { + | ^^^ ^^^ + | + = help: consider using different names for the generic parameters + +error: aborting due to 8 previous errors +