1
1
use clippy_utils:: diagnostics:: span_lint_hir_and_then;
2
- use clippy_utils:: source:: snippet;
2
+ use clippy_utils:: source:: { snippet, trim_span } ;
3
3
use clippy_utils:: sugg:: DiagExt ;
4
4
use clippy_utils:: { is_default_equivalent_call, return_ty} ;
5
5
use rustc_errors:: Applicability ;
6
6
use rustc_hir as hir;
7
7
use rustc_hir:: HirIdMap ;
8
8
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9
- use rustc_middle:: ty:: Ty ;
9
+ use rustc_middle:: ty:: { Adt , Ty , VariantDef } ;
10
10
use rustc_session:: impl_lint_pass;
11
- use rustc_span:: sym;
11
+ use rustc_span:: { BytePos , Pos as _ , Span , sym} ;
12
12
13
13
declare_clippy_lint ! {
14
14
/// ### What it does
@@ -199,30 +199,13 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
199
199
return ;
200
200
}
201
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 {
202
+ } else if let hir:: ExprKind :: Block ( block, _) = cx. tcx . hir ( ) . body ( body_id) . value . kind
203
+ && !is_unit_struct ( cx, self_ty)
204
+ {
203
205
// this type has an automatically derived `Default` implementation
204
206
// check if `new` and `default` are equivalent
205
- if !check_block_calls_default ( cx, block) {
206
- let self_ty_fmt = self_ty. to_string ( ) ;
207
- let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
208
- span_lint_hir_and_then (
209
- cx,
210
- DEFAULT_MISMATCHES_NEW ,
211
- id. into ( ) ,
212
- impl_item. span ,
213
- format ! (
214
- "you should consider delegating to the auto-derived `Default` for `{self_type_snip}`"
215
- ) ,
216
- |diag| {
217
- diag. suggest_prepend_item (
218
- cx,
219
- block. span ,
220
- "try using this" ,
221
- & "Self::default()" ,
222
- Applicability :: MaybeIncorrect ,
223
- ) ;
224
- } ,
225
- ) ;
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) ;
226
209
}
227
210
}
228
211
}
@@ -233,36 +216,56 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
233
216
}
234
217
}
235
218
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
+
236
237
/// Check if a block contains one of these:
237
238
/// - Empty block with an expr (e.g., `{ Self::default() }`)
238
239
/// - One statement (e.g., `{ return Self::default(); }`)
239
- fn check_block_calls_default ( cx : & LateContext < ' _ > , block : & hir:: Block < ' _ > ) -> bool {
240
+ fn check_block_calls_default ( cx : & LateContext < ' _ > , block : & hir:: Block < ' _ > ) -> Option < Span > {
240
241
if let Some ( expr) = block. expr
241
242
&& block. stmts . is_empty ( )
242
243
{
243
244
// Check the block's trailing expression (e.g., `Self::default()`)
244
- check_expr_call_default ( cx, expr)
245
+ if check_expr_call_default ( cx, expr) {
246
+ return None ;
247
+ }
245
248
} else if let [ hir:: Stmt { kind, .. } ] = block. stmts
246
249
&& let hir:: StmtKind :: Expr ( expr) | hir:: StmtKind :: Semi ( expr) = kind
250
+ && let hir:: ExprKind :: Ret ( Some ( ret_expr) ) = expr. kind
247
251
{
248
- // Check if the single statement is a return or expr
249
- match expr. kind {
250
- // Case 1: `return Self::default();`
251
- hir:: ExprKind :: Ret ( Some ( ret_expr) ) => check_expr_call_default ( cx, ret_expr) ,
252
- // Case 2: `Self::default()` (possibly inside a block)
253
- hir:: ExprKind :: Block ( block, _) => check_block_calls_default ( cx, block) ,
254
- // Case 3: Direct call (e.g., `Self::default()` as the entire body)
255
- _ => check_expr_call_default ( cx, expr) ,
252
+ if check_expr_call_default ( cx, ret_expr) {
253
+ return None ;
256
254
}
257
- } else {
258
- false
259
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)
260
264
}
261
265
262
266
/// Check for `Self::default()` call syntax or equivalent
263
267
fn check_expr_call_default ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> bool {
264
- if let hir:: ExprKind :: Call ( callee, args) = expr. kind
265
- && args. is_empty ( )
268
+ if let hir:: ExprKind :: Call ( callee, & [ ] ) = expr. kind
266
269
// FIXME: does this include `Self { }` style calls, which is equivalent,
267
270
// but not the same as `Self::default()`?
268
271
// FIXME: what should the whole_call_expr (3rd arg) be?
@@ -274,6 +277,36 @@ fn check_expr_call_default(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
274
277
}
275
278
}
276
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
+
277
310
fn suggest_new_without_default < ' tcx > (
278
311
cx : & LateContext < ' tcx > ,
279
312
item : & hir:: Item < ' _ > ,
0 commit comments