@@ -2,16 +2,16 @@ use clippy_config::msrvs::{self, Msrv};
22use clippy_config:: Conf ;
33use clippy_utils:: diagnostics:: { span_lint_and_then, span_lint_hir_and_then} ;
44use clippy_utils:: is_doc_hidden;
5- use clippy_utils:: source:: SpanRangeExt ;
6- use rustc_ast :: ast :: { self , VisibilityKind } ;
5+ use clippy_utils:: source:: snippet_indent ;
6+ use itertools :: Itertools ;
77use rustc_ast:: attr;
88use rustc_data_structures:: fx:: FxHashSet ;
99use rustc_errors:: Applicability ;
1010use rustc_hir:: def:: { CtorKind , CtorOf , DefKind , Res } ;
11- use rustc_hir:: { self as hir , Expr , ExprKind , QPath } ;
12- use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass , LintContext } ;
11+ use rustc_hir:: { Expr , ExprKind , Item , ItemKind , QPath , TyKind , VariantData } ;
12+ use rustc_lint:: { LateContext , LateLintPass } ;
1313use rustc_session:: impl_lint_pass;
14- use rustc_span:: def_id:: { DefId , LocalDefId } ;
14+ use rustc_span:: def_id:: LocalDefId ;
1515use rustc_span:: { sym, Span } ;
1616
1717declare_clippy_lint ! {
@@ -62,29 +62,13 @@ declare_clippy_lint! {
6262 "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
6363}
6464
65- #[ expect( clippy:: module_name_repetitions) ]
66- pub struct ManualNonExhaustiveStruct {
65+ pub struct ManualNonExhaustive {
6766 msrv : Msrv ,
68- }
69-
70- impl ManualNonExhaustiveStruct {
71- pub fn new ( conf : & ' static Conf ) -> Self {
72- Self {
73- msrv : conf. msrv . clone ( ) ,
74- }
75- }
76- }
77-
78- impl_lint_pass ! ( ManualNonExhaustiveStruct => [ MANUAL_NON_EXHAUSTIVE ] ) ;
79-
80- #[ expect( clippy:: module_name_repetitions) ]
81- pub struct ManualNonExhaustiveEnum {
82- msrv : Msrv ,
83- constructed_enum_variants : FxHashSet < ( DefId , DefId ) > ,
67+ constructed_enum_variants : FxHashSet < LocalDefId > ,
8468 potential_enums : Vec < ( LocalDefId , LocalDefId , Span , Span ) > ,
8569}
8670
87- impl ManualNonExhaustiveEnum {
71+ impl ManualNonExhaustive {
8872 pub fn new ( conf : & ' static Conf ) -> Self {
8973 Self {
9074 msrv : conf. msrv . clone ( ) ,
@@ -94,96 +78,78 @@ impl ManualNonExhaustiveEnum {
9478 }
9579}
9680
97- impl_lint_pass ! ( ManualNonExhaustiveEnum => [ MANUAL_NON_EXHAUSTIVE ] ) ;
98-
99- impl EarlyLintPass for ManualNonExhaustiveStruct {
100- fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & ast:: Item ) {
101- if let ast:: ItemKind :: Struct ( variant_data, _) = & item. kind
102- && let ( fields, delimiter) = match variant_data {
103- ast:: VariantData :: Struct { fields, .. } => ( & * * fields, '{' ) ,
104- ast:: VariantData :: Tuple ( fields, _) => ( & * * fields, '(' ) ,
105- ast:: VariantData :: Unit ( _) => return ,
106- }
107- && fields. len ( ) > 1
108- && self . msrv . meets ( msrvs:: NON_EXHAUSTIVE )
109- {
110- let mut iter = fields. iter ( ) . filter_map ( |f| match f. vis . kind {
111- VisibilityKind :: Public => None ,
112- VisibilityKind :: Inherited => Some ( Ok ( f) ) ,
113- VisibilityKind :: Restricted { .. } => Some ( Err ( ( ) ) ) ,
114- } ) ;
115- if let Some ( Ok ( field) ) = iter. next ( )
116- && iter. next ( ) . is_none ( )
117- && field. ty . kind . is_unit ( )
118- {
119- span_lint_and_then (
120- cx,
121- MANUAL_NON_EXHAUSTIVE ,
122- item. span ,
123- "this seems like a manual implementation of the non-exhaustive pattern" ,
124- |diag| {
125- if !item. attrs . iter ( ) . any ( |attr| attr. has_name ( sym:: non_exhaustive) )
126- && let header_span = cx. sess ( ) . source_map ( ) . span_until_char ( item. span , delimiter)
127- && let Some ( snippet) = header_span. get_source_text ( cx)
128- {
129- diag. span_suggestion (
130- header_span,
131- "add the attribute" ,
132- format ! ( "#[non_exhaustive] {snippet}" ) ,
133- Applicability :: Unspecified ,
134- ) ;
135- }
136- diag. span_help ( field. span , "remove this field" ) ;
137- } ,
138- ) ;
139- }
140- }
141- }
81+ impl_lint_pass ! ( ManualNonExhaustive => [ MANUAL_NON_EXHAUSTIVE ] ) ;
14282
143- extract_msrv_attr ! ( EarlyContext ) ;
144- }
145-
146- impl < ' tcx > LateLintPass < ' tcx > for ManualNonExhaustiveEnum {
147- fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
148- if !self . msrv . meets ( msrvs:: NON_EXHAUSTIVE ) {
83+ impl < ' tcx > LateLintPass < ' tcx > for ManualNonExhaustive {
84+ fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
85+ if !self . msrv . meets ( msrvs:: NON_EXHAUSTIVE ) || !cx. effective_visibilities . is_exported ( item. owner_id . def_id ) {
14986 return ;
15087 }
15188
152- if let hir:: ItemKind :: Enum ( def, _) = & item. kind
153- && def. variants . len ( ) > 1
154- {
155- let mut iter = def. variants . iter ( ) . filter_map ( |v| {
156- ( matches ! ( v. data, hir:: VariantData :: Unit ( _, _) )
157- && is_doc_hidden ( cx. tcx . hir ( ) . attrs ( v. hir_id ) )
158- && !attr:: contains_name ( cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) , sym:: non_exhaustive) )
159- . then_some ( ( v. def_id , v. span ) )
160- } ) ;
161- if let Some ( ( id, span) ) = iter. next ( )
162- && iter. next ( ) . is_none ( )
163- {
164- self . potential_enums . push ( ( item. owner_id . def_id , id, item. span , span) ) ;
165- }
89+ match item. kind {
90+ ItemKind :: Enum ( def, _) if def. variants . len ( ) > 1 => {
91+ let iter = def. variants . iter ( ) . filter_map ( |v| {
92+ ( matches ! ( v. data, VariantData :: Unit ( _, _) ) && is_doc_hidden ( cx. tcx . hir ( ) . attrs ( v. hir_id ) ) )
93+ . then_some ( ( v. def_id , v. span ) )
94+ } ) ;
95+ if let Ok ( ( id, span) ) = iter. exactly_one ( )
96+ && !attr:: contains_name ( cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) , sym:: non_exhaustive)
97+ {
98+ self . potential_enums . push ( ( item. owner_id . def_id , id, item. span , span) ) ;
99+ }
100+ } ,
101+ ItemKind :: Struct ( variant_data, _) => {
102+ let fields = variant_data. fields ( ) ;
103+ let private_fields = fields
104+ . iter ( )
105+ . filter ( |field| !cx. effective_visibilities . is_exported ( field. def_id ) ) ;
106+ if fields. len ( ) > 1
107+ && let Ok ( field) = private_fields. exactly_one ( )
108+ && let TyKind :: Tup ( [ ] ) = field. ty . kind
109+ {
110+ span_lint_and_then (
111+ cx,
112+ MANUAL_NON_EXHAUSTIVE ,
113+ item. span ,
114+ "this seems like a manual implementation of the non-exhaustive pattern" ,
115+ |diag| {
116+ if let Some ( non_exhaustive) =
117+ attr:: find_by_name ( cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) , sym:: non_exhaustive)
118+ {
119+ diag. span_note ( non_exhaustive. span , "the struct is already non-exhaustive" ) ;
120+ } else {
121+ let indent = snippet_indent ( cx, item. span ) . unwrap_or_default ( ) ;
122+ diag. span_suggestion_verbose (
123+ item. span . shrink_to_lo ( ) ,
124+ "use the `#[non_exhaustive]` attribute instead" ,
125+ format ! ( "#[non_exhaustive]\n {indent}" ) ,
126+ Applicability :: MaybeIncorrect ,
127+ ) ;
128+ }
129+ diag. span_help ( field. span , "remove this field" ) ;
130+ } ,
131+ ) ;
132+ }
133+ } ,
134+ _ => { } ,
166135 }
167136 }
168137
169138 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
170139 if let ExprKind :: Path ( QPath :: Resolved ( None , p) ) = & e. kind
171- && let Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , CtorKind :: Const ) , id) = p. res
140+ && let Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , CtorKind :: Const ) , ctor_id) = p. res
141+ && let Some ( local_ctor) = ctor_id. as_local ( )
172142 {
173- let variant_id = cx. tcx . parent ( id) ;
174- let enum_id = cx. tcx . parent ( variant_id) ;
175-
176- self . constructed_enum_variants . insert ( ( enum_id, variant_id) ) ;
143+ let variant_id = cx. tcx . local_parent ( local_ctor) ;
144+ self . constructed_enum_variants . insert ( variant_id) ;
177145 }
178146 }
179147
180148 fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
181- for & ( enum_id, _, enum_span, variant_span) in
182- self . potential_enums . iter ( ) . filter ( |& & ( enum_id, variant_id, _, _) | {
183- !self
184- . constructed_enum_variants
185- . contains ( & ( enum_id. to_def_id ( ) , variant_id. to_def_id ( ) ) )
186- } )
149+ for & ( enum_id, _, enum_span, variant_span) in self
150+ . potential_enums
151+ . iter ( )
152+ . filter ( |( _, variant_id, _, _) | !self . constructed_enum_variants . contains ( variant_id) )
187153 {
188154 let hir_id = cx. tcx . local_def_id_to_hir_id ( enum_id) ;
189155 span_lint_hir_and_then (
@@ -193,15 +159,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
193159 enum_span,
194160 "this seems like a manual implementation of the non-exhaustive pattern" ,
195161 |diag| {
196- let header_span = cx. sess ( ) . source_map ( ) . span_until_char ( enum_span, '{' ) ;
197- if let Some ( snippet) = header_span. get_source_text ( cx) {
198- diag. span_suggestion (
199- header_span,
200- "add the attribute" ,
201- format ! ( "#[non_exhaustive] {snippet}" ) ,
202- Applicability :: Unspecified ,
203- ) ;
204- }
162+ let indent = snippet_indent ( cx, enum_span) . unwrap_or_default ( ) ;
163+ diag. span_suggestion_verbose (
164+ enum_span. shrink_to_lo ( ) ,
165+ "use the `#[non_exhaustive]` attribute instead" ,
166+ format ! ( "#[non_exhaustive]\n {indent}" ) ,
167+ Applicability :: MaybeIncorrect ,
168+ ) ;
205169 diag. span_help ( variant_span, "remove this variant" ) ;
206170 } ,
207171 ) ;
0 commit comments