11use clippy_utils:: attrs:: is_doc_hidden;
22use clippy_utils:: diagnostics:: span_lint_and_then;
33use clippy_utils:: source:: snippet_opt;
4- use clippy_utils:: { meets_msrv, msrvs} ;
4+ use clippy_utils:: { is_lint_allowed , meets_msrv, msrvs} ;
55use if_chain:: if_chain;
6- use rustc_ast:: ast:: { FieldDef , Item , ItemKind , Variant , VariantData , VisibilityKind } ;
6+ use rustc_ast:: ast:: { self , FieldDef , VisibilityKind } ;
7+ use rustc_data_structures:: fx:: FxHashSet ;
78use rustc_errors:: Applicability ;
8- use rustc_lint:: { EarlyContext , EarlyLintPass , LintContext } ;
9+ use rustc_hir:: def:: { CtorKind , CtorOf , DefKind , Res } ;
10+ use rustc_hir:: { self as hir, Expr , ExprKind , QPath } ;
11+ use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass , LintContext } ;
12+ use rustc_middle:: ty:: DefIdTree ;
913use rustc_semver:: RustcVersion ;
1014use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
15+ use rustc_span:: def_id:: { DefId , LocalDefId } ;
1116use rustc_span:: { sym, Span } ;
1217
1318declare_clippy_lint ! {
@@ -58,55 +63,84 @@ declare_clippy_lint! {
5863 "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
5964}
6065
61- #[ derive ( Clone ) ]
62- pub struct ManualNonExhaustive {
66+ #[ allow ( clippy :: module_name_repetitions ) ]
67+ pub struct ManualNonExhaustiveStruct {
6368 msrv : Option < RustcVersion > ,
6469}
6570
66- impl ManualNonExhaustive {
71+ impl ManualNonExhaustiveStruct {
6772 #[ must_use]
6873 pub fn new ( msrv : Option < RustcVersion > ) -> Self {
6974 Self { msrv }
7075 }
7176}
7277
73- impl_lint_pass ! ( ManualNonExhaustive => [ MANUAL_NON_EXHAUSTIVE ] ) ;
78+ impl_lint_pass ! ( ManualNonExhaustiveStruct => [ MANUAL_NON_EXHAUSTIVE ] ) ;
7479
75- impl EarlyLintPass for ManualNonExhaustive {
76- fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & Item ) {
80+ #[ allow( clippy:: module_name_repetitions) ]
81+ pub struct ManualNonExhaustiveEnum {
82+ msrv : Option < RustcVersion > ,
83+ constructed_enum_variants : FxHashSet < ( DefId , DefId ) > ,
84+ potential_enums : Vec < ( LocalDefId , LocalDefId , Span , Span ) > ,
85+ }
86+
87+ impl ManualNonExhaustiveEnum {
88+ #[ must_use]
89+ pub fn new ( msrv : Option < RustcVersion > ) -> Self {
90+ Self {
91+ msrv,
92+ constructed_enum_variants : FxHashSet :: default ( ) ,
93+ potential_enums : Vec :: new ( ) ,
94+ }
95+ }
96+ }
97+
98+ impl_lint_pass ! ( ManualNonExhaustiveEnum => [ MANUAL_NON_EXHAUSTIVE ] ) ;
99+
100+ impl EarlyLintPass for ManualNonExhaustiveStruct {
101+ fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & ast:: Item ) {
77102 if !meets_msrv ( self . msrv . as_ref ( ) , & msrvs:: NON_EXHAUSTIVE ) {
78103 return ;
79104 }
80105
81- match & item. kind {
82- ItemKind :: Enum ( def, _) => {
83- check_manual_non_exhaustive_enum ( cx, item, & def. variants ) ;
84- } ,
85- ItemKind :: Struct ( variant_data, _) => {
86- if let VariantData :: Unit ( ..) = variant_data {
87- return ;
88- }
89-
90- check_manual_non_exhaustive_struct ( cx, item, variant_data) ;
91- } ,
92- _ => { } ,
106+ if let ast:: ItemKind :: Struct ( variant_data, _) = & item. kind {
107+ if let ast:: VariantData :: Unit ( ..) = variant_data {
108+ return ;
109+ }
110+
111+ check_manual_non_exhaustive_struct ( cx, item, variant_data) ;
93112 }
94113 }
95114
96115 extract_msrv_attr ! ( EarlyContext ) ;
97116}
98117
99- fn check_manual_non_exhaustive_enum ( cx : & EarlyContext < ' _ > , item : & Item , variants : & [ Variant ] ) {
100- fn is_non_exhaustive_marker ( variant : & Variant ) -> bool {
101- matches ! ( variant. data, VariantData :: Unit ( _) )
102- && variant. ident . as_str ( ) . starts_with ( '_' )
103- && is_doc_hidden ( & variant. attrs )
118+ fn check_manual_non_exhaustive_struct ( cx : & EarlyContext < ' _ > , item : & ast:: Item , data : & ast:: VariantData ) {
119+ fn is_private ( field : & FieldDef ) -> bool {
120+ matches ! ( field. vis. kind, VisibilityKind :: Inherited )
121+ }
122+
123+ fn is_non_exhaustive_marker ( field : & FieldDef ) -> bool {
124+ is_private ( field) && field. ty . kind . is_unit ( ) && field. ident . map_or ( true , |n| n. as_str ( ) . starts_with ( '_' ) )
125+ }
126+
127+ fn find_header_span ( cx : & EarlyContext < ' _ > , item : & ast:: Item , data : & ast:: VariantData ) -> Span {
128+ let delimiter = match data {
129+ ast:: VariantData :: Struct ( ..) => '{' ,
130+ ast:: VariantData :: Tuple ( ..) => '(' ,
131+ ast:: VariantData :: Unit ( _) => unreachable ! ( "`VariantData::Unit` is already handled above" ) ,
132+ } ;
133+
134+ cx. sess ( ) . source_map ( ) . span_until_char ( item. span , delimiter)
104135 }
105136
106- let mut markers = variants. iter ( ) . filter ( |v| is_non_exhaustive_marker ( v) ) ;
137+ let fields = data. fields ( ) ;
138+ let private_fields = fields. iter ( ) . filter ( |f| is_private ( f) ) . count ( ) ;
139+ let public_fields = fields. iter ( ) . filter ( |f| f. vis . kind . is_pub ( ) ) . count ( ) ;
140+
107141 if_chain ! {
108- if let Some ( marker ) = markers . next ( ) ;
109- if markers . count ( ) == 0 && variants . len ( ) > 1 ;
142+ if private_fields == 1 && public_fields >= 1 && public_fields == fields . len ( ) - 1 ;
143+ if let Some ( marker ) = fields . iter ( ) . find ( |f| is_non_exhaustive_marker ( f ) ) ;
110144 then {
111145 span_lint_and_then(
112146 cx,
@@ -116,7 +150,7 @@ fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants
116150 |diag| {
117151 if_chain! {
118152 if !item. attrs. iter( ) . any( |attr| attr. has_name( sym:: non_exhaustive) ) ;
119- let header_span = cx . sess ( ) . source_map ( ) . span_until_char ( item. span , '{' ) ;
153+ let header_span = find_header_span ( cx , item, data ) ;
120154 if let Some ( snippet) = snippet_opt( cx, header_span) ;
121155 then {
122156 diag. span_suggestion(
@@ -127,60 +161,79 @@ fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants
127161 ) ;
128162 }
129163 }
130- diag. span_help( marker. span, "remove this variant " ) ;
164+ diag. span_help( marker. span, "remove this field " ) ;
131165 } ) ;
132166 }
133167 }
134168}
135169
136- fn check_manual_non_exhaustive_struct ( cx : & EarlyContext < ' _ > , item : & Item , data : & VariantData ) {
137- fn is_private ( field : & FieldDef ) -> bool {
138- matches ! ( field. vis. kind, VisibilityKind :: Inherited )
139- }
170+ impl < ' tcx > LateLintPass < ' tcx > for ManualNonExhaustiveEnum {
171+ fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
172+ if !meets_msrv ( self . msrv . as_ref ( ) , & msrvs:: NON_EXHAUSTIVE ) {
173+ return ;
174+ }
140175
141- fn is_non_exhaustive_marker ( field : & FieldDef ) -> bool {
142- is_private ( field) && field. ty . kind . is_unit ( ) && field. ident . map_or ( true , |n| n. as_str ( ) . starts_with ( '_' ) )
176+ if let hir:: ItemKind :: Enum ( def, _) = & item. kind
177+ && def. variants . len ( ) > 1
178+ {
179+ let mut iter = def. variants . iter ( ) . filter_map ( |v| {
180+ let id = cx. tcx . hir ( ) . local_def_id ( v. id ) ;
181+ ( matches ! ( v. data, hir:: VariantData :: Unit ( _) )
182+ && v. ident . as_str ( ) . starts_with ( '_' )
183+ && is_doc_hidden ( cx. tcx . get_attrs ( id. to_def_id ( ) ) ) )
184+ . then ( || ( id, v. span ) )
185+ } ) ;
186+ if let Some ( ( id, span) ) = iter. next ( )
187+ && iter. next ( ) . is_none ( )
188+ {
189+ self . potential_enums . push ( ( item. def_id , id, item. span , span) ) ;
190+ }
191+ }
143192 }
144193
145- fn find_header_span ( cx : & EarlyContext < ' _ > , item : & Item , data : & VariantData ) -> Span {
146- let delimiter = match data {
147- VariantData :: Struct ( ..) => '{' ,
148- VariantData :: Tuple ( ..) => '(' ,
149- VariantData :: Unit ( _) => unreachable ! ( "`VariantData::Unit` is already handled above" ) ,
150- } ;
151-
152- cx. sess ( ) . source_map ( ) . span_until_char ( item. span , delimiter)
194+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
195+ if let ExprKind :: Path ( QPath :: Resolved ( None , p) ) = & e. kind
196+ && let [ .., name] = p. segments
197+ && let Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , CtorKind :: Const ) , id) = p. res
198+ && name. ident . as_str ( ) . starts_with ( '_' )
199+ && let Some ( variant_id) = cx. tcx . parent ( id)
200+ && let Some ( enum_id) = cx. tcx . parent ( variant_id)
201+ {
202+ self . constructed_enum_variants . insert ( ( enum_id, variant_id) ) ;
203+ }
153204 }
154205
155- let fields = data. fields ( ) ;
156- let private_fields = fields. iter ( ) . filter ( |f| is_private ( f) ) . count ( ) ;
157- let public_fields = fields. iter ( ) . filter ( |f| f. vis . kind . is_pub ( ) ) . count ( ) ;
158-
159- if_chain ! {
160- if private_fields == 1 && public_fields >= 1 && public_fields == fields. len( ) - 1 ;
161- if let Some ( marker) = fields. iter( ) . find( |f| is_non_exhaustive_marker( f) ) ;
162- then {
206+ fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
207+ for & ( enum_id, _, enum_span, variant_span) in
208+ self . potential_enums . iter ( ) . filter ( |& & ( enum_id, variant_id, _, _) | {
209+ !self
210+ . constructed_enum_variants
211+ . contains ( & ( enum_id. to_def_id ( ) , variant_id. to_def_id ( ) ) )
212+ && !is_lint_allowed ( cx, MANUAL_NON_EXHAUSTIVE , cx. tcx . hir ( ) . local_def_id_to_hir_id ( enum_id) )
213+ } )
214+ {
163215 span_lint_and_then (
164216 cx,
165217 MANUAL_NON_EXHAUSTIVE ,
166- item . span ,
218+ enum_span ,
167219 "this seems like a manual implementation of the non-exhaustive pattern" ,
168220 |diag| {
169- if_chain! {
170- if !item. attrs. iter( ) . any( |attr| attr. has_name( sym:: non_exhaustive) ) ;
171- let header_span = find_header_span( cx, item, data) ;
172- if let Some ( snippet) = snippet_opt( cx, header_span) ;
173- then {
221+ if !cx. tcx . adt_def ( enum_id) . is_variant_list_non_exhaustive ( )
222+ && let header_span = cx. sess ( ) . source_map ( ) . span_until_char ( enum_span, '{' )
223+ && let Some ( snippet) = snippet_opt ( cx, header_span)
224+ {
174225 diag. span_suggestion (
175226 header_span,
176227 "add the attribute" ,
177228 format ! ( "#[non_exhaustive] {}" , snippet) ,
178229 Applicability :: Unspecified ,
179230 ) ;
180- }
181231 }
182- diag. span_help( marker. span, "remove this field" ) ;
183- } ) ;
232+ diag. span_help ( variant_span, "remove this variant" ) ;
233+ } ,
234+ ) ;
184235 }
185236 }
237+
238+ extract_msrv_attr ! ( LateContext ) ;
186239}
0 commit comments