11use 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 } ;
33use 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} ;
57use if_chain:: if_chain;
68use 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+ } ;
813use rustc_lint:: LateContext ;
914use rustc_middle:: ty;
1015use rustc_semver:: RustcVersion ;
11- use rustc_span:: { symbol :: sym, Span , SyntaxContext } ;
16+ use rustc_span:: { sym, Span , Symbol , SyntaxContext } ;
1217
1318use super :: { MANUAL_SPLIT_ONCE , NEEDLESS_SPLITN } ;
1419
@@ -25,40 +30,41 @@ pub(super) fn check(
2530 return ;
2631 }
2732
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 {
3234 IterUsageKind :: Nth ( n) => count > n + 1 ,
3335 IterUsageKind :: NextTuple => count > 2 ,
3436 } ;
37+ let manual = count == 2 && meets_msrv ( msrv, & msrvs:: STR_SPLIT_ONCE ) ;
3538
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+ _ => { } ,
5946 }
6047}
6148
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+
6268fn check_manual_split_once (
6369 cx : & LateContext < ' _ > ,
6470 method_name : & str ,
@@ -107,16 +113,171 @@ fn check_manual_split_once(
107113 span_lint_and_sugg ( cx, MANUAL_SPLIT_ONCE , usage. span , msg, "try this" , sugg, app) ;
108114}
109115
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 ) ]
110269enum IterUsageKind {
111270 Nth ( u128 ) ,
112271 NextTuple ,
113272}
114273
274+ #[ derive( Debug , PartialEq ) ]
115275enum UnwrapKind {
116276 Unwrap ,
117277 QuestionMark ,
118278}
119279
280+ #[ derive( Debug ) ]
120281struct IterUsage {
121282 kind : IterUsageKind ,
122283 unwrap_kind : Option < UnwrapKind > ,
0 commit comments