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