11use clippy_utils:: diagnostics:: span_lint_hir_and_then;
2- use clippy_utils:: source:: snippet;
2+ use clippy_utils:: source:: { snippet, trim_span } ;
33use clippy_utils:: sugg:: DiagExt ;
44use clippy_utils:: { is_default_equivalent_call, return_ty} ;
55use rustc_errors:: Applicability ;
66use rustc_hir as hir;
77use rustc_hir:: HirIdMap ;
88use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9- use rustc_middle:: ty:: Ty ;
9+ use rustc_middle:: ty:: { Adt , Ty , VariantDef } ;
1010use rustc_session:: impl_lint_pass;
11- use rustc_span:: sym;
11+ use rustc_span:: { BytePos , Pos as _ , Span , sym} ;
1212
1313declare_clippy_lint ! {
1414 /// ### What it does
@@ -199,30 +199,13 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
199199 return ;
200200 }
201201 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+ {
203205 // this type has an automatically derived `Default` implementation
204206 // 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) ;
226209 }
227210 }
228211 }
@@ -233,36 +216,56 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
233216 }
234217}
235218
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+
236237/// Check if a block contains one of these:
237238/// - Empty block with an expr (e.g., `{ Self::default() }`)
238239/// - 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 > {
240241 if let Some ( expr) = block. expr
241242 && block. stmts . is_empty ( )
242243 {
243244 // 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+ }
245248 } else if let [ hir:: Stmt { kind, .. } ] = block. stmts
246249 && let hir:: StmtKind :: Expr ( expr) | hir:: StmtKind :: Semi ( expr) = kind
250+ && let hir:: ExprKind :: Ret ( Some ( ret_expr) ) = expr. kind
247251 {
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 ;
256254 }
257- } else {
258- false
259255 }
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)
260264}
261265
262266/// Check for `Self::default()` call syntax or equivalent
263267fn 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
266269 // FIXME: does this include `Self { }` style calls, which is equivalent,
267270 // but not the same as `Self::default()`?
268271 // 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 {
274277 }
275278}
276279
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+
277310fn suggest_new_without_default < ' tcx > (
278311 cx : & LateContext < ' tcx > ,
279312 item : & hir:: Item < ' _ > ,
0 commit comments