1
1
use clippy_utils:: consts:: { constant, Constant } ;
2
- use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
+ use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then } ;
3
3
use clippy_utils:: source:: snippet_with_context;
4
- use clippy_utils:: { is_diag_item_method, match_def_path, meets_msrv, msrvs, paths} ;
4
+ use clippy_utils:: usage:: local_used_after_expr;
5
+ use clippy_utils:: visitors:: expr_visitor;
6
+ use clippy_utils:: { is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths} ;
5
7
use if_chain:: if_chain;
6
8
use rustc_errors:: Applicability ;
7
- use rustc_hir:: { Expr , ExprKind , HirId , LangItem , Node , QPath } ;
9
+ use rustc_hir:: intravisit:: Visitor ;
10
+ use rustc_hir:: {
11
+ BindingAnnotation , Expr , ExprKind , HirId , LangItem , Local , MatchSource , Node , Pat , PatKind , QPath , Stmt , StmtKind ,
12
+ } ;
8
13
use rustc_lint:: LateContext ;
9
14
use rustc_middle:: ty;
10
15
use rustc_semver:: RustcVersion ;
11
- use rustc_span:: { symbol :: sym, Span , SyntaxContext } ;
16
+ use rustc_span:: { sym, Span , Symbol , SyntaxContext } ;
12
17
13
18
use super :: { MANUAL_SPLIT_ONCE , NEEDLESS_SPLITN } ;
14
19
@@ -25,40 +30,41 @@ pub(super) fn check(
25
30
return ;
26
31
}
27
32
28
- let ctxt = expr. span . ctxt ( ) ;
29
- let Some ( usage) = parse_iter_usage ( cx, ctxt, cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) ) else { return } ;
30
-
31
- let needless = match usage. kind {
33
+ let needless = |usage_kind| match usage_kind {
32
34
IterUsageKind :: Nth ( n) => count > n + 1 ,
33
35
IterUsageKind :: NextTuple => count > 2 ,
34
36
} ;
37
+ let manual = count == 2 && meets_msrv ( msrv, & msrvs:: STR_SPLIT_ONCE ) ;
35
38
36
- if needless {
37
- let mut app = Applicability :: MachineApplicable ;
38
- let ( r, message) = if method_name == "splitn" {
39
- ( "" , "unnecessary use of `splitn`" )
40
- } else {
41
- ( "r" , "unnecessary use of `rsplitn`" )
42
- } ;
43
-
44
- span_lint_and_sugg (
45
- cx,
46
- NEEDLESS_SPLITN ,
47
- expr. span ,
48
- message,
49
- "try this" ,
50
- format ! (
51
- "{}.{r}split({})" ,
52
- snippet_with_context( cx, self_arg. span, ctxt, ".." , & mut app) . 0 ,
53
- snippet_with_context( cx, pat_arg. span, ctxt, ".." , & mut app) . 0 ,
54
- ) ,
55
- app,
56
- ) ;
57
- } else if count == 2 && meets_msrv ( msrv, & msrvs:: STR_SPLIT_ONCE ) {
58
- check_manual_split_once ( cx, method_name, expr, self_arg, pat_arg, & usage) ;
39
+ match parse_iter_usage ( cx, expr. span . ctxt ( ) , cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) ) {
40
+ Some ( usage) if needless ( usage. kind ) => lint_needless ( cx, method_name, expr, self_arg, pat_arg) ,
41
+ Some ( usage) if manual => check_manual_split_once ( cx, method_name, expr, self_arg, pat_arg, & usage) ,
42
+ None if manual => {
43
+ check_manual_split_once_indirect ( cx, method_name, expr, self_arg, pat_arg) ;
44
+ } ,
45
+ _ => { } ,
59
46
}
60
47
}
61
48
49
+ fn lint_needless ( cx : & LateContext < ' _ > , method_name : & str , expr : & Expr < ' _ > , self_arg : & Expr < ' _ > , pat_arg : & Expr < ' _ > ) {
50
+ let mut app = Applicability :: MachineApplicable ;
51
+ let r = if method_name == "splitn" { "" } else { "r" } ;
52
+
53
+ span_lint_and_sugg (
54
+ cx,
55
+ NEEDLESS_SPLITN ,
56
+ expr. span ,
57
+ & format ! ( "unnecessary use of `{r}splitn`" ) ,
58
+ "try this" ,
59
+ format ! (
60
+ "{}.{r}split({})" ,
61
+ snippet_with_context( cx, self_arg. span, expr. span. ctxt( ) , ".." , & mut app) . 0 ,
62
+ snippet_with_context( cx, pat_arg. span, expr. span. ctxt( ) , ".." , & mut app) . 0 ,
63
+ ) ,
64
+ app,
65
+ ) ;
66
+ }
67
+
62
68
fn check_manual_split_once (
63
69
cx : & LateContext < ' _ > ,
64
70
method_name : & str ,
@@ -107,16 +113,171 @@ fn check_manual_split_once(
107
113
span_lint_and_sugg ( cx, MANUAL_SPLIT_ONCE , usage. span , msg, "try this" , sugg, app) ;
108
114
}
109
115
116
+ /// checks for
117
+ ///
118
+ /// ```
119
+ /// let mut iter = "a.b.c".splitn(2, '.');
120
+ /// let a = iter.next();
121
+ /// let b = iter.next();
122
+ /// ```
123
+ fn check_manual_split_once_indirect (
124
+ cx : & LateContext < ' _ > ,
125
+ method_name : & str ,
126
+ expr : & Expr < ' _ > ,
127
+ self_arg : & Expr < ' _ > ,
128
+ pat_arg : & Expr < ' _ > ,
129
+ ) -> Option < ( ) > {
130
+ let ctxt = expr. span . ctxt ( ) ;
131
+ let mut parents = cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) ;
132
+ if let ( _, Node :: Local ( local) ) = parents. next ( ) ?
133
+ && let PatKind :: Binding ( BindingAnnotation :: Mutable , iter_binding_id, iter_ident, None ) = local. pat . kind
134
+ && let ( iter_stmt_id, Node :: Stmt ( _) ) = parents. next ( ) ?
135
+ && let ( _, Node :: Block ( enclosing_block) ) = parents. next ( ) ?
136
+
137
+ && let mut stmts = enclosing_block
138
+ . stmts
139
+ . iter ( )
140
+ . skip_while ( |stmt| stmt. hir_id != iter_stmt_id)
141
+ . skip ( 1 )
142
+
143
+ && let first = indirect_usage ( cx, stmts. next ( ) ?, iter_binding_id, ctxt) ?
144
+ && let second = indirect_usage ( cx, stmts. next ( ) ?, iter_binding_id, ctxt) ?
145
+ && first. unwrap_kind == second. unwrap_kind
146
+ && first. name != second. name
147
+ && !local_used_after_expr ( cx, iter_binding_id, second. init_expr )
148
+ {
149
+ let ( r, lhs, rhs) = if method_name == "splitn" {
150
+ ( "" , first. name , second. name )
151
+ } else {
152
+ ( "r" , second. name , first. name )
153
+ } ;
154
+ let msg = format ! ( "manual implementation of `{r}split_once`" ) ;
155
+
156
+ let mut app = Applicability :: MachineApplicable ;
157
+ let self_snip = snippet_with_context ( cx, self_arg. span , ctxt, ".." , & mut app) . 0 ;
158
+ let pat_snip = snippet_with_context ( cx, pat_arg. span , ctxt, ".." , & mut app) . 0 ;
159
+
160
+ span_lint_and_then ( cx, MANUAL_SPLIT_ONCE , local. span , & msg, |diag| {
161
+ diag. span_label ( first. span , "first usage here" ) ;
162
+ diag. span_label ( second. span , "second usage here" ) ;
163
+
164
+ let unwrap = match first. unwrap_kind {
165
+ UnwrapKind :: Unwrap => ".unwrap()" ,
166
+ UnwrapKind :: QuestionMark => "?" ,
167
+ } ;
168
+ diag. span_suggestion_verbose (
169
+ local. span ,
170
+ & format ! ( "try `{r}split_once`" ) ,
171
+ format ! ( "let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};" ) ,
172
+ app,
173
+ ) ;
174
+
175
+ let remove_msg = format ! ( "remove the `{iter_ident}` usages" ) ;
176
+ diag. span_suggestion (
177
+ first. span ,
178
+ & remove_msg,
179
+ String :: new ( ) ,
180
+ app,
181
+ ) ;
182
+ diag. span_suggestion (
183
+ second. span ,
184
+ & remove_msg,
185
+ String :: new ( ) ,
186
+ app,
187
+ ) ;
188
+ } ) ;
189
+ }
190
+
191
+ Some ( ( ) )
192
+ }
193
+
194
+ #[ derive( Debug ) ]
195
+ struct IndirectUsage < ' a > {
196
+ name : Symbol ,
197
+ span : Span ,
198
+ init_expr : & ' a Expr < ' a > ,
199
+ unwrap_kind : UnwrapKind ,
200
+ }
201
+
202
+ /// returns `Some(IndirectUsage)` for e.g.
203
+ ///
204
+ /// ```ignore
205
+ /// let name = binding.next()?;
206
+ /// let name = binding.next().unwrap();
207
+ /// ```
208
+ fn indirect_usage < ' tcx > (
209
+ cx : & LateContext < ' tcx > ,
210
+ stmt : & Stmt < ' tcx > ,
211
+ binding : HirId ,
212
+ ctxt : SyntaxContext ,
213
+ ) -> Option < IndirectUsage < ' tcx > > {
214
+ if let StmtKind :: Local ( Local {
215
+ pat :
216
+ Pat {
217
+ kind : PatKind :: Binding ( BindingAnnotation :: Unannotated , _, ident, None ) ,
218
+ ..
219
+ } ,
220
+ init : Some ( init_expr) ,
221
+ hir_id : local_hir_id,
222
+ ..
223
+ } ) = stmt. kind
224
+ {
225
+ let mut path_to_binding = None ;
226
+ expr_visitor ( cx, |expr| {
227
+ if path_to_local_id ( expr, binding) {
228
+ path_to_binding = Some ( expr) ;
229
+ }
230
+
231
+ path_to_binding. is_none ( )
232
+ } )
233
+ . visit_expr ( init_expr) ;
234
+
235
+ let mut parents = cx. tcx . hir ( ) . parent_iter ( path_to_binding?. hir_id ) ;
236
+ let iter_usage = parse_iter_usage ( cx, ctxt, & mut parents) ?;
237
+
238
+ let ( parent_id, _) = parents. find ( |( _, node) | {
239
+ !matches ! (
240
+ node,
241
+ Node :: Expr ( Expr {
242
+ kind: ExprKind :: Match ( .., MatchSource :: TryDesugar ) ,
243
+ ..
244
+ } )
245
+ )
246
+ } ) ?;
247
+
248
+ if let IterUsage {
249
+ kind : IterUsageKind :: Nth ( 0 ) ,
250
+ unwrap_kind : Some ( unwrap_kind) ,
251
+ ..
252
+ } = iter_usage
253
+ {
254
+ if parent_id == * local_hir_id {
255
+ return Some ( IndirectUsage {
256
+ name : ident. name ,
257
+ span : stmt. span ,
258
+ init_expr,
259
+ unwrap_kind,
260
+ } ) ;
261
+ }
262
+ }
263
+ }
264
+
265
+ None
266
+ }
267
+
268
+ #[ derive( Debug , Clone , Copy ) ]
110
269
enum IterUsageKind {
111
270
Nth ( u128 ) ,
112
271
NextTuple ,
113
272
}
114
273
274
+ #[ derive( Debug , PartialEq ) ]
115
275
enum UnwrapKind {
116
276
Unwrap ,
117
277
QuestionMark ,
118
278
}
119
279
280
+ #[ derive( Debug ) ]
120
281
struct IterUsage {
121
282
kind : IterUsageKind ,
122
283
unwrap_kind : Option < UnwrapKind > ,
0 commit comments