blob: 599ff696f6aed03b6109a5cb713dc9d028523435 [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{is_bool, sym};
use rustc_abi::ExternAbi;
use rustc_hir::{self as hir, FnRetTy, FnSig, GenericParamKind, ImplItem, LifetimeParamKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::edition::Edition::{self, Edition2015, Edition2021};
use rustc_span::{Symbol, kw};
use super::SHOULD_IMPLEMENT_TRAIT;
use super::lib::SelfKind;
pub(super) fn check_impl_item<'tcx>(
cx: &LateContext<'tcx>,
impl_item: &'tcx ImplItem<'_>,
self_ty: Ty<'tcx>,
impl_implements_trait: bool,
first_arg_ty_opt: Option<Ty<'tcx>>,
sig: &FnSig<'_>,
) {
// if this impl block implements a trait, lint in trait definition instead
if !impl_implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id)
// check missing trait implementations
&& let Some(method_config) = TRAIT_METHODS.iter().find(|case| case.method_name == impl_item.ident.name)
&& sig.decl.inputs.len() == method_config.param_count
&& method_config.output_type.matches(&sig.decl.output)
// in case there is no first arg, since we already have checked the number of arguments
// it's should be always true
&& first_arg_ty_opt
.is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty))
&& sig.header.is_safe()
&& !sig.header.is_const()
&& !sig.header.is_async()
&& sig.header.abi == ExternAbi::Rust
&& method_config.lifetime_param_cond(impl_item)
&& method_config.in_prelude_since <= cx.tcx.sess.edition()
{
span_lint_and_help(
cx,
SHOULD_IMPLEMENT_TRAIT,
impl_item.span,
format!(
"method `{}` can be confused for the standard trait method `{}::{}`",
method_config.method_name, method_config.trait_name, method_config.method_name
),
None,
format!(
"consider implementing the trait `{}` or choosing a less ambiguous method name",
method_config.trait_name
),
);
}
}
struct ShouldImplTraitCase {
trait_name: &'static str,
method_name: Symbol,
param_count: usize,
// implicit self kind expected (none, self, &self, ...)
self_kind: SelfKind,
// checks against the output type
output_type: OutType,
// certain methods with explicit lifetimes can't implement the equivalent trait method
lint_explicit_lifetime: bool,
in_prelude_since: Edition,
}
impl ShouldImplTraitCase {
const fn new(
trait_name: &'static str,
method_name: Symbol,
param_count: usize,
self_kind: SelfKind,
output_type: OutType,
lint_explicit_lifetime: bool,
in_prelude_since: Edition,
) -> ShouldImplTraitCase {
ShouldImplTraitCase {
trait_name,
method_name,
param_count,
self_kind,
output_type,
lint_explicit_lifetime,
in_prelude_since,
}
}
fn lifetime_param_cond(&self, impl_item: &ImplItem<'_>) -> bool {
self.lint_explicit_lifetime
|| !impl_item.generics.params.iter().any(|p| {
matches!(
p.kind,
GenericParamKind::Lifetime {
kind: LifetimeParamKind::Explicit
}
)
})
}
}
#[rustfmt::skip]
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015),
ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, SelfKind::Ref, OutType::Ref, true, Edition2015),
ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, SelfKind::Ref, OutType::Ref, true, Edition2015),
ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015),
ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, SelfKind::Ref, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, SelfKind::Ref, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, SelfKind::No, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, SelfKind::Ref, OutType::Ref, true, Edition2015),
ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, SelfKind::RefMut, OutType::Unit, true, Edition2015),
ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, SelfKind::Ref, OutType::Bool, true, Edition2015),
ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, SelfKind::No, OutType::Any, true, Edition2021),
ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, SelfKind::No, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, SelfKind::Ref, OutType::Unit, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, SelfKind::Ref, OutType::Ref, true, Edition2015),
ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, SelfKind::RefMut, OutType::Ref, true, Edition2015),
ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, SelfKind::RefMut, OutType::Any, false, Edition2015),
ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, SelfKind::Value, OutType::Any, true, Edition2015),
ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, SelfKind::Value, OutType::Any, true, Edition2015),
];
#[derive(Clone, Copy)]
enum OutType {
Unit,
Bool,
Any,
Ref,
}
impl OutType {
fn matches(self, ty: &FnRetTy<'_>) -> bool {
let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[]));
match (self, ty) {
(Self::Unit, &FnRetTy::DefaultReturn(_)) => true,
(Self::Unit, &FnRetTy::Return(ty)) if is_unit(ty) => true,
(Self::Bool, &FnRetTy::Return(ty)) if is_bool(ty) => true,
(Self::Any, &FnRetTy::Return(ty)) if !is_unit(ty) => true,
(Self::Ref, &FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)),
_ => false,
}
}
}