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