11use crate :: utils:: { snippet_opt, span_lint_and_then} ;
22use if_chain:: if_chain;
3- use rustc_ast:: ast:: { Attribute , Ident , Item , ItemKind , StructField , TyKind , Variant , VariantData , VisibilityKind } ;
3+ use rustc_ast:: ast:: { Attribute , Item , ItemKind , StructField , Variant , VariantData , VisibilityKind } ;
44use rustc_attr as attr;
55use rustc_errors:: Applicability ;
66use rustc_lint:: { EarlyContext , EarlyLintPass } ;
77use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
8+ use rustc_span:: Span ;
89
910declare_clippy_lint ! {
1011 /// **What it does:** Checks for manual implementations of the non-exhaustive pattern.
@@ -90,29 +91,30 @@ fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants
9091 }
9192
9293 if_chain ! {
93- if !attr:: contains_name( & item. attrs, sym!( non_exhaustive) ) ;
94- let markers = variants. iter( ) . filter( |v| is_non_exhaustive_marker( v) ) . count( ) ;
95- if markers == 1 && variants. len( ) > markers;
96- if let Some ( variant) = variants. last( ) ;
97- if is_non_exhaustive_marker( variant) ;
94+ let mut markers = variants. iter( ) . filter( |v| is_non_exhaustive_marker( v) ) ;
95+ if let Some ( marker) = markers. next( ) ;
96+ if markers. count( ) == 0 && variants. len( ) > 1 ;
9897 then {
9998 span_lint_and_then(
10099 cx,
101100 MANUAL_NON_EXHAUSTIVE ,
102101 item. span,
103102 "this seems like a manual implementation of the non-exhaustive pattern" ,
104103 |diag| {
105- let header_span = cx. sess. source_map( ) . span_until_char( item. span, '{' ) ;
106-
107- if let Some ( snippet) = snippet_opt( cx, header_span) {
108- diag. span_suggestion(
109- item. span,
110- "add the attribute" ,
111- format!( "#[non_exhaustive] {}" , snippet) ,
112- Applicability :: Unspecified ,
113- ) ;
114- diag. span_help( variant. span, "and remove this variant" ) ;
104+ if_chain! {
105+ if !attr:: contains_name( & item. attrs, sym!( non_exhaustive) ) ;
106+ let header_span = cx. sess. source_map( ) . span_until_char( item. span, '{' ) ;
107+ if let Some ( snippet) = snippet_opt( cx, header_span) ;
108+ then {
109+ diag. span_suggestion(
110+ header_span,
111+ "add the attribute" ,
112+ format!( "#[non_exhaustive] {}" , snippet) ,
113+ Applicability :: Unspecified ,
114+ ) ;
115+ }
115116 }
117+ diag. span_help( marker. span, "remove this variant" ) ;
116118 } ) ;
117119 }
118120 }
@@ -123,44 +125,48 @@ fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data:
123125 matches ! ( field. vis. node, VisibilityKind :: Inherited )
124126 }
125127
126- fn is_non_exhaustive_marker ( name : & Option < Ident > ) -> bool {
127- name. map ( |n| n. as_str ( ) . starts_with ( '_' ) ) . unwrap_or ( true )
128+ fn is_non_exhaustive_marker ( field : & StructField ) -> bool {
129+ is_private ( field) && field. ty . kind . is_unit ( ) && field. ident . map_or ( true , |n| n. as_str ( ) . starts_with ( '_' ) )
130+ }
131+
132+ fn find_header_span ( cx : & EarlyContext < ' _ > , item : & Item , data : & VariantData ) -> Span {
133+ let delimiter = match data {
134+ VariantData :: Struct ( ..) => '{' ,
135+ VariantData :: Tuple ( ..) => '(' ,
136+ _ => unreachable ! ( "`VariantData::Unit` is already handled above" ) ,
137+ } ;
138+
139+ cx. sess . source_map ( ) . span_until_char ( item. span , delimiter)
128140 }
129141
130142 let fields = data. fields ( ) ;
131143 let private_fields = fields. iter ( ) . filter ( |f| is_private ( f) ) . count ( ) ;
132144 let public_fields = fields. iter ( ) . filter ( |f| f. vis . node . is_pub ( ) ) . count ( ) ;
133145
134146 if_chain ! {
135- if !attr:: contains_name( & item. attrs, sym!( non_exhaustive) ) ;
136- if private_fields == 1 && public_fields >= private_fields && public_fields == fields. len( ) - 1 ;
137- if let Some ( field) = fields. iter( ) . find( |f| is_private( f) ) ;
138- if is_non_exhaustive_marker( & field. ident) ;
139- if let TyKind :: Tup ( tup_fields) = & field. ty. kind;
140- if tup_fields. is_empty( ) ;
147+ if private_fields == 1 && public_fields >= 1 && public_fields == fields. len( ) - 1 ;
148+ if let Some ( marker) = fields. iter( ) . find( |f| is_non_exhaustive_marker( f) ) ;
141149 then {
142150 span_lint_and_then(
143151 cx,
144152 MANUAL_NON_EXHAUSTIVE ,
145153 item. span,
146154 "this seems like a manual implementation of the non-exhaustive pattern" ,
147155 |diag| {
148- let delimiter = match data {
149- VariantData :: Struct ( ..) => '{' ,
150- VariantData :: Tuple ( ..) => '(' ,
151- _ => unreachable!( ) ,
152- } ;
153- let header_span = cx. sess. source_map( ) . span_until_char( item. span, delimiter) ;
154-
155- if let Some ( snippet) = snippet_opt( cx, header_span) {
156- diag. span_suggestion(
157- item. span,
158- "add the attribute" ,
159- format!( "#[non_exhaustive] {}" , snippet) ,
160- Applicability :: Unspecified ,
161- ) ;
162- diag. span_help( field. span, "and remove this field" ) ;
156+ if_chain! {
157+ if !attr:: contains_name( & item. attrs, sym!( non_exhaustive) ) ;
158+ let header_span = find_header_span( cx, item, data) ;
159+ if let Some ( snippet) = snippet_opt( cx, header_span) ;
160+ then {
161+ diag. span_suggestion(
162+ header_span,
163+ "add the attribute" ,
164+ format!( "#[non_exhaustive] {}" , snippet) ,
165+ Applicability :: Unspecified ,
166+ ) ;
167+ }
163168 }
169+ diag. span_help( marker. span, "remove this field" ) ;
164170 } ) ;
165171 }
166172 }
0 commit comments