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,61 @@ 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
111- // Check if a Default implementation exists for the Self type, regardless of
112- // generics
181+ let mut default_type = None ;
182+ // Check if a Default implementation exists for the Self type, regardless of 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+ // TODO: handle generics
205+ && generics. params . is_empty ( )
206+ // check if `new` and `default` are equivalent
207+ && let Some ( span) = check_block_calls_default ( cx, block)
208+ {
209+ suggest_default_mismatch_new ( cx, span, id, block, self_ty, impl_self_ty) ;
210+ }
151211 }
152212 }
153213 }
@@ -156,16 +216,128 @@ 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+ && check_expr_call_default ( cx, expr)
244+ {
245+ // Block only has a trailing expression, e.g. `Self::default()`
246+ return None ;
247+ } else if let [ hir:: Stmt { kind, .. } ] = block. stmts
248+ && let hir:: StmtKind :: Expr ( expr) | hir:: StmtKind :: Semi ( expr) = kind
249+ && let hir:: ExprKind :: Ret ( Some ( ret_expr) ) = expr. kind
250+ && check_expr_call_default ( cx, ret_expr)
251+ {
252+ // Block has a single statement, e.g. `return Self::default();`
253+ return None ;
254+ }
255+
256+ // trim first and last character, and trim spaces
257+ let mut span = block. span ;
258+ span = span. with_lo ( span. lo ( ) + BytePos :: from_usize ( 1 ) ) ;
259+ span = span. with_hi ( span. hi ( ) - BytePos :: from_usize ( 1 ) ) ;
260+ span = trim_span ( cx. sess ( ) . source_map ( ) , span) ;
261+
262+ Some ( span)
263+ }
264+
265+ /// Check for `Self::default()` call syntax or equivalent
266+ fn check_expr_call_default ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> bool {
267+ if let hir:: ExprKind :: Call ( callee, & [ ] ) = expr. kind
268+ // FIXME: does this include `Self { }` style calls, which is equivalent,
269+ // but not the same as `Self::default()`?
270+ // FIXME: what should the whole_call_expr (3rd arg) be?
271+ && is_default_equivalent_call ( cx, callee, None )
272+ {
273+ true
274+ } else {
275+ false
276+ }
277+ }
278+
279+ fn suggest_default_mismatch_new < ' tcx > (
280+ cx : & LateContext < ' tcx > ,
281+ span : Span ,
282+ id : rustc_hir:: OwnerId ,
283+ block : & rustc_hir:: Block < ' _ > ,
284+ self_ty : Ty < ' tcx > ,
285+ impl_self_ty : & rustc_hir:: Ty < ' _ > ,
286+ ) {
287+ let self_ty_fmt = self_ty. to_string ( ) ;
288+ let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
289+ span_lint_hir_and_then (
290+ cx,
291+ DEFAULT_MISMATCHES_NEW ,
292+ id. into ( ) ,
293+ block. span ,
294+ format ! ( "consider delegating to the auto-derived `Default` for `{self_type_snip}`" ) ,
295+ |diag| {
296+ // This would replace any comments, and we could work around the first comment,
297+ // but in case of a block of code with multiple statements and comment lines,
298+ // we can't do much. For now, we always mark this as a MaybeIncorrect suggestion.
299+ diag. span_suggestion ( span, "use" , "Self::default()" , Applicability :: Unspecified ) ;
300+ } ,
301+ ) ;
302+ }
303+
304+ fn suggest_new_without_default < ' tcx > (
305+ cx : & LateContext < ' tcx > ,
306+ item : & hir:: Item < ' _ > ,
307+ impl_item : & hir:: ImplItem < ' _ > ,
308+ id : hir:: OwnerId ,
309+ self_ty : Ty < ' tcx > ,
310+ generics : & hir:: Generics < ' _ > ,
311+ impl_self_ty : & hir:: Ty < ' _ > ,
312+ ) {
313+ let generics_sugg = snippet ( cx, generics. span , "" ) ;
314+ let where_clause_sugg = if generics. has_where_clause_predicates {
315+ format ! ( "\n {}\n " , snippet( cx, generics. where_clause_span, "" ) )
316+ } else {
317+ String :: new ( )
318+ } ;
319+ let self_ty_fmt = self_ty. to_string ( ) ;
320+ let self_type_snip = snippet ( cx, impl_self_ty. span , & self_ty_fmt) ;
321+ span_lint_hir_and_then (
322+ cx,
323+ NEW_WITHOUT_DEFAULT ,
324+ id. into ( ) ,
325+ impl_item. span ,
326+ format ! ( "you should consider adding a `Default` implementation for `{self_type_snip}`" ) ,
327+ |diag| {
328+ diag. suggest_prepend_item (
329+ cx,
330+ item. span ,
331+ "try adding this" ,
332+ & format ! (
333+ "impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
167334 fn default() -> Self {{
168335 Self::new()
169336 }}
170- }}" )
337+ }}"
338+ ) ,
339+ Applicability :: MachineApplicable ,
340+ ) ;
341+ } ,
342+ ) ;
171343}
0 commit comments