]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/new_without_default.rs
Auto merge of #3570 - muth:master, r=phansch
[rust.git] / clippy_lints / src / new_without_default.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 use crate::rustc::hir;
11 use crate::rustc::hir::def_id::DefId;
12 use crate::rustc::lint::{in_external_macro, LateContext, LateLintPass, LintArray, LintContext, LintPass};
13 use crate::rustc::ty::{self, Ty};
14 use crate::rustc::util::nodemap::NodeSet;
15 use crate::rustc::{declare_tool_lint, lint_array};
16 use crate::rustc_errors::Applicability;
17 use crate::syntax::source_map::Span;
18 use crate::utils::paths;
19 use crate::utils::sugg::DiagnosticBuilderExt;
20 use crate::utils::{get_trait_def_id, implements_trait, return_ty, same_tys, span_lint_node_and_then};
21 use if_chain::if_chain;
22
23 /// **What it does:** Checks for types with a `fn new() -> Self` method and no
24 /// implementation of
25 /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html).
26 ///
27 /// **Why is this bad?** The user might expect to be able to use
28 /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
29 /// type can be constructed without arguments.
30 ///
31 /// **Known problems:** Hopefully none.
32 ///
33 /// **Example:**
34 ///
35 /// ```rust
36 /// struct Foo(Bar);
37 ///
38 /// impl Foo {
39 ///     fn new() -> Self {
40 ///         Foo(Bar::new())
41 ///     }
42 /// }
43 /// ```
44 ///
45 /// Instead, use:
46 ///
47 /// ```rust
48 /// struct Foo(Bar);
49 ///
50 /// impl Default for Foo {
51 ///     fn default() -> Self {
52 ///         Foo(Bar::new())
53 ///     }
54 /// }
55 /// ```
56 ///
57 /// You can also have `new()` call `Default::default()`.
58 declare_clippy_lint! {
59     pub NEW_WITHOUT_DEFAULT,
60     style,
61     "`fn new() -> Self` method without `Default` implementation"
62 }
63
64 /// **What it does:** Checks for types with a `fn new() -> Self` method
65 /// and no implementation of
66 /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html),
67 /// where the `Default` can be derived by `#[derive(Default)]`.
68 ///
69 /// **Why is this bad?** The user might expect to be able to use
70 /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
71 /// type can be constructed without arguments.
72 ///
73 /// **Known problems:** Hopefully none.
74 ///
75 /// **Example:**
76 ///
77 /// ```rust
78 /// struct Foo;
79 ///
80 /// impl Foo {
81 ///     fn new() -> Self {
82 ///         Foo
83 ///     }
84 /// }
85 /// ```
86 ///
87 /// Just prepend `#[derive(Default)]` before the `struct` definition.
88 declare_clippy_lint! {
89     pub NEW_WITHOUT_DEFAULT_DERIVE,
90     style,
91     "`fn new() -> Self` without `#[derive]`able `Default` implementation"
92 }
93
94 #[derive(Clone, Default)]
95 pub struct NewWithoutDefault {
96     impling_types: Option<NodeSet>,
97 }
98
99 impl LintPass for NewWithoutDefault {
100     fn get_lints(&self) -> LintArray {
101         lint_array!(NEW_WITHOUT_DEFAULT, NEW_WITHOUT_DEFAULT_DERIVE)
102     }
103 }
104
105 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NewWithoutDefault {
106     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
107         if let hir::ItemKind::Impl(_, _, _, _, None, _, ref items) = item.node {
108             for assoc_item in items {
109                 if let hir::AssociatedItemKind::Method { has_self: false } = assoc_item.kind {
110                     let impl_item = cx.tcx.hir().impl_item(assoc_item.id);
111                     if in_external_macro(cx.sess(), impl_item.span) {
112                         return;
113                     }
114                     if let hir::ImplItemKind::Method(ref sig, _) = impl_item.node {
115                         let name = impl_item.ident.name;
116                         let id = impl_item.id;
117                         if sig.header.constness == hir::Constness::Const {
118                             // can't be implemented by default
119                             return;
120                         }
121                         if sig.header.unsafety == hir::Unsafety::Unsafe {
122                             // can't be implemented for unsafe new
123                             return;
124                         }
125                         if impl_item.generics.params.iter().any(|gen| match gen.kind {
126                             hir::GenericParamKind::Type { .. } => true,
127                             _ => false,
128                         }) {
129                             // when the result of `new()` depends on a type parameter we should not require
130                             // an
131                             // impl of `Default`
132                             return;
133                         }
134                         if sig.decl.inputs.is_empty() && name == "new" && cx.access_levels.is_reachable(id) {
135                             let self_did = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent(id));
136                             let self_ty = cx.tcx.type_of(self_did);
137                             if_chain! {
138                                 if same_tys(cx, self_ty, return_ty(cx, id));
139                                 if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT);
140                                 then {
141                                     if self.impling_types.is_none() {
142                                         let mut impls = NodeSet::default();
143                                         cx.tcx.for_each_impl(default_trait_id, |d| {
144                                             if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
145                                                 if let Some(node_id) = cx.tcx.hir().as_local_node_id(ty_def.did) {
146                                                     impls.insert(node_id);
147                                                 }
148                                             }
149                                         });
150                                         self.impling_types = Some(impls);
151                                     }
152
153                                     // Check if a Default implementation exists for the Self type, regardless of
154                                     // generics
155                                     if_chain! {
156                                         if let Some(ref impling_types) = self.impling_types;
157                                         if let Some(self_def) = cx.tcx.type_of(self_did).ty_adt_def();
158                                         if self_def.did.is_local();
159                                         then {
160                                             let self_id = cx.tcx.hir().local_def_id_to_node_id(self_def.did.to_local());
161                                             if impling_types.contains(&self_id) {
162                                                 return;
163                                             }
164                                         }
165                                     }
166
167                                     if let Some(sp) = can_derive_default(self_ty, cx, default_trait_id) {
168                                         span_lint_node_and_then(
169                                             cx,
170                                             NEW_WITHOUT_DEFAULT_DERIVE,
171                                             id,
172                                             impl_item.span,
173                                             &format!(
174                                                 "you should consider deriving a `Default` implementation for `{}`",
175                                                 self_ty
176                                             ),
177                                             |db| {
178                                                 db.suggest_item_with_attr(
179                                                     cx,
180                                                     sp,
181                                                     "try this",
182                                                     "#[derive(Default)]",
183                                                     Applicability::MaybeIncorrect,
184                                                 );
185                                             });
186                                     } else {
187                                         span_lint_node_and_then(
188                                             cx,
189                                             NEW_WITHOUT_DEFAULT,
190                                             id,
191                                             impl_item.span,
192                                             &format!(
193                                                 "you should consider adding a `Default` implementation for `{}`",
194                                                 self_ty
195                                             ),
196                                             |db| {
197                                                 db.suggest_prepend_item(
198                                                     cx,
199                                                     item.span,
200                                                     "try this",
201                                                     &create_new_without_default_suggest_msg(self_ty),
202                                                     Applicability::MaybeIncorrect,
203                                                 );
204                                             },
205                                         );
206                                     }
207                                 }
208                             }
209                         }
210                     }
211                 }
212             }
213         }
214     }
215 }
216
217 fn create_new_without_default_suggest_msg(ty: Ty<'_>) -> String {
218     #[rustfmt::skip]
219     format!(
220 "impl Default for {} {{
221     fn default() -> Self {{
222         Self::new()
223     }}
224 }}", ty)
225 }
226
227 fn can_derive_default<'t, 'c>(ty: Ty<'t>, cx: &LateContext<'c, 't>, default_trait_id: DefId) -> Option<Span> {
228     match ty.sty {
229         ty::Adt(adt_def, substs) if adt_def.is_struct() => {
230             for field in adt_def.all_fields() {
231                 let f_ty = field.ty(cx.tcx, substs);
232                 if !implements_trait(cx, f_ty, default_trait_id, &[]) {
233                     return None;
234                 }
235             }
236             Some(cx.tcx.def_span(adt_def.did))
237         },
238         _ => None,
239     }
240 }