Skip to content

Commit

Permalink
Auto merge of #12259 - GuillaumeGomez:multiple-bound-locations, r=llogiq
Browse files Browse the repository at this point in the history
Add new `multiple_bound_locations` lint

Fixes #7181.

r? `@llogiq`

changelog: Add new `multiple_bound_locations` lint
  • Loading branch information
bors committed Feb 24, 2024
2 parents 6405469 + 762448b commit a2c1d56
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,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,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,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;
Expand Down Expand Up @@ -1116,6 +1117,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`
}

Expand Down
84 changes: 84 additions & 0 deletions clippy_lints/src/multiple_bound_locations.rs
Original file line number Diff line number Diff line change
@@ -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",
);
}
60 changes: 60 additions & 0 deletions tests/ui/multiple_bound_locations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#![warn(clippy::multiple_bound_locations)]

fn ty<F: std::fmt::Debug>(a: F)
//~^ ERROR: bound is defined in more than one place
where
F: Sized,
{
}

fn lifetime<'a, 'b: 'a, 'c>(a: &'b str, b: &'a str, c: &'c str)
//~^ ERROR: bound is defined in more than one place
where
'b: 'c,
{
}

fn ty_pred<F: Sized>()
//~^ ERROR: bound is defined in more than one place
where
for<'a> F: Send + 'a,
{
}

struct B;

impl B {
fn ty<F: std::fmt::Debug>(a: F)
//~^ ERROR: bound is defined in more than one place
where
F: Sized,
{
}

fn lifetime<'a, 'b: 'a, 'c>(a: &'b str, b: &'a str, c: &'c str)
//~^ ERROR: bound is defined in more than one place
where
'b: 'c,
{
}

fn ty_pred<F: Sized>()
//~^ ERROR: bound is defined in more than one place
where
for<'a> F: Send + 'a,
{
}
}

struct C<F>(F);

impl<F> C<F> {
fn foo(_f: F) -> Self
where
F: std::fmt::Display,
{
todo!()
}
}

fn main() {}
59 changes: 59 additions & 0 deletions tests/ui/multiple_bound_locations.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
error: bound is defined in more than one place
--> tests/ui/multiple_bound_locations.rs:3:7
|
LL | fn ty<F: std::fmt::Debug>(a: F)
| ^
...
LL | F: Sized,
| ^
|
= note: `-D clippy::multiple-bound-locations` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::multiple_bound_locations)]`

error: bound is defined in more than one place
--> tests/ui/multiple_bound_locations.rs:10:17
|
LL | fn lifetime<'a, 'b: 'a, 'c>(a: &'b str, b: &'a str, c: &'c str)
| ^^
...
LL | 'b: 'c,
| ^^

error: bound is defined in more than one place
--> tests/ui/multiple_bound_locations.rs:17:12
|
LL | fn ty_pred<F: Sized>()
| ^
...
LL | for<'a> F: Send + 'a,
| ^

error: bound is defined in more than one place
--> tests/ui/multiple_bound_locations.rs:27:11
|
LL | fn ty<F: std::fmt::Debug>(a: F)
| ^
...
LL | F: Sized,
| ^

error: bound is defined in more than one place
--> tests/ui/multiple_bound_locations.rs:34:21
|
LL | fn lifetime<'a, 'b: 'a, 'c>(a: &'b str, b: &'a str, c: &'c str)
| ^^
...
LL | 'b: 'c,
| ^^

error: bound is defined in more than one place
--> tests/ui/multiple_bound_locations.rs:41:16
|
LL | fn ty_pred<F: Sized>()
| ^
...
LL | for<'a> F: Send + 'a,
| ^

error: aborting due to 6 previous errors

1 change: 1 addition & 0 deletions tests/ui/trait_duplication_in_bounds_unfixable.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![deny(clippy::trait_duplication_in_bounds)]
#![allow(clippy::multiple_bound_locations)]

use std::collections::BTreeMap;
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
Expand Down
16 changes: 8 additions & 8 deletions tests/ui/trait_duplication_in_bounds_unfixable.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: this trait bound is already specified in the where clause
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:6:15
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:7:15
|
LL | fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z)
| ^^^^^
Expand All @@ -12,55 +12,55 @@ LL | #![deny(clippy::trait_duplication_in_bounds)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: this trait bound is already specified in the where clause
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:6:23
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:7:23
|
LL | fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z)
| ^^^^^^^
|
= help: consider removing this trait bound

error: this trait bound is already specified in trait declaration
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:37:15
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:38:15
|
LL | Self: Default;
| ^^^^^^^
|
= help: consider removing this trait bound

error: this trait bound is already specified in trait declaration
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:52:15
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:53:15
|
LL | Self: Default + Clone;
| ^^^^^^^
|
= help: consider removing this trait bound

error: this trait bound is already specified in trait declaration
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:59:15
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:60:15
|
LL | Self: Default + Clone;
| ^^^^^^^
|
= help: consider removing this trait bound

error: this trait bound is already specified in trait declaration
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:59:25
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:60:25
|
LL | Self: Default + Clone;
| ^^^^^
|
= help: consider removing this trait bound

error: this trait bound is already specified in trait declaration
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:64:15
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:65:15
|
LL | Self: Default;
| ^^^^^^^
|
= help: consider removing this trait bound

error: this trait bound is already specified in trait declaration
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:100:15
--> tests/ui/trait_duplication_in_bounds_unfixable.rs:101:15
|
LL | Self: Iterator<Item = Foo>,
| ^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/type_repetition_in_bounds.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![deny(clippy::type_repetition_in_bounds)]
#![allow(clippy::extra_unused_type_parameters)]
#![allow(clippy::extra_unused_type_parameters, clippy::multiple_bound_locations)]

use serde::Deserialize;
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
Expand Down
1 change: 1 addition & 0 deletions tests/ui/unnecessary_cast.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![warn(clippy::unnecessary_cast)]
#![allow(
clippy::borrow_as_ptr,
clippy::multiple_bound_locations,
clippy::no_effect,
clippy::nonstandard_macro_braces,
clippy::unnecessary_operation,
Expand Down
1 change: 1 addition & 0 deletions tests/ui/unnecessary_cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![warn(clippy::unnecessary_cast)]
#![allow(
clippy::borrow_as_ptr,
clippy::multiple_bound_locations,
clippy::no_effect,
clippy::nonstandard_macro_braces,
clippy::unnecessary_operation,
Expand Down
Loading

0 comments on commit a2c1d56

Please sign in to comment.