| 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() | |
| && matches!(sig.header.constness, hir::Constness::NotConst) | |
| && !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, | |
| } | |
| } | |
| } |