|
| 1 | +use clippy_utils::diagnostics::span_lint_hir_and_then; |
| 2 | +use clippy_utils::has_non_exhaustive_attr; |
| 3 | +use clippy_utils::ty::implements_trait_with_env; |
| 4 | +use rustc_errors::Applicability; |
| 5 | +use rustc_hir as hir; |
| 6 | +use rustc_hir::def_id::DefId; |
| 7 | +use rustc_lint::LateContext; |
| 8 | +use rustc_middle::ty::{self, ClauseKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast}; |
| 9 | +use rustc_span::{Span, sym}; |
| 10 | + |
| 11 | +use super::DERIVE_PARTIAL_EQ_WITHOUT_EQ; |
| 12 | + |
| 13 | +/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. |
| 14 | +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { |
| 15 | + if let ty::Adt(adt, args) = ty.kind() |
| 16 | + && cx.tcx.visibility(adt.did()).is_public() |
| 17 | + && let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq) |
| 18 | + && let Some(def_id) = trait_ref.trait_def_id() |
| 19 | + && cx.tcx.is_diagnostic_item(sym::PartialEq, def_id) |
| 20 | + && !has_non_exhaustive_attr(cx.tcx, *adt) |
| 21 | + && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) |
| 22 | + && let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) |
| 23 | + && let Some(local_def_id) = adt.did().as_local() |
| 24 | + // If all of our fields implement `Eq`, we can implement `Eq` too |
| 25 | + && adt |
| 26 | + .all_fields() |
| 27 | + .map(|f| f.ty(cx.tcx, args)) |
| 28 | + .all(|ty| implements_trait_with_env(cx.tcx, typing_env, ty, eq_trait_def_id, None, &[])) |
| 29 | + { |
| 30 | + span_lint_hir_and_then( |
| 31 | + cx, |
| 32 | + DERIVE_PARTIAL_EQ_WITHOUT_EQ, |
| 33 | + cx.tcx.local_def_id_to_hir_id(local_def_id), |
| 34 | + span.ctxt().outer_expn_data().call_site, |
| 35 | + "you are deriving `PartialEq` and can implement `Eq`", |
| 36 | + |diag| { |
| 37 | + diag.span_suggestion( |
| 38 | + span.ctxt().outer_expn_data().call_site, |
| 39 | + "consider deriving `Eq` as well", |
| 40 | + "PartialEq, Eq", |
| 41 | + Applicability::MachineApplicable, |
| 42 | + ); |
| 43 | + }, |
| 44 | + ); |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: DefId) -> bool { |
| 49 | + tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some() |
| 50 | +} |
| 51 | + |
| 52 | +/// Creates the `ParamEnv` used for the given type's derived `Eq` impl. |
| 53 | +fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> { |
| 54 | + // Initial map from generic index to param def. |
| 55 | + // Vec<(param_def, needs_eq)> |
| 56 | + let mut params = tcx |
| 57 | + .generics_of(did) |
| 58 | + .own_params |
| 59 | + .iter() |
| 60 | + .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. }))) |
| 61 | + .collect::<Vec<_>>(); |
| 62 | + |
| 63 | + let ty_predicates = tcx.predicates_of(did).predicates; |
| 64 | + for (p, _) in ty_predicates { |
| 65 | + if let ClauseKind::Trait(p) = p.kind().skip_binder() |
| 66 | + && p.trait_ref.def_id == eq_trait_id |
| 67 | + && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() |
| 68 | + { |
| 69 | + // Flag types which already have an `Eq` bound. |
| 70 | + params[self_ty.index as usize].1 = false; |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + let param_env = ParamEnv::new(tcx.mk_clauses_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain( |
| 75 | + params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { |
| 76 | + ClauseKind::Trait(TraitPredicate { |
| 77 | + trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]), |
| 78 | + polarity: ty::PredicatePolarity::Positive, |
| 79 | + }) |
| 80 | + .upcast(tcx) |
| 81 | + }), |
| 82 | + ))); |
| 83 | + ty::TypingEnv { |
| 84 | + typing_mode: ty::TypingMode::non_body_analysis(), |
| 85 | + param_env, |
| 86 | + } |
| 87 | +} |
0 commit comments