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,74 @@ 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` implementation and a `new` method with
55
+ /// no parameters, this lint checks if the `new` method simply calls the `default`
56
+ /// method.
57
+ ///
58
+ /// ### Why is this bad?
59
+ /// The `new` method should use auto-derived `default()` instead to be consistent.
60
+ /// Otherwise, there is a risk of inconsistency between the two methods, even though
61
+ /// most users will expect them to be equivalent.
62
+ ///
63
+ /// ### Example
64
+ /// ```no_run
65
+ /// #[derive(Default)]
66
+ /// struct MyStruct(i32);
67
+ /// impl MyStruct {
68
+ /// fn new() -> Self {
69
+ /// Self(42)
70
+ /// }
71
+ /// }
72
+ /// ```
73
+ ///
74
+ /// Users are unlikely to notice that `MyStruct::new()` and `MyStruct::default()` would produce
75
+ /// different results. The `new()` method should use auto-derived `default()` instead to be consistent:
76
+ ///
77
+ /// ```no_run
78
+ /// #[derive(Default)]
79
+ /// struct MyStruct(i32);
80
+ /// impl MyStruct {
81
+ /// fn new() -> Self {
82
+ /// Self::default()
83
+ /// }
84
+ /// }
85
+ /// ```
86
+ ///
87
+ /// Alternatively, if `new` method requires non-default initialization, implement a custom `Default`.
88
+ /// This also allows you to mark the `new()` implementation as `const`:
89
+ ///
90
+ /// ```no_run
91
+ /// struct MyStruct(i32);
92
+ /// impl MyStruct {
93
+ /// const fn new() -> Self {
94
+ /// Self(42)
95
+ /// }
96
+ /// }
97
+ /// impl Default for MyStruct {
98
+ /// fn default() -> Self {
99
+ /// Self::new()
100
+ /// }
101
+ /// }
102
+ #[ clippy:: version = "1.86.0" ]
103
+ pub DEFAULT_MISMATCHES_NEW ,
104
+ suspicious,
105
+ "`fn new() -> Self` method that does not match the `Default` implementation"
106
+ }
107
+
108
+ #[ derive( Debug , Clone , Copy ) ]
109
+ enum DefaultType {
110
+ AutoDerived ,
111
+ Manual ,
112
+ }
113
+
51
114
#[ derive( Clone , Default ) ]
52
115
pub struct NewWithoutDefault {
53
- impling_types : Option < HirIdSet > ,
116
+ impling_types : Option < HirIdMap < DefaultType > > ,
54
117
}
55
118
56
- impl_lint_pass ! ( NewWithoutDefault => [ NEW_WITHOUT_DEFAULT ] ) ;
119
+ impl_lint_pass ! ( NewWithoutDefault => [ NEW_WITHOUT_DEFAULT , DEFAULT_MISMATCHES_NEW ] ) ;
57
120
58
121
impl < ' tcx > LateLintPass < ' tcx > for NewWithoutDefault {
59
122
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
@@ -71,7 +134,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
71
134
if impl_item. span . in_external_macro ( cx. sess ( ) . source_map ( ) ) {
72
135
return ;
73
136
}
74
- if let hir:: ImplItemKind :: Fn ( ref sig, _ ) = impl_item. kind {
137
+ if let hir:: ImplItemKind :: Fn ( ref sig, body_id ) = impl_item. kind {
75
138
let name = impl_item. ident . name ;
76
139
let id = impl_item. owner_id ;
77
140
if sig. header . is_unsafe ( ) {
@@ -89,65 +152,62 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
89
152
}
90
153
if sig. decl . inputs . is_empty ( )
91
154
&& name == sym:: new
92
- && cx. effective_visibilities . is_reachable ( impl_item. owner_id . def_id )
93
155
&& let self_def_id = cx. tcx . hir ( ) . get_parent_item ( id. into ( ) )
94
156
&& let self_ty = cx. tcx . type_of ( self_def_id) . instantiate_identity ( )
95
157
&& self_ty == return_ty ( cx, id)
96
158
&& let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
97
159
{
98
160
if self . impling_types . is_none ( ) {
99
- let mut impls = HirIdSet :: default ( ) ;
161
+ let mut impls = HirIdMap :: default ( ) ;
100
162
cx. tcx . for_each_impl ( default_trait_id, |d| {
101
163
let ty = cx. tcx . type_of ( d) . instantiate_identity ( ) ;
102
164
if let Some ( ty_def) = ty. ty_adt_def ( ) {
103
165
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) ) ;
166
+ impls. insert (
167
+ cx. tcx . local_def_id_to_hir_id ( local_def_id) ,
168
+ if cx. tcx . is_builtin_derived ( d) {
169
+ DefaultType :: AutoDerived
170
+ } else {
171
+ DefaultType :: Manual
172
+ } ,
173
+ ) ;
105
174
}
106
175
}
107
176
} ) ;
108
177
self . impling_types = Some ( impls) ;
109
178
}
110
179
180
+ let mut default_type = None ;
111
181
// Check if a Default implementation exists for the Self type, regardless of
112
182
// 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
+ {
205
+ // this type has an automatically derived `Default` implementation
206
+ // check if `new` and `default` are equivalent
207
+ if let Some ( span) = check_block_calls_default ( cx, block) {
208
+ suggest_default_mismatch_new ( cx, span, id, block, self_ty, impl_self_ty) ;
209
+ }
210
+ }
151
211
}
152
212
}
153
213
}
@@ -156,16 +216,134 @@ 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
+ {
244
+ // Check the block's trailing expression (e.g., `Self::default()`)
245
+ if check_expr_call_default ( cx, expr) {
246
+ return None ;
247
+ }
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
+ {
252
+ if check_expr_call_default ( cx, ret_expr) {
253
+ return None ;
254
+ }
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 (
301
+ span,
302
+ "try using this" ,
303
+ & "Self::default()" ,
304
+ Applicability :: MaybeIncorrect ,
305
+ ) ;
306
+ } ,
307
+ ) ;
308
+ }
309
+
310
+ fn suggest_new_without_default < ' tcx > (
311
+ cx : & LateContext < ' tcx > ,
312
+ item : & hir:: Item < ' _ > ,
313
+ impl_item : & hir:: ImplItem < ' _ > ,
314
+ id : hir:: OwnerId ,
315
+ self_ty : Ty < ' tcx > ,
316
+ generics : & hir:: Generics < ' _ > ,
317
+ impl_self_ty : & hir:: Ty < ' _ > ,
318
+ ) {
319
+ let generics_sugg = snippet ( cx, generics. span , "" ) ;
320
+ let where_clause_sugg = if generics. has_where_clause_predicates {
321
+ format ! ( "\n {}\n " , snippet( cx, generics. where_clause_span, "" ) )
322
+ } else {
323
+ String :: new ( )
324
+ } ;
325
+ let self_ty_fmt = self_ty. to_string ( ) ;
326
+ let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
327
+ span_lint_hir_and_then (
328
+ cx,
329
+ NEW_WITHOUT_DEFAULT ,
330
+ id. into ( ) ,
331
+ impl_item. span ,
332
+ format ! ( "you should consider adding a `Default` implementation for `{self_type_snip}`" ) ,
333
+ |diag| {
334
+ diag. suggest_prepend_item (
335
+ cx,
336
+ item. span ,
337
+ "try adding this" ,
338
+ & format ! (
339
+ "impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
167
340
fn default() -> Self {{
168
341
Self::new()
169
342
}}
170
- }}" )
343
+ }}"
344
+ ) ,
345
+ Applicability :: MachineApplicable ,
346
+ ) ;
347
+ } ,
348
+ ) ;
171
349
}
0 commit comments