]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::utils::paths; |
2 | use crate::utils::sugg::DiagnosticBuilderExt; | |
3 | use crate::utils::{get_trait_def_id, return_ty, span_lint_hir_and_then}; | |
4 | use if_chain::if_chain; | |
5 | use rustc_errors::Applicability; | |
6 | use rustc_hir as hir; | |
7 | use rustc_hir::HirIdSet; | |
8 | use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
9 | use rustc_middle::lint::in_external_macro; | |
10 | use rustc_middle::ty::{Ty, TyS}; | |
11 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
12 | use rustc_span::sym; | |
13 | ||
14 | declare_clippy_lint! { | |
15 | /// **What it does:** Checks for types with a `fn new() -> Self` method and no | |
16 | /// implementation of | |
17 | /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html). | |
18 | /// | |
19 | /// **Why is this bad?** The user might expect to be able to use | |
20 | /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the | |
21 | /// type can be constructed without arguments. | |
22 | /// | |
23 | /// **Known problems:** Hopefully none. | |
24 | /// | |
25 | /// **Example:** | |
26 | /// | |
27 | /// ```ignore | |
28 | /// struct Foo(Bar); | |
29 | /// | |
30 | /// impl Foo { | |
31 | /// fn new() -> Self { | |
32 | /// Foo(Bar::new()) | |
33 | /// } | |
34 | /// } | |
35 | /// ``` | |
36 | /// | |
37 | /// To fix the lint, add a `Default` implementation that delegates to `new`: | |
38 | /// | |
39 | /// ```ignore | |
40 | /// struct Foo(Bar); | |
41 | /// | |
42 | /// impl Default for Foo { | |
43 | /// fn default() -> Self { | |
44 | /// Foo::new() | |
45 | /// } | |
46 | /// } | |
47 | /// ``` | |
48 | pub NEW_WITHOUT_DEFAULT, | |
49 | style, | |
50 | "`fn new() -> Self` method without `Default` implementation" | |
51 | } | |
52 | ||
53 | #[derive(Clone, Default)] | |
54 | pub struct NewWithoutDefault { | |
55 | impling_types: Option<HirIdSet>, | |
56 | } | |
57 | ||
58 | impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]); | |
59 | ||
60 | impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { | |
61 | #[allow(clippy::too_many_lines)] | |
62 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { | |
63 | if let hir::ItemKind::Impl(hir::Impl { | |
64 | of_trait: None, items, .. | |
65 | }) = item.kind | |
66 | { | |
67 | for assoc_item in items { | |
68 | if let hir::AssocItemKind::Fn { has_self: false } = assoc_item.kind { | |
69 | let impl_item = cx.tcx.hir().impl_item(assoc_item.id); | |
70 | if in_external_macro(cx.sess(), impl_item.span) { | |
71 | return; | |
72 | } | |
73 | if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind { | |
74 | let name = impl_item.ident.name; | |
75 | let id = impl_item.hir_id(); | |
76 | if sig.header.constness == hir::Constness::Const { | |
77 | // can't be implemented by default | |
78 | return; | |
79 | } | |
80 | if sig.header.unsafety == hir::Unsafety::Unsafe { | |
81 | // can't be implemented for unsafe new | |
82 | return; | |
83 | } | |
84 | if impl_item | |
85 | .generics | |
86 | .params | |
87 | .iter() | |
88 | .any(|gen| matches!(gen.kind, hir::GenericParamKind::Type { .. })) | |
89 | { | |
90 | // when the result of `new()` depends on a type parameter we should not require | |
91 | // an | |
92 | // impl of `Default` | |
93 | return; | |
94 | } | |
95 | if sig.decl.inputs.is_empty() && name == sym::new && cx.access_levels.is_reachable(id) { | |
96 | let self_def_id = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent_item(id)); | |
97 | let self_ty = cx.tcx.type_of(self_def_id); | |
98 | if_chain! { | |
99 | if TyS::same_type(self_ty, return_ty(cx, id)); | |
100 | if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); | |
101 | then { | |
102 | if self.impling_types.is_none() { | |
103 | let mut impls = HirIdSet::default(); | |
104 | cx.tcx.for_each_impl(default_trait_id, |d| { | |
105 | if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() { | |
106 | if let Some(local_def_id) = ty_def.did.as_local() { | |
107 | impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id)); | |
108 | } | |
109 | } | |
110 | }); | |
111 | self.impling_types = Some(impls); | |
112 | } | |
113 | ||
114 | // Check if a Default implementation exists for the Self type, regardless of | |
115 | // generics | |
116 | if_chain! { | |
117 | if let Some(ref impling_types) = self.impling_types; | |
118 | if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def(); | |
119 | if let Some(self_local_did) = self_def.did.as_local(); | |
120 | then { | |
121 | let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did); | |
122 | if impling_types.contains(&self_id) { | |
123 | return; | |
124 | } | |
125 | } | |
126 | } | |
127 | ||
128 | span_lint_hir_and_then( | |
129 | cx, | |
130 | NEW_WITHOUT_DEFAULT, | |
131 | id, | |
132 | impl_item.span, | |
133 | &format!( | |
134 | "you should consider adding a `Default` implementation for `{}`", | |
135 | self_ty | |
136 | ), | |
137 | |diag| { | |
138 | diag.suggest_prepend_item( | |
139 | cx, | |
140 | item.span, | |
141 | "try this", | |
142 | &create_new_without_default_suggest_msg(self_ty), | |
143 | Applicability::MaybeIncorrect, | |
144 | ); | |
145 | }, | |
146 | ); | |
147 | } | |
148 | } | |
149 | } | |
150 | } | |
151 | } | |
152 | } | |
153 | } | |
154 | } | |
155 | } | |
156 | ||
157 | fn create_new_without_default_suggest_msg(ty: Ty<'_>) -> String { | |
158 | #[rustfmt::skip] | |
159 | format!( | |
160 | "impl Default for {} {{ | |
161 | fn default() -> Self {{ | |
162 | Self::new() | |
163 | }} | |
164 | }}", ty) | |
165 | } |