]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/derive.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / derive.rs
CommitLineData
923072b8 1use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
cdc7bbd5 2use clippy_utils::paths;
923072b8 3use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
04454e1e 4use clippy_utils::{is_lint_allowed, match_def_path};
f20569fa 5use if_chain::if_chain;
923072b8
FG
6use rustc_errors::Applicability;
7use rustc_hir::def_id::DefId;
5099ac24 8use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
f20569fa 9use rustc_hir::{
923072b8
FG
10 self as hir, BlockCheckMode, BodyId, Constness, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, UnsafeSource,
11 Unsafety,
f20569fa
XL
12};
13use rustc_lint::{LateContext, LateLintPass};
5099ac24 14use rustc_middle::hir::nested_filter;
923072b8
FG
15use rustc_middle::traits::Reveal;
16use rustc_middle::ty::{
f25598a0
FG
17 self, Binder, BoundConstness, Clause, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, PredicateKind,
18 TraitPredicate, Ty, TyCtxt,
923072b8 19};
f20569fa 20use rustc_session::{declare_lint_pass, declare_tool_lint};
17df50a5 21use rustc_span::source_map::Span;
5e7ed085 22use rustc_span::sym;
f20569fa
XL
23
24declare_clippy_lint! {
94222f64
XL
25 /// ### What it does
26 /// Checks for deriving `Hash` but implementing `PartialEq`
f20569fa
XL
27 /// explicitly or vice versa.
28 ///
94222f64
XL
29 /// ### Why is this bad?
30 /// The implementation of these traits must agree (for
f20569fa
XL
31 /// example for use with `HashMap`) so it’s probably a bad idea to use a
32 /// default-generated `Hash` implementation with an explicitly defined
33 /// `PartialEq`. In particular, the following must hold for any type:
34 ///
35 /// ```text
36 /// k1 == k2 ⇒ hash(k1) == hash(k2)
37 /// ```
38 ///
94222f64 39 /// ### Example
f20569fa
XL
40 /// ```ignore
41 /// #[derive(Hash)]
42 /// struct Foo;
43 ///
44 /// impl PartialEq for Foo {
45 /// ...
46 /// }
47 /// ```
a2a8927a 48 #[clippy::version = "pre 1.29.0"]
f25598a0 49 pub DERIVED_HASH_WITH_MANUAL_EQ,
f20569fa
XL
50 correctness,
51 "deriving `Hash` but implementing `PartialEq` explicitly"
52}
53
54declare_clippy_lint! {
94222f64
XL
55 /// ### What it does
56 /// Checks for deriving `Ord` but implementing `PartialOrd`
f20569fa
XL
57 /// explicitly or vice versa.
58 ///
94222f64
XL
59 /// ### Why is this bad?
60 /// The implementation of these traits must agree (for
f20569fa
XL
61 /// example for use with `sort`) so it’s probably a bad idea to use a
62 /// default-generated `Ord` implementation with an explicitly defined
63 /// `PartialOrd`. In particular, the following must hold for any type
64 /// implementing `Ord`:
65 ///
66 /// ```text
67 /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()
68 /// ```
69 ///
94222f64 70 /// ### Example
f20569fa
XL
71 /// ```rust,ignore
72 /// #[derive(Ord, PartialEq, Eq)]
73 /// struct Foo;
74 ///
75 /// impl PartialOrd for Foo {
76 /// ...
77 /// }
78 /// ```
79 /// Use instead:
80 /// ```rust,ignore
81 /// #[derive(PartialEq, Eq)]
82 /// struct Foo;
83 ///
84 /// impl PartialOrd for Foo {
85 /// fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
86 /// Some(self.cmp(other))
87 /// }
88 /// }
89 ///
90 /// impl Ord for Foo {
91 /// ...
92 /// }
93 /// ```
94 /// or, if you don't need a custom ordering:
95 /// ```rust,ignore
96 /// #[derive(Ord, PartialOrd, PartialEq, Eq)]
97 /// struct Foo;
98 /// ```
a2a8927a 99 #[clippy::version = "1.47.0"]
f20569fa
XL
100 pub DERIVE_ORD_XOR_PARTIAL_ORD,
101 correctness,
102 "deriving `Ord` but implementing `PartialOrd` explicitly"
103}
104
105declare_clippy_lint! {
94222f64
XL
106 /// ### What it does
107 /// Checks for explicit `Clone` implementations for `Copy`
f20569fa
XL
108 /// types.
109 ///
94222f64 110 /// ### Why is this bad?
923072b8
FG
111 /// To avoid surprising behavior, these traits should
112 /// agree and the behavior of `Copy` cannot be overridden. In almost all
f20569fa
XL
113 /// situations a `Copy` type should have a `Clone` implementation that does
114 /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
115 /// gets you.
116 ///
94222f64 117 /// ### Example
f20569fa
XL
118 /// ```rust,ignore
119 /// #[derive(Copy)]
120 /// struct Foo;
121 ///
122 /// impl Clone for Foo {
123 /// // ..
124 /// }
125 /// ```
a2a8927a 126 #[clippy::version = "pre 1.29.0"]
f20569fa
XL
127 pub EXPL_IMPL_CLONE_ON_COPY,
128 pedantic,
129 "implementing `Clone` explicitly on `Copy` types"
130}
131
132declare_clippy_lint! {
94222f64
XL
133 /// ### What it does
134 /// Checks for deriving `serde::Deserialize` on a type that
f20569fa
XL
135 /// has methods using `unsafe`.
136 ///
94222f64
XL
137 /// ### Why is this bad?
138 /// Deriving `serde::Deserialize` will create a constructor
f20569fa
XL
139 /// that may violate invariants hold by another constructor.
140 ///
94222f64 141 /// ### Example
f20569fa
XL
142 /// ```rust,ignore
143 /// use serde::Deserialize;
144 ///
145 /// #[derive(Deserialize)]
146 /// pub struct Foo {
147 /// // ..
148 /// }
149 ///
150 /// impl Foo {
151 /// pub fn new() -> Self {
152 /// // setup here ..
153 /// }
154 ///
155 /// pub unsafe fn parts() -> (&str, &str) {
156 /// // assumes invariants hold
157 /// }
158 /// }
159 /// ```
a2a8927a 160 #[clippy::version = "1.45.0"]
f20569fa
XL
161 pub UNSAFE_DERIVE_DESERIALIZE,
162 pedantic,
163 "deriving `serde::Deserialize` on a type that has methods using `unsafe`"
164}
165
923072b8
FG
166declare_clippy_lint! {
167 /// ### What it does
168 /// Checks for types that derive `PartialEq` and could implement `Eq`.
169 ///
170 /// ### Why is this bad?
171 /// If a type `T` derives `PartialEq` and all of its members implement `Eq`,
172 /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used
173 /// in APIs that require `Eq` types. It also allows structs containing `T` to derive
174 /// `Eq` themselves.
175 ///
176 /// ### Example
177 /// ```rust
178 /// #[derive(PartialEq)]
179 /// struct Foo {
180 /// i_am_eq: i32,
181 /// i_am_eq_too: Vec<String>,
182 /// }
183 /// ```
184 /// Use instead:
185 /// ```rust
186 /// #[derive(PartialEq, Eq)]
187 /// struct Foo {
188 /// i_am_eq: i32,
189 /// i_am_eq_too: Vec<String>,
190 /// }
191 /// ```
064997fb 192 #[clippy::version = "1.63.0"]
923072b8 193 pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
2b03887a 194 nursery,
923072b8
FG
195 "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
196}
197
f20569fa
XL
198declare_lint_pass!(Derive => [
199 EXPL_IMPL_CLONE_ON_COPY,
f25598a0 200 DERIVED_HASH_WITH_MANUAL_EQ,
f20569fa 201 DERIVE_ORD_XOR_PARTIAL_ORD,
923072b8
FG
202 UNSAFE_DERIVE_DESERIALIZE,
203 DERIVE_PARTIAL_EQ_WITHOUT_EQ
f20569fa
XL
204]);
205
206impl<'tcx> LateLintPass<'tcx> for Derive {
207 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
208 if let ItemKind::Impl(Impl {
209 of_trait: Some(ref trait_ref),
210 ..
211 }) = item.kind
212 {
2b03887a
FG
213 let ty = cx.tcx.type_of(item.owner_id);
214 let is_automatically_derived = cx.tcx.has_attr(item.owner_id.to_def_id(), sym::automatically_derived);
f20569fa
XL
215
216 check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
217 check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
218
219 if is_automatically_derived {
220 check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
923072b8 221 check_partial_eq_without_eq(cx, item.span, trait_ref, ty);
f20569fa
XL
222 } else {
223 check_copy_clone(cx, item, trait_ref, ty);
224 }
225 }
226 }
227}
228
f25598a0 229/// Implementation of the `DERIVED_HASH_WITH_MANUAL_EQ` lint.
f20569fa
XL
230fn check_hash_peq<'tcx>(
231 cx: &LateContext<'tcx>,
232 span: Span,
923072b8 233 trait_ref: &hir::TraitRef<'_>,
f20569fa
XL
234 ty: Ty<'tcx>,
235 hash_is_automatically_derived: bool,
236) {
237 if_chain! {
238 if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
239 if let Some(def_id) = trait_ref.trait_def_id();
5e7ed085 240 if cx.tcx.is_diagnostic_item(sym::Hash, def_id);
f20569fa
XL
241 then {
242 // Look for the PartialEq implementations for `ty`
243 cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
04454e1e 244 let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
f20569fa 245
f25598a0 246 if !hash_is_automatically_derived || peq_is_automatically_derived {
f20569fa
XL
247 return;
248 }
249
250 let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
251
252 // Only care about `impl PartialEq<Foo> for Foo`
253 // For `impl PartialEq<B> for A, input_types is [A, B]
f25598a0 254 if trait_ref.subst_identity().substs.type_at(1) == ty {
f20569fa
XL
255 span_lint_and_then(
256 cx,
f25598a0 257 DERIVED_HASH_WITH_MANUAL_EQ,
f20569fa 258 span,
f25598a0 259 "you are deriving `Hash` but have implemented `PartialEq` explicitly",
f20569fa
XL
260 |diag| {
261 if let Some(local_def_id) = impl_id.as_local() {
262 let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
263 diag.span_note(
264 cx.tcx.hir().span(hir_id),
265 "`PartialEq` implemented here"
266 );
267 }
268 }
269 );
270 }
271 });
272 }
273 }
274}
275
276/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
277fn check_ord_partial_ord<'tcx>(
278 cx: &LateContext<'tcx>,
279 span: Span,
923072b8 280 trait_ref: &hir::TraitRef<'_>,
f20569fa
XL
281 ty: Ty<'tcx>,
282 ord_is_automatically_derived: bool,
283) {
284 if_chain! {
5e7ed085 285 if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord);
f20569fa
XL
286 if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
287 if let Some(def_id) = &trait_ref.trait_def_id();
288 if *def_id == ord_trait_def_id;
289 then {
290 // Look for the PartialOrd implementations for `ty`
291 cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
04454e1e 292 let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
f20569fa
XL
293
294 if partial_ord_is_automatically_derived == ord_is_automatically_derived {
295 return;
296 }
297
298 let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
299
300 // Only care about `impl PartialOrd<Foo> for Foo`
301 // For `impl PartialOrd<B> for A, input_types is [A, B]
f25598a0 302 if trait_ref.subst_identity().substs.type_at(1) == ty {
f20569fa
XL
303 let mess = if partial_ord_is_automatically_derived {
304 "you are implementing `Ord` explicitly but have derived `PartialOrd`"
305 } else {
306 "you are deriving `Ord` but have implemented `PartialOrd` explicitly"
307 };
308
309 span_lint_and_then(
310 cx,
311 DERIVE_ORD_XOR_PARTIAL_ORD,
312 span,
313 mess,
314 |diag| {
315 if let Some(local_def_id) = impl_id.as_local() {
316 let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
317 diag.span_note(
318 cx.tcx.hir().span(hir_id),
319 "`PartialOrd` implemented here"
320 );
321 }
322 }
323 );
324 }
325 });
326 }
327 }
328}
329
330/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
923072b8 331fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
cdc7bbd5
XL
332 let clone_id = match cx.tcx.lang_items().clone_trait() {
333 Some(id) if trait_ref.trait_def_id() == Some(id) => id,
334 _ => return,
335 };
2b03887a 336 let Some(copy_id) = cx.tcx.lang_items().copy_trait() else { return };
cdc7bbd5
XL
337 let (ty_adt, ty_subs) = match *ty.kind() {
338 // Unions can't derive clone.
339 ty::Adt(adt, subs) if !adt.is_union() => (adt, subs),
340 _ => return,
341 };
342 // If the current self type doesn't implement Copy (due to generic constraints), search to see if
343 // there's a Copy impl for any instance of the adt.
344 if !is_copy(cx, ty) {
345 if ty_subs.non_erasable_generics().next().is_some() {
17df50a5
XL
346 let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(&copy_id).map_or(false, |impls| {
347 impls
348 .iter()
5e7ed085 349 .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did()))
17df50a5 350 });
cdc7bbd5
XL
351 if !has_copy_impl {
352 return;
353 }
354 } else {
f20569fa
XL
355 return;
356 }
f20569fa 357 }
cdc7bbd5
XL
358 // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for
359 // this impl.
360 if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) {
361 return;
362 }
f25598a0
FG
363 // `#[repr(packed)]` structs with type/const parameters can't derive `Clone`.
364 // https://github.com/rust-lang/rust-clippy/issues/10188
365 if ty_adt.repr().packed()
366 && ty_subs
367 .iter()
368 .any(|arg| matches!(arg.unpack(), GenericArgKind::Type(_) | GenericArgKind::Const(_)))
369 {
370 return;
371 }
cdc7bbd5
XL
372
373 span_lint_and_note(
374 cx,
375 EXPL_IMPL_CLONE_ON_COPY,
376 item.span,
377 "you are implementing `Clone` explicitly on a `Copy` type",
378 Some(item.span),
379 "consider deriving `Clone` or removing `Copy`",
380 );
f20569fa
XL
381}
382
383/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
384fn check_unsafe_derive_deserialize<'tcx>(
385 cx: &LateContext<'tcx>,
386 item: &Item<'_>,
923072b8 387 trait_ref: &hir::TraitRef<'_>,
f20569fa
XL
388 ty: Ty<'tcx>,
389) {
f20569fa
XL
390 fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
391 let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
392 walk_item(&mut visitor, item);
393 visitor.has_unsafe
394 }
395
396 if_chain! {
397 if let Some(trait_def_id) = trait_ref.trait_def_id();
398 if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
399 if let ty::Adt(def, _) = ty.kind();
5e7ed085 400 if let Some(local_def_id) = def.did().as_local();
f20569fa 401 let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
136023e0 402 if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
5e7ed085 403 if cx.tcx.inherent_impls(def.did())
f20569fa 404 .iter()
a2a8927a 405 .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
f20569fa
XL
406 .any(|imp| has_unsafe(cx, imp));
407 then {
408 span_lint_and_help(
409 cx,
410 UNSAFE_DERIVE_DESERIALIZE,
411 item.span,
412 "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
413 None,
414 "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
415 );
416 }
417 }
418}
419
420struct UnsafeVisitor<'a, 'tcx> {
421 cx: &'a LateContext<'tcx>,
422 has_unsafe: bool,
423}
424
425impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
5099ac24 426 type NestedFilter = nested_filter::All;
f20569fa 427
f2b60f7d 428 fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: HirId) {
f20569fa
XL
429 if self.has_unsafe {
430 return;
431 }
432
433 if_chain! {
434 if let Some(header) = kind.header();
c295e0f8 435 if header.unsafety == Unsafety::Unsafe;
f20569fa
XL
436 then {
437 self.has_unsafe = true;
438 }
439 }
440
f2b60f7d 441 walk_fn(self, kind, decl, body_id, id);
f20569fa
XL
442 }
443
444 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
445 if self.has_unsafe {
446 return;
447 }
448
449 if let ExprKind::Block(block, _) = expr.kind {
c295e0f8 450 if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
136023e0 451 self.has_unsafe = true;
f20569fa
XL
452 }
453 }
454
455 walk_expr(self, expr);
456 }
457
5099ac24
FG
458 fn nested_visit_map(&mut self) -> Self::Map {
459 self.cx.tcx.hir()
f20569fa
XL
460 }
461}
923072b8
FG
462
463/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
464fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
465 if_chain! {
466 if let ty::Adt(adt, substs) = ty.kind();
f2b60f7d 467 if cx.tcx.visibility(adt.did()).is_public();
923072b8
FG
468 if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
469 if let Some(def_id) = trait_ref.trait_def_id();
470 if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
471 let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id);
487cf647 472 if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, []);
923072b8
FG
473 // If all of our fields implement `Eq`, we can implement `Eq` too
474 if adt
475 .all_fields()
476 .map(|f| f.ty(cx.tcx, substs))
487cf647 477 .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, []));
923072b8
FG
478 then {
479 span_lint_and_sugg(
480 cx,
481 DERIVE_PARTIAL_EQ_WITHOUT_EQ,
482 span.ctxt().outer_expn_data().call_site,
483 "you are deriving `PartialEq` and can implement `Eq`",
484 "consider deriving `Eq` as well",
485 "PartialEq, Eq".to_string(),
486 Applicability::MachineApplicable,
487 )
488 }
489 }
490}
491
492/// Creates the `ParamEnv` used for the give type's derived `Eq` impl.
493fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ParamEnv<'_> {
494 // Initial map from generic index to param def.
495 // Vec<(param_def, needs_eq)>
496 let mut params = tcx
497 .generics_of(did)
498 .params
499 .iter()
500 .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. })))
501 .collect::<Vec<_>>();
502
503 let ty_predicates = tcx.predicates_of(did).predicates;
504 for (p, _) in ty_predicates {
487cf647 505 if let PredicateKind::Clause(Clause::Trait(p)) = p.kind().skip_binder()
923072b8
FG
506 && p.trait_ref.def_id == eq_trait_id
507 && let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
508 && p.constness == BoundConstness::NotConst
509 {
510 // Flag types which already have an `Eq` bound.
511 params[self_ty.index as usize].1 = false;
512 }
513 }
514
515 ParamEnv::new(
516 tcx.mk_predicates(ty_predicates.iter().map(|&(p, _)| p).chain(
517 params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| {
487cf647 518 tcx.mk_predicate(Binder::dummy(PredicateKind::Clause(Clause::Trait(TraitPredicate {
f25598a0 519 trait_ref: tcx.mk_trait_ref(eq_trait_id, [tcx.mk_param_from_def(param)]),
923072b8
FG
520 constness: BoundConstness::NotConst,
521 polarity: ImplPolarity::Positive,
487cf647 522 }))))
923072b8
FG
523 }),
524 )),
525 Reveal::UserFacing,
526 Constness::NotConst,
527 )
528}