Skip to content

Commit 8dbc4b7

Browse files
committed
Improve manual_map
In some cases check if a borrow made in the scrutinee expression would prevent creating the closure used by `map`
1 parent 1fbc3fb commit 8dbc4b7

File tree

4 files changed

+145
-20
lines changed

4 files changed

+145
-20
lines changed

clippy_lints/src/manual_map.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
44
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
55
use clippy_utils::{
66
can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id,
7-
peel_hir_expr_refs,
7+
peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
88
};
99
use rustc_ast::util::parser::PREC_POSTFIX;
1010
use rustc_errors::Applicability;
1111
use rustc_hir::LangItem::{OptionNone, OptionSome};
12-
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, MatchSource, Mutability, Pat, PatKind};
12+
use rustc_hir::{
13+
def::Res, Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, MatchSource, Mutability, Pat, PatKind, Path, QPath,
14+
};
1315
use rustc_lint::{LateContext, LateLintPass, LintContext};
1416
use rustc_middle::lint::in_external_macro;
1517
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -111,10 +113,6 @@ impl LateLintPass<'_> for ManualMap {
111113
return;
112114
}
113115

114-
if !can_move_expr_to_closure(cx, some_expr) {
115-
return;
116-
}
117-
118116
// Determine which binding mode to use.
119117
let explicit_ref = some_pat.contains_explicit_ref_binding();
120118
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
@@ -125,6 +123,32 @@ impl LateLintPass<'_> for ManualMap {
125123
None => "",
126124
};
127125

126+
match can_move_expr_to_closure(cx, some_expr) {
127+
Some(captures) => {
128+
// Check if captures the closure will need conflict with borrows made in the scrutinee.
129+
// TODO: check all the references made in the scrutinee expression. This will require interacting
130+
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
131+
if let Some(binding_ref_mutability) = binding_ref {
132+
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
133+
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
134+
_ => None,
135+
});
136+
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
137+
match captures.get(l) {
138+
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
139+
Some(CaptureKind::Ref(Mutability::Not))
140+
if binding_ref_mutability == Mutability::Mut =>
141+
{
142+
return;
143+
}
144+
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
145+
}
146+
}
147+
}
148+
},
149+
None => return,
150+
};
151+
128152
let mut app = Applicability::MachineApplicable;
129153

130154
// Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or

clippy_utils/src/lib.rs

Lines changed: 103 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,20 @@ use rustc_data_structures::unhash::UnhashMap;
6767
use rustc_hir as hir;
6868
use rustc_hir::def::{DefKind, Res};
6969
use rustc_hir::def_id::DefId;
70-
use rustc_hir::hir_id::HirIdSet;
70+
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
7171
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
7272
use rustc_hir::LangItem::{ResultErr, ResultOk};
7373
use rustc_hir::{
7474
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
75-
ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path,
76-
PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
75+
ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node, Param, Pat,
76+
PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
7777
};
7878
use rustc_lint::{LateContext, Level, Lint, LintContext};
7979
use rustc_middle::hir::exports::Export;
8080
use rustc_middle::hir::map::Map;
8181
use rustc_middle::ty as rustc_ty;
82-
use rustc_middle::ty::{layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable};
82+
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
83+
use rustc_middle::ty::{layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable};
8384
use rustc_semver::RustcVersion;
8485
use rustc_session::Session;
8586
use rustc_span::hygiene::{ExpnKind, MacroKind};
@@ -603,8 +604,82 @@ pub fn can_move_expr_to_closure_no_visit(
603604
}
604605
}
605606

606-
/// Checks if the expression can be moved into a closure as is.
607-
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
607+
/// How a local is captured by a closure
608+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
609+
pub enum CaptureKind {
610+
Value,
611+
Ref(Mutability),
612+
}
613+
impl std::ops::BitOr for CaptureKind {
614+
type Output = Self;
615+
fn bitor(self, rhs: Self) -> Self::Output {
616+
match (self, rhs) {
617+
(CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
618+
(CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
619+
| (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
620+
(CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
621+
}
622+
}
623+
}
624+
impl std::ops::BitOrAssign for CaptureKind {
625+
fn bitor_assign(&mut self, rhs: Self) {
626+
*self = *self | rhs;
627+
}
628+
}
629+
630+
/// Given an expression referencing a local, determines how it would be captured in a closure.
631+
/// Note as this will walk up to parent expressions until the capture can be determined it should
632+
/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or
633+
/// function argument (other than a receiver).
634+
pub fn capture_local_usage(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind {
635+
debug_assert!(matches!(
636+
e.kind,
637+
ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
638+
));
639+
640+
let map = cx.tcx.hir();
641+
let mut child_id = e.hir_id;
642+
let mut capture = CaptureKind::Value;
643+
644+
for (parent_id, parent) in map.parent_iter(e.hir_id) {
645+
if let [Adjustment {
646+
kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
647+
target,
648+
}, ref adjust @ ..] = *cx
649+
.typeck_results()
650+
.adjustments()
651+
.get(child_id)
652+
.map_or(&[][..], |x| &**x)
653+
{
654+
if let rustc_ty::RawPtr(TypeAndMut { mutbl: mutability, .. }) | rustc_ty::Ref(_, _, mutability) =
655+
*adjust.last().map_or(target, |a| a.target).kind()
656+
{
657+
return CaptureKind::Ref(mutability);
658+
}
659+
}
660+
661+
if let Node::Expr(e) = parent {
662+
match e.kind {
663+
ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
664+
ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
665+
ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => {
666+
return CaptureKind::Ref(Mutability::Mut);
667+
},
668+
_ => break,
669+
}
670+
} else {
671+
break;
672+
}
673+
674+
child_id = parent_id;
675+
}
676+
677+
capture
678+
}
679+
680+
/// Checks if the expression can be moved into a closure as is. This will return a list of captures
681+
/// if so, otherwise, `None`.
682+
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
608683
struct V<'cx, 'tcx> {
609684
cx: &'cx LateContext<'tcx>,
610685
// Stack of potential break targets contained in the expression.
@@ -613,6 +688,9 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
613688
locals: HirIdSet,
614689
/// Whether this expression can be turned into a closure.
615690
allow_closure: bool,
691+
/// Locals which need to be captured, and whether they need to be by value, reference, or
692+
/// mutable reference.
693+
captures: HirIdMap<CaptureKind>,
616694
}
617695
impl Visitor<'tcx> for V<'_, 'tcx> {
618696
type Map = ErasedMap<'tcx>;
@@ -624,13 +702,23 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
624702
if !self.allow_closure {
625703
return;
626704
}
627-
if let ExprKind::Loop(b, ..) = e.kind {
628-
self.loops.push(e.hir_id);
629-
self.visit_block(b);
630-
self.loops.pop();
631-
} else {
632-
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
633-
walk_expr(self, e);
705+
706+
match e.kind {
707+
ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
708+
if !self.locals.contains(&l) {
709+
let cap = capture_local_usage(self.cx, e);
710+
self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
711+
}
712+
},
713+
ExprKind::Loop(b, ..) => {
714+
self.loops.push(e.hir_id);
715+
self.visit_block(b);
716+
self.loops.pop();
717+
},
718+
_ => {
719+
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
720+
walk_expr(self, e);
721+
},
634722
}
635723
}
636724

@@ -646,9 +734,10 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
646734
allow_closure: true,
647735
loops: Vec::new(),
648736
locals: HirIdSet::default(),
737+
captures: HirIdMap::default(),
649738
};
650739
v.visit_expr(expr);
651-
v.allow_closure
740+
v.allow_closure.then(|| v.captures)
652741
}
653742

654743
/// Returns the method names and argument list of nested method call expressions that make up

tests/ui/manual_map_option_2.fixed

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,10 @@ fn main() {
77
let y = (String::new(), String::new());
88
(x, y.0)
99
});
10+
11+
let s = Some(String::new());
12+
let _ = match &s {
13+
Some(x) => Some((x.clone(), s)),
14+
None => None,
15+
};
1016
}

tests/ui/manual_map_option_2.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,10 @@ fn main() {
1010
}),
1111
None => None,
1212
};
13+
14+
let s = Some(String::new());
15+
let _ = match &s {
16+
Some(x) => Some((x.clone(), s)),
17+
None => None,
18+
};
1319
}

0 commit comments

Comments
 (0)