Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions book/src/lint_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`cloned_instead_of_copied`](https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied)
* [`collapsible_match`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match)
* [`collapsible_str_replace`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace)
* [`could_be_assoc_type_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#could_be_assoc_type_bounds)
* [`deprecated_cfg_attr`](https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr)
* [`derivable_impls`](https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls)
* [`err_expect`](https://rust-lang.github.io/rust-clippy/master/index.html#err_expect)
Expand Down
1 change: 1 addition & 0 deletions clippy_config/src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@ define_Conf! {
cloned_instead_of_copied,
collapsible_match,
collapsible_str_replace,
could_be_assoc_type_bounds,
deprecated_cfg_attr,
derivable_impls,
err_expect,
Expand Down
674 changes: 674 additions & 0 deletions clippy_lints/src/could_be_assoc_type_bounds.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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`
}
2 changes: 1 addition & 1 deletion clippy_utils/src/hir_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion clippy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
3 changes: 2 additions & 1 deletion clippy_utils/src/msrvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
8 changes: 4 additions & 4 deletions clippy_utils/src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>(
ty: Ty<'tcx>,
trait_id: DefId,
callee_id: Option<DefId>,
args: impl IntoIterator<Item = impl Into<Option<GenericArg<'tcx>>>>,
args: impl IntoIterator<Item: Into<Option<GenericArg<'tcx>>>>,
) -> bool {
// Clippy shouldn't have infer types
assert!(!ty.has_infer());
Expand Down Expand Up @@ -1073,7 +1073,7 @@ pub fn make_projection<'tcx>(
tcx: TyCtxt<'tcx>,
container_id: DefId,
assoc_ty: Symbol,
args: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
args: impl IntoIterator<Item: Into<GenericArg<'tcx>>>,
) -> Option<AliasTy<'tcx>> {
fn helper<'tcx>(
tcx: TyCtxt<'tcx>,
Expand Down Expand Up @@ -1114,7 +1114,7 @@ pub fn make_normalized_projection<'tcx>(
param_env: ParamEnv<'tcx>,
container_id: DefId,
assoc_ty: Symbol,
args: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
args: impl IntoIterator<Item: Into<GenericArg<'tcx>>>,
) -> Option<Ty<'tcx>> {
fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option<Ty<'tcx>> {
#[cfg(debug_assertions)]
Expand Down Expand Up @@ -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<Item = impl Into<GenericArg<'tcx>>>,
args: impl IntoIterator<Item: Into<GenericArg<'tcx>>>,
) -> Option<Ty<'tcx>> {
fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option<Ty<'tcx>> {
#[cfg(debug_assertions)]
Expand Down
95 changes: 95 additions & 0 deletions tests/ui/could_be_assoc_type_bounds.fixed
Original file line number Diff line number Diff line change
@@ -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<T>()
where
T: Iterator<Item: Clone + Copy + Sized>,

//~^ could_be_assoc_type_bounds
{
}

fn projection_with_existing_bounds<T: Iterator<Item: Clone + Copy + Sized>>()
//~^ could_be_assoc_type_bounds
{
}

fn no_fully_qualified_path<T: Iterator<Item: Clone>>()
where
// False negative for now: `T::Item` has a `Res::Err` resolution
T::Item: Copy + Sized,
{
}

fn ty_param<T: Iterator<Item: Clone>, >() {}
Copy link
Member Author

@y21 y21 Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One case where the lint relies on rustfmt to cleanup. Naively removing both the leading or trailing comma would create overlapping spans when trying to remove two bounds next to each other (removing the middle comma in T: Copy, U: Copy twice), so that's why it only tries to remove trailing commas


fn multiple_projections<T>()
where
T: Iterator<Item: Sized + Clone>,

{
}

fn ty_param_used_in_body<T: Iterator<Item = P>, P: Clone + Default>() {
P::default();
}

fn nested_impl_trait(_: impl Iterator<Item = impl Sized>) {}

fn impl_trait_generic(_: impl Iterator<Item: Copy>) {}

fn single_impl_trait(_: impl Iterator<Item = ()>) {}

fn parenthesized<T: Iterator<Item: Fn()>, >() {} //~ could_be_assoc_type_bounds

// Make sure implicit generic lifetime parameters for delim doesn't mess up spans
pub fn elided_lifetime<I, >(iter: I, delim: &str)
where
I: IntoIterator<Item: std::fmt::Display>,
//~ could_be_assoc_type_bounds
{
}

fn parenthesized2<F: Fn()>()
where
F::Output: Copy,
{
}
fn many_ty_params<T, X>()
//~^ could_be_assoc_type_bounds
where
T: Iterator<Item: Copy>,
{
}

#[clippy::msrv = "1.78.0"]
fn low_msrv<T: Iterator<Item = P>, P: Copy + Default>() {
#[clippy::msrv = "1.79.0"]
P::default();
}

// More involved test case with multiple associated types and generic parameters
trait Trait1<G1, G2>: Default {
type A2;
type A3;
type A4;
}

fn complex<T, G1, G2>()
where
(T, T): Trait1<G1, G2, A2 = u32, A3: Clone, A4: Clone>,

{
}

proc_macros::external! {
fn external<T: Iterator<Item = I>, I: Copy>() {}
}
proc_macros::with_span! {
span
fn external2<T: Iterator<Item = I>, I: Copy>() {}
}

fn main() {}
101 changes: 101 additions & 0 deletions tests/ui/could_be_assoc_type_bounds.rs
Original file line number Diff line number Diff line change
@@ -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<T>()
where
T: Iterator<Item: Clone>,
<T as Iterator>::Item: Copy + Sized,
//~^ could_be_assoc_type_bounds
{
}

fn projection_with_existing_bounds<T: Iterator<Item: Clone>>()
where
<T as Iterator>::Item: Copy + Sized,
//~^ could_be_assoc_type_bounds
{
}

fn no_fully_qualified_path<T: Iterator<Item: Clone>>()
where
// False negative for now: `T::Item` has a `Res::Err` resolution
T::Item: Copy + Sized,
{
}

fn ty_param<T: Iterator<Item = P>, P: Clone>() {}

fn multiple_projections<T>()
where
T: Iterator,
<T as Iterator>::Item: Sized,
//~^ could_be_assoc_type_bounds
<T as Iterator>::Item: Clone,
{
}

fn ty_param_used_in_body<T: Iterator<Item = P>, P: Clone + Default>() {
P::default();
}

fn nested_impl_trait(_: impl Iterator<Item = impl Sized>) {}

fn impl_trait_generic<T: Copy>(_: impl Iterator<Item = T>) {}

fn single_impl_trait(_: impl Iterator<Item = ()>) {}

fn parenthesized<T: Iterator<Item = F>, F: Fn()>() {} //~ could_be_assoc_type_bounds

// Make sure implicit generic lifetime parameters for delim doesn't mess up spans
pub fn elided_lifetime<I, T>(iter: I, delim: &str)
where
I: IntoIterator<Item = T>,
T: std::fmt::Display, //~ could_be_assoc_type_bounds
{
}

fn parenthesized2<F: Fn()>()
where
F::Output: Copy,
{
}
fn many_ty_params<T, U: Copy, X>()
//~^ could_be_assoc_type_bounds
where
T: Iterator<Item = U>,
{
}

#[clippy::msrv = "1.78.0"]
fn low_msrv<T: Iterator<Item = P>, P: Copy + Default>() {
#[clippy::msrv = "1.79.0"]
P::default();
}

// More involved test case with multiple associated types and generic parameters
trait Trait1<G1, G2>: Default {
type A2;
type A3;
type A4;
}

fn complex<T, U, G1, G2>()
where
(T, T): Trait1<G1, G2, A2 = u32, A3 = U>,
<(T, T) as Trait1<G1, G2>>::A4: Clone,
//~^ could_be_assoc_type_bounds
U: Clone,
{
}

proc_macros::external! {
fn external<T: Iterator<Item = I>, I: Copy>() {}
}
proc_macros::with_span! {
span
fn external2<T: Iterator<Item = I>, I: Copy>() {}
}

fn main() {}
Loading