diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index aaee0caa070e7..43b2428b28a42 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -209,6 +209,7 @@ macro_rules! late_lint_mod_passes { UnstableFeatures: UnstableFeatures, ArrayIntoIter: ArrayIntoIter::default(), DropTraitConstraints: DropTraitConstraints, + DuplicateTraitBounds: DuplicateTraitBounds, TemporaryCStringAsPtr: TemporaryCStringAsPtr, NonPanicFmt: NonPanicFmt, NoopMethodCall: NoopMethodCall, diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs index df1587c5948f5..d25369f507a31 100644 --- a/compiler/rustc_lint/src/traits.rs +++ b/compiler/rustc_lint/src/traits.rs @@ -1,6 +1,8 @@ use crate::LateContext; use crate::LateLintPass; use crate::LintContext; +use hir::GenericBound; +use rustc_data_structures::stable_set::FxHashSet; use rustc_errors::fluent; use rustc_hir as hir; use rustc_span::symbol::sym; @@ -132,3 +134,44 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { } } } + +declare_lint! { + pub DUP_TRAIT_BOUNDS, + Deny, + "Duplicate trait bounds are meaningless" +} + +declare_lint_pass!( + DuplicateTraitBounds => [DUP_TRAIT_BOUNDS] +); + +impl<'tcx> LateLintPass<'tcx> for DuplicateTraitBounds { + fn check_item(&mut self, cx: &LateContext<'_>, item: &'tcx hir::Item<'tcx>) { + let bounds: &[hir::GenericBound<'_>] = match &item.kind { + hir::ItemKind::Trait(_, _, _, bounds, _) => bounds, + hir::ItemKind::TraitAlias(_, bounds) => bounds, + _ => return, + }; + + let mut set = FxHashSet::default(); + for bound in bounds.into_iter() { + match bound { + GenericBound::Trait(polytraitref, _) => { + let Some(did) = polytraitref.trait_ref.trait_def_id() else { continue; }; + // If inserting the trait bound into the set returns `false`, + // there is a duplicate. + if !set.insert(did) { + let span_of_dup = polytraitref.span; + cx.struct_span_lint(DUP_TRAIT_BOUNDS, span_of_dup, |lint| { + let msg = format!("duplicate trait bound"); + lint.build(&msg) + .span_help(span_of_dup, "Remove this duplicate trait bound") + .emit(); + }); + }; + } + _ => continue, + } + } + } +} diff --git a/src/test/ui/traits/duplicate-trait-bounds.rs b/src/test/ui/traits/duplicate-trait-bounds.rs new file mode 100644 index 0000000000000..246f95d576b44 --- /dev/null +++ b/src/test/ui/traits/duplicate-trait-bounds.rs @@ -0,0 +1,12 @@ +pub trait DirectedGraph {} +pub trait WithStartNode {} +pub trait WithPredecessors {} +pub trait WithSuccessors {} +pub trait WithNumNodes {} + +pub trait ControlFlowGraph: + DirectedGraph + WithStartNode + WithPredecessors + WithStartNode + WithSuccessors + WithNumNodes + //~ ERROR duplicate trait bound +{} + +fn main() {} diff --git a/src/test/ui/traits/duplicate-trait-bounds.stderr b/src/test/ui/traits/duplicate-trait-bounds.stderr new file mode 100644 index 0000000000000..0ff5f33357540 --- /dev/null +++ b/src/test/ui/traits/duplicate-trait-bounds.stderr @@ -0,0 +1,15 @@ +error: duplicate trait bound + --> $DIR/duplicate-trait-bounds.rs:8:56 + | +LL | DirectedGraph + WithStartNode + WithPredecessors + WithStartNode + WithSuccessors + WithNumNodes + | ^^^^^^^^^^^^^ + | + = note: `#[deny(dup_trait_bounds)]` on by default +help: Remove this duplicate trait bound + --> $DIR/duplicate-trait-bounds.rs:8:56 + | +LL | DirectedGraph + WithStartNode + WithPredecessors + WithStartNode + WithSuccessors + WithNumNodes + | ^^^^^^^^^^^^^ + +error: aborting due to previous error +