11use 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} ;
43use clippy_utils:: sugg:: DiagExt ;
4+ use clippy_utils:: { is_default_equivalent_call, return_ty} ;
55use rustc_errors:: Applicability ;
66use rustc_hir as hir;
7- use rustc_hir:: HirIdSet ;
7+ use rustc_hir:: HirIdMap ;
88use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9+ use rustc_middle:: ty:: { Adt , Ty , VariantDef } ;
910use rustc_session:: impl_lint_pass;
10- use rustc_span:: sym;
11+ use rustc_span:: { BytePos , Pos as _ , Span , sym} ;
1112
1213declare_clippy_lint ! {
1314 /// ### What it does
@@ -48,12 +49,74 @@ declare_clippy_lint! {
4849 "`pub fn new() -> Self` method without `Default` implementation"
4950}
5051
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+
51114#[ derive( Clone , Default ) ]
52115pub struct NewWithoutDefault {
53- impling_types : Option < HirIdSet > ,
116+ impling_types : Option < HirIdMap < DefaultType > > ,
54117}
55118
56- impl_lint_pass ! ( NewWithoutDefault => [ NEW_WITHOUT_DEFAULT ] ) ;
119+ impl_lint_pass ! ( NewWithoutDefault => [ NEW_WITHOUT_DEFAULT , DEFAULT_MISMATCHES_NEW ] ) ;
57120
58121impl < ' tcx > LateLintPass < ' tcx > for NewWithoutDefault {
59122 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
@@ -71,7 +134,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
71134 if impl_item. span . in_external_macro ( cx. sess ( ) . source_map ( ) ) {
72135 return ;
73136 }
74- if let hir:: ImplItemKind :: Fn ( ref sig, _ ) = impl_item. kind {
137+ if let hir:: ImplItemKind :: Fn ( ref sig, body_id ) = impl_item. kind {
75138 let name = impl_item. ident . name ;
76139 let id = impl_item. owner_id ;
77140 if sig. header . is_unsafe ( ) {
@@ -89,65 +152,62 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
89152 }
90153 if sig. decl . inputs . is_empty ( )
91154 && name == sym:: new
92- && cx. effective_visibilities . is_reachable ( impl_item. owner_id . def_id )
93155 && let self_def_id = cx. tcx . hir ( ) . get_parent_item ( id. into ( ) )
94156 && let self_ty = cx. tcx . type_of ( self_def_id) . instantiate_identity ( )
95157 && self_ty == return_ty ( cx, id)
96158 && let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
97159 {
98160 if self . impling_types . is_none ( ) {
99- let mut impls = HirIdSet :: default ( ) ;
161+ let mut impls = HirIdMap :: default ( ) ;
100162 cx. tcx . for_each_impl ( default_trait_id, |d| {
101163 let ty = cx. tcx . type_of ( d) . instantiate_identity ( ) ;
102164 if let Some ( ty_def) = ty. ty_adt_def ( ) {
103165 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+ ) ;
105174 }
106175 }
107176 } ) ;
108177 self . impling_types = Some ( impls) ;
109178 }
110179
180+ let mut default_type = None ;
111181 // Check if a Default implementation exists for the Self type, regardless of
112182 // generics
113183 if let Some ( ref impling_types) = self . impling_types
114184 && let self_def = cx. tcx . type_of ( self_def_id) . instantiate_identity ( )
115185 && let Some ( self_def) = self_def. ty_adt_def ( )
116186 && 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)
119187 {
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+ }
121194 }
122195
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+ }
151211 }
152212 }
153213 }
@@ -156,16 +216,134 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
156216 }
157217}
158218
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} {{
167340 fn default() -> Self {{
168341 Self::new()
169342 }}
170- }}" )
343+ }}"
344+ ) ,
345+ Applicability :: MachineApplicable ,
346+ ) ;
347+ } ,
348+ ) ;
171349}
0 commit comments