85 lines
2.9 KiB
Rust
85 lines
2.9 KiB
Rust
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.78.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",
|
|
);
|
|
}
|