@@ -2,16 +2,22 @@ use itertools::Itertools;
2
2
use reexport:: * ;
3
3
use rustc:: hir:: * ;
4
4
use rustc:: hir:: def:: Def ;
5
+ use rustc:: hir:: def_id;
5
6
use rustc:: hir:: intravisit:: { walk_block, walk_decl, walk_expr, walk_pat, walk_stmt, NestedVisitorMap , Visitor } ;
6
7
use rustc:: hir:: map:: Node :: { NodeBlock , NodeExpr , NodeStmt } ;
7
8
use rustc:: lint:: * ;
8
9
use rustc:: middle:: const_val:: ConstVal ;
9
10
use rustc:: middle:: region;
11
+ // use rustc::middle::region::CodeExtent;
12
+ use rustc:: middle:: expr_use_visitor:: * ;
13
+ use rustc:: middle:: mem_categorization:: Categorization ;
14
+ use rustc:: middle:: mem_categorization:: cmt;
10
15
use rustc:: ty:: { self , Ty } ;
11
16
use rustc:: ty:: subst:: { Subst , Substs } ;
12
17
use rustc_const_eval:: ConstContext ;
13
18
use std:: collections:: { HashMap , HashSet } ;
14
19
use syntax:: ast;
20
+ use syntax:: codemap:: Span ;
15
21
use utils:: sugg;
16
22
use utils:: const_to_u64;
17
23
@@ -328,6 +334,14 @@ declare_lint! {
328
334
"any loop that will always `break` or `return`"
329
335
}
330
336
337
+ /// TODO: add documentation
338
+
339
+ declare_lint ! {
340
+ pub MUT_RANGE_BOUND ,
341
+ Warn ,
342
+ "for loop over a range where one of the bounds is a mutable variable"
343
+ }
344
+
331
345
#[ derive( Copy , Clone ) ]
332
346
pub struct Pass ;
333
347
@@ -348,7 +362,8 @@ impl LintPass for Pass {
348
362
EMPTY_LOOP ,
349
363
WHILE_LET_ON_ITERATOR ,
350
364
FOR_KV_MAP ,
351
- NEVER_LOOP
365
+ NEVER_LOOP ,
366
+ MUT_RANGE_BOUND
352
367
)
353
368
}
354
369
}
@@ -605,6 +620,7 @@ fn check_for_loop<'a, 'tcx>(
605
620
check_for_loop_arg ( cx, pat, arg, expr) ;
606
621
check_for_loop_explicit_counter ( cx, arg, body, expr) ;
607
622
check_for_loop_over_map_kv ( cx, pat, arg, body, expr) ;
623
+ check_for_mut_range_bound ( cx, arg, body) ;
608
624
detect_manual_memcpy ( cx, pat, arg, body, expr) ;
609
625
}
610
626
@@ -1294,6 +1310,102 @@ fn check_for_loop_over_map_kv<'a, 'tcx>(
1294
1310
}
1295
1311
}
1296
1312
1313
+ struct MutateDelegate {
1314
+ node_id_low : Option < NodeId > ,
1315
+ node_id_high : Option < NodeId > ,
1316
+ span_low : Option < Span > ,
1317
+ span_high : Option < Span > ,
1318
+ }
1319
+
1320
+ impl < ' tcx > Delegate < ' tcx > for MutateDelegate {
1321
+ fn consume ( & mut self , _: NodeId , _: Span , _: cmt < ' tcx > , _: ConsumeMode ) {
1322
+ }
1323
+
1324
+ fn matched_pat ( & mut self , _: & Pat , _: cmt < ' tcx > , _: MatchMode ) {
1325
+ }
1326
+
1327
+ fn consume_pat ( & mut self , _: & Pat , _: cmt < ' tcx > , _: ConsumeMode ) {
1328
+ }
1329
+
1330
+ fn borrow ( & mut self , _: NodeId , sp : Span , cmt : cmt < ' tcx > , _: ty:: Region , bk : ty:: BorrowKind , _: LoanCause ) {
1331
+ if let ty:: BorrowKind :: MutBorrow = bk {
1332
+ if let Categorization :: Local ( id) = cmt. cat {
1333
+ if Some ( id) == self . node_id_low {
1334
+ self . span_low = Some ( sp)
1335
+ }
1336
+ if Some ( id) == self . node_id_high {
1337
+ self . span_high = Some ( sp)
1338
+ }
1339
+ }
1340
+ }
1341
+ }
1342
+
1343
+ fn mutate ( & mut self , _: NodeId , sp : Span , cmt : cmt < ' tcx > , _: MutateMode ) {
1344
+ if let Categorization :: Local ( id) = cmt. cat {
1345
+ if Some ( id) == self . node_id_low {
1346
+ self . span_low = Some ( sp)
1347
+ }
1348
+ if Some ( id) == self . node_id_high {
1349
+ self . span_high = Some ( sp)
1350
+ }
1351
+ }
1352
+ }
1353
+
1354
+ fn decl_without_init ( & mut self , _: NodeId , _: Span ) {
1355
+ }
1356
+ }
1357
+
1358
+ impl < ' tcx > MutateDelegate {
1359
+ fn mutation_span ( & self ) -> ( Option < Span > , Option < Span > ) {
1360
+ ( self . span_low , self . span_high )
1361
+ }
1362
+ }
1363
+
1364
+ fn check_for_mut_range_bound ( cx : & LateContext , arg : & Expr , body : & Expr ) {
1365
+ if let Some ( higher:: Range { start : Some ( start) , end : Some ( end) , .. } ) = higher:: range ( arg) {
1366
+ let mut_ids = vec ! [ check_for_mutability( cx, start) , check_for_mutability( cx, end) ] ;
1367
+ if mut_ids[ 0 ] . is_some ( ) || mut_ids[ 1 ] . is_some ( ) {
1368
+ let ( span_low, span_high) = check_for_mutation ( cx, body, & mut_ids) ;
1369
+ mut_warn_with_span ( cx, span_low) ;
1370
+ mut_warn_with_span ( cx, span_high) ;
1371
+ }
1372
+ }
1373
+ }
1374
+
1375
+ fn mut_warn_with_span ( cx : & LateContext , span : Option < Span > ) {
1376
+ if let Some ( sp) = span {
1377
+ span_lint ( cx, MUT_RANGE_BOUND , sp, "attempt to mutate range bound within loop; note that the range of the loop is unchanged" ) ;
1378
+ }
1379
+ }
1380
+
1381
+ fn check_for_mutability ( cx : & LateContext , bound : & Expr ) -> Option < NodeId > {
1382
+ if_let_chain ! { [
1383
+ let ExprPath ( ref qpath) = bound. node,
1384
+ let QPath :: Resolved ( None , _) = * qpath,
1385
+ ] , {
1386
+ let def = cx. tables. qpath_def( qpath, bound. hir_id) ;
1387
+ if let Def :: Local ( node_id) = def {
1388
+ let node_str = cx. tcx. hir. get( node_id) ;
1389
+ if_let_chain! { [
1390
+ let map:: Node :: NodeBinding ( pat) = node_str,
1391
+ let PatKind :: Binding ( bind_ann, _, _, _) = pat. node,
1392
+ let BindingAnnotation :: Mutable = bind_ann,
1393
+ ] , {
1394
+ return Some ( node_id) ;
1395
+ } }
1396
+ }
1397
+ } }
1398
+ None
1399
+ }
1400
+
1401
+ fn check_for_mutation ( cx : & LateContext , body : & Expr , bound_ids : & [ Option < NodeId > ] ) -> ( Option < Span > , Option < Span > ) {
1402
+ let mut delegate = MutateDelegate { node_id_low : bound_ids[ 0 ] , node_id_high : bound_ids[ 1 ] , span_low : None , span_high : None } ;
1403
+ let def_id = def_id:: DefId :: local ( body. hir_id . owner ) ;
1404
+ let region_scope_tree = & cx. tcx . region_scope_tree ( def_id) ;
1405
+ ExprUseVisitor :: new ( & mut delegate, cx. tcx , cx. param_env , region_scope_tree, cx. tables ) . walk_expr ( body) ;
1406
+ delegate. mutation_span ( )
1407
+ }
1408
+
1297
1409
/// Return true if the pattern is a `PatWild` or an ident prefixed with `'_'`.
1298
1410
fn pat_is_wild < ' tcx > ( pat : & ' tcx PatKind , body : & ' tcx Expr ) -> bool {
1299
1411
match * pat {
0 commit comments