-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathnew_without_default.rs
More file actions
214 lines (204 loc) · 9.18 KB
/
new_without_default.rs
File metadata and controls
214 lines (204 loc) · 9.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::return_ty;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability};
use clippy_utils::sugg::DiagExt;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{Attribute, HirIdSet};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::AssocKind;
use rustc_session::impl_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for public types with a `pub fn new() -> Self` method and no
/// implementation of
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html).
///
/// ### Why is this bad?
/// The user might expect to be able to use
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
/// type can be constructed without arguments.
///
/// ### Example
/// ```ignore
/// pub struct Foo(Bar);
///
/// impl Foo {
/// pub fn new() -> Self {
/// Foo(Bar::new())
/// }
/// }
/// ```
///
/// To fix the lint, add a `Default` implementation that delegates to `new`:
///
/// ```ignore
/// pub struct Foo(Bar);
///
/// impl Default for Foo {
/// fn default() -> Self {
/// Foo::new()
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub NEW_WITHOUT_DEFAULT,
style,
"`pub fn new() -> Self` method without `Default` implementation"
}
impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]);
#[derive(Clone, Default)]
pub struct NewWithoutDefault {
impling_types: Option<HirIdSet>,
}
impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
#[expect(clippy::too_many_lines)]
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let hir::ItemKind::Impl(hir::Impl {
of_trait: None,
generics,
self_ty: impl_self_ty,
..
}) = item.kind
else {
return;
};
for assoc_item in cx
.tcx
.associated_items(item.owner_id.def_id)
.filter_by_name_unhygienic(sym::new)
{
if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind
&& let assoc_item_hir_id = cx.tcx.local_def_id_to_hir_id(assoc_item.def_id.expect_local())
&& let impl_item = cx.tcx.hir_node(assoc_item_hir_id).expect_impl_item()
&& !impl_item.span.in_external_macro(cx.sess().source_map())
&& let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind
&& let id = impl_item.owner_id
// can't be implemented for unsafe new
&& !sig.header.is_unsafe()
// shouldn't be implemented when it is hidden in docs
&& !cx.tcx.is_doc_hidden(impl_item.owner_id.def_id)
// when the result of `new()` depends on a parameter we should not require
// an impl of `Default`
&& impl_item.generics.params.is_empty()
&& sig.decl.inputs.is_empty()
&& cx.effective_visibilities.is_exported(impl_item.owner_id.def_id)
&& let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& self_ty == return_ty(cx, impl_item.owner_id)
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
{
if self.impling_types.is_none() {
let mut impls = HirIdSet::default();
for &d in cx.tcx.local_trait_impls(default_trait_id) {
let ty = cx.tcx.type_of(d).instantiate_identity();
if let Some(ty_def) = ty.ty_adt_def()
&& let Some(local_def_id) = ty_def.did().as_local()
{
impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id));
}
}
self.impling_types = Some(impls);
}
// Check if a Default implementation exists for the Self type, regardless of
// generics
if let Some(ref impling_types) = self.impling_types
&& let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& let Some(self_def) = self_def.ty_adt_def()
&& let Some(self_local_did) = self_def.did().as_local()
&& let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did)
&& impling_types.contains(&self_id)
{
return;
}
let mut app = Applicability::MachineApplicable;
let attrs_sugg = {
let mut sugg = String::new();
for attr in cx.tcx.hir_attrs(assoc_item_hir_id) {
let Attribute::Parsed(AttributeKind::CfgTrace(attrs)) = attr else {
// This might be some other attribute that the `impl Default` ought to inherit.
// But it could also be one of the many attributes that:
// - can't be put on an impl block -- like `#[inline]`
// - we can't even build a suggestion for, since `Attribute::span` may panic.
//
// Because of all that, remain on the safer side -- don't inherit this attr, and just
// reduce the applicability
app = Applicability::MaybeIncorrect;
continue;
};
for (_, attr_span) in attrs {
sugg.push_str(&snippet_with_applicability(cx.sess(), *attr_span, "_", &mut app));
sugg.push('\n');
}
}
sugg
};
let generics_sugg = snippet_with_applicability(cx, generics.span, "", &mut app);
let where_clause_sugg = if generics.has_where_clause_predicates {
let where_clause_sugg =
snippet_with_applicability(cx, generics.where_clause_span, "", &mut app).to_string();
let mut where_clause_sugg = reindent_multiline(&where_clause_sugg, true, Some(4));
if impl_item.generics.has_where_clause_predicates {
if !where_clause_sugg.ends_with(',') {
where_clause_sugg.push(',');
}
let additional_where_preds =
snippet_with_applicability(cx, impl_item.generics.where_clause_span, "", &mut app);
let ident = indent_of(cx, generics.where_clause_span).unwrap_or(0);
// Remove the leading `where ` keyword
let additional_where_preds = additional_where_preds.trim_start_matches("where").trim_start();
where_clause_sugg.push('\n');
where_clause_sugg.extend(std::iter::repeat_n(' ', ident));
where_clause_sugg.push_str(additional_where_preds);
}
format!("\n{where_clause_sugg}\n")
} else if impl_item.generics.has_where_clause_predicates {
let where_clause_sugg =
snippet_with_applicability(cx, impl_item.generics.where_clause_span, "", &mut app);
let where_clause_sugg = reindent_multiline(&where_clause_sugg, true, Some(4));
format!("\n{}\n", where_clause_sugg.trim_start())
} else {
String::new()
};
let self_ty_fmt = self_ty.to_string();
let self_type_snip = snippet_with_applicability(cx, impl_self_ty.span, &self_ty_fmt, &mut app);
span_lint_hir_and_then(
cx,
NEW_WITHOUT_DEFAULT,
id.into(),
impl_item.span,
format!("you should consider adding a `Default` implementation for `{self_type_snip}`"),
|diag| {
diag.suggest_prepend_item(
cx,
item.span,
"try adding this",
&create_new_without_default_suggest_msg(
&attrs_sugg,
&self_type_snip,
&generics_sugg,
&where_clause_sugg,
),
app,
);
},
);
}
}
}
}
fn create_new_without_default_suggest_msg(
attrs_sugg: &str,
self_type_snip: &str,
generics_sugg: &str,
where_clause_sugg: &str,
) -> String {
#[rustfmt::skip]
format!(
"{attrs_sugg}impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
fn default() -> Self {{
Self::new()
}}
}}")
}