src/tools/clippy/clippy_lints/src/non_canonical_impls.rs RUST 382 lines View on github.com → Search inside
1use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};2use clippy_utils::res::{MaybeDef, MaybeQPath};3use clippy_utils::ty::implements_trait;4use clippy_utils::{is_from_proc_macro, last_path_segment, std_or_core};5use rustc_errors::Applicability;6use rustc_hir::def_id::DefId;7use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, LangItem, UnOp};8use rustc_lint::{LateContext, LateLintPass, LintContext};9use rustc_middle::ty::{TyCtxt, TypeckResults};10use rustc_session::impl_lint_pass;11use rustc_span::sym;12use rustc_span::symbol::kw;1314declare_clippy_lint! {15    /// ### What it does16    /// Checks for non-canonical implementations of `Clone` when `Copy` is already implemented.17    ///18    /// ### Why is this bad?19    /// If both `Clone` and `Copy` are implemented, they must agree. This can done by dereferencing20    /// `self` in `Clone`'s implementation, which will avoid any possibility of the implementations21    /// becoming out of sync.22    ///23    /// ### Example24    /// ```rust,ignore25    /// #[derive(Eq, PartialEq)]26    /// struct A(u32);27    ///28    /// impl Clone for A {29    ///     fn clone(&self) -> Self {30    ///         Self(self.0)31    ///     }32    /// }33    ///34    /// impl Copy for A {}35    /// ```36    /// Use instead:37    /// ```rust,ignore38    /// #[derive(Eq, PartialEq)]39    /// struct A(u32);40    ///41    /// impl Clone for A {42    ///     fn clone(&self) -> Self {43    ///         *self44    ///     }45    /// }46    ///47    /// impl Copy for A {}48    /// ```49    #[clippy::version = "1.72.0"]50    pub NON_CANONICAL_CLONE_IMPL,51    suspicious,52    "non-canonical implementation of `Clone` on a `Copy` type"53}5455declare_clippy_lint! {56    /// ### What it does57    /// Checks for non-canonical implementations of `PartialOrd` when `Ord` is already implemented.58    ///59    /// ### Why is this bad?60    /// If both `PartialOrd` and `Ord` are implemented, they must agree. This is commonly done by61    /// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently62    /// introduce an error upon refactoring.63    ///64    /// ### Known issues65    /// Code that calls the `.into()` method instead will be flagged, despite `.into()` wrapping it66    /// in `Some`.67    ///68    /// ### Example69    /// ```no_run70    /// # use std::cmp::Ordering;71    /// #[derive(Eq, PartialEq)]72    /// struct A(u32);73    ///74    /// impl Ord for A {75    ///     fn cmp(&self, other: &Self) -> Ordering {76    ///         // ...77    /// #       todo!();78    ///     }79    /// }80    ///81    /// impl PartialOrd for A {82    ///     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {83    ///         // ...84    /// #       todo!();85    ///     }86    /// }87    /// ```88    /// Use instead:89    /// ```no_run90    /// # use std::cmp::Ordering;91    /// #[derive(Eq, PartialEq)]92    /// struct A(u32);93    ///94    /// impl Ord for A {95    ///     fn cmp(&self, other: &Self) -> Ordering {96    ///         // ...97    /// #       todo!();98    ///     }99    /// }100    ///101    /// impl PartialOrd for A {102    ///     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {103    ///         Some(self.cmp(other))   // or self.cmp(other).into()104    ///     }105    /// }106    /// ```107    #[clippy::version = "1.73.0"]108    pub NON_CANONICAL_PARTIAL_ORD_IMPL,109    suspicious,110    "non-canonical implementation of `PartialOrd` on an `Ord` type"111}112113impl_lint_pass!(NonCanonicalImpls => [114    NON_CANONICAL_CLONE_IMPL,115    NON_CANONICAL_PARTIAL_ORD_IMPL,116]);117118#[expect(119    clippy::struct_field_names,120    reason = "`_trait` suffix is meaningful on its own, \121              and creating an inner `StoredTraits` struct would just add a level of indirection"122)]123pub(crate) struct NonCanonicalImpls {124    partial_ord_trait: Option<DefId>,125    ord_trait: Option<DefId>,126    clone_trait: Option<DefId>,127    copy_trait: Option<DefId>,128}129130impl NonCanonicalImpls {131    pub(crate) fn new(tcx: TyCtxt<'_>) -> Self {132        let lang_items = tcx.lang_items();133        Self {134            partial_ord_trait: lang_items.partial_ord_trait(),135            ord_trait: tcx.get_diagnostic_item(sym::Ord),136            clone_trait: lang_items.clone_trait(),137            copy_trait: lang_items.copy_trait(),138        }139    }140}141142/// The traits that this lint looks at143enum Trait {144    Clone,145    PartialOrd,146}147148impl LateLintPass<'_> for NonCanonicalImpls {149    fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {150        if let ItemKind::Impl(impl_) = item.kind151            // Both `PartialOrd` and `Clone` have one required method, and `PartialOrd` can have 5 methods in total152            && (1..=5).contains(&impl_.items.len())153            && let Some(of_trait) = impl_.of_trait154            && let Some(trait_did) = of_trait.trait_ref.trait_def_id()155            // Check this early to hopefully bail out as soon as possible156            && let trait_ = if Some(trait_did) == self.clone_trait {157                Trait::Clone158            } else if Some(trait_did) == self.partial_ord_trait {159                Trait::PartialOrd160            } else {161                return;162            }163            && !cx.tcx.is_automatically_derived(item.owner_id.to_def_id())164        {165            let mut assoc_fns = impl_166                .items167                .iter()168                .map(|id| cx.tcx.hir_impl_item(*id))169                .filter_map(|assoc| {170                    if let ImplItemKind::Fn(_, body_id) = assoc.kind171                        && let body = cx.tcx.hir_body(body_id)172                        && let ExprKind::Block(block, ..) = body.value.kind173                        && !block.span.in_external_macro(cx.sess().source_map())174                    {175                        Some((assoc, body, block))176                    } else {177                        None178                    }179                });180181            let trait_impl = cx.tcx.impl_trait_ref(item.owner_id).skip_binder();182183            match trait_ {184                Trait::Clone => {185                    if let Some(copy_trait) = self.copy_trait186                        && implements_trait(cx, trait_impl.self_ty(), copy_trait, &[])187                    {188                        for (assoc, _, block) in assoc_fns {189                            check_clone_on_copy(cx, assoc, block);190                        }191                    }192                },193                Trait::PartialOrd => {194                    // If `Self` and `Rhs` are not the same type, then a corresponding `Ord` impl is not possible,195                    // since it doesn't have an `Rhs`196                    if let [lhs, rhs] = trait_impl.args.as_slice()197                        && lhs == rhs198                        && let Some(ord_trait) = self.ord_trait199                        && implements_trait(cx, trait_impl.self_ty(), ord_trait, &[])200                        && let Some((assoc, body, block)) =201                            assoc_fns.find(|(assoc, _, _)| assoc.ident.name == sym::partial_cmp)202                    {203                        check_partial_ord_on_ord(cx, assoc, item, body, block);204                    }205                },206            }207        }208    }209}210211fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &Block<'_>) {212    if impl_item.ident.name == sym::clone {213        if block.stmts.is_empty()214            && let Some(expr) = block.expr215            && let ExprKind::Unary(UnOp::Deref, deref) = expr.kind216            && let ExprKind::Path(qpath) = deref.kind217            && last_path_segment(&qpath).ident.name == kw::SelfLower218        {219            // this is the canonical implementation, `fn clone(&self) -> Self { *self }`220            return;221        }222223        if is_from_proc_macro(cx, impl_item) {224            return;225        }226227        span_lint_and_sugg(228            cx,229            NON_CANONICAL_CLONE_IMPL,230            block.span,231            "non-canonical implementation of `clone` on a `Copy` type",232            "change this to",233            "{ *self }".to_owned(),234            Applicability::MaybeIncorrect,235        );236    }237238    if impl_item.ident.name == sym::clone_from && !is_from_proc_macro(cx, impl_item) {239        span_lint_and_sugg(240            cx,241            NON_CANONICAL_CLONE_IMPL,242            impl_item.span,243            "unnecessary implementation of `clone_from` on a `Copy` type",244            "remove it",245            String::new(),246            Applicability::MaybeIncorrect,247        );248    }249}250251fn check_partial_ord_on_ord<'tcx>(252    cx: &LateContext<'tcx>,253    impl_item: &ImplItem<'_>,254    item: &Item<'_>,255    body: &Body<'_>,256    block: &Block<'tcx>,257) {258    // If the `cmp` call likely needs to be fully qualified in the suggestion259    // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't260    // access `cmp_expr` in the suggestion without major changes, as we lint in `else`.261262    let mut needs_fully_qualified = false;263    if block.stmts.is_empty()264        && let Some(expr) = block.expr265        && expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified)266    {267        return;268    }269    // Fix #12683, allow [`needless_return`] here270    else if block.expr.is_none()271        && let Some(stmt) = block.stmts.first()272        && let rustc_hir::StmtKind::Semi(Expr {273            kind: ExprKind::Ret(Some(ret)),274            ..275        }) = stmt.kind276        && expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)277    {278        return;279    } else if is_from_proc_macro(cx, impl_item) {280        return;281    }282283    span_lint_and_then(284        cx,285        NON_CANONICAL_PARTIAL_ORD_IMPL,286        item.span,287        "non-canonical implementation of `partial_cmp` on an `Ord` type",288        |diag| {289            let [_, other] = body.params else {290                return;291            };292            let Some(std_or_core) = std_or_core(cx) else {293                return;294            };295296            let suggs = match (other.pat.simple_ident(), needs_fully_qualified) {297                (Some(other_ident), true) => vec![(298                    block.span,299                    format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name),300                )],301                (Some(other_ident), false) => {302                    vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]303                },304                (None, true) => vec![305                    (306                        block.span,307                        format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"),308                    ),309                    (other.pat.span, "other".to_owned()),310                ],311                (None, false) => vec![312                    (block.span, "{ Some(self.cmp(other)) }".to_owned()),313                    (other.pat.span, "other".to_owned()),314                ],315            };316317            diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified);318        },319    );320}321322/// Return true if `expr_kind` is a `cmp` call.323fn expr_is_cmp<'tcx>(324    cx: &LateContext<'tcx>,325    expr: &'tcx Expr<'tcx>,326    impl_item: &ImplItem<'_>,327    needs_fully_qualified: &mut bool,328) -> bool {329    let typeck = cx.tcx.typeck(impl_item.owner_id.def_id);330    match expr.kind {331        ExprKind::Call(332            Expr {333                kind: ExprKind::Path(some_path),334                hir_id: some_hir_id,335                ..336            },337            [cmp_expr],338        ) => {339            typeck.qpath_res(some_path, *some_hir_id).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome)340                // Fix #11178, allow `Self::cmp(self, ..)`341                && self_cmp_call(cx, typeck, cmp_expr, needs_fully_qualified)342        },343        ExprKind::MethodCall(_, recv, [], _) => {344            typeck345                .type_dependent_def(expr.hir_id)346                .assoc_parent(cx)347                .is_diag_item(cx, sym::Into)348                && self_cmp_call(cx, typeck, recv, needs_fully_qualified)349        },350        _ => false,351    }352}353354/// Returns whether this is any of `self.cmp(..)`, `Self::cmp(self, ..)` or `Ord::cmp(self, ..)`.355fn self_cmp_call<'tcx>(356    cx: &LateContext<'tcx>,357    typeck: &TypeckResults<'tcx>,358    cmp_expr: &'tcx Expr<'tcx>,359    needs_fully_qualified: &mut bool,360) -> bool {361    match cmp_expr.kind {362        ExprKind::Call(path, [_, _]) => path.res(typeck).is_diag_item(cx, sym::ord_cmp_method),363        ExprKind::MethodCall(_, recv, [_], ..) => {364            let ExprKind::Path(path) = recv.kind else {365                return false;366            };367            if last_path_segment(&path).ident.name != kw::SelfLower {368                return false;369            }370371            // We can set this to true here no matter what as if it's a `MethodCall` and goes to the372            // `else` branch, it must be a method named `cmp` that isn't `Ord::cmp`373            *needs_fully_qualified = true;374375            typeck376                .type_dependent_def_id(cmp_expr.hir_id)377                .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id))378        },379        _ => false,380    }381}

Code quality findings 6

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
if let [lhs, rhs] = trait_impl.args.as_slice()
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
// Fix #12683, allow [`needless_return`] here
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let [_, other] = body.params else {
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// # todo!();
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// # todo!();
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
/// # todo!();

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.