1
1
use crate :: utils:: { snippet_opt, span_lint_and_then} ;
2
2
use 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 } ;
4
4
use rustc_attr as attr;
5
5
use rustc_errors:: Applicability ;
6
6
use rustc_lint:: { EarlyContext , EarlyLintPass } ;
7
7
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
8
+ use rustc_span:: Span ;
8
9
9
10
declare_clippy_lint ! {
10
11
/// **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
90
91
}
91
92
92
93
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 ;
98
97
then {
99
98
span_lint_and_then(
100
99
cx,
101
100
MANUAL_NON_EXHAUSTIVE ,
102
101
item. span,
103
102
"this seems like a manual implementation of the non-exhaustive pattern" ,
104
103
|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
+ }
115
116
}
117
+ diag. span_help( marker. span, "remove this variant" ) ;
116
118
} ) ;
117
119
}
118
120
}
@@ -123,44 +125,48 @@ fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data:
123
125
matches ! ( field. vis. node, VisibilityKind :: Inherited )
124
126
}
125
127
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)
128
140
}
129
141
130
142
let fields = data. fields ( ) ;
131
143
let private_fields = fields. iter ( ) . filter ( |f| is_private ( f) ) . count ( ) ;
132
144
let public_fields = fields. iter ( ) . filter ( |f| f. vis . node . is_pub ( ) ) . count ( ) ;
133
145
134
146
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) ) ;
141
149
then {
142
150
span_lint_and_then(
143
151
cx,
144
152
MANUAL_NON_EXHAUSTIVE ,
145
153
item. span,
146
154
"this seems like a manual implementation of the non-exhaustive pattern" ,
147
155
|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
+ }
163
168
}
169
+ diag. span_help( marker. span, "remove this field" ) ;
164
170
} ) ;
165
171
}
166
172
}
0 commit comments