Skip to content

Commit 9500974

Browse files
committed
Fix tracking of which locals would need to be captured in a closure.
* Captures by sub closures are now considered * Copy types are correctly borrowed by reference when their value is used * Fields are no longer automatically borrowed by value * Bindings in `match` and `let` patterns are now checked to determine how a local is captured
1 parent 251dd30 commit 9500974

File tree

4 files changed

+176
-13
lines changed

4 files changed

+176
-13
lines changed

clippy_utils/src/lib.rs

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ use std::collections::hash_map::Entry;
6262
use std::hash::BuildHasherDefault;
6363

6464
use if_chain::if_chain;
65-
use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind};
65+
use rustc_ast::ast::{self, Attribute, LitKind};
6666
use rustc_data_structures::unhash::UnhashMap;
6767
use rustc_hir as hir;
6868
use rustc_hir::def::{DefKind, Res};
@@ -78,9 +78,11 @@ use rustc_hir::{
7878
use rustc_lint::{LateContext, Level, Lint, LintContext};
7979
use rustc_middle::hir::exports::Export;
8080
use rustc_middle::hir::map::Map;
81+
use rustc_middle::hir::place::PlaceBase;
8182
use rustc_middle::ty as rustc_ty;
8283
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
83-
use rustc_middle::ty::{layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable};
84+
use rustc_middle::ty::binding::BindingMode;
85+
use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture};
8486
use rustc_semver::RustcVersion;
8587
use rustc_session::Session;
8688
use rustc_span::hygiene::{ExpnKind, MacroKind};
@@ -91,7 +93,7 @@ use rustc_span::{Span, DUMMY_SP};
9193
use rustc_target::abi::Integer;
9294

9395
use crate::consts::{constant, Constant};
94-
use crate::ty::{can_partially_move_ty, is_recursively_primitive_type};
96+
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
9597

9698
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
9799
if let Ok(version) = RustcVersion::parse(msrv) {
@@ -628,6 +630,11 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -
628630
}
629631

630632
/// Checks if the top level expression can be moved into a closure as is.
633+
/// Currently checks for:
634+
/// * Break/Continue outside the given jump targets
635+
/// * Yield/Return statments.
636+
/// * Inline assembly
637+
/// * Usages of a field of a local where the type of the local can be partially moved.
631638
pub fn can_move_expr_to_closure_no_visit(
632639
cx: &LateContext<'tcx>,
633640
expr: &'tcx Expr<'_>,
@@ -699,6 +706,22 @@ impl std::ops::BitOrAssign for CaptureKind {
699706
/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or
700707
/// function argument (other than a receiver).
701708
pub fn capture_local_usage(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind {
709+
fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
710+
let mut capture = CaptureKind::Ref(Mutability::Not);
711+
pat.each_binding(|_, id, span, _| {
712+
match cx.typeck_results().extract_binding_mode(cx.sess(), id, span).unwrap() {
713+
BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => {
714+
capture = CaptureKind::Value;
715+
},
716+
BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => {
717+
capture = CaptureKind::Ref(Mutability::Mut);
718+
},
719+
_ => (),
720+
}
721+
});
722+
capture
723+
}
724+
702725
debug_assert!(matches!(
703726
e.kind,
704727
ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
@@ -707,6 +730,7 @@ pub fn capture_local_usage(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind
707730
let map = cx.tcx.hir();
708731
let mut child_id = e.hir_id;
709732
let mut capture = CaptureKind::Value;
733+
let mut capture_expr_ty = e;
710734

711735
for (parent_id, parent) in map.parent_iter(e.hir_id) {
712736
if let [Adjustment {
@@ -725,23 +749,47 @@ pub fn capture_local_usage(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind
725749
}
726750
}
727751

728-
if let Node::Expr(e) = parent {
729-
match e.kind {
752+
match parent {
753+
Node::Expr(e) => match e.kind {
730754
ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
731755
ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
732756
ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => {
733757
return CaptureKind::Ref(Mutability::Mut);
734758
},
759+
ExprKind::Field(..) => {
760+
if capture == CaptureKind::Value {
761+
capture_expr_ty = e;
762+
}
763+
},
764+
ExprKind::Match(_, arms, _) => {
765+
let mut mutability = Mutability::Not;
766+
for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
767+
match capture {
768+
CaptureKind::Value => break,
769+
CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
770+
CaptureKind::Ref(Mutability::Not) => (),
771+
}
772+
}
773+
return CaptureKind::Ref(mutability);
774+
},
735775
_ => break,
736-
}
737-
} else {
738-
break;
776+
},
777+
Node::Local(l) => match pat_capture_kind(cx, l.pat) {
778+
CaptureKind::Value => break,
779+
capture @ CaptureKind::Ref(_) => return capture,
780+
},
781+
_ => break,
739782
}
740783

741784
child_id = parent_id;
742785
}
743786

744-
capture
787+
if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
788+
// Copy types are never automatically captured by value.
789+
CaptureKind::Ref(Mutability::Not)
790+
} else {
791+
capture
792+
}
745793
}
746794

747795
/// Checks if the expression can be moved into a closure as is. This will return a list of captures
@@ -777,6 +825,31 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
777825
self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
778826
}
779827
},
828+
ExprKind::Closure(..) => {
829+
let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id).to_def_id();
830+
for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) {
831+
let local_id = match capture.place.base {
832+
PlaceBase::Local(id) => id,
833+
PlaceBase::Upvar(var) => var.var_path.hir_id,
834+
_ => continue,
835+
};
836+
if !self.locals.contains(&local_id) {
837+
let capture = match capture.info.capture_kind {
838+
UpvarCapture::ByValue(_) => CaptureKind::Value,
839+
UpvarCapture::ByRef(borrow) => match borrow.kind {
840+
BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not),
841+
BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => {
842+
CaptureKind::Ref(Mutability::Mut)
843+
},
844+
},
845+
};
846+
self.captures
847+
.entry(local_id)
848+
.and_modify(|e| *e |= capture)
849+
.or_insert(capture);
850+
}
851+
}
852+
},
780853
ExprKind::Loop(b, ..) => {
781854
self.loops.push(e.hir_id);
782855
self.visit_block(b);
@@ -1830,7 +1903,7 @@ pub fn peel_hir_expr_while<'tcx>(
18301903
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
18311904
let mut remaining = count;
18321905
let e = peel_hir_expr_while(expr, |e| match e.kind {
1833-
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
1906+
ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
18341907
remaining -= 1;
18351908
Some(e)
18361909
},
@@ -1844,7 +1917,7 @@ pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>,
18441917
pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
18451918
let mut count = 0;
18461919
let e = peel_hir_expr_while(expr, |e| match e.kind {
1847-
ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
1920+
ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
18481921
count += 1;
18491922
Some(e)
18501923
},

tests/ui/manual_map_option_2.fixed

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,50 @@
11
// run-rustfix
22

33
#![warn(clippy::manual_map)]
4+
#![allow(clippy::toplevel_ref_arg)]
45

56
fn main() {
7+
// Lint. `y` is declared within the arm, so it isn't captured by the map closure
68
let _ = Some(0).map(|x| {
79
let y = (String::new(), String::new());
810
(x, y.0)
911
});
1012

13+
// Don't lint. `s` is borrowed until partway through the arm, but needs to be captured by the map
14+
// closure
1115
let s = Some(String::new());
1216
let _ = match &s {
1317
Some(x) => Some((x.clone(), s)),
1418
None => None,
1519
};
20+
21+
// Don't lint. `s` is borrowed until partway through the arm, but needs to be captured by the map
22+
// closure
23+
let s = Some(String::new());
24+
let _ = match &s {
25+
Some(x) => Some({
26+
let clone = x.clone();
27+
let s = || s;
28+
(clone, s())
29+
}),
30+
None => None,
31+
};
32+
33+
// Don't lint. `s` is borrowed until partway through the arm, but needs to be captured as a mutable
34+
// reference by the map closure
35+
let mut s = Some(String::new());
36+
let _ = match &s {
37+
Some(x) => Some({
38+
let clone = x.clone();
39+
let ref mut s = s;
40+
(clone, s)
41+
}),
42+
None => None,
43+
};
44+
45+
// Lint. `s` is captured by reference, so no lifetime issues.
46+
let s = Some(String::new());
47+
let _ = s.as_ref().map(|x| {
48+
if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
49+
});
1650
}

tests/ui/manual_map_option_2.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// run-rustfix
22

33
#![warn(clippy::manual_map)]
4+
#![allow(clippy::toplevel_ref_arg)]
45

56
fn main() {
7+
// Lint. `y` is declared within the arm, so it isn't captured by the map closure
68
let _ = match Some(0) {
79
Some(x) => Some({
810
let y = (String::new(), String::new());
@@ -11,9 +13,44 @@ fn main() {
1113
None => None,
1214
};
1315

16+
// Don't lint. `s` is borrowed until partway through the arm, but needs to be captured by the map
17+
// closure
1418
let s = Some(String::new());
1519
let _ = match &s {
1620
Some(x) => Some((x.clone(), s)),
1721
None => None,
1822
};
23+
24+
// Don't lint. `s` is borrowed until partway through the arm, but needs to be captured by the map
25+
// closure
26+
let s = Some(String::new());
27+
let _ = match &s {
28+
Some(x) => Some({
29+
let clone = x.clone();
30+
let s = || s;
31+
(clone, s())
32+
}),
33+
None => None,
34+
};
35+
36+
// Don't lint. `s` is borrowed until partway through the arm, but needs to be captured as a mutable
37+
// reference by the map closure
38+
let mut s = Some(String::new());
39+
let _ = match &s {
40+
Some(x) => Some({
41+
let clone = x.clone();
42+
let ref mut s = s;
43+
(clone, s)
44+
}),
45+
None => None,
46+
};
47+
48+
// Lint. `s` is captured by reference, so no lifetime issues.
49+
let s = Some(String::new());
50+
let _ = match &s {
51+
Some(x) => Some({
52+
if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
53+
}),
54+
None => None,
55+
};
1956
}

tests/ui/manual_map_option_2.stderr

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: manual implementation of `Option::map`
2-
--> $DIR/manual_map_option_2.rs:6:13
2+
--> $DIR/manual_map_option_2.rs:8:13
33
|
44
LL | let _ = match Some(0) {
55
| _____________^
@@ -20,5 +20,24 @@ LL | (x, y.0)
2020
LL | });
2121
|
2222

23-
error: aborting due to previous error
23+
error: manual implementation of `Option::map`
24+
--> $DIR/manual_map_option_2.rs:50:13
25+
|
26+
LL | let _ = match &s {
27+
| _____________^
28+
LL | | Some(x) => Some({
29+
LL | | if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
30+
LL | | }),
31+
LL | | None => None,
32+
LL | | };
33+
| |_____^
34+
|
35+
help: try this
36+
|
37+
LL | let _ = s.as_ref().map(|x| {
38+
LL | if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
39+
LL | });
40+
|
41+
42+
error: aborting due to 2 previous errors
2443

0 commit comments

Comments
 (0)