Skip to content

Commit d654acd

Browse files
Add new multiple_bound_locations lint
1 parent 6aa5f1a commit d654acd

File tree

4 files changed

+88
-0
lines changed

4 files changed

+88
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5430,6 +5430,7 @@ Released 2018-09-13
54305430
[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
54315431
[`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one
54325432
[`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments
5433+
[`multiple_bound_locations`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_bound_locations
54335434
[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
54345435
[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
54355436
[`multiple_unsafe_ops_per_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_unsafe_ops_per_block

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
498498
crate::module_style::MOD_MODULE_FILES_INFO,
499499
crate::module_style::SELF_NAMED_MODULE_FILES_INFO,
500500
crate::multi_assignments::MULTI_ASSIGNMENTS_INFO,
501+
crate::multiple_bound_locations::MULTIPLE_BOUND_LOCATIONS_INFO,
501502
crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO,
502503
crate::mut_key::MUTABLE_KEY_TYPE_INFO,
503504
crate::mut_mut::MUT_MUT_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ mod missing_trait_methods;
226226
mod mixed_read_write_in_expression;
227227
mod module_style;
228228
mod multi_assignments;
229+
mod multiple_bound_locations;
229230
mod multiple_unsafe_ops_per_block;
230231
mod mut_key;
231232
mod mut_mut;
@@ -1111,6 +1112,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
11111112
});
11121113
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
11131114
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
1115+
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
11141116
// add lints here, do not remove this comment, it's used in `new_lint`
11151117
}
11161118

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use rustc_ast::visit::FnKind;
2+
use rustc_ast::{NodeId, WherePredicate};
3+
use rustc_data_structures::fx::FxHashMap;
4+
use rustc_lint::{EarlyContext, EarlyLintPass};
5+
use rustc_session::declare_lint_pass;
6+
use rustc_span::Span;
7+
8+
use clippy_utils::diagnostics::span_lint;
9+
use clippy_utils::source::snippet_opt;
10+
11+
declare_clippy_lint! {
12+
/// ### What it does
13+
/// Check if a generic is defined both in the bound predicate and in the `where` clause.
14+
///
15+
/// ### Why is this bad?
16+
/// It can be confusing for developers when seeing bounds for a generic in multiple places.
17+
///
18+
/// ### Example
19+
/// ```no_run
20+
/// fn ty<F: std::fmt::Debug>(a: F)
21+
/// where
22+
/// F: Sized,
23+
/// {}
24+
/// ```
25+
/// Use instead:
26+
/// ```no_run
27+
/// fn ty<F>(a: F)
28+
/// where
29+
/// F: Sized + std::fmt::Debug,
30+
/// {}
31+
/// ```
32+
#[clippy::version = "1.77.0"]
33+
pub MULTIPLE_BOUND_LOCATIONS,
34+
suspicious,
35+
"defining generic bounds in multiple locations"
36+
}
37+
38+
declare_lint_pass!(MultipleBoundLocations => [MULTIPLE_BOUND_LOCATIONS]);
39+
40+
impl EarlyLintPass for MultipleBoundLocations {
41+
fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) {
42+
if let FnKind::Fn(_, _, _, _, generics, _) = kind
43+
&& !generics.params.is_empty()
44+
&& !generics.where_clause.predicates.is_empty()
45+
{
46+
let mut generic_params_with_bounds = FxHashMap::default();
47+
48+
for param in &generics.params {
49+
if !param.bounds.is_empty() {
50+
generic_params_with_bounds.insert(param.ident.name.as_str(), param.ident.span);
51+
}
52+
}
53+
for clause in &generics.where_clause.predicates {
54+
match clause {
55+
WherePredicate::BoundPredicate(pred) => {
56+
if (!pred.bound_generic_params.is_empty() || !pred.bounds.is_empty())
57+
&& let Some(name) = snippet_opt(cx, pred.bounded_ty.span)
58+
&& let Some(bound_span) = generic_params_with_bounds.get(name.as_str())
59+
{
60+
emit_lint(cx, *bound_span, pred.bounded_ty.span);
61+
}
62+
},
63+
WherePredicate::RegionPredicate(pred) => {
64+
if !pred.bounds.is_empty()
65+
&& let Some(bound_span) = generic_params_with_bounds.get(&pred.lifetime.ident.name.as_str())
66+
{
67+
emit_lint(cx, *bound_span, pred.lifetime.ident.span);
68+
}
69+
},
70+
WherePredicate::EqPredicate(_) => {},
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
fn emit_lint(cx: &EarlyContext<'_>, bound_span: Span, where_span: Span) {
78+
span_lint(
79+
cx,
80+
MULTIPLE_BOUND_LOCATIONS,
81+
vec![bound_span, where_span],
82+
"bound is defined in more than one place",
83+
);
84+
}

0 commit comments

Comments
 (0)