diff --git a/clippy_lints/src/implied_bounds_in_impls.rs b/clippy_lints/src/implied_bounds_in_impls.rs index 7ba467a3e06..c6d1acad3a0 100644 --- a/clippy_lints/src/implied_bounds_in_impls.rs +++ b/clippy_lints/src/implied_bounds_in_impls.rs @@ -1,11 +1,14 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl, FnRetTy, GenericArg, GenericBound, ItemKind, TraitBoundModifier, TyKind}; +use rustc_hir::{ + Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem, + TraitItemKind, TyKind, +}; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::{LateContext, LateLintPass}; -use rustc_errors::{Applicability, SuggestionStyle}; use rustc_middle::ty::{self, ClauseKind, TyCtxt}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; @@ -94,6 +97,86 @@ fn is_same_generics( }) } +fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) { + if let FnRetTy::Return(ty) = decl.output + &&let TyKind::OpaqueDef(item_id, ..) = ty.kind + && let item = cx.tcx.hir().item(item_id) + && let ItemKind::OpaqueTy(opaque_ty) = item.kind + // Very often there is only a single bound, e.g. `impl Deref<..>`, in which case + // we can avoid doing a bunch of stuff unnecessarily. + && opaque_ty.bounds.len() > 1 + { + // Get all the (implied) trait predicates in the bounds. + // For `impl Deref + DerefMut` this will contain [`Deref`]. + // The implied `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`. + // N.B. (G)ATs are fine to disregard, because they must be the same for all of its supertraits. + // Example: + // `impl Deref + DerefMut` is not allowed. + // `DerefMut::Target` needs to match `Deref::Target`. + let implied_bounds: Vec<_> = opaque_ty.bounds.iter().filter_map(|bound| { + if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound + && let [.., path] = poly_trait.trait_ref.path.segments + && poly_trait.bound_generic_params.is_empty() + && let Some(trait_def_id) = path.res.opt_def_id() + && let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates + && !predicates.is_empty() // If the trait has no supertrait, there is nothing to add. + { + Some((bound.span(), path.args.map_or([].as_slice(), |a| a.args), predicates)) + } else { + None + } + }).collect(); + + // Lint all bounds in the `impl Trait` type that are also in the `implied_bounds` vec. + // This involves some extra logic when generic arguments are present, since + // simply comparing trait `DefId`s won't be enough. We also need to compare the generics. + for (index, bound) in opaque_ty.bounds.iter().enumerate() { + if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound + && let [.., path] = poly_trait.trait_ref.path.segments + && let implied_args = path.args.map_or([].as_slice(), |a| a.args) + && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id() + && let Some(implied_by_span) = implied_bounds.iter().find_map(|&(span, implied_by_args, preds)| { + preds.iter().find_map(|(clause, _)| { + if let ClauseKind::Trait(tr) = clause.kind().skip_binder() + && tr.def_id() == def_id + && is_same_generics(cx.tcx, tr.trait_ref.args, implied_by_args, implied_args) + { + Some(span) + } else { + None + } + }) + }) + { + let implied_by = snippet(cx, implied_by_span, ".."); + span_lint_and_then( + cx, IMPLIED_BOUNDS_IN_IMPLS, + poly_trait.span, + &format!("this bound is already specified as the supertrait of `{implied_by}`"), + |diag| { + // If we suggest removing a bound, we may also need extend the span + // to include the `+` token, so we don't end up with something like `impl + B` + + let implied_span_extended = if let Some(next_bound) = opaque_ty.bounds.get(index + 1) { + poly_trait.span.to(next_bound.span().shrink_to_lo()) + } else { + poly_trait.span + }; + + diag.span_suggestion_with_style( + implied_span_extended, + "try removing this bound", + "", + Applicability::MachineApplicable, + SuggestionStyle::ShowAlways + ); + } + ); + } + } + } +} + impl LateLintPass<'_> for ImpliedBoundsInImpls { fn check_fn( &mut self, @@ -104,82 +187,16 @@ fn check_fn( _: Span, _: LocalDefId, ) { - if let FnRetTy::Return(ty) = decl.output - &&let TyKind::OpaqueDef(item_id, ..) = ty.kind - && let item = cx.tcx.hir().item(item_id) - && let ItemKind::OpaqueTy(opaque_ty) = item.kind - // Very often there is only a single bound, e.g. `impl Deref<..>`, in which case - // we can avoid doing a bunch of stuff unnecessarily. - && opaque_ty.bounds.len() > 1 - { - // Get all the (implied) trait predicates in the bounds. - // For `impl Deref + DerefMut` this will contain [`Deref`]. - // The implied `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`. - // N.B. (G)ATs are fine to disregard, because they must be the same for all of its supertraits. - // Example: - // `impl Deref + DerefMut` is not allowed. - // `DerefMut::Target` needs to match `Deref::Target`. - let implied_bounds: Vec<_> = opaque_ty.bounds.iter().filter_map(|bound| { - if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound - && let [.., path] = poly_trait.trait_ref.path.segments - && poly_trait.bound_generic_params.is_empty() - && let Some(trait_def_id) = path.res.opt_def_id() - && let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates - && !predicates.is_empty() // If the trait has no supertrait, there is nothing to add. - { - Some((bound.span(), path.args.map_or([].as_slice(), |a| a.args), predicates)) - } else { - None - } - }).collect(); - - // Lint all bounds in the `impl Trait` type that are also in the `implied_bounds` vec. - // This involves some extra logic when generic arguments are present, since - // simply comparing trait `DefId`s won't be enough. We also need to compare the generics. - for (index, bound) in opaque_ty.bounds.iter().enumerate() { - if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound - && let [.., path] = poly_trait.trait_ref.path.segments - && let implied_args = path.args.map_or([].as_slice(), |a| a.args) - && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id() - && let Some(implied_by_span) = implied_bounds.iter().find_map(|&(span, implied_by_args, preds)| { - preds.iter().find_map(|(clause, _)| { - if let ClauseKind::Trait(tr) = clause.kind().skip_binder() - && tr.def_id() == def_id - && is_same_generics(cx.tcx, tr.trait_ref.args, implied_by_args, implied_args) - { - Some(span) - } else { - None - } - }) - }) - { - let implied_by = snippet(cx, implied_by_span, ".."); - span_lint_and_then( - cx, IMPLIED_BOUNDS_IN_IMPLS, - poly_trait.span, - &format!("this bound is already specified as the supertrait of `{}`", implied_by), - |diag| { - // If we suggest removing a bound, we may also need extend the span - // to include the `+` token, so we don't end up with something like `impl + B` - - let implied_span_extended = if let Some(next_bound) = opaque_ty.bounds.get(index + 1) { - poly_trait.span.to(next_bound.span().shrink_to_lo()) - } else { - poly_trait.span - }; - - diag.span_suggestion_with_style( - implied_span_extended, - "try removing this bound", - "", - Applicability::MachineApplicable, - SuggestionStyle::ShowAlways - ); - } - ); - } - } + check(cx, decl); + } + fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { + if let TraitItemKind::Fn(sig, ..) = &item.kind { + check(cx, sig.decl); + } + } + fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) { + if let ImplItemKind::Fn(sig, ..) = &item.kind { + check(cx, sig.decl); } } } diff --git a/tests/ui/implied_bounds_in_impls.fixed b/tests/ui/implied_bounds_in_impls.fixed index 72239afae4a..952c2b70619 100644 --- a/tests/ui/implied_bounds_in_impls.fixed +++ b/tests/ui/implied_bounds_in_impls.fixed @@ -49,9 +49,7 @@ fn generics_same() -> impl GenericSubtrait<(), i32, ()> {} trait SomeTrait { // Check that it works in trait declarations. - fn f() -> impl DerefMut { - Box::new(0) - } + fn f() -> impl DerefMut; } struct SomeStruct; impl SomeStruct { diff --git a/tests/ui/implied_bounds_in_impls.rs b/tests/ui/implied_bounds_in_impls.rs index d59241d9ad1..475f2621bbf 100644 --- a/tests/ui/implied_bounds_in_impls.rs +++ b/tests/ui/implied_bounds_in_impls.rs @@ -49,20 +49,18 @@ fn generics_same() -> impl GenericTrait + GenericSubtrait<(), i32, ()> {} trait SomeTrait { // Check that it works in trait declarations. - fn f() -> impl Deref + DerefMut { - Box::new(0) - } + fn f() -> impl Deref + DerefMut; } struct SomeStruct; impl SomeStruct { // Check that it works in inherent impl blocks. - fn f() -> impl DerefMut { + fn f() -> impl Deref + DerefMut { Box::new(123) } } impl SomeTrait for SomeStruct { // Check that it works in trait impls. - fn f() -> impl DerefMut { + fn f() -> impl Deref + DerefMut { Box::new(42) } } diff --git a/tests/ui/implied_bounds_in_impls.stderr b/tests/ui/implied_bounds_in_impls.stderr index 86ce64a47fd..8dffc674444 100644 --- a/tests/ui/implied_bounds_in_impls.stderr +++ b/tests/ui/implied_bounds_in_impls.stderr @@ -86,6 +86,18 @@ LL + fn generics_same() -> impl GenericSubtrait<(), i32, ()> {} error: this bound is already specified as the supertrait of `DerefMut` --> $DIR/implied_bounds_in_impls.rs:52:20 | +LL | fn f() -> impl Deref + DerefMut; + | ^^^^^ + | +help: try removing this bound + | +LL - fn f() -> impl Deref + DerefMut; +LL + fn f() -> impl DerefMut; + | + +error: this bound is already specified as the supertrait of `DerefMut` + --> $DIR/implied_bounds_in_impls.rs:57:20 + | LL | fn f() -> impl Deref + DerefMut { | ^^^^^ | @@ -95,5 +107,17 @@ LL - fn f() -> impl Deref + DerefMut { LL + fn f() -> impl DerefMut { | -error: aborting due to 8 previous errors +error: this bound is already specified as the supertrait of `DerefMut` + --> $DIR/implied_bounds_in_impls.rs:63:20 + | +LL | fn f() -> impl Deref + DerefMut { + | ^^^^^ + | +help: try removing this bound + | +LL - fn f() -> impl Deref + DerefMut { +LL + fn f() -> impl DerefMut { + | + +error: aborting due to 10 previous errors