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 @@ -6523,6 +6523,7 @@ Released 2018-09-13
[`mem_replace_option_with_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_some
[`mem_replace_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default
[`mem_replace_with_uninit`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_uninit
[`method_without_self_relation`]: https://rust-lang.github.io/rust-clippy/master/index.html#method_without_self_relation
[`min_ident_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_ident_chars
[`min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_max
[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::methods::MAP_IDENTITY_INFO,
crate::methods::MAP_UNWRAP_OR_INFO,
crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO,
crate::methods::METHOD_WITHOUT_SELF_RELATION_INFO,
crate::methods::MUT_MUTEX_LOCK_INFO,
crate::methods::NAIVE_BYTECOUNT_INFO,
crate::methods::NEEDLESS_AS_BYTES_INFO,
Expand Down
126 changes: 126 additions & 0 deletions clippy_lints/src/methods/method_without_self_relation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::{FnSig, ImplItem, ImplItemKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::Span;

use super::METHOD_WITHOUT_SELF_RELATION;

/// Check if a type contains a reference to `Self` anywhere in its structure.
/// This includes direct references and generic parameters.
fn contains_self<'tcx>(ty: Ty<'tcx>, self_ty: Ty<'tcx>) -> bool {
// Direct comparison with Self type
if ty == self_ty {
return true;
}

match ty.kind() {
// Check if this is a reference or raw pointer to Self
ty::Ref(_, inner_ty, _) | ty::RawPtr(inner_ty, _) => contains_self(*inner_ty, self_ty),

// Check generic types like Option<Self>, Vec<Self>, Result<Self, E>, etc.
ty::Adt(_, args) => args.types().any(|arg_ty| contains_self(arg_ty, self_ty)),

// Check tuples like (Self, i32) or (String, Self)
ty::Tuple(types) => types.iter().any(|ty| contains_self(ty, self_ty)),

// Check array types like [Self; 10]
ty::Array(elem_ty, _) | ty::Slice(elem_ty) => contains_self(*elem_ty, self_ty),

// Check function pointer types
ty::FnPtr(sig, _) => {
let sig = sig.skip_binder();
sig.inputs().iter().any(|&ty| contains_self(ty, self_ty)) || contains_self(sig.output(), self_ty)
},

// Check closures (uncommon but possible)
ty::Closure(_, args) => {
args.as_closure()
.sig()
.inputs()
.skip_binder()
.iter()
.any(|&ty| contains_self(ty, self_ty))
|| contains_self(args.as_closure().sig().output().skip_binder(), self_ty)
},

// Check opaque types (impl Trait, async fn return types)
ty::Alias(ty::AliasTyKind::Opaque, alias_ty) => {
// Check the bounds of the opaque type
alias_ty.args.types().any(|arg_ty| contains_self(arg_ty, self_ty))
},

// Check trait objects (dyn Trait)
ty::Dynamic(predicates, _) => {
use rustc_middle::ty::ExistentialPredicate;
// Check if any of the trait bounds reference Self
predicates.iter().any(|predicate| match predicate.skip_binder() {
ExistentialPredicate::Trait(trait_ref) => {
trait_ref.args.types().any(|arg_ty| contains_self(arg_ty, self_ty))
},
ExistentialPredicate::Projection(projection) => {
projection.args.types().any(|arg_ty| contains_self(arg_ty, self_ty))
|| contains_self(projection.term.as_type().unwrap(), self_ty)
},
ExistentialPredicate::AutoTrait(_) => false,
})
},

_ => false,
}
}

pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
impl_item: &'tcx ImplItem<'_>,
self_ty: Ty<'tcx>,
implements_trait: bool,
) {
if let ImplItemKind::Fn(ref sig, _) = impl_item.kind {
// Don't lint trait implementations - these methods are defined by the trait
if implements_trait {
return;
}

// Get the method signature from the type system
let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity();
let method_sig = method_sig.skip_binder();

// Check if there's a self parameter (self, &self, &mut self, self: Arc<Self>, etc.)
if has_self_parameter(sig) {
return;
}

// Check all input parameters for Self references
for &param_ty in method_sig.inputs() {
if contains_self(param_ty, self_ty) {
return;
}
}

// Check return type for Self references
let return_ty = method_sig.output();
if contains_self(return_ty, self_ty) {
return;
}

// If we reach here, the method has no relationship to Self
emit_lint(cx, impl_item.span, impl_item.ident.name.as_str());
}
}

/// Check if the function signature has an explicit self parameter
fn has_self_parameter(sig: &FnSig<'_>) -> bool {
sig.decl.implicit_self.has_implicit_self()
}

fn emit_lint(cx: &LateContext<'_>, span: Span, method_name: &str) {
span_lint_and_help(
cx,
METHOD_WITHOUT_SELF_RELATION,
span,
format!("method `{method_name}` has no relationship to `Self`"),
None,
"consider making this a standalone function instead of a method",
);
}
71 changes: 71 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ mod map_flatten;
mod map_identity;
mod map_unwrap_or;
mod map_with_unused_argument_over_ranges;
mod method_without_self_relation;
mod mut_mutex_lock;
mod needless_as_bytes;
mod needless_character_iteration;
Expand Down Expand Up @@ -1162,6 +1163,74 @@ declare_clippy_lint! {
"not returning type containing `Self` in a `new` method"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for methods in impl blocks that have no relationship to `Self`.
///
/// ### Why is this bad?
/// Methods that don't use `Self` in their signature (parameters or return type)
/// are not actually methods but rather associated functions. They would be
/// better expressed as standalone functions, making the code more modular
/// and easier to discover.
///
/// ### Known issues
/// This lint is intentionally set to `restriction` category because there are
/// valid reasons to keep functions within an impl block:
/// - Helper functions that logically belong to the type
/// - Functions meant to be used through the type's namespace
/// - Private implementation details that should be grouped with the type
///
/// ### Example
/// ```no_run
/// struct Calculator;
///
/// impl Calculator {
/// // Bad: No relationship to Self
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
/// }
/// ```
/// Use instead:
/// ```no_run
/// struct Calculator;
///
/// // Good: Standalone function
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
/// ```
///
/// Methods with Self references are fine:
/// ```no_run
/// #[derive(Default)]
/// struct Calculator {
/// precision: u32,
/// }
///
/// impl Calculator {
/// // Good: Uses &self
/// fn add(&self, a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// // Good: Returns Self
/// fn new(precision: u32) -> Self {
/// Self { precision }
/// }
///
/// // Good: Takes Self in generic parameter
/// fn from_option(opt: Option<Self>) -> Self {
/// opt.unwrap_or_default()
/// }
/// }
/// ```
#[clippy::version = "1.92.0"]
pub METHOD_WITHOUT_SELF_RELATION,
restriction,
"methods in impl blocks that have no relationship to `Self`"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for calling `.step_by(0)` on iterators which panics.
Expand Down Expand Up @@ -4720,6 +4789,7 @@ impl_lint_pass!(Methods => [
FLAT_MAP_OPTION,
INEFFICIENT_TO_STRING,
NEW_RET_NO_SELF,
METHOD_WITHOUT_SELF_RELATION,
SINGLE_CHAR_ADD_STR,
SEARCH_IS_SOME,
FILTER_NEXT,
Expand Down Expand Up @@ -4929,6 +4999,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
}

new_ret_no_self::check_impl_item(cx, impl_item, self_ty, implements_trait);
method_without_self_relation::check(cx, impl_item, self_ty, implements_trait);
}
}

Expand Down
Empty file.
Loading