Skip to content

Commit eed311f

Browse files
committed
add check_borrow_conflicts_in_at_patterns analysis
1 parent 6a87f99 commit eed311f

File tree

1 file changed

+104
-30
lines changed

1 file changed

+104
-30
lines changed

src/librustc_mir/hair/pattern/check_match.rs

Lines changed: 104 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use rustc::ty::subst::{InternalSubsts, SubstsRef};
1515
use rustc::ty::{self, Ty, TyCtxt};
1616
use rustc_error_codes::*;
1717
use rustc_errors::{Applicability, DiagnosticBuilder};
18+
use syntax::ast::Mutability;
1819
use syntax::feature_gate::feature_err;
1920
use syntax_pos::symbol::sym;
2021
use syntax_pos::{MultiSpan, Span};
@@ -122,6 +123,7 @@ impl PatCtxt<'_, '_> {
122123
impl<'tcx> MatchVisitor<'_, 'tcx> {
123124
fn check_patterns(&mut self, has_guard: bool, pat: &Pat) {
124125
check_legality_of_move_bindings(self, has_guard, pat);
126+
check_borrow_conflicts_in_at_patterns(self, pat);
125127
if !self.tcx.features().bindings_after_at {
126128
check_legality_of_bindings_in_at_patterns(self, pat);
127129
}
@@ -639,44 +641,116 @@ fn check_legality_of_move_bindings(cx: &mut MatchVisitor<'_, '_>, has_guard: boo
639641
}
640642
}
641643

642-
/// Forbids bindings in `@` patterns. This is necessary for memory safety,
643-
/// because of the way rvalues are handled in the borrow check. (See issue
644-
/// #14587.)
645-
fn check_legality_of_bindings_in_at_patterns(cx: &MatchVisitor<'_, '_>, pat: &Pat) {
646-
AtBindingPatternVisitor { cx, bindings_allowed: true }.visit_pat(pat);
647-
}
644+
fn check_borrow_conflicts_in_at_patterns(cx: &MatchVisitor<'_, '_>, pat: &Pat) {
645+
// Get the mutability of `p` if it's by-ref.
646+
let extract_binding_mut = |hir_id, span| match cx.tables.pat_binding_modes().get(hir_id) {
647+
None => {
648+
cx.tcx.sess.delay_span_bug(span, "missing binding mode");
649+
None
650+
}
651+
Some(ty::BindByValue(..)) => None,
652+
Some(ty::BindByReference(m)) => Some(*m),
653+
};
654+
pat.walk(|pat| {
655+
// Extract `sub` in `binding @ sub`.
656+
let (name, sub) = match &pat.kind {
657+
hir::PatKind::Binding(.., name, Some(sub)) => (*name, sub),
658+
_ => return true,
659+
};
660+
661+
// Extract the mutability.
662+
let mut_outer = match extract_binding_mut(pat.hir_id, pat.span) {
663+
None => return true,
664+
Some(m) => m,
665+
};
666+
667+
// We now have `ref $mut_outer binding @ sub` (semantically).
668+
// Recurse into each binding in `sub` and find mutability conflicts.
669+
let mut conflicts_mut_mut = Vec::new();
670+
let mut conflicts_mut_ref = Vec::new();
671+
sub.each_binding(|_, hir_id, span, _| {
672+
if let Some(mut_inner) = extract_binding_mut(hir_id, span) {
673+
match (mut_outer, mut_inner) {
674+
(Mutability::Immutable, Mutability::Immutable) => {}
675+
(Mutability::Mutable, Mutability::Mutable) => conflicts_mut_mut.push(span),
676+
_ => conflicts_mut_ref.push(span),
677+
}
678+
}
679+
});
680+
681+
// Report errors if any.
682+
let binding_span = pat.span.with_hi(name.span.hi());
683+
if !conflicts_mut_mut.is_empty() {
684+
// Report mutability conflicts for e.g. `ref mut x @ Some(ref mut y)`.
685+
let msg = &format!("cannot borrow `{}` as mutable more than once at a time", name);
686+
let mut err = cx.tcx.sess.struct_span_err(pat.span, msg);
687+
err.span_label(binding_span, "first mutable borrow occurs here");
688+
for sp in conflicts_mut_mut {
689+
err.span_label(sp, "another mutable borrow occurs here");
690+
}
691+
for sp in conflicts_mut_ref {
692+
err.span_label(sp, "also borrowed as immutable here");
693+
}
694+
err.emit();
695+
} else if !conflicts_mut_ref.is_empty() {
696+
// Report mutability conflicts for e.g. `ref x @ Some(ref mut y)` or the converse.
697+
let (primary, also) = match mut_outer {
698+
Mutability::Mutable => ("mutable", "immutable"),
699+
Mutability::Immutable => ("immutable", "mutable"),
700+
};
701+
let msg = &format!(
702+
"cannot borrow `{}` as {} because it is also borrowed as {}",
703+
name, primary, also,
704+
);
705+
let mut err = cx.tcx.sess.struct_span_err(pat.span, msg);
706+
err.span_label(binding_span, &format!("{} borrow occurs here", primary));
707+
for sp in conflicts_mut_ref {
708+
err.span_label(sp, &format!("{} borrow occurs here", also));
709+
}
710+
err.emit();
711+
}
648712

649-
struct AtBindingPatternVisitor<'a, 'b, 'tcx> {
650-
cx: &'a MatchVisitor<'b, 'tcx>,
651-
bindings_allowed: bool,
713+
true
714+
});
652715
}
653716

654-
impl<'v> Visitor<'v> for AtBindingPatternVisitor<'_, '_, '_> {
655-
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> {
656-
NestedVisitorMap::None
717+
/// Forbids bindings in `@` patterns. This used to be is necessary for memory safety,
718+
/// because of the way rvalues were handled in the borrow check. (See issue #14587.)
719+
fn check_legality_of_bindings_in_at_patterns(cx: &MatchVisitor<'_, '_>, pat: &Pat) {
720+
AtBindingPatternVisitor { cx, bindings_allowed: true }.visit_pat(pat);
721+
722+
struct AtBindingPatternVisitor<'a, 'b, 'tcx> {
723+
cx: &'a MatchVisitor<'b, 'tcx>,
724+
bindings_allowed: bool,
657725
}
658726

659-
fn visit_pat(&mut self, pat: &Pat) {
660-
match pat.kind {
661-
hir::PatKind::Binding(.., ref subpat) => {
662-
if !self.bindings_allowed {
663-
feature_err(
664-
&self.cx.tcx.sess.parse_sess,
665-
sym::bindings_after_at,
666-
pat.span,
667-
"pattern bindings after an `@` are unstable",
668-
)
669-
.emit();
670-
}
727+
impl<'v> Visitor<'v> for AtBindingPatternVisitor<'_, '_, '_> {
728+
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> {
729+
NestedVisitorMap::None
730+
}
671731

672-
if subpat.is_some() {
673-
let bindings_were_allowed = self.bindings_allowed;
674-
self.bindings_allowed = false;
675-
intravisit::walk_pat(self, pat);
676-
self.bindings_allowed = bindings_were_allowed;
732+
fn visit_pat(&mut self, pat: &Pat) {
733+
match pat.kind {
734+
hir::PatKind::Binding(.., ref subpat) => {
735+
if !self.bindings_allowed {
736+
feature_err(
737+
&self.cx.tcx.sess.parse_sess,
738+
sym::bindings_after_at,
739+
pat.span,
740+
"pattern bindings after an `@` are unstable",
741+
)
742+
.emit();
743+
}
744+
745+
if subpat.is_some() {
746+
let bindings_were_allowed = self.bindings_allowed;
747+
self.bindings_allowed = false;
748+
intravisit::walk_pat(self, pat);
749+
self.bindings_allowed = bindings_were_allowed;
750+
}
677751
}
752+
_ => intravisit::walk_pat(self, pat),
678753
}
679-
_ => intravisit::walk_pat(self, pat),
680754
}
681755
}
682756
}

0 commit comments

Comments
 (0)