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,62 @@ 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
181
+ let mut default_type = None ;
111
182
// Check if a Default implementation exists for the Self type, regardless of
112
183
// generics
113
184
if let Some ( ref impling_types) = self . impling_types
114
185
&& let self_def = cx. tcx . type_of ( self_def_id) . instantiate_identity ( )
115
186
&& let Some ( self_def) = self_def. ty_adt_def ( )
116
187
&& 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
188
{
120
- return ;
189
+ let self_id = cx. tcx . local_def_id_to_hir_id ( self_local_did) ;
190
+ default_type = impling_types. get ( & self_id) ;
191
+ if let Some ( DefaultType :: Manual ) = default_type {
192
+ // both `new` and `default` are manually implemented
193
+ return ;
194
+ }
121
195
}
122
196
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
- ) ;
197
+ if default_type. is_none ( ) {
198
+ // there are no `Default` implementations for this type
199
+ if !cx. effective_visibilities . is_reachable ( impl_item. owner_id . def_id ) {
200
+ return ;
201
+ }
202
+ suggest_new_without_default ( cx, item, impl_item, id, self_ty, generics, impl_self_ty) ;
203
+ } else if let hir:: ExprKind :: Block ( block, _) = cx. tcx . hir ( ) . body ( body_id) . value . kind
204
+ && !is_unit_struct ( cx, self_ty)
205
+ {
206
+ // this type has an automatically derived `Default` implementation
207
+ // check if `new` and `default` are equivalent
208
+ if let Some ( span) = check_block_calls_default ( cx, block) {
209
+ suggest_default_mismatch_new ( cx, span, id, block, self_ty, impl_self_ty) ;
210
+ }
211
+ }
151
212
}
152
213
}
153
214
}
@@ -156,16 +217,128 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
156
217
}
157
218
}
158
219
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} {{
220
+ // Check if Self is a unit struct, and avoid any kind of suggestions
221
+ // FIXME: this was copied from DefaultConstructedUnitStructs,
222
+ // and should be refactored into a common function
223
+ fn is_unit_struct ( _cx : & LateContext < ' _ > , ty : Ty < ' _ > ) -> bool {
224
+ if let Adt ( def, ..) = ty. kind ( )
225
+ && def. is_struct ( )
226
+ && let var @ VariantDef {
227
+ ctor : Some ( ( hir:: def:: CtorKind :: Const , _) ) ,
228
+ ..
229
+ } = def. non_enum_variant ( )
230
+ && !var. is_field_list_non_exhaustive ( )
231
+ {
232
+ true
233
+ } else {
234
+ false
235
+ }
236
+ }
237
+
238
+ /// Check if a block contains one of these:
239
+ /// - Empty block with an expr (e.g., `{ Self::default() }`)
240
+ /// - One statement (e.g., `{ return Self::default(); }`)
241
+ fn check_block_calls_default ( cx : & LateContext < ' _ > , block : & hir:: Block < ' _ > ) -> Option < Span > {
242
+ if let Some ( expr) = block. expr
243
+ && block. stmts . is_empty ( )
244
+ && check_expr_call_default ( cx, expr)
245
+ {
246
+ // Block only has a trailing expression, e.g. `Self::default()`
247
+ return None ;
248
+ } else if let [ hir:: Stmt { kind, .. } ] = block. stmts
249
+ && let hir:: StmtKind :: Expr ( expr) | hir:: StmtKind :: Semi ( expr) = kind
250
+ && let hir:: ExprKind :: Ret ( Some ( ret_expr) ) = expr. kind
251
+ && check_expr_call_default ( cx, ret_expr)
252
+ {
253
+ // Block has a single statement, e.g. `return Self::default();`
254
+ return None ;
255
+ }
256
+
257
+ // trim first and last character, and trim spaces
258
+ let mut span = block. span ;
259
+ span = span. with_lo ( span. lo ( ) + BytePos :: from_usize ( 1 ) ) ;
260
+ span = span. with_hi ( span. hi ( ) - BytePos :: from_usize ( 1 ) ) ;
261
+ span = trim_span ( cx. sess ( ) . source_map ( ) , span) ;
262
+
263
+ Some ( span)
264
+ }
265
+
266
+ /// Check for `Self::default()` call syntax or equivalent
267
+ fn check_expr_call_default ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> bool {
268
+ if let hir:: ExprKind :: Call ( callee, & [ ] ) = expr. kind
269
+ // FIXME: does this include `Self { }` style calls, which is equivalent,
270
+ // but not the same as `Self::default()`?
271
+ // FIXME: what should the whole_call_expr (3rd arg) be?
272
+ && is_default_equivalent_call ( cx, callee, None )
273
+ {
274
+ true
275
+ } else {
276
+ false
277
+ }
278
+ }
279
+
280
+ fn suggest_default_mismatch_new < ' tcx > (
281
+ cx : & LateContext < ' tcx > ,
282
+ span : Span ,
283
+ id : rustc_hir:: OwnerId ,
284
+ block : & rustc_hir:: Block < ' _ > ,
285
+ self_ty : Ty < ' tcx > ,
286
+ impl_self_ty : & rustc_hir:: Ty < ' _ > ,
287
+ ) {
288
+ let self_ty_fmt = self_ty. to_string ( ) ;
289
+ let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
290
+ span_lint_hir_and_then (
291
+ cx,
292
+ DEFAULT_MISMATCHES_NEW ,
293
+ id. into ( ) ,
294
+ block. span ,
295
+ format ! ( "you should consider delegating to the auto-derived `Default` for `{self_type_snip}`" ) ,
296
+ |diag| {
297
+ // This would replace any comments, and we could work around the first comment,
298
+ // but in case of a block of code with multiple statements and comment lines,
299
+ // we can't do much. For now, we always mark this as a MaybeIncorrect suggestion.
300
+ diag. span_suggestion ( span, "try using this" , "Self::default()" , Applicability :: MaybeIncorrect ) ;
301
+ } ,
302
+ ) ;
303
+ }
304
+
305
+ fn suggest_new_without_default < ' tcx > (
306
+ cx : & LateContext < ' tcx > ,
307
+ item : & hir:: Item < ' _ > ,
308
+ impl_item : & hir:: ImplItem < ' _ > ,
309
+ id : hir:: OwnerId ,
310
+ self_ty : Ty < ' tcx > ,
311
+ generics : & hir:: Generics < ' _ > ,
312
+ impl_self_ty : & hir:: Ty < ' _ > ,
313
+ ) {
314
+ let generics_sugg = snippet ( cx, generics. span , "" ) ;
315
+ let where_clause_sugg = if generics. has_where_clause_predicates {
316
+ format ! ( "\n {}\n " , snippet( cx, generics. where_clause_span, "" ) )
317
+ } else {
318
+ String :: new ( )
319
+ } ;
320
+ let self_ty_fmt = self_ty. to_string ( ) ;
321
+ let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
322
+ span_lint_hir_and_then (
323
+ cx,
324
+ NEW_WITHOUT_DEFAULT ,
325
+ id. into ( ) ,
326
+ impl_item. span ,
327
+ format ! ( "you should consider adding a `Default` implementation for `{self_type_snip}`" ) ,
328
+ |diag| {
329
+ diag. suggest_prepend_item (
330
+ cx,
331
+ item. span ,
332
+ "try adding this" ,
333
+ & format ! (
334
+ "impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
167
335
fn default() -> Self {{
168
336
Self::new()
169
337
}}
170
- }}" )
338
+ }}"
339
+ ) ,
340
+ Applicability :: MachineApplicable ,
341
+ ) ;
342
+ } ,
343
+ ) ;
171
344
}
0 commit comments