1
1
use clippy_utils:: diagnostics:: span_lint_hir_and_then;
2
- use clippy_utils:: return_ty;
3
- use clippy_utils:: source:: snippet;
2
+ use clippy_utils:: source:: { snippet, trim_span} ;
4
3
use clippy_utils:: sugg:: DiagExt ;
4
+ use clippy_utils:: { is_default_equivalent_call, return_ty} ;
5
5
use rustc_errors:: Applicability ;
6
6
use rustc_hir as hir;
7
- use rustc_hir:: HirIdSet ;
7
+ use rustc_hir:: HirIdMap ;
8
8
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9
+ use rustc_middle:: ty:: { Adt , Ty , VariantDef } ;
9
10
use rustc_session:: impl_lint_pass;
10
- use rustc_span:: sym;
11
+ use rustc_span:: { BytePos , Pos as _ , Span , sym} ;
11
12
12
13
declare_clippy_lint ! {
13
14
/// ### What it does
@@ -48,12 +49,75 @@ declare_clippy_lint! {
48
49
"`pub fn new() -> Self` method without `Default` implementation"
49
50
}
50
51
52
+ declare_clippy_lint ! {
53
+ /// ### What it does
54
+ /// If a type has an auto-derived `Default` trait and a `fn new() -> Self`,
55
+ /// this lint checks if the `new()` method performs custom logic rather
56
+ /// than simply calling the `default()` method.
57
+ ///
58
+ /// ### Why is this bad?
59
+ /// Users expect the `new()` method to be equivalent to `default()`,
60
+ /// so if the `Default` trait is auto-derived, the `new()` method should
61
+ /// not perform custom logic. Otherwise, there is a risk of different
62
+ /// behavior between the two instantiation methods.
63
+ ///
64
+ /// ### Example
65
+ /// ```no_run
66
+ /// #[derive(Default)]
67
+ /// struct MyStruct(i32);
68
+ /// impl MyStruct {
69
+ /// fn new() -> Self {
70
+ /// Self(42)
71
+ /// }
72
+ /// }
73
+ /// ```
74
+ ///
75
+ /// Users are unlikely to notice that `MyStruct::new()` and `MyStruct::default()` would produce
76
+ /// different results. The `new()` method should use auto-derived `default()` instead to be consistent:
77
+ ///
78
+ /// ```no_run
79
+ /// #[derive(Default)]
80
+ /// struct MyStruct(i32);
81
+ /// impl MyStruct {
82
+ /// fn new() -> Self {
83
+ /// Self::default()
84
+ /// }
85
+ /// }
86
+ /// ```
87
+ ///
88
+ /// Alternatively, if the `new()` method requires a non-default initialization, implement a custom `Default`.
89
+ /// This also allows you to mark the `new()` implementation as `const`:
90
+ ///
91
+ /// ```no_run
92
+ /// struct MyStruct(i32);
93
+ /// impl MyStruct {
94
+ /// const fn new() -> Self {
95
+ /// Self(42)
96
+ /// }
97
+ /// }
98
+ /// impl Default for MyStruct {
99
+ /// fn default() -> Self {
100
+ /// Self::new()
101
+ /// }
102
+ /// }
103
+ #[ clippy:: version = "1.86.0" ]
104
+ pub DEFAULT_MISMATCHES_NEW ,
105
+ suspicious,
106
+ "`fn new() -> Self` method does not forward to auto-derived `Default` implementation"
107
+ }
108
+
109
+ #[ derive( Debug , Clone , Copy ) ]
110
+ enum DefaultType {
111
+ AutoDerived ,
112
+ Manual ,
113
+ }
114
+
51
115
#[ derive( Clone , Default ) ]
52
116
pub struct NewWithoutDefault {
53
- impling_types : Option < HirIdSet > ,
117
+ impling_types : Option < HirIdMap < DefaultType > > ,
54
118
}
55
119
56
- impl_lint_pass ! ( NewWithoutDefault => [ NEW_WITHOUT_DEFAULT ] ) ;
120
+ impl_lint_pass ! ( NewWithoutDefault => [ NEW_WITHOUT_DEFAULT , DEFAULT_MISMATCHES_NEW ] ) ;
57
121
58
122
impl < ' tcx > LateLintPass < ' tcx > for NewWithoutDefault {
59
123
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
@@ -71,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
71
135
if impl_item. span . in_external_macro ( cx. sess ( ) . source_map ( ) ) {
72
136
return ;
73
137
}
74
- if let hir:: ImplItemKind :: Fn ( ref sig, _ ) = impl_item. kind {
138
+ if let hir:: ImplItemKind :: Fn ( ref sig, body_id ) = impl_item. kind {
75
139
let name = impl_item. ident . name ;
76
140
let id = impl_item. owner_id ;
77
141
if sig. header . is_unsafe ( ) {
@@ -89,65 +153,61 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
89
153
}
90
154
if sig. decl . inputs . is_empty ( )
91
155
&& name == sym:: new
92
- && cx. effective_visibilities . is_reachable ( impl_item. owner_id . def_id )
93
156
&& let self_def_id = cx. tcx . hir ( ) . get_parent_item ( id. into ( ) )
94
157
&& let self_ty = cx. tcx . type_of ( self_def_id) . instantiate_identity ( )
95
158
&& self_ty == return_ty ( cx, id)
96
159
&& let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
97
160
{
98
161
if self . impling_types . is_none ( ) {
99
- let mut impls = HirIdSet :: default ( ) ;
162
+ let mut impls = HirIdMap :: default ( ) ;
100
163
cx. tcx . for_each_impl ( default_trait_id, |d| {
101
164
let ty = cx. tcx . type_of ( d) . instantiate_identity ( ) ;
102
165
if let Some ( ty_def) = ty. ty_adt_def ( ) {
103
166
if let Some ( local_def_id) = ty_def. did ( ) . as_local ( ) {
104
- impls. insert ( cx. tcx . local_def_id_to_hir_id ( local_def_id) ) ;
167
+ impls. insert (
168
+ cx. tcx . local_def_id_to_hir_id ( local_def_id) ,
169
+ if cx. tcx . is_builtin_derived ( d) {
170
+ DefaultType :: AutoDerived
171
+ } else {
172
+ DefaultType :: Manual
173
+ } ,
174
+ ) ;
105
175
}
106
176
}
107
177
} ) ;
108
178
self . impling_types = Some ( impls) ;
109
179
}
110
180
111
- // Check if a Default implementation exists for the Self type, regardless of
112
- // generics
181
+ let mut default_type = None ;
182
+ // Check if a Default implementation exists for the Self type, regardless of generics
113
183
if let Some ( ref impling_types) = self . impling_types
114
184
&& let self_def = cx. tcx . type_of ( self_def_id) . instantiate_identity ( )
115
185
&& let Some ( self_def) = self_def. ty_adt_def ( )
116
186
&& let Some ( self_local_did) = self_def. did ( ) . as_local ( )
117
- && let self_id = cx. tcx . local_def_id_to_hir_id ( self_local_did)
118
- && impling_types. contains ( & self_id)
119
187
{
120
- return ;
188
+ let self_id = cx. tcx . local_def_id_to_hir_id ( self_local_did) ;
189
+ default_type = impling_types. get ( & self_id) ;
190
+ if let Some ( DefaultType :: Manual ) = default_type {
191
+ // both `new` and `default` are manually implemented
192
+ return ;
193
+ }
121
194
}
122
195
123
- let generics_sugg = snippet ( cx, generics. span , "" ) ;
124
- let where_clause_sugg = if generics. has_where_clause_predicates {
125
- format ! ( "\n {}\n " , snippet( cx, generics. where_clause_span, "" ) )
126
- } else {
127
- String :: new ( )
128
- } ;
129
- let self_ty_fmt = self_ty. to_string ( ) ;
130
- let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
131
- span_lint_hir_and_then (
132
- cx,
133
- NEW_WITHOUT_DEFAULT ,
134
- id. into ( ) ,
135
- impl_item. span ,
136
- format ! ( "you should consider adding a `Default` implementation for `{self_type_snip}`" ) ,
137
- |diag| {
138
- diag. suggest_prepend_item (
139
- cx,
140
- item. span ,
141
- "try adding this" ,
142
- & create_new_without_default_suggest_msg (
143
- & self_type_snip,
144
- & generics_sugg,
145
- & where_clause_sugg,
146
- ) ,
147
- Applicability :: MachineApplicable ,
148
- ) ;
149
- } ,
150
- ) ;
196
+ if default_type. is_none ( ) {
197
+ // there are no `Default` implementations for this type
198
+ if !cx. effective_visibilities . is_reachable ( impl_item. owner_id . def_id ) {
199
+ return ;
200
+ }
201
+ suggest_new_without_default ( cx, item, impl_item, id, self_ty, generics, impl_self_ty) ;
202
+ } else if let hir:: ExprKind :: Block ( block, _) = cx. tcx . hir_body ( body_id) . value . kind
203
+ && !is_unit_struct ( cx, self_ty)
204
+ // TODO: handle generics
205
+ && generics. params . is_empty ( )
206
+ // check if `new` and `default` are equivalent
207
+ && let Some ( span) = check_block_calls_default ( cx, block)
208
+ {
209
+ suggest_default_mismatch_new ( cx, span, id, block, self_ty, impl_self_ty) ;
210
+ }
151
211
}
152
212
}
153
213
}
@@ -156,16 +216,128 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
156
216
}
157
217
}
158
218
159
- fn create_new_without_default_suggest_msg (
160
- self_type_snip : & str ,
161
- generics_sugg : & str ,
162
- where_clause_sugg : & str ,
163
- ) -> String {
164
- #[ rustfmt:: skip]
165
- format ! (
166
- "impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
219
+ // Check if Self is a unit struct, and avoid any kind of suggestions
220
+ // FIXME: this was copied from DefaultConstructedUnitStructs,
221
+ // and should be refactored into a common function
222
+ fn is_unit_struct ( _cx : & LateContext < ' _ > , ty : Ty < ' _ > ) -> bool {
223
+ if let Adt ( def, ..) = ty. kind ( )
224
+ && def. is_struct ( )
225
+ && let var @ VariantDef {
226
+ ctor : Some ( ( hir:: def:: CtorKind :: Const , _) ) ,
227
+ ..
228
+ } = def. non_enum_variant ( )
229
+ && !var. is_field_list_non_exhaustive ( )
230
+ {
231
+ true
232
+ } else {
233
+ false
234
+ }
235
+ }
236
+
237
+ /// Check if a block contains one of these:
238
+ /// - Empty block with an expr (e.g., `{ Self::default() }`)
239
+ /// - One statement (e.g., `{ return Self::default(); }`)
240
+ fn check_block_calls_default ( cx : & LateContext < ' _ > , block : & hir:: Block < ' _ > ) -> Option < Span > {
241
+ if let Some ( expr) = block. expr
242
+ && block. stmts . is_empty ( )
243
+ && check_expr_call_default ( cx, expr)
244
+ {
245
+ // Block only has a trailing expression, e.g. `Self::default()`
246
+ return None ;
247
+ } else if let [ hir:: Stmt { kind, .. } ] = block. stmts
248
+ && let hir:: StmtKind :: Expr ( expr) | hir:: StmtKind :: Semi ( expr) = kind
249
+ && let hir:: ExprKind :: Ret ( Some ( ret_expr) ) = expr. kind
250
+ && check_expr_call_default ( cx, ret_expr)
251
+ {
252
+ // Block has a single statement, e.g. `return Self::default();`
253
+ return None ;
254
+ }
255
+
256
+ // trim first and last character, and trim spaces
257
+ let mut span = block. span ;
258
+ span = span. with_lo ( span. lo ( ) + BytePos :: from_usize ( 1 ) ) ;
259
+ span = span. with_hi ( span. hi ( ) - BytePos :: from_usize ( 1 ) ) ;
260
+ span = trim_span ( cx. sess ( ) . source_map ( ) , span) ;
261
+
262
+ Some ( span)
263
+ }
264
+
265
+ /// Check for `Self::default()` call syntax or equivalent
266
+ fn check_expr_call_default ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> bool {
267
+ if let hir:: ExprKind :: Call ( callee, & [ ] ) = expr. kind
268
+ // FIXME: does this include `Self { }` style calls, which is equivalent,
269
+ // but not the same as `Self::default()`?
270
+ // FIXME: what should the whole_call_expr (3rd arg) be?
271
+ && is_default_equivalent_call ( cx, callee, None )
272
+ {
273
+ true
274
+ } else {
275
+ false
276
+ }
277
+ }
278
+
279
+ fn suggest_default_mismatch_new < ' tcx > (
280
+ cx : & LateContext < ' tcx > ,
281
+ span : Span ,
282
+ id : rustc_hir:: OwnerId ,
283
+ block : & rustc_hir:: Block < ' _ > ,
284
+ self_ty : Ty < ' tcx > ,
285
+ impl_self_ty : & rustc_hir:: Ty < ' _ > ,
286
+ ) {
287
+ let self_ty_fmt = self_ty. to_string ( ) ;
288
+ let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
289
+ span_lint_hir_and_then (
290
+ cx,
291
+ DEFAULT_MISMATCHES_NEW ,
292
+ id. into ( ) ,
293
+ block. span ,
294
+ format ! ( "consider delegating to the auto-derived `Default` for `{self_type_snip}`" ) ,
295
+ |diag| {
296
+ // This would replace any comments, and we could work around the first comment,
297
+ // but in case of a block of code with multiple statements and comment lines,
298
+ // we can't do much. For now, we always mark this as a MaybeIncorrect suggestion.
299
+ diag. span_suggestion ( span, "use" , "Self::default()" , Applicability :: Unspecified ) ;
300
+ } ,
301
+ ) ;
302
+ }
303
+
304
+ fn suggest_new_without_default < ' tcx > (
305
+ cx : & LateContext < ' tcx > ,
306
+ item : & hir:: Item < ' _ > ,
307
+ impl_item : & hir:: ImplItem < ' _ > ,
308
+ id : hir:: OwnerId ,
309
+ self_ty : Ty < ' tcx > ,
310
+ generics : & hir:: Generics < ' _ > ,
311
+ impl_self_ty : & hir:: Ty < ' _ > ,
312
+ ) {
313
+ let generics_sugg = snippet ( cx, generics. span , "" ) ;
314
+ let where_clause_sugg = if generics. has_where_clause_predicates {
315
+ format ! ( "\n {}\n " , snippet( cx, generics. where_clause_span, "" ) )
316
+ } else {
317
+ String :: new ( )
318
+ } ;
319
+ let self_ty_fmt = self_ty. to_string ( ) ;
320
+ let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
321
+ span_lint_hir_and_then (
322
+ cx,
323
+ NEW_WITHOUT_DEFAULT ,
324
+ id. into ( ) ,
325
+ impl_item. span ,
326
+ format ! ( "you should consider adding a `Default` implementation for `{self_type_snip}`" ) ,
327
+ |diag| {
328
+ diag. suggest_prepend_item (
329
+ cx,
330
+ item. span ,
331
+ "try adding this" ,
332
+ & format ! (
333
+ "impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
167
334
fn default() -> Self {{
168
335
Self::new()
169
336
}}
170
- }}" )
337
+ }}"
338
+ ) ,
339
+ Applicability :: MachineApplicable ,
340
+ ) ;
341
+ } ,
342
+ ) ;
171
343
}
0 commit comments