about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2024-02-10 15:04:50 +0100
committerGuillaume Gomez <guillaume1.gomez@gmail.com>2024-02-23 13:22:21 +0100
commitd654acd554b679a61d9bde0247aa3ac8e691b964 (patch)
treecf88ee74ef7b5e6b2c0334bc9ac9e837e9a34c68
parent6aa5f1ac6ff7628d02d9b21acb288ad3048e2f70 (diff)
downloadrust-d654acd554b679a61d9bde0247aa3ac8e691b964.tar.gz
rust-d654acd554b679a61d9bde0247aa3ac8e691b964.zip
Add new `multiple_bound_locations` lint
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/multiple_bound_locations.rs84
4 files changed, 88 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b35475c7340..d2afaa6ff9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5430,6 +5430,7 @@ Released 2018-09-13
 [`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
 [`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one
 [`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments
+[`multiple_bound_locations`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_bound_locations
 [`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
 [`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
 [`multiple_unsafe_ops_per_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_unsafe_ops_per_block
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 60744fee34d..455bf990721 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -498,6 +498,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::module_style::MOD_MODULE_FILES_INFO,
     crate::module_style::SELF_NAMED_MODULE_FILES_INFO,
     crate::multi_assignments::MULTI_ASSIGNMENTS_INFO,
+    crate::multiple_bound_locations::MULTIPLE_BOUND_LOCATIONS_INFO,
     crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO,
     crate::mut_key::MUTABLE_KEY_TYPE_INFO,
     crate::mut_mut::MUT_MUT_INFO,
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 5636f46b22f..99bae807e2c 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -226,6 +226,7 @@ mod missing_trait_methods;
 mod mixed_read_write_in_expression;
 mod module_style;
 mod multi_assignments;
+mod multiple_bound_locations;
 mod multiple_unsafe_ops_per_block;
 mod mut_key;
 mod mut_mut;
@@ -1111,6 +1112,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     });
     store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
     store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
+    store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
 
diff --git a/clippy_lints/src/multiple_bound_locations.rs b/clippy_lints/src/multiple_bound_locations.rs
new file mode 100644
index 00000000000..191b32408ef
--- /dev/null
+++ b/clippy_lints/src/multiple_bound_locations.rs
@@ -0,0 +1,84 @@
+use rustc_ast::visit::FnKind;
+use rustc_ast::{NodeId, WherePredicate};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::declare_lint_pass;
+use rustc_span::Span;
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::source::snippet_opt;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Check if a generic is defined both in the bound predicate and in the `where` clause.
+    ///
+    /// ### Why is this bad?
+    /// It can be confusing for developers when seeing bounds for a generic in multiple places.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// fn ty<F: std::fmt::Debug>(a: F)
+    /// where
+    ///     F: Sized,
+    /// {}
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// fn ty<F>(a: F)
+    /// where
+    ///     F: Sized + std::fmt::Debug,
+    /// {}
+    /// ```
+    #[clippy::version = "1.77.0"]
+    pub MULTIPLE_BOUND_LOCATIONS,
+    suspicious,
+    "defining generic bounds in multiple locations"
+}
+
+declare_lint_pass!(MultipleBoundLocations => [MULTIPLE_BOUND_LOCATIONS]);
+
+impl EarlyLintPass for MultipleBoundLocations {
+    fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) {
+        if let FnKind::Fn(_, _, _, _, generics, _) = kind
+            && !generics.params.is_empty()
+            && !generics.where_clause.predicates.is_empty()
+        {
+            let mut generic_params_with_bounds = FxHashMap::default();
+
+            for param in &generics.params {
+                if !param.bounds.is_empty() {
+                    generic_params_with_bounds.insert(param.ident.name.as_str(), param.ident.span);
+                }
+            }
+            for clause in &generics.where_clause.predicates {
+                match clause {
+                    WherePredicate::BoundPredicate(pred) => {
+                        if (!pred.bound_generic_params.is_empty() || !pred.bounds.is_empty())
+                            && let Some(name) = snippet_opt(cx, pred.bounded_ty.span)
+                            && let Some(bound_span) = generic_params_with_bounds.get(name.as_str())
+                        {
+                            emit_lint(cx, *bound_span, pred.bounded_ty.span);
+                        }
+                    },
+                    WherePredicate::RegionPredicate(pred) => {
+                        if !pred.bounds.is_empty()
+                            && let Some(bound_span) = generic_params_with_bounds.get(&pred.lifetime.ident.name.as_str())
+                        {
+                            emit_lint(cx, *bound_span, pred.lifetime.ident.span);
+                        }
+                    },
+                    WherePredicate::EqPredicate(_) => {},
+                }
+            }
+        }
+    }
+}
+
+fn emit_lint(cx: &EarlyContext<'_>, bound_span: Span, where_span: Span) {
+    span_lint(
+        cx,
+        MULTIPLE_BOUND_LOCATIONS,
+        vec![bound_span, where_span],
+        "bound is defined in more than one place",
+    );
+}