@@ -1311,43 +1311,167 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
13111311 issue_span : Span ,
13121312 expr_span : Span ,
13131313 body_expr : Option < & ' hir hir:: Expr < ' hir > > ,
1314- loop_bind : Option < Symbol > ,
1314+ loop_bind : Option < & ' hir Ident > ,
1315+ loop_span : Option < Span > ,
1316+ head_span : Option < Span > ,
1317+ pat_span : Option < Span > ,
1318+ head : Option < & ' hir hir:: Expr < ' hir > > ,
13151319 }
13161320 impl < ' hir > Visitor < ' hir > for ExprFinder < ' hir > {
13171321 fn visit_expr ( & mut self , ex : & ' hir hir:: Expr < ' hir > ) {
1318- if let hir:: ExprKind :: Loop ( hir:: Block { stmts : [ stmt, ..] , ..} , _, hir:: LoopSource :: ForLoop , _) = ex. kind &&
1319- let hir:: StmtKind :: Expr ( hir:: Expr { kind : hir:: ExprKind :: Match ( call, [ _, bind, ..] , _) , ..} ) = stmt. kind &&
1320- let hir:: ExprKind :: Call ( path, _args) = call. kind &&
1321- let hir:: ExprKind :: Path ( hir:: QPath :: LangItem ( LangItem :: IteratorNext , _, _, ) ) = path. kind &&
1322- let hir:: PatKind :: Struct ( path, [ field, ..] , _) = bind. pat . kind &&
1323- let hir:: QPath :: LangItem ( LangItem :: OptionSome , _, _) = path &&
1324- let PatField { pat : hir:: Pat { kind : hir:: PatKind :: Binding ( _, _, ident, ..) , .. } , ..} = field &&
1325- self . issue_span . source_equal ( call. span ) {
1326- self . loop_bind = Some ( ident. name ) ;
1322+ // Try to find
1323+ // let result = match IntoIterator::into_iter(<head>) {
1324+ // mut iter => {
1325+ // [opt_ident]: loop {
1326+ // match Iterator::next(&mut iter) {
1327+ // None => break,
1328+ // Some(<pat>) => <body>,
1329+ // };
1330+ // }
1331+ // }
1332+ // };
1333+ // corresponding to the desugaring of a for loop `for <pat> in <head> { <body> }`.
1334+ if let hir:: ExprKind :: Call ( path, [ arg] ) = ex. kind
1335+ && let hir:: ExprKind :: Path (
1336+ hir:: QPath :: LangItem ( LangItem :: IntoIterIntoIter , _, _) ,
1337+ ) = path. kind
1338+ && arg. span == self . issue_span
1339+ {
1340+ // Find `IntoIterator::into_iter(<head>)`
1341+ self . head = Some ( arg) ;
1342+ }
1343+ if let hir:: ExprKind :: Loop (
1344+ hir:: Block { stmts : [ stmt, ..] , .. } ,
1345+ _,
1346+ hir:: LoopSource :: ForLoop ,
1347+ _,
1348+ ) = ex. kind
1349+ && let hir:: StmtKind :: Expr ( hir:: Expr {
1350+ kind : hir:: ExprKind :: Match ( call, [ _, bind, ..] , _) ,
1351+ span : head_span,
1352+ ..
1353+ } ) = stmt. kind
1354+ && let hir:: ExprKind :: Call ( path, _args) = call. kind
1355+ && let hir:: ExprKind :: Path (
1356+ hir:: QPath :: LangItem ( LangItem :: IteratorNext , _, _) ,
1357+ ) = path. kind
1358+ && let hir:: PatKind :: Struct ( path, [ field, ..] , _) = bind. pat . kind
1359+ && let hir:: QPath :: LangItem ( LangItem :: OptionSome , pat_span, _) = path
1360+ && self . issue_span . source_equal ( call. span )
1361+ {
1362+ // Find `<pat>` and the span for the whole `for` loop.
1363+ if let PatField { pat : hir:: Pat {
1364+ kind : hir:: PatKind :: Binding ( _, _, ident, ..) ,
1365+ ..
1366+ } , ..} = field {
1367+ self . loop_bind = Some ( ident) ;
13271368 }
1369+ self . head_span = Some ( * head_span) ;
1370+ self . pat_span = Some ( pat_span) ;
1371+ self . loop_span = Some ( stmt. span ) ;
1372+ }
13281373
1329- if let hir:: ExprKind :: MethodCall ( body_call, _recv, ..) = ex. kind &&
1330- body_call. ident . name == sym:: next && ex. span . source_equal ( self . expr_span ) {
1331- self . body_expr = Some ( ex) ;
1374+ if let hir:: ExprKind :: MethodCall ( body_call, _recv, ..) = ex. kind
1375+ && body_call. ident . name == sym:: next && ex. span . source_equal ( self . expr_span )
1376+ {
1377+ self . body_expr = Some ( ex) ;
13321378 }
13331379
13341380 hir:: intravisit:: walk_expr ( self , ex) ;
13351381 }
13361382 }
1337- let mut finder =
1338- ExprFinder { expr_span : span, issue_span, loop_bind : None , body_expr : None } ;
1383+ let mut finder = ExprFinder {
1384+ expr_span : span,
1385+ issue_span,
1386+ loop_bind : None ,
1387+ body_expr : None ,
1388+ head_span : None ,
1389+ loop_span : None ,
1390+ pat_span : None ,
1391+ head : None ,
1392+ } ;
13391393 finder. visit_expr ( hir. body ( body_id) . value ) ;
13401394
1341- if let Some ( loop_bind) = finder. loop_bind &&
1342- let Some ( body_expr) = finder. body_expr &&
1343- let Some ( def_id) = typeck_results. type_dependent_def_id ( body_expr. hir_id ) &&
1344- let Some ( trait_did) = tcx. trait_of_item ( def_id) &&
1345- tcx. is_diagnostic_item ( sym:: Iterator , trait_did) {
1346- err. note ( format ! (
1347- "a for loop advances the iterator for you, the result is stored in `{}`." ,
1348- loop_bind
1395+ if let Some ( body_expr) = finder. body_expr
1396+ && let Some ( loop_span) = finder. loop_span
1397+ && let Some ( def_id) = typeck_results. type_dependent_def_id ( body_expr. hir_id )
1398+ && let Some ( trait_did) = tcx. trait_of_item ( def_id)
1399+ && tcx. is_diagnostic_item ( sym:: Iterator , trait_did)
1400+ {
1401+ if let Some ( loop_bind) = finder. loop_bind {
1402+ err. note ( format ! (
1403+ "a for loop advances the iterator for you, the result is stored in `{}`" ,
1404+ loop_bind. name,
1405+ ) ) ;
1406+ } else {
1407+ err. note (
1408+ "a for loop advances the iterator for you, the result is stored in its pattern" ,
1409+ ) ;
1410+ }
1411+ let msg = "if you want to call `next` on a iterator within the loop, consider using \
1412+ `while let`";
1413+ if let Some ( head) = finder. head
1414+ && let Some ( pat_span) = finder. pat_span
1415+ && loop_span. contains ( body_expr. span )
1416+ && loop_span. contains ( head. span )
1417+ {
1418+ let sm = self . infcx . tcx . sess . source_map ( ) ;
1419+
1420+ let mut sugg = vec ! [ ] ;
1421+ if let hir:: ExprKind :: Path ( hir:: QPath :: Resolved ( None , _) ) = head. kind {
1422+ // A bare path doesn't need a `let` assignment, it's already a simple
1423+ // binding access.
1424+ // As a new binding wasn't added, we don't need to modify the advancing call.
1425+ sugg. push ( (
1426+ loop_span. with_hi ( pat_span. lo ( ) ) ,
1427+ format ! ( "while let Some(" ) ,
1428+ ) ) ;
1429+ sugg. push ( (
1430+ pat_span. shrink_to_hi ( ) . with_hi ( head. span . lo ( ) ) ,
1431+ ") = " . to_string ( ) ,
1432+ ) ) ;
1433+ sugg. push ( (
1434+ head. span . shrink_to_hi ( ) ,
1435+ ".next()" . to_string ( ) ,
1436+ ) ) ;
1437+ } else {
1438+ // Needs a new a `let` binding.
1439+ let indent = if let Some ( indent) = sm. indentation_before ( loop_span) {
1440+ format ! ( "\n {indent}" )
1441+ } else {
1442+ " " . to_string ( )
1443+ } ;
1444+ let Ok ( head_str) = sm. span_to_snippet ( head. span ) else {
1445+ err. help ( msg) ;
1446+ return ;
1447+ } ;
1448+ sugg. push ( (
1449+ loop_span. with_hi ( pat_span. lo ( ) ) ,
1450+ format ! ( "let iter = {head_str};{indent}while let Some(" ) ,
13491451 ) ) ;
1350- err. help ( "if you want to call `next` on a iterator within the loop, consider using `while let`." ) ;
1452+ sugg. push ( (
1453+ pat_span. shrink_to_hi ( ) . with_hi ( head. span . hi ( ) ) ,
1454+ ") = iter.next()" . to_string ( ) ,
1455+ ) ) ;
1456+ // As a new binding was added, we should change how the iterator is advanced to
1457+ // use the newly introduced binding.
1458+ if let hir:: ExprKind :: MethodCall ( _, recv, ..) = body_expr. kind
1459+ && let hir:: ExprKind :: Path ( hir:: QPath :: Resolved ( None , ..) ) = recv. kind
1460+ {
1461+ // As we introduced a `let iter = <head>;`, we need to change where the
1462+ // already borrowed value was accessed from `<recv>.next()` to
1463+ // `iter.next()`.
1464+ sugg. push ( ( recv. span , "iter" . to_string ( ) ) ) ;
1465+ }
1466+ }
1467+ err. multipart_suggestion (
1468+ msg,
1469+ sugg,
1470+ Applicability :: MaybeIncorrect ,
1471+ ) ;
1472+ } else {
1473+ err. help ( msg) ;
1474+ }
13511475 }
13521476 }
13531477
0 commit comments