Add inline_trait_bounds lint

This commit is contained in:
moses7054
2026-05-11 22:08:21 +05:30
parent f763854b8b
commit a453e8cfcf
7 changed files with 491 additions and 0 deletions
+1
View File
@@ -6805,6 +6805,7 @@ Released 2018-09-13
[`inline_asm_x86_intel_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_intel_syntax
[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
[`inline_modules`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_modules
[`inline_trait_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_trait_bounds
[`inspect_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#inspect_for_each
[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
+1
View File
@@ -232,6 +232,7 @@
crate::inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY_INFO,
crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO,
crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO,
crate::inline_trait_bounds::INLINE_TRAIT_BOUNDS_INFO,
crate::int_plus_one::INT_PLUS_ONE_INFO,
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
+137
View File
@@ -0,0 +1,137 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{HasSession, snippet};
use rustc_ast::NodeId;
use rustc_ast::ast::{Fn, FnRetTy, GenericParam, GenericParamKind};
use rustc_ast::visit::{FnCtxt, FnKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Enforce that `where` bounds are used for all trait bounds.
///
/// ### Why restrict this?
/// Enforce a single style throughout a codebase.
/// Avoid uncertainty about whether a bound should be inline
/// or out-of-line (i.e. a where bound).
/// Avoid complex inline bounds, which could make a function declaration more difficult to read.
///
/// ### Known limitations
/// Only lints functions and method declararions. Bounds on structs, enums,
/// and impl blocks are not yet covered.
///
/// ### Example
/// ```no_run
/// fn foo<T: Clone>() {}
/// ```
///
/// Use instead:
/// ```no_run
/// fn foo<T>() where T: Clone {}
/// ```
#[clippy::version = "1.97.0"]
pub INLINE_TRAIT_BOUNDS,
restriction,
"enforce that `where` bounds are used for all trait bounds"
}
declare_lint_pass!(InlineTraitBounds => [INLINE_TRAIT_BOUNDS]);
impl EarlyLintPass for InlineTraitBounds {
fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) {
let FnKind::Fn(ctxt, _vis, f) = kind else {
return;
};
// Skip foreign functions (extern "C" etc.)
if !matches!(ctxt, FnCtxt::Free | FnCtxt::Assoc(..)) {
return;
}
if f.sig.span.in_external_macro(cx.sess().source_map()) {
return;
}
lint_fn(cx, f);
}
}
fn lint_fn(cx: &EarlyContext<'_>, f: &Fn) {
let generics = &f.generics;
let offenders: Vec<&GenericParam> = generics
.params
.iter()
.filter(|param| {
!param.bounds.is_empty() && matches!(param.kind, GenericParamKind::Lifetime | GenericParamKind::Type { .. })
})
.collect();
if offenders.is_empty() {
return;
}
let predicates = offenders
.iter()
.map(|param| build_predicate_text(cx, param))
.collect::<Vec<_>>();
let mut edits = Vec::new();
for param in offenders {
if let Some(colon) = param.colon_span {
let remove_span = colon.to(param.bounds.last().unwrap().span());
edits.push((remove_span, String::new()));
}
}
let predicate_text = predicates.join(", ");
let where_clause = &generics.where_clause;
if where_clause.has_where_token {
let (insert_at, suffix) = if let Some(last_pred) = where_clause.predicates.last() {
// existing `where` with predicates: append after last predicate
(last_pred.span.shrink_to_hi(), format!(", {predicate_text}"))
} else {
// `where` token present but empty predicate list
(where_clause.span.shrink_to_hi(), format!(" {predicate_text}"))
};
edits.push((insert_at, suffix));
} else {
let insert_at = match &f.sig.decl.output {
FnRetTy::Default(span) => span.shrink_to_lo(),
FnRetTy::Ty(ty) => ty.span.shrink_to_hi(),
};
edits.push((insert_at, format!(" where {predicate_text}")));
}
span_lint_and_then(
cx,
INLINE_TRAIT_BOUNDS,
generics.span,
"inline trait bounds used",
|diag| {
diag.multipart_suggestion(
"move bounds to a `where` clause",
edits,
Applicability::MachineApplicable,
);
},
);
}
fn build_predicate_text(cx: &EarlyContext<'_>, param: &GenericParam) -> String {
// bounds is guaranteed non-empty by the filter in `lint_fn`
let first = param.bounds.first().unwrap();
let last = param.bounds.last().unwrap();
let bounds_span = first.span().to(last.span());
let lhs = snippet(cx, param.ident.span, "..");
let rhs = snippet(cx, bounds_span, "..");
format!("{lhs}: {rhs}")
}
+2
View File
@@ -168,6 +168,7 @@
mod inherent_to_string;
mod init_numbered_fields;
mod inline_fn_without_body;
mod inline_trait_bounds;
mod int_plus_one;
mod item_name_repetitions;
mod items_after_statements;
@@ -518,6 +519,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)),
Box::new(|| Box::new(cfg_not_test::CfgNotTest)),
Box::new(|| Box::new(empty_line_after::EmptyLineAfter::new())),
Box::new(|| Box::new(inline_trait_bounds::InlineTraitBounds)),
// add early passes here, used by `cargo dev new_lint`
];
store.early_passes.extend(early_lints);
+94
View File
@@ -0,0 +1,94 @@
#![warn(clippy::inline_trait_bounds)]
// Free functions
fn inline_simple<T>() where T: Clone {}
//~^ inline_trait_bounds
fn inline_multiple<T, U>() where T: Clone + Copy, U: core::fmt::Debug {}
//~^ inline_trait_bounds
fn inline_lifetime<'a, 'b>(x: &'a str, y: &'b str) -> &'b str where 'a: 'b {
//~^ inline_trait_bounds
y
}
#[allow(clippy::multiple_bound_locations)]
fn inline_with_where<T>()
//~^ inline_trait_bounds
where
T: core::fmt::Debug, T: Clone,
{
}
fn inline_with_const<T, const N: usize>() where T: Clone {}
//~^ inline_trait_bounds
fn inline_with_return<T>(val: T) -> T where T: Clone {
//~^ inline_trait_bounds
val
}
// Trait methods
trait MyTrait {
fn trait_method_inline<T>(&self) where T: Clone;
//~^ inline_trait_bounds
fn trait_method_default<T>(&self) where T: Clone + Copy {}
//~^ inline_trait_bounds
fn trait_method_where<T>(&self)
where
T: Clone;
}
// Impl methods
struct MyStruct;
impl MyStruct {
fn impl_method_inline<T>(&self) where T: Clone {}
//~^ inline_trait_bounds
fn impl_method_multiple<T, U>(&self) where T: Clone, U: core::fmt::Debug {}
//~^ inline_trait_bounds
}
impl MyTrait for MyStruct {
fn trait_method_inline<T>(&self) where T: Clone {}
//~^ inline_trait_bounds
fn trait_method_default<T>(&self) where T: Clone + Copy {}
//~^ inline_trait_bounds
fn trait_method_where<T>(&self)
where
T: Clone,
{
}
}
// Should NOT lint
fn where_only<T>()
where
T: Clone,
{
}
fn no_bounds<T, U>() {}
fn no_generics() {}
struct InlineStruct<T: Clone>(T);
enum InlineEnum<T: Clone> {
A(T),
}
#[allow(invalid_type_param_default)]
//~v inline_trait_bounds
fn with_default_value<T = u32>(x: T) -> T where T: Clone {
x
}
+94
View File
@@ -0,0 +1,94 @@
#![warn(clippy::inline_trait_bounds)]
// Free functions
fn inline_simple<T: Clone>() {}
//~^ inline_trait_bounds
fn inline_multiple<T: Clone + Copy, U: core::fmt::Debug>() {}
//~^ inline_trait_bounds
fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str {
//~^ inline_trait_bounds
y
}
#[allow(clippy::multiple_bound_locations)]
fn inline_with_where<T: Clone>()
//~^ inline_trait_bounds
where
T: core::fmt::Debug,
{
}
fn inline_with_const<T: Clone, const N: usize>() {}
//~^ inline_trait_bounds
fn inline_with_return<T: Clone>(val: T) -> T {
//~^ inline_trait_bounds
val
}
// Trait methods
trait MyTrait {
fn trait_method_inline<T: Clone>(&self);
//~^ inline_trait_bounds
fn trait_method_default<T: Clone + Copy>(&self) {}
//~^ inline_trait_bounds
fn trait_method_where<T>(&self)
where
T: Clone;
}
// Impl methods
struct MyStruct;
impl MyStruct {
fn impl_method_inline<T: Clone>(&self) {}
//~^ inline_trait_bounds
fn impl_method_multiple<T: Clone, U: core::fmt::Debug>(&self) {}
//~^ inline_trait_bounds
}
impl MyTrait for MyStruct {
fn trait_method_inline<T: Clone>(&self) {}
//~^ inline_trait_bounds
fn trait_method_default<T: Clone + Copy>(&self) {}
//~^ inline_trait_bounds
fn trait_method_where<T>(&self)
where
T: Clone,
{
}
}
// Should NOT lint
fn where_only<T>()
where
T: Clone,
{
}
fn no_bounds<T, U>() {}
fn no_generics() {}
struct InlineStruct<T: Clone>(T);
enum InlineEnum<T: Clone> {
A(T),
}
#[allow(invalid_type_param_default)]
//~v inline_trait_bounds
fn with_default_value<T: Clone = u32>(x: T) -> T {
x
}
+162
View File
@@ -0,0 +1,162 @@
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:5:17
|
LL | fn inline_simple<T: Clone>() {}
| ^^^^^^^^^^
|
= note: `-D clippy::inline-trait-bounds` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::inline_trait_bounds)]`
help: move bounds to a `where` clause
|
LL - fn inline_simple<T: Clone>() {}
LL + fn inline_simple<T>() where T: Clone {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:8:19
|
LL | fn inline_multiple<T: Clone + Copy, U: core::fmt::Debug>() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn inline_multiple<T: Clone + Copy, U: core::fmt::Debug>() {}
LL + fn inline_multiple<T, U>() where T: Clone + Copy, U: core::fmt::Debug {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:11:19
|
LL | fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str {
| ^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn inline_lifetime<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str {
LL + fn inline_lifetime<'a, 'b>(x: &'a str, y: &'b str) -> &'b str where 'a: 'b {
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:17:21
|
LL | fn inline_with_where<T: Clone>()
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL ~ fn inline_with_where<T>()
LL |
LL | where
LL ~ T: core::fmt::Debug, T: Clone,
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:24:21
|
LL | fn inline_with_const<T: Clone, const N: usize>() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn inline_with_const<T: Clone, const N: usize>() {}
LL + fn inline_with_const<T, const N: usize>() where T: Clone {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:27:22
|
LL | fn inline_with_return<T: Clone>(val: T) -> T {
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn inline_with_return<T: Clone>(val: T) -> T {
LL + fn inline_with_return<T>(val: T) -> T where T: Clone {
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:35:27
|
LL | fn trait_method_inline<T: Clone>(&self);
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn trait_method_inline<T: Clone>(&self);
LL + fn trait_method_inline<T>(&self) where T: Clone;
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:38:28
|
LL | fn trait_method_default<T: Clone + Copy>(&self) {}
| ^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn trait_method_default<T: Clone + Copy>(&self) {}
LL + fn trait_method_default<T>(&self) where T: Clone + Copy {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:51:26
|
LL | fn impl_method_inline<T: Clone>(&self) {}
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn impl_method_inline<T: Clone>(&self) {}
LL + fn impl_method_inline<T>(&self) where T: Clone {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:54:28
|
LL | fn impl_method_multiple<T: Clone, U: core::fmt::Debug>(&self) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn impl_method_multiple<T: Clone, U: core::fmt::Debug>(&self) {}
LL + fn impl_method_multiple<T, U>(&self) where T: Clone, U: core::fmt::Debug {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:59:27
|
LL | fn trait_method_inline<T: Clone>(&self) {}
| ^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn trait_method_inline<T: Clone>(&self) {}
LL + fn trait_method_inline<T>(&self) where T: Clone {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:62:28
|
LL | fn trait_method_default<T: Clone + Copy>(&self) {}
| ^^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn trait_method_default<T: Clone + Copy>(&self) {}
LL + fn trait_method_default<T>(&self) where T: Clone + Copy {}
|
error: inline trait bounds used
--> tests/ui/inline_trait_bounds.rs:92:22
|
LL | fn with_default_value<T: Clone = u32>(x: T) -> T {
| ^^^^^^^^^^^^^^^^
|
help: move bounds to a `where` clause
|
LL - fn with_default_value<T: Clone = u32>(x: T) -> T {
LL + fn with_default_value<T = u32>(x: T) -> T where T: Clone {
|
error: aborting due to 13 previous errors