From 10ed145d5d7bda88410d909b23669277952d9f8a Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sun, 17 Nov 2024 23:29:12 +0000 Subject: [PATCH] add lint: `could_be_assoc_type_bounds` --- CHANGELOG.md | 1 + .../src/could_be_assoc_type_bounds.rs | 674 ++++++++++++++++++ clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_utils/src/hir_utils.rs | 2 +- clippy_utils/src/lib.rs | 2 +- clippy_utils/src/msrvs.rs | 3 +- clippy_utils/src/ty.rs | 8 +- tests/ui/could_be_assoc_type_bounds.fixed | 95 +++ tests/ui/could_be_assoc_type_bounds.rs | 101 +++ tests/ui/could_be_assoc_type_bounds.stderr | 142 ++++ tests/ui/crashes/ice-11803.rs | 1 + tests/ui/crashes/ice-11803.stderr | 4 +- tests/ui/len_without_is_empty.rs | 2 +- .../needless_borrows_for_generic_args.fixed | 3 +- tests/ui/needless_borrows_for_generic_args.rs | 3 +- .../needless_borrows_for_generic_args.stderr | 22 +- tests/ui/redundant_async_block.fixed | 2 +- tests/ui/redundant_async_block.rs | 2 +- .../trait_duplication_in_bounds_unfixable.rs | 2 +- 20 files changed, 1046 insertions(+), 26 deletions(-) create mode 100644 clippy_lints/src/could_be_assoc_type_bounds.rs create mode 100644 tests/ui/could_be_assoc_type_bounds.fixed create mode 100644 tests/ui/could_be_assoc_type_bounds.rs create mode 100644 tests/ui/could_be_assoc_type_bounds.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index cf2f2a871ec9..1081a7175319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5410,6 +5410,7 @@ Released 2018-09-13 [`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty [`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime [`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator +[`could_be_assoc_type_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#could_be_assoc_type_bounds [`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def [`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir [`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute diff --git a/clippy_lints/src/could_be_assoc_type_bounds.rs b/clippy_lints/src/could_be_assoc_type_bounds.rs new file mode 100644 index 000000000000..3008f52af88b --- /dev/null +++ b/clippy_lints/src/could_be_assoc_type_bounds.rs @@ -0,0 +1,674 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::{SourceText, SpanRangeExt}; +use clippy_utils::{SpanlessEq, WithSearchPat, is_from_proc_macro, over}; +use itertools::Itertools; +use rustc_data_structures::fx::FxIndexMap; +use rustc_errors::{Applicability, MultiSpan}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, AssocItemConstraintKind, HirId, PathSegment, PredicateOrigin, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::impl_lint_pass; +use rustc_span::Span; +use rustc_span::symbol::Ident; + +declare_clippy_lint! { + /// ### What it does + /// Checks for trait bounds that constrain another trait bound's associated type, + /// which could be expressed with [associated type bounds] directly. + /// + /// ### Why is this bad? + /// Removing extra bounds and type parameters reduces noise and complexity + /// and generally makes trait bounds easier to read. + /// + /// ### Example + /// ```no_run + /// fn example1() + /// where + /// I: Iterator, + /// ::Item: Copy + /// {} + /// + /// fn example2, T: Copy>() {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// fn example1() + /// where + /// I: Iterator, + /// {} + /// + /// fn example2>() {} + /// ``` + /// + /// [associated type bounds]: https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html#bounds-in-associated-type-position + #[clippy::version = "1.84.0"] + pub COULD_BE_ASSOC_TYPE_BOUNDS, + complexity, + "trait bounds that could be expressed using associated type bounds" +} + +#[derive(Debug)] +struct TyParamState { + /// `DefId` of the type parameter + def_id: LocalDefId, + + /// This count has two roles: + /// - Any time this generic type parameter is referenced by a path within the item it is bound + /// at, this count is incremented. + /// - When postprocessing an item's generics in `check_item_post`, anytime the generic parameter + /// is (1) the self type of a trait bound or (2) the associated type of a single other trait + /// bound, we decrement it. + /// + /// This gives us a cheap way to check if the type parameter is only used in ways that could be + /// replaced with associated type bounds and not anywhere else without having to visit the + /// item's HIR tree again in another visitor: if the count is zero after the decrementing + /// phase (and `self.referenced_in_assoc_type` is true), then all uses of the generic parameter + /// are valid uses and can be linted. + /// Example: + /// + /// fn f, P: Copy>() -> P {} + /// ^ ^ ^ + /// + /// After having visited everything in the item, the `use_count` of the generic parameter `P` + /// is 3. + /// We then check uses of `P` in the where clause and count 2 valid uses. `3 - 2 != 0` so at + /// this point we see that we shouldn't lint. + /// If the return type didn't mention `P` then the count would correctly be zero and we could + /// suggest writing: + /// + /// fn f>() {} + use_count: u32, + + /// If this type parameter has been used to constrain an associated type in the where clause, + /// then this stores the index into `trait_bounds` of that bound as well as the name of the + /// associated type. + constrained_assoc_type: Option<(usize, Ident)>, + + /// The index of this parameter into `hir::Generics::params` + param_index: usize, +} + +#[derive(Debug)] +struct ItemState { + ty_params: Vec, + item_def_id: LocalDefId, +} + +impl ItemState { + fn new(item_def_id: LocalDefId, generics: &hir::Generics<'_>) -> Self { + Self { + item_def_id, + ty_params: generics + .params + .iter() + .enumerate() + .filter_map(|(param_index, &hir::GenericParam { def_id, kind, .. })| { + if let hir::GenericParamKind::Type { synthetic: false, .. } = kind { + Some(TyParamState { + def_id, + param_index, + use_count: 0, + constrained_assoc_type: None, + }) + } else { + None + } + }) + .collect(), + } + } + + fn param_state(&mut self, def_id: LocalDefId) -> Option<&mut TyParamState> { + self.ty_params.iter_mut().find(|t| t.def_id == def_id) + } + + /// If the type is a type parameter bound by this item, return its state + fn param_state_of_ty(&mut self, ty: &hir::Ty<'_>) -> Option<&mut TyParamState> { + hir_ty_param(ty).and_then(|def_id| self.param_state(def_id)) + } + + /// Collects all `Type: Trait` bounds and finalizes the state of the type parameters + /// (decrement the "use counts" of the type parameters along the way if in a valid position, see + /// [`TyParamState::use_count`] for details). + /// + /// After this, all generic type parameters in `self.ty_params` that could be linted + /// will have `use_count == 0 && constrained_assoc_type.is_some()` + fn collect_trait_bounds_and_decrement_counts<'tcx>( + &mut self, + generics: &'tcx hir::Generics<'tcx>, + ) -> Vec> { + let mut trait_bounds = Vec::new(); + + for (self_ty, generic_bounds, predicate_index) in type_trait_bounds(generics) { + if let Some(ty_param) = self.param_state_of_ty(self_ty) { + // (1) Used as the self type of a trait bound + ty_param.use_count -= 1; + } + + for generic_bound in generic_bounds { + if let hir::GenericBound::Trait(poly_trait_ref) = generic_bound + && let [.., trait_path_segment] = poly_trait_ref.trait_ref.path.segments + && let Res::Def(DefKind::Trait, trait_def_id) = trait_path_segment.res + && trait_path_segment.args().parenthesized == hir::GenericArgsParentheses::No + { + trait_bounds.push(TraitBound { + self_ty, + trait_def_id, + trait_path_segment, + emissions: Vec::new(), + span: generics.predicates[predicate_index].span(), + }); + + for constraint in trait_path_segment.args().constraints { + if let AssocItemConstraintKind::Equality { term } = constraint.kind + && let hir::Term::Ty(constrained_ty) = term + && let Some(ty_param) = self.param_state_of_ty(constrained_ty) + && ty_param.constrained_assoc_type.is_none() + // Make sure we don't lint something weird like `T: Trait` where the associated type is the same as the self type + && hir_ty_param(self_ty).is_none_or(|p| p != ty_param.def_id) + { + // (2) Used as the associated type of a trait bound **once** + ty_param.use_count -= 1; + ty_param.constrained_assoc_type = Some((trait_bounds.len() - 1, constraint.ident)); + } + } + } + } + } + + trait_bounds + } + + /// Looks for trait bounds that constrain another trait bound's associated type once and add it + /// to its `emissions` (delay a lint that is later emitted). Specifically, look for the + /// following predicates: + /// + /// - `::Assoc: Copy` if the bound `T: Trait` exists in `trait_bounds` and add + /// `Emission::ProjectionBound(::Assoc: Copy)` to `T: Trait`s + /// [`TraitBound::emissions`] + /// + /// - `U: Trait` if exactly one `T: Trait` bound exists in `trait_bounds` (i.e., + /// exactly one trait bound that has `U` as its associated type and is not used anywhere else, + /// ensured by `use_count` == 0). Add `Emission::TyParamBound(U: Trait)` to `T: Trait`s [`TraitBound::emissions`]. + fn collect_emissions( + &mut self, + cx: &LateContext<'_>, + trait_bounds: &mut [TraitBound<'_>], + generics: &hir::Generics<'_>, + ) { + for (ty, trait_refs, predicate_index) in type_trait_bounds(generics) { + let Some(bounds_span) = trait_refs + .first() + .zip(trait_refs.last()) + .map(|(first, last)| first.span().to(last.span())) + .filter(|s| !s.from_expansion()) + else { + continue; + }; + + if let hir::TyKind::Path(QPath::Resolved(Some(self_ty), path)) = ty.kind + && let [.., trait_path, assoc_ty_path] = path.segments + && let Res::Def(DefKind::Trait, projection_trait_def_id) = trait_path.res + && let Res::Def(DefKind::AssocTy, _) = assoc_ty_path.res + && let mut spanless_eq = SpanlessEq::new(cx).paths_by_resolution().inter_expr() + && let Some(trait_bound) = trait_bounds.iter_mut().find(|t| { + t.trait_def_id == projection_trait_def_id + && spanless_eq.eq_ty(self_ty, t.self_ty) + // NB: intentionally don't check associated types + && over( + trait_path.args().args, + t.trait_path_segment.args().args, + |left, right| spanless_eq.eq_generic_arg(left, right) + ) + }) + { + trait_bound.emissions.push(Emission::ProjectionBound { + predicate_index, + bounds_span, + assoc_type: assoc_ty_path.ident, + }); + } else if let Some(ty_param_def_id) = hir_ty_param(ty) + && let Some((ty_param_index, ty_param)) = self + .ty_params + .iter() + .enumerate() + .find(|(_, t)| t.def_id == ty_param_def_id) + && ty_param.use_count == 0 + && let Some((trait_bound_index, _)) = ty_param.constrained_assoc_type + { + trait_bounds[trait_bound_index].emissions.push(Emission::TyParamBound { + predicate_index, + bounds_span, + ty_param_index, + }); + } + } + } + + /// Emit lints for all previously collected [`TraitBound::emissions`]. + /// + /// See the documentation for `TraitBound::emissions` for why we don't just immediately emit a + /// warning in `ItemState::fill_emissions` and instead delay them for here. + #[expect(clippy::too_many_lines)] + fn lint_emissions(&mut self, cx: &LateContext<'_>, trait_bounds: &[TraitBound<'_>], generics: &hir::Generics<'_>) { + for &TraitBound { + self_ty: _, + trait_def_id: _, + trait_path_segment, + span, + ref emissions, + } in trait_bounds.iter().filter(|b| !b.emissions.is_empty()) + { + let (message, label) = if emissions.len() > 1 { + ( + "these trait bounds only exist to constrain another bound's associated type", + "merge them with this bound", + ) + } else { + ( + "this trait bound only exists to constrain another bound's associated type", + "merge it with this bound", + ) + }; + + let mut spans = MultiSpan::from_spans(emissions.iter().map(|e| e.predicate(generics).span).collect()); + spans.push_span_label(span, label); + + span_lint_and_then(cx, COULD_BE_ASSOC_TYPE_BOUNDS, spans, message, |diag| { + let mut suggestions = Vec::new(); + let mut removed_generic_param = false; + + // Group the emissions so we can merge all bounds from all predicates for + // any single associated type at the same time + let emissions = group_emissions_by_assoc_type(emissions, self); + + let exactly_one_where_bound = generics + .predicates + .iter() + .filter(|p| p.in_where_clause()) + .exactly_one() + .is_ok(); + + let exactly_one_generic_param = generics + .params + .iter() + .filter(|p| !p.is_elided_lifetime() && !p.is_impl_trait()) + .exactly_one() + .is_ok(); + + for emission in emissions.iter().flat_map(|(_, e)| e) { + // Only explicitly remove predicates that live in the where clause, or remove the whole where clause + // if this is the only one. Trait bounds in the generic parameter list don't + // need to be removed here as we remove the whole generic parameter including + // all bounds in one go further down. + if emission.predicate(generics).origin == PredicateOrigin::WhereClause { + if exactly_one_where_bound { + suggestions.push((generics.where_clause_span, String::new())); + } else { + suggestions.push((emission.predicate_span_including_comma(generics), String::new())); + } + } + } + + if let Some(args) = trait_path_segment.args + && let Some(args_span) = args.span() + { + let insert_span = args_span.shrink_to_hi(); + + // Generic arguments are present, insert `AssocTy: Bound1 + Bound2 + Bound3` for each associated + // type, or add them to the existing constraint + let mut new_bounds = String::new(); + + for (assoc, emissions) in emissions { + if let Some(constraint) = args.constraints.iter().find(|c| c.ident == assoc) { + // Associated type is already bound in the generics. Don't introduce + // a new associated type constraint and instead append bounds to the + // existing one. + + match constraint.kind { + AssocItemConstraintKind::Equality { term } => { + if let hir::Term::Ty(ty) = term + && let Some(&mut TyParamState { + param_index, + constrained_assoc_type: Some(_), + use_count: 0, + .. + }) = self.param_state_of_ty(ty) + { + // Remove the type parameter, including the following comma, or if this is the + // only generic parameter, remove the whole generic argument list + let removal_span = match generics.params.get(param_index..) { + Some([own, next, ..]) + if !next.is_impl_trait() && !next.is_elided_lifetime() => + { + own.span.until(next.span) + }, + _ if exactly_one_generic_param => generics.span, + _ => generics.params[param_index] + .span + .with_hi(generics.span.hi() - rustc_span::BytePos(1)), + }; + suggestions.push((removal_span, String::new())); + removed_generic_param = true; + + // Replace ` = P` of `Iterator` with `: Bounds` + let mut sugg = String::from(": "); + append_emission_bounds(&emissions, cx, &mut sugg, false); + suggestions.push((constraint.span.with_lo(constraint.ident.span.hi()), sugg)); + } + }, + AssocItemConstraintKind::Bound { bounds } => { + let mut suggestion = String::new(); + append_emission_bounds(&emissions, cx, &mut suggestion, !bounds.is_empty()); + suggestions.push((constraint.span.shrink_to_hi(), suggestion)); + }, + } + } else { + new_bounds += ", "; + new_bounds += assoc.as_str(); + new_bounds += ": "; + + append_emission_bounds(&emissions, cx, &mut new_bounds, false); + } + } + + if !new_bounds.is_empty() { + if let Some((_, sugg)) = suggestions.iter_mut().find(|(sp, _)| sp.hi() == insert_span.lo()) { + // rustfix considers replacements like `[(10:20, "foo"), (20:20, "bar")]` to have + // overlapping parts when they aren't overlapping. So work around it by + // extending the existing part in that case instead of pushing a new one. + *sugg += &new_bounds; + } else { + suggestions.push((insert_span, new_bounds)); + } + } + } else { + // Generic arguments not present + let mut new_bounds = String::from("<"); + + for (i, (assoc, emissions)) in emissions.iter().enumerate() { + if i > 0 { + new_bounds += ", "; + } + + new_bounds += assoc.as_str(); + new_bounds += ": "; + + append_emission_bounds(emissions, cx, &mut new_bounds, false); + } + + new_bounds += ">"; + suggestions.push((trait_path_segment.ident.span.shrink_to_hi(), new_bounds)); + } + + diag.multipart_suggestion_verbose( + "remove any extra trait bounds add them directly to this trait bound using associated type bounds", + suggestions, + if removed_generic_param { + // Possibly requires changes at callsites + Applicability::Unspecified + } else { + Applicability::MachineApplicable + }, + ); + }); + } + } + + fn postprocess_generics<'tcx>( + mut self, + cx: &LateContext<'tcx>, + generics: &'tcx hir::Generics<'tcx>, + item: &impl WithSearchPat<'tcx, Context = LateContext<'tcx>>, + ) { + // (Post)processing an item's generics is split into three parts (each one implemented as its own + // method and documented in more detail): + // + // 1) Collect all trait bounds and decrement the use_counts of each type parameter + // 2) Look for bounds that constrain another bound's associated type and add to its emissions if its + // use_count is 0 + // 3) Build suggestions and emit warnings for all of the collected `emissions` in step 2 + if generics.span.from_expansion() || is_from_proc_macro(cx, item) { + return; + } + + let mut trait_bounds = self.collect_trait_bounds_and_decrement_counts(generics); + self.collect_emissions(cx, &mut trait_bounds, generics); + self.lint_emissions(cx, &trait_bounds, generics); + } +} + +pub struct ManualAssocTypeBounds { + msrv: Msrv, + states: Vec, +} + +impl ManualAssocTypeBounds { + pub fn new(conf: &'static Conf) -> Self { + Self { + msrv: conf.msrv.clone(), + states: Vec::new(), + } + } +} + +impl_lint_pass!(ManualAssocTypeBounds => [COULD_BE_ASSOC_TYPE_BOUNDS]); + +#[derive(Debug)] +enum Emission { + ProjectionBound { + predicate_index: usize, + bounds_span: Span, + assoc_type: Ident, + }, + TyParamBound { + predicate_index: usize, + bounds_span: Span, + /// Index into `ItemState::ty_params` + ty_param_index: usize, + }, +} + +impl Emission { + fn predicate_index(&self) -> usize { + match *self { + Emission::ProjectionBound { predicate_index, .. } | Emission::TyParamBound { predicate_index, .. } => { + predicate_index + }, + } + } + + fn predicate<'tcx>(&self, generics: &'tcx hir::Generics<'tcx>) -> &'tcx hir::WhereBoundPredicate<'tcx> { + match &generics.predicates[self.predicate_index()] { + hir::WherePredicate::BoundPredicate(bound) => bound, + _ => unreachable!("this lint only looks for bound predicates"), + } + } + + fn predicate_span_including_comma(&self, generics: &hir::Generics<'_>) -> Span { + let index = self.predicate_index(); + + if let Some([own, next, ..]) = generics.predicates.get(index..) + && next.in_where_clause() + { + own.span().until(next.span()) + } else { + generics.predicates[index] + .span() + .until(generics.where_clause_span.shrink_to_hi()) + } + } + + fn bounds_span(&self) -> Span { + match *self { + Emission::ProjectionBound { bounds_span, .. } | Emission::TyParamBound { bounds_span, .. } => bounds_span, + } + } + + fn assoc_ty(&self, item_state: &ItemState) -> Ident { + match *self { + Emission::ProjectionBound { assoc_type, .. } => assoc_type, + Emission::TyParamBound { ty_param_index, .. } => { + item_state.ty_params[ty_param_index] + .constrained_assoc_type + .expect("`Emission::TyParamBound` is only ever created for type parameters where `constrained_assoc_type.is_some()`") + .1 + }, + } + } +} + +fn group_emissions_by_assoc_type<'e>( + emissions: &'e [Emission], + item: &ItemState, +) -> FxIndexMap> { + emissions.iter().fold(FxIndexMap::default(), |mut emissions, emission| { + emissions.entry(emission.assoc_ty(item)).or_default().push(emission); + emissions + }) +} + +fn append_emission_bounds( + emissions: &[&Emission], + cx: &LateContext<'_>, + out: &mut String, + prepend_plus_at_start: bool, +) { + for (i, emission) in emissions.iter().enumerate() { + if i > 0 || prepend_plus_at_start { + *out += " + "; + } + + *out += emission + .bounds_span() + .get_source_text(cx) + .as_ref() + .map_or("..", SourceText::as_str); + } +} + +#[derive(Debug)] +struct TraitBound<'tcx> { + self_ty: &'tcx hir::Ty<'tcx>, + trait_def_id: DefId, + trait_path_segment: &'tcx PathSegment<'tcx>, + span: Span, + /// We don't immediately emit lints when finding a predicate that can be merged with this one + /// and instead delay it by pushing into this vec so we can build up a suggestion at the end + /// once we know about all mergeable bounds, as otherwise the suggestion could end up with a + /// broken suggestion when trying to insert `` twice. Example: + /// + /// fn foo() + /// where T: Iterator, + /// ::Item: Copy + Sized, + /// ::Item: Clone {} + /// + /// This should have one warning mentioning the last two bounds and suggest + /// `T: Iterator`, instead of two warnings with + /// both of them inserting `` and `` + /// becoming `T: Iterator`. + /// + /// It also overall simplifies the interaction between the two kinds of emissions in + /// combination, e.g. + /// + /// fn foo() + /// where T: Iterator, + /// ::Item: Copy + Sized, + /// U: Clone {} + emissions: Vec, +} + +fn type_trait_bounds<'tcx>( + generics: &'tcx hir::Generics<'tcx>, +) -> impl Iterator, &'tcx [hir::GenericBound<'tcx>], usize)> { + generics + .predicates + .iter() + .enumerate() + .filter_map(|(predicate_index, predicate)| match predicate { + hir::WherePredicate::BoundPredicate(predicate) => { + Some((predicate.bounded_ty, predicate.bounds, predicate_index)) + }, + _ => None, + }) +} + +fn hir_generics_of_item<'tcx>(item: &'tcx hir::Item<'tcx>) -> &'tcx hir::Generics<'tcx> { + match item.kind { + hir::ItemKind::Enum(_, generics) + | hir::ItemKind::Fn(_, generics, _) + | hir::ItemKind::Const(_, generics, _) + | hir::ItemKind::Impl(&hir::Impl { generics, .. }) + | hir::ItemKind::Struct(_, generics) + | hir::ItemKind::Trait(_, _, generics, ..) + | hir::ItemKind::TraitAlias(generics, _) + | hir::ItemKind::TyAlias(_, generics) + | hir::ItemKind::Union(_, generics) => generics, + _ => hir::Generics::empty(), + } +} + +fn hir_ty_param(ty: &hir::Ty<'_>) -> Option { + if let hir::TyKind::Path(QPath::Resolved(None, path)) = ty.kind + && let Res::Def(DefKind::TyParam, ty_param) = path.res + { + Some(ty_param.as_local().expect("type parameters are always crate local")) + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for ManualAssocTypeBounds { + fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + if self.msrv.meets(msrvs::ASSOCIATED_TYPE_BOUNDS) { + self.states + .push(ItemState::new(item.owner_id.def_id, hir_generics_of_item(item))); + } + } + + fn check_impl_item(&mut self, _: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) { + if self.msrv.meets(msrvs::ASSOCIATED_TYPE_BOUNDS) { + self.states.push(ItemState::new(item.owner_id.def_id, item.generics)); + } + } + + fn check_impl_item_post(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) { + if self.msrv.meets(msrvs::ASSOCIATED_TYPE_BOUNDS) { + let state = self.states.pop().unwrap(); + debug_assert_eq!(state.item_def_id, item.owner_id.def_id); + + state.postprocess_generics(cx, item.generics, item); + } + } + + fn check_item_post(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + if self.msrv.meets(msrvs::ASSOCIATED_TYPE_BOUNDS) { + let state = self.states.pop().unwrap(); + debug_assert_eq!(state.item_def_id, item.owner_id.def_id); + + state.postprocess_generics(cx, hir_generics_of_item(item), item); + } + } + + fn check_path(&mut self, cx: &LateContext<'tcx>, path: &hir::Path<'tcx>, _: HirId) { + if let Res::Def(DefKind::TyParam, ty_param_def_id) = path.res + && let ty_param_def_id = ty_param_def_id.expect_local() + && let ty_param_bound_at = cx.tcx.parent(ty_param_def_id.to_def_id()).expect_local() + && let Some(state) = self + .states + .iter_mut() + .rev() + .find(|s| s.item_def_id == ty_param_bound_at) + && let Some(ty_param_count) = state.ty_params.iter_mut().find(|p| p.def_id == ty_param_def_id) + { + ty_param_count.use_count += 1; + } + } + + extract_msrv_attr!(LateContext); +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c4a0c8f18651..4794c431c216 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -111,6 +111,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::copies::IF_SAME_THEN_ELSE_INFO, crate::copies::SAME_FUNCTIONS_IN_IF_CONDITION_INFO, crate::copy_iterator::COPY_ITERATOR_INFO, + crate::could_be_assoc_type_bounds::COULD_BE_ASSOC_TYPE_BOUNDS_INFO, crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO, crate::create_dir::CREATE_DIR_INFO, crate::dbg_macro::DBG_MACRO_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c9064df25ac8..a1bda217f8c2 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -100,6 +100,7 @@ mod collection_is_never_read; mod comparison_chain; mod copies; mod copy_iterator; +mod could_be_assoc_type_bounds; mod crate_in_macro_def; mod create_dir; mod dbg_macro; @@ -963,5 +964,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); + store.register_late_pass(move |_| Box::new(could_be_assoc_type_bounds::ManualAssocTypeBounds::new(conf))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 5c93a9948b82..eaa77a5c1899 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -455,7 +455,7 @@ impl HirEqInterExpr<'_, '_, '_> { left.ident.name == right.ident.name && self.eq_expr(left.expr, right.expr) } - fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool { + pub fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool { match (left, right) { (GenericArg::Const(l), GenericArg::Const(r)) => self.eq_const_arg(l, r), (GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt), diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 6ad796fdd61f..eab7f8b51ba2 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -79,7 +79,7 @@ pub mod usage; pub mod visitors; pub use self::attrs::*; -pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match}; +pub use self::check_proc_macro::{WithSearchPat, is_from_proc_macro, is_span_if, is_span_match}; pub use self::hir_utils::{ HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over, }; diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 1eb7d54e133d..c36fc617780a 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -21,7 +21,8 @@ msrv_aliases! { 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY } 1,82,0 { IS_NONE_OR, REPEAT_N } 1,81,0 { LINT_REASONS_STABILIZATION } - 1,80,0 { BOX_INTO_ITER} + 1,80,0 { BOX_INTO_ITER } + 1,79,0 { ASSOCIATED_TYPE_BOUNDS } 1,77,0 { C_STR_LITERALS } 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } 1,73,0 { MANUAL_DIV_CEIL } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 770cd9c37865..dcb8078a3b73 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -251,7 +251,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>( ty: Ty<'tcx>, trait_id: DefId, callee_id: Option, - args: impl IntoIterator>>>, + args: impl IntoIterator>>>, ) -> bool { // Clippy shouldn't have infer types assert!(!ty.has_infer()); @@ -1073,7 +1073,7 @@ pub fn make_projection<'tcx>( tcx: TyCtxt<'tcx>, container_id: DefId, assoc_ty: Symbol, - args: impl IntoIterator>>, + args: impl IntoIterator>>, ) -> Option> { fn helper<'tcx>( tcx: TyCtxt<'tcx>, @@ -1114,7 +1114,7 @@ pub fn make_normalized_projection<'tcx>( param_env: ParamEnv<'tcx>, container_id: DefId, assoc_ty: Symbol, - args: impl IntoIterator>>, + args: impl IntoIterator>>, ) -> Option> { fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option> { #[cfg(debug_assertions)] @@ -1241,7 +1241,7 @@ pub fn make_normalized_projection_with_regions<'tcx>( param_env: ParamEnv<'tcx>, container_id: DefId, assoc_ty: Symbol, - args: impl IntoIterator>>, + args: impl IntoIterator>>, ) -> Option> { fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option> { #[cfg(debug_assertions)] diff --git a/tests/ui/could_be_assoc_type_bounds.fixed b/tests/ui/could_be_assoc_type_bounds.fixed new file mode 100644 index 000000000000..f95e4fa1c028 --- /dev/null +++ b/tests/ui/could_be_assoc_type_bounds.fixed @@ -0,0 +1,95 @@ +//@aux-build:proc_macros.rs +#![allow(clippy::extra_unused_type_parameters)] + +extern crate proc_macros; + +fn projection_with_existing_assoc_bounds() +where + T: Iterator, + + //~^ could_be_assoc_type_bounds +{ +} + +fn projection_with_existing_bounds>() + //~^ could_be_assoc_type_bounds +{ +} + +fn no_fully_qualified_path>() +where + // False negative for now: `T::Item` has a `Res::Err` resolution + T::Item: Copy + Sized, +{ +} + +fn ty_param, >() {} + +fn multiple_projections() +where + T: Iterator, + +{ +} + +fn ty_param_used_in_body, P: Clone + Default>() { + P::default(); +} + +fn nested_impl_trait(_: impl Iterator) {} + +fn impl_trait_generic(_: impl Iterator) {} + +fn single_impl_trait(_: impl Iterator) {} + +fn parenthesized, >() {} //~ could_be_assoc_type_bounds + +// Make sure implicit generic lifetime parameters for delim doesn't mess up spans +pub fn elided_lifetime(iter: I, delim: &str) +where + I: IntoIterator, + //~ could_be_assoc_type_bounds +{ +} + +fn parenthesized2() +where + F::Output: Copy, +{ +} +fn many_ty_params() +//~^ could_be_assoc_type_bounds +where + T: Iterator, +{ +} + +#[clippy::msrv = "1.78.0"] +fn low_msrv, P: Copy + Default>() { + #[clippy::msrv = "1.79.0"] + P::default(); +} + +// More involved test case with multiple associated types and generic parameters +trait Trait1: Default { + type A2; + type A3; + type A4; +} + +fn complex() +where + (T, T): Trait1, + +{ +} + +proc_macros::external! { + fn external, I: Copy>() {} +} +proc_macros::with_span! { + span + fn external2, I: Copy>() {} +} + +fn main() {} diff --git a/tests/ui/could_be_assoc_type_bounds.rs b/tests/ui/could_be_assoc_type_bounds.rs new file mode 100644 index 000000000000..fdef05739219 --- /dev/null +++ b/tests/ui/could_be_assoc_type_bounds.rs @@ -0,0 +1,101 @@ +//@aux-build:proc_macros.rs +#![allow(clippy::extra_unused_type_parameters)] + +extern crate proc_macros; + +fn projection_with_existing_assoc_bounds() +where + T: Iterator, + ::Item: Copy + Sized, + //~^ could_be_assoc_type_bounds +{ +} + +fn projection_with_existing_bounds>() +where + ::Item: Copy + Sized, + //~^ could_be_assoc_type_bounds +{ +} + +fn no_fully_qualified_path>() +where + // False negative for now: `T::Item` has a `Res::Err` resolution + T::Item: Copy + Sized, +{ +} + +fn ty_param, P: Clone>() {} + +fn multiple_projections() +where + T: Iterator, + ::Item: Sized, + //~^ could_be_assoc_type_bounds + ::Item: Clone, +{ +} + +fn ty_param_used_in_body, P: Clone + Default>() { + P::default(); +} + +fn nested_impl_trait(_: impl Iterator) {} + +fn impl_trait_generic(_: impl Iterator) {} + +fn single_impl_trait(_: impl Iterator) {} + +fn parenthesized, F: Fn()>() {} //~ could_be_assoc_type_bounds + +// Make sure implicit generic lifetime parameters for delim doesn't mess up spans +pub fn elided_lifetime(iter: I, delim: &str) +where + I: IntoIterator, + T: std::fmt::Display, //~ could_be_assoc_type_bounds +{ +} + +fn parenthesized2() +where + F::Output: Copy, +{ +} +fn many_ty_params() +//~^ could_be_assoc_type_bounds +where + T: Iterator, +{ +} + +#[clippy::msrv = "1.78.0"] +fn low_msrv, P: Copy + Default>() { + #[clippy::msrv = "1.79.0"] + P::default(); +} + +// More involved test case with multiple associated types and generic parameters +trait Trait1: Default { + type A2; + type A3; + type A4; +} + +fn complex() +where + (T, T): Trait1, + <(T, T) as Trait1>::A4: Clone, + //~^ could_be_assoc_type_bounds + U: Clone, +{ +} + +proc_macros::external! { + fn external, I: Copy>() {} +} +proc_macros::with_span! { + span + fn external2, I: Copy>() {} +} + +fn main() {} diff --git a/tests/ui/could_be_assoc_type_bounds.stderr b/tests/ui/could_be_assoc_type_bounds.stderr new file mode 100644 index 000000000000..70379627878d --- /dev/null +++ b/tests/ui/could_be_assoc_type_bounds.stderr @@ -0,0 +1,142 @@ +error: this trait bound only exists to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:9:5 + | +LL | T: Iterator, + | ------------------------ merge it with this bound +LL | ::Item: Copy + Sized, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::could-be-assoc-type-bounds` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::could_be_assoc_type_bounds)]` +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL ~ T: Iterator, +LL ~ + | + +error: this trait bound only exists to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:16:5 + | +LL | fn projection_with_existing_bounds>() + | ----------------------- merge it with this bound +LL | where +LL | ::Item: Copy + Sized, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL - fn projection_with_existing_bounds>() +LL + fn projection_with_existing_bounds>() + | + +error: this trait bound only exists to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:28:37 + | +LL | fn ty_param, P: Clone>() {} + | -------------------- ^^^^^^^ + | | + | merge it with this bound + | +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL - fn ty_param, P: Clone>() {} +LL + fn ty_param, >() {} + | + +error: these trait bounds only exist to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:33:5 + | +LL | T: Iterator, + | ----------- merge them with this bound +LL | ::Item: Sized, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | ::Item: Clone, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL ~ T: Iterator, +LL ~ + | + +error: this trait bound only exists to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:45:24 + | +LL | fn impl_trait_generic(_: impl Iterator) {} + | ^^^^^^ ------------------ merge it with this bound + | +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL - fn impl_trait_generic(_: impl Iterator) {} +LL + fn impl_trait_generic(_: impl Iterator) {} + | + +error: this trait bound only exists to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:49:42 + | +LL | fn parenthesized, F: Fn()>() {} + | -------------------- ^^^^^^ + | | + | merge it with this bound + | +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL - fn parenthesized, F: Fn()>() {} +LL + fn parenthesized, >() {} + | + +error: this trait bound only exists to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:55:5 + | +LL | I: IntoIterator, + | ------------------------- merge it with this bound +LL | T: std::fmt::Display, + | ^^^^^^^^^^^^^^^^^^^^ + | +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL ~ pub fn elided_lifetime(iter: I, delim: &str) +LL | where +LL ~ I: IntoIterator, +LL ~ + | + +error: this trait bound only exists to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:64:23 + | +LL | fn many_ty_params() + | ^^^^^^ +... +LL | T: Iterator, + | --------------------- merge it with this bound + | +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL ~ fn many_ty_params() +LL | +LL | where +LL ~ T: Iterator, + | + +error: these trait bounds only exist to constrain another bound's associated type + --> tests/ui/could_be_assoc_type_bounds.rs:87:5 + | +LL | (T, T): Trait1, + | ---------------------------------------- merge them with this bound +LL | <(T, T) as Trait1>::A4: Clone, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | U: Clone, + | ^^^^^^^^ + | +help: remove any extra trait bounds add them directly to this trait bound using associated type bounds + | +LL ~ fn complex() +LL | where +LL ~ (T, T): Trait1, +LL ~ + | + +error: aborting due to 9 previous errors + diff --git a/tests/ui/crashes/ice-11803.rs b/tests/ui/crashes/ice-11803.rs index 1bb8bf0c746b..761aa523814c 100644 --- a/tests/ui/crashes/ice-11803.rs +++ b/tests/ui/crashes/ice-11803.rs @@ -1,6 +1,7 @@ //@no-rustfix #![warn(clippy::impl_trait_in_params)] +#![allow(clippy::could_be_assoc_type_bounds)] pub fn g>>() { extern "C" fn implementation_detail() {} diff --git a/tests/ui/crashes/ice-11803.stderr b/tests/ui/crashes/ice-11803.stderr index f62de8e2b9db..4e2b058e63f9 100644 --- a/tests/ui/crashes/ice-11803.stderr +++ b/tests/ui/crashes/ice-11803.stderr @@ -1,5 +1,5 @@ error: `impl Trait` used as a function parameter - --> tests/ui/crashes/ice-11803.rs:5:54 + --> tests/ui/crashes/ice-11803.rs:6:54 | LL | pub fn g>>() { | ^^^^^^^^^^ @@ -12,7 +12,7 @@ LL | pub fn g>, { /* Gen | +++++++++++++++++++++++++++++++ error: `impl Trait` used as a function parameter - --> tests/ui/crashes/ice-11803.rs:5:33 + --> tests/ui/crashes/ice-11803.rs:6:33 | LL | pub fn g>>() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/len_without_is_empty.rs b/tests/ui/len_without_is_empty.rs index d623601110e2..74a8a6265481 100644 --- a/tests/ui/len_without_is_empty.rs +++ b/tests/ui/len_without_is_empty.rs @@ -1,5 +1,5 @@ #![warn(clippy::len_without_is_empty)] -#![allow(dead_code, unused)] +#![allow(dead_code, unused, clippy::could_be_assoc_type_bounds)] pub struct PubOne; diff --git a/tests/ui/needless_borrows_for_generic_args.fixed b/tests/ui/needless_borrows_for_generic_args.fixed index c1dc8b5e8d09..ea0b87cb76c7 100644 --- a/tests/ui/needless_borrows_for_generic_args.fixed +++ b/tests/ui/needless_borrows_for_generic_args.fixed @@ -2,7 +2,8 @@ #![allow( clippy::unnecessary_to_owned, clippy::unnecessary_literal_unwrap, - clippy::needless_borrow + clippy::needless_borrow, + clippy::could_be_assoc_type_bounds )] use core::ops::Deref; diff --git a/tests/ui/needless_borrows_for_generic_args.rs b/tests/ui/needless_borrows_for_generic_args.rs index c7f66824d581..f4fda1664f62 100644 --- a/tests/ui/needless_borrows_for_generic_args.rs +++ b/tests/ui/needless_borrows_for_generic_args.rs @@ -2,7 +2,8 @@ #![allow( clippy::unnecessary_to_owned, clippy::unnecessary_literal_unwrap, - clippy::needless_borrow + clippy::needless_borrow, + clippy::could_be_assoc_type_bounds )] use core::ops::Deref; diff --git a/tests/ui/needless_borrows_for_generic_args.stderr b/tests/ui/needless_borrows_for_generic_args.stderr index fba0755d14b5..77cfa28c3bb6 100644 --- a/tests/ui/needless_borrows_for_generic_args.stderr +++ b/tests/ui/needless_borrows_for_generic_args.stderr @@ -1,5 +1,5 @@ error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:16:37 + --> tests/ui/needless_borrows_for_generic_args.rs:17:37 | LL | let _ = Command::new("ls").args(&["-a", "-l"]).status().unwrap(); | ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]` @@ -8,61 +8,61 @@ LL | let _ = Command::new("ls").args(&["-a", "-l"]).status().unwrap(); = help: to override `-D warnings` add `#[allow(clippy::needless_borrows_for_generic_args)]` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:17:33 + --> tests/ui/needless_borrows_for_generic_args.rs:18:33 | LL | let _ = Path::new(".").join(&&"."); | ^^^^^ help: change this to: `"."` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:21:33 + --> tests/ui/needless_borrows_for_generic_args.rs:22:33 | LL | let _ = std::fs::write("x", &"".to_string()); | ^^^^^^^^^^^^^^^ help: change this to: `"".to_string()` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:36:27 + --> tests/ui/needless_borrows_for_generic_args.rs:37:27 | LL | deref_target_is_x(&X); | ^^ help: change this to: `X` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:49:30 + --> tests/ui/needless_borrows_for_generic_args.rs:50:30 | LL | multiple_constraints(&[[""]]); | ^^^^^^^ help: change this to: `[[""]]` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:69:49 + --> tests/ui/needless_borrows_for_generic_args.rs:70:49 | LL | multiple_constraints_normalizes_to_same(&X, X); | ^^ help: change this to: `X` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:127:24 + --> tests/ui/needless_borrows_for_generic_args.rs:128:24 | LL | takes_iter(&mut x) | ^^^^^^ help: change this to: `x` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:136:41 + --> tests/ui/needless_borrows_for_generic_args.rs:137:41 | LL | let _ = Command::new("ls").args(&["-a", "-l"]).status().unwrap(); | ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:247:13 + --> tests/ui/needless_borrows_for_generic_args.rs:248:13 | LL | foo(&a); | ^^ help: change this to: `a` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:331:11 + --> tests/ui/needless_borrows_for_generic_args.rs:332:11 | LL | f(&String::new()); // Lint, makes no difference | ^^^^^^^^^^^^^^ help: change this to: `String::new()` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:334:11 + --> tests/ui/needless_borrows_for_generic_args.rs:335:11 | LL | f(&"".to_owned()); // Lint | ^^^^^^^^^^^^^^ help: change this to: `"".to_owned()` diff --git a/tests/ui/redundant_async_block.fixed b/tests/ui/redundant_async_block.fixed index a1875c1c06e1..ef2408719da6 100644 --- a/tests/ui/redundant_async_block.fixed +++ b/tests/ui/redundant_async_block.fixed @@ -1,4 +1,4 @@ -#![allow(unused, clippy::manual_async_fn)] +#![allow(unused, clippy::manual_async_fn, clippy::could_be_assoc_type_bounds)] #![warn(clippy::redundant_async_block)] use std::future::{Future, IntoFuture}; diff --git a/tests/ui/redundant_async_block.rs b/tests/ui/redundant_async_block.rs index bb43403a043e..a2942cd4926b 100644 --- a/tests/ui/redundant_async_block.rs +++ b/tests/ui/redundant_async_block.rs @@ -1,4 +1,4 @@ -#![allow(unused, clippy::manual_async_fn)] +#![allow(unused, clippy::manual_async_fn, clippy::could_be_assoc_type_bounds)] #![warn(clippy::redundant_async_block)] use std::future::{Future, IntoFuture}; diff --git a/tests/ui/trait_duplication_in_bounds_unfixable.rs b/tests/ui/trait_duplication_in_bounds_unfixable.rs index b0095bb77b5a..3157d7476c29 100644 --- a/tests/ui/trait_duplication_in_bounds_unfixable.rs +++ b/tests/ui/trait_duplication_in_bounds_unfixable.rs @@ -1,5 +1,5 @@ #![deny(clippy::trait_duplication_in_bounds)] -#![allow(clippy::multiple_bound_locations)] +#![allow(clippy::multiple_bound_locations, clippy::could_be_assoc_type_bounds)] use std::collections::BTreeMap; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};