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,75 @@ 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` 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+
51115#[ derive( Clone , Default ) ]
52116pub struct NewWithoutDefault {
53- impling_types : Option < HirIdSet > ,
117+ impling_types : Option < HirIdMap < DefaultType > > ,
54118}
55119
56- impl_lint_pass ! ( NewWithoutDefault => [ NEW_WITHOUT_DEFAULT ] ) ;
120+ impl_lint_pass ! ( NewWithoutDefault => [ NEW_WITHOUT_DEFAULT , DEFAULT_MISMATCHES_NEW ] ) ;
57121
58122impl < ' tcx > LateLintPass < ' tcx > for NewWithoutDefault {
59123 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
@@ -71,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
71135 if impl_item. span . in_external_macro ( cx. sess ( ) . source_map ( ) ) {
72136 return ;
73137 }
74- if let hir:: ImplItemKind :: Fn ( ref sig, _ ) = impl_item. kind {
138+ if let hir:: ImplItemKind :: Fn ( ref sig, body_id ) = impl_item. kind {
75139 let name = impl_item. ident . name ;
76140 let id = impl_item. owner_id ;
77141 if sig. header . is_unsafe ( ) {
@@ -89,65 +153,62 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
89153 }
90154 if sig. decl . inputs . is_empty ( )
91155 && name == sym:: new
92- && cx. effective_visibilities . is_reachable ( impl_item. owner_id . def_id )
93156 && let self_def_id = cx. tcx . hir ( ) . get_parent_item ( id. into ( ) )
94157 && let self_ty = cx. tcx . type_of ( self_def_id) . instantiate_identity ( )
95158 && self_ty == return_ty ( cx, id)
96159 && let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
97160 {
98161 if self . impling_types . is_none ( ) {
99- let mut impls = HirIdSet :: default ( ) ;
162+ let mut impls = HirIdMap :: default ( ) ;
100163 cx. tcx . for_each_impl ( default_trait_id, |d| {
101164 let ty = cx. tcx . type_of ( d) . instantiate_identity ( ) ;
102165 if let Some ( ty_def) = ty. ty_adt_def ( ) {
103166 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+ ) ;
105175 }
106176 }
107177 } ) ;
108178 self . impling_types = Some ( impls) ;
109179 }
110180
181+ let mut default_type = None ;
111182 // Check if a Default implementation exists for the Self type, regardless of
112183 // generics
113184 if let Some ( ref impling_types) = self . impling_types
114185 && let self_def = cx. tcx . type_of ( self_def_id) . instantiate_identity ( )
115186 && let Some ( self_def) = self_def. ty_adt_def ( )
116187 && 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)
119188 {
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+ }
121195 }
122196
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+ }
151212 }
152213 }
153214 }
@@ -156,16 +217,128 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
156217 }
157218}
158219
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} {{
167335 fn default() -> Self {{
168336 Self::new()
169337 }}
170- }}" )
338+ }}"
339+ ) ,
340+ Applicability :: MachineApplicable ,
341+ ) ;
342+ } ,
343+ ) ;
171344}
0 commit comments