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