]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then}; |
2 | use clippy_utils::paths; | |
3 | use clippy_utils::ty::{implements_trait, is_copy}; | |
ee023bcb | 4 | use clippy_utils::{is_automatically_derived, is_lint_allowed, match_def_path}; |
f20569fa | 5 | use if_chain::if_chain; |
5099ac24 | 6 | use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; |
f20569fa XL |
7 | use rustc_hir::{ |
8 | BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety, | |
9 | }; | |
10 | use rustc_lint::{LateContext, LateLintPass}; | |
5099ac24 | 11 | use rustc_middle::hir::nested_filter; |
f20569fa XL |
12 | use rustc_middle::ty::{self, Ty}; |
13 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
17df50a5 | 14 | use rustc_span::source_map::Span; |
ee023bcb | 15 | use rustc_span::sym; |
f20569fa XL |
16 | |
17 | declare_clippy_lint! { | |
94222f64 XL |
18 | /// ### What it does |
19 | /// Checks for deriving `Hash` but implementing `PartialEq` | |
f20569fa XL |
20 | /// explicitly or vice versa. |
21 | /// | |
94222f64 XL |
22 | /// ### Why is this bad? |
23 | /// The implementation of these traits must agree (for | |
f20569fa XL |
24 | /// example for use with `HashMap`) so it’s probably a bad idea to use a |
25 | /// default-generated `Hash` implementation with an explicitly defined | |
26 | /// `PartialEq`. In particular, the following must hold for any type: | |
27 | /// | |
28 | /// ```text | |
29 | /// k1 == k2 ⇒ hash(k1) == hash(k2) | |
30 | /// ``` | |
31 | /// | |
94222f64 | 32 | /// ### Example |
f20569fa XL |
33 | /// ```ignore |
34 | /// #[derive(Hash)] | |
35 | /// struct Foo; | |
36 | /// | |
37 | /// impl PartialEq for Foo { | |
38 | /// ... | |
39 | /// } | |
40 | /// ``` | |
a2a8927a | 41 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
42 | pub DERIVE_HASH_XOR_EQ, |
43 | correctness, | |
44 | "deriving `Hash` but implementing `PartialEq` explicitly" | |
45 | } | |
46 | ||
47 | declare_clippy_lint! { | |
94222f64 XL |
48 | /// ### What it does |
49 | /// Checks for deriving `Ord` but implementing `PartialOrd` | |
f20569fa XL |
50 | /// explicitly or vice versa. |
51 | /// | |
94222f64 XL |
52 | /// ### Why is this bad? |
53 | /// The implementation of these traits must agree (for | |
f20569fa XL |
54 | /// example for use with `sort`) so it’s probably a bad idea to use a |
55 | /// default-generated `Ord` implementation with an explicitly defined | |
56 | /// `PartialOrd`. In particular, the following must hold for any type | |
57 | /// implementing `Ord`: | |
58 | /// | |
59 | /// ```text | |
60 | /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap() | |
61 | /// ``` | |
62 | /// | |
94222f64 | 63 | /// ### Example |
f20569fa XL |
64 | /// ```rust,ignore |
65 | /// #[derive(Ord, PartialEq, Eq)] | |
66 | /// struct Foo; | |
67 | /// | |
68 | /// impl PartialOrd for Foo { | |
69 | /// ... | |
70 | /// } | |
71 | /// ``` | |
72 | /// Use instead: | |
73 | /// ```rust,ignore | |
74 | /// #[derive(PartialEq, Eq)] | |
75 | /// struct Foo; | |
76 | /// | |
77 | /// impl PartialOrd for Foo { | |
78 | /// fn partial_cmp(&self, other: &Foo) -> Option<Ordering> { | |
79 | /// Some(self.cmp(other)) | |
80 | /// } | |
81 | /// } | |
82 | /// | |
83 | /// impl Ord for Foo { | |
84 | /// ... | |
85 | /// } | |
86 | /// ``` | |
87 | /// or, if you don't need a custom ordering: | |
88 | /// ```rust,ignore | |
89 | /// #[derive(Ord, PartialOrd, PartialEq, Eq)] | |
90 | /// struct Foo; | |
91 | /// ``` | |
a2a8927a | 92 | #[clippy::version = "1.47.0"] |
f20569fa XL |
93 | pub DERIVE_ORD_XOR_PARTIAL_ORD, |
94 | correctness, | |
95 | "deriving `Ord` but implementing `PartialOrd` explicitly" | |
96 | } | |
97 | ||
98 | declare_clippy_lint! { | |
94222f64 XL |
99 | /// ### What it does |
100 | /// Checks for explicit `Clone` implementations for `Copy` | |
f20569fa XL |
101 | /// types. |
102 | /// | |
94222f64 XL |
103 | /// ### Why is this bad? |
104 | /// To avoid surprising behaviour, these traits should | |
f20569fa XL |
105 | /// agree and the behaviour of `Copy` cannot be overridden. In almost all |
106 | /// situations a `Copy` type should have a `Clone` implementation that does | |
107 | /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]` | |
108 | /// gets you. | |
109 | /// | |
94222f64 | 110 | /// ### Example |
f20569fa XL |
111 | /// ```rust,ignore |
112 | /// #[derive(Copy)] | |
113 | /// struct Foo; | |
114 | /// | |
115 | /// impl Clone for Foo { | |
116 | /// // .. | |
117 | /// } | |
118 | /// ``` | |
a2a8927a | 119 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
120 | pub EXPL_IMPL_CLONE_ON_COPY, |
121 | pedantic, | |
122 | "implementing `Clone` explicitly on `Copy` types" | |
123 | } | |
124 | ||
125 | declare_clippy_lint! { | |
94222f64 XL |
126 | /// ### What it does |
127 | /// Checks for deriving `serde::Deserialize` on a type that | |
f20569fa XL |
128 | /// has methods using `unsafe`. |
129 | /// | |
94222f64 XL |
130 | /// ### Why is this bad? |
131 | /// Deriving `serde::Deserialize` will create a constructor | |
f20569fa XL |
132 | /// that may violate invariants hold by another constructor. |
133 | /// | |
94222f64 | 134 | /// ### Example |
f20569fa XL |
135 | /// ```rust,ignore |
136 | /// use serde::Deserialize; | |
137 | /// | |
138 | /// #[derive(Deserialize)] | |
139 | /// pub struct Foo { | |
140 | /// // .. | |
141 | /// } | |
142 | /// | |
143 | /// impl Foo { | |
144 | /// pub fn new() -> Self { | |
145 | /// // setup here .. | |
146 | /// } | |
147 | /// | |
148 | /// pub unsafe fn parts() -> (&str, &str) { | |
149 | /// // assumes invariants hold | |
150 | /// } | |
151 | /// } | |
152 | /// ``` | |
a2a8927a | 153 | #[clippy::version = "1.45.0"] |
f20569fa XL |
154 | pub UNSAFE_DERIVE_DESERIALIZE, |
155 | pedantic, | |
156 | "deriving `serde::Deserialize` on a type that has methods using `unsafe`" | |
157 | } | |
158 | ||
159 | declare_lint_pass!(Derive => [ | |
160 | EXPL_IMPL_CLONE_ON_COPY, | |
161 | DERIVE_HASH_XOR_EQ, | |
162 | DERIVE_ORD_XOR_PARTIAL_ORD, | |
163 | UNSAFE_DERIVE_DESERIALIZE | |
164 | ]); | |
165 | ||
166 | impl<'tcx> LateLintPass<'tcx> for Derive { | |
167 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { | |
168 | if let ItemKind::Impl(Impl { | |
169 | of_trait: Some(ref trait_ref), | |
170 | .. | |
171 | }) = item.kind | |
172 | { | |
173 | let ty = cx.tcx.type_of(item.def_id); | |
174 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
175 | let is_automatically_derived = is_automatically_derived(attrs); | |
176 | ||
177 | check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); | |
178 | check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived); | |
179 | ||
180 | if is_automatically_derived { | |
181 | check_unsafe_derive_deserialize(cx, item, trait_ref, ty); | |
182 | } else { | |
183 | check_copy_clone(cx, item, trait_ref, ty); | |
184 | } | |
185 | } | |
186 | } | |
187 | } | |
188 | ||
189 | /// Implementation of the `DERIVE_HASH_XOR_EQ` lint. | |
190 | fn check_hash_peq<'tcx>( | |
191 | cx: &LateContext<'tcx>, | |
192 | span: Span, | |
193 | trait_ref: &TraitRef<'_>, | |
194 | ty: Ty<'tcx>, | |
195 | hash_is_automatically_derived: bool, | |
196 | ) { | |
197 | if_chain! { | |
198 | if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait(); | |
199 | if let Some(def_id) = trait_ref.trait_def_id(); | |
ee023bcb | 200 | if cx.tcx.is_diagnostic_item(sym::Hash, def_id); |
f20569fa XL |
201 | then { |
202 | // Look for the PartialEq implementations for `ty` | |
203 | cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { | |
cdc7bbd5 | 204 | let peq_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id)); |
f20569fa XL |
205 | |
206 | if peq_is_automatically_derived == hash_is_automatically_derived { | |
207 | return; | |
208 | } | |
209 | ||
210 | let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); | |
211 | ||
212 | // Only care about `impl PartialEq<Foo> for Foo` | |
213 | // For `impl PartialEq<B> for A, input_types is [A, B] | |
214 | if trait_ref.substs.type_at(1) == ty { | |
215 | let mess = if peq_is_automatically_derived { | |
216 | "you are implementing `Hash` explicitly but have derived `PartialEq`" | |
217 | } else { | |
218 | "you are deriving `Hash` but have implemented `PartialEq` explicitly" | |
219 | }; | |
220 | ||
221 | span_lint_and_then( | |
222 | cx, | |
223 | DERIVE_HASH_XOR_EQ, | |
224 | span, | |
225 | mess, | |
226 | |diag| { | |
227 | if let Some(local_def_id) = impl_id.as_local() { | |
228 | let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); | |
229 | diag.span_note( | |
230 | cx.tcx.hir().span(hir_id), | |
231 | "`PartialEq` implemented here" | |
232 | ); | |
233 | } | |
234 | } | |
235 | ); | |
236 | } | |
237 | }); | |
238 | } | |
239 | } | |
240 | } | |
241 | ||
242 | /// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint. | |
243 | fn check_ord_partial_ord<'tcx>( | |
244 | cx: &LateContext<'tcx>, | |
245 | span: Span, | |
246 | trait_ref: &TraitRef<'_>, | |
247 | ty: Ty<'tcx>, | |
248 | ord_is_automatically_derived: bool, | |
249 | ) { | |
250 | if_chain! { | |
ee023bcb | 251 | if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord); |
f20569fa XL |
252 | if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait(); |
253 | if let Some(def_id) = &trait_ref.trait_def_id(); | |
254 | if *def_id == ord_trait_def_id; | |
255 | then { | |
256 | // Look for the PartialOrd implementations for `ty` | |
257 | cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { | |
cdc7bbd5 | 258 | let partial_ord_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id)); |
f20569fa XL |
259 | |
260 | if partial_ord_is_automatically_derived == ord_is_automatically_derived { | |
261 | return; | |
262 | } | |
263 | ||
264 | let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); | |
265 | ||
266 | // Only care about `impl PartialOrd<Foo> for Foo` | |
267 | // For `impl PartialOrd<B> for A, input_types is [A, B] | |
268 | if trait_ref.substs.type_at(1) == ty { | |
269 | let mess = if partial_ord_is_automatically_derived { | |
270 | "you are implementing `Ord` explicitly but have derived `PartialOrd`" | |
271 | } else { | |
272 | "you are deriving `Ord` but have implemented `PartialOrd` explicitly" | |
273 | }; | |
274 | ||
275 | span_lint_and_then( | |
276 | cx, | |
277 | DERIVE_ORD_XOR_PARTIAL_ORD, | |
278 | span, | |
279 | mess, | |
280 | |diag| { | |
281 | if let Some(local_def_id) = impl_id.as_local() { | |
282 | let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); | |
283 | diag.span_note( | |
284 | cx.tcx.hir().span(hir_id), | |
285 | "`PartialOrd` implemented here" | |
286 | ); | |
287 | } | |
288 | } | |
289 | ); | |
290 | } | |
291 | }); | |
292 | } | |
293 | } | |
294 | } | |
295 | ||
296 | /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. | |
297 | fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) { | |
cdc7bbd5 XL |
298 | let clone_id = match cx.tcx.lang_items().clone_trait() { |
299 | Some(id) if trait_ref.trait_def_id() == Some(id) => id, | |
300 | _ => return, | |
301 | }; | |
302 | let copy_id = match cx.tcx.lang_items().copy_trait() { | |
303 | Some(id) => id, | |
304 | None => return, | |
305 | }; | |
306 | let (ty_adt, ty_subs) = match *ty.kind() { | |
307 | // Unions can't derive clone. | |
308 | ty::Adt(adt, subs) if !adt.is_union() => (adt, subs), | |
309 | _ => return, | |
310 | }; | |
311 | // If the current self type doesn't implement Copy (due to generic constraints), search to see if | |
312 | // there's a Copy impl for any instance of the adt. | |
313 | if !is_copy(cx, ty) { | |
314 | if ty_subs.non_erasable_generics().next().is_some() { | |
17df50a5 XL |
315 | let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(©_id).map_or(false, |impls| { |
316 | impls | |
317 | .iter() | |
ee023bcb | 318 | .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did())) |
17df50a5 | 319 | }); |
cdc7bbd5 XL |
320 | if !has_copy_impl { |
321 | return; | |
322 | } | |
323 | } else { | |
f20569fa XL |
324 | return; |
325 | } | |
f20569fa | 326 | } |
cdc7bbd5 XL |
327 | // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for |
328 | // this impl. | |
329 | if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) { | |
330 | return; | |
331 | } | |
332 | ||
333 | span_lint_and_note( | |
334 | cx, | |
335 | EXPL_IMPL_CLONE_ON_COPY, | |
336 | item.span, | |
337 | "you are implementing `Clone` explicitly on a `Copy` type", | |
338 | Some(item.span), | |
339 | "consider deriving `Clone` or removing `Copy`", | |
340 | ); | |
f20569fa XL |
341 | } |
342 | ||
343 | /// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. | |
344 | fn check_unsafe_derive_deserialize<'tcx>( | |
345 | cx: &LateContext<'tcx>, | |
346 | item: &Item<'_>, | |
347 | trait_ref: &TraitRef<'_>, | |
348 | ty: Ty<'tcx>, | |
349 | ) { | |
f20569fa XL |
350 | fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { |
351 | let mut visitor = UnsafeVisitor { cx, has_unsafe: false }; | |
352 | walk_item(&mut visitor, item); | |
353 | visitor.has_unsafe | |
354 | } | |
355 | ||
356 | if_chain! { | |
357 | if let Some(trait_def_id) = trait_ref.trait_def_id(); | |
358 | if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE); | |
359 | if let ty::Adt(def, _) = ty.kind(); | |
ee023bcb | 360 | if let Some(local_def_id) = def.did().as_local(); |
f20569fa | 361 | let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); |
136023e0 | 362 | if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id); |
ee023bcb | 363 | if cx.tcx.inherent_impls(def.did()) |
f20569fa | 364 | .iter() |
a2a8927a | 365 | .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local())) |
f20569fa XL |
366 | .any(|imp| has_unsafe(cx, imp)); |
367 | then { | |
368 | span_lint_and_help( | |
369 | cx, | |
370 | UNSAFE_DERIVE_DESERIALIZE, | |
371 | item.span, | |
372 | "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", | |
373 | None, | |
374 | "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html" | |
375 | ); | |
376 | } | |
377 | } | |
378 | } | |
379 | ||
380 | struct UnsafeVisitor<'a, 'tcx> { | |
381 | cx: &'a LateContext<'tcx>, | |
382 | has_unsafe: bool, | |
383 | } | |
384 | ||
385 | impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { | |
5099ac24 | 386 | type NestedFilter = nested_filter::All; |
f20569fa XL |
387 | |
388 | fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, span: Span, id: HirId) { | |
389 | if self.has_unsafe { | |
390 | return; | |
391 | } | |
392 | ||
393 | if_chain! { | |
394 | if let Some(header) = kind.header(); | |
c295e0f8 | 395 | if header.unsafety == Unsafety::Unsafe; |
f20569fa XL |
396 | then { |
397 | self.has_unsafe = true; | |
398 | } | |
399 | } | |
400 | ||
401 | walk_fn(self, kind, decl, body_id, span, id); | |
402 | } | |
403 | ||
404 | fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
405 | if self.has_unsafe { | |
406 | return; | |
407 | } | |
408 | ||
409 | if let ExprKind::Block(block, _) = expr.kind { | |
c295e0f8 | 410 | if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { |
136023e0 | 411 | self.has_unsafe = true; |
f20569fa XL |
412 | } |
413 | } | |
414 | ||
415 | walk_expr(self, expr); | |
416 | } | |
417 | ||
5099ac24 FG |
418 | fn nested_visit_map(&mut self) -> Self::Map { |
419 | self.cx.tcx.hir() | |
f20569fa XL |
420 | } |
421 | } |