diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/librustc_mir/borrow_check/error_reporting.rs | 122 | ||||
| -rw-r--r-- | src/test/ui/nll/issue-57100.rs | 69 | ||||
| -rw-r--r-- | src/test/ui/nll/issue-57100.stderr | 27 |
3 files changed, 187 insertions, 31 deletions
diff --git a/src/librustc_mir/borrow_check/error_reporting.rs b/src/librustc_mir/borrow_check/error_reporting.rs index 6f12ff994c4..aeba56cb7e2 100644 --- a/src/librustc_mir/borrow_check/error_reporting.rs +++ b/src/librustc_mir/borrow_check/error_reporting.rs @@ -329,16 +329,12 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { "closure" }; - let (desc_place, msg_place, msg_borrow) = if issued_borrow.borrowed_place == *place { - let desc_place = self.describe_place(place).unwrap_or_else(|| "_".to_owned()); - (desc_place, "".to_string(), "".to_string()) - } else { - let (desc_place, msg_place) = self.describe_place_for_conflicting_borrow(place); - let (_, msg_borrow) = self.describe_place_for_conflicting_borrow( - &issued_borrow.borrowed_place - ); - (desc_place, msg_place, msg_borrow) - }; + let (desc_place, msg_place, msg_borrow) = self.describe_place_for_conflicting_borrow( + place, &issued_borrow.borrowed_place, + ); + let via = |msg: String| if msg.is_empty() { msg } else { format!(" (via `{}`)", msg) }; + let msg_place = via(msg_place); + let msg_borrow = via(msg_borrow); let explanation = self.explain_why_borrow_contains_point(context, issued_borrow, None); let second_borrow_desc = if explanation.is_explained() { @@ -526,33 +522,97 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { err.buffer(&mut self.errors_buffer); } - /// Returns a description of a place and an associated message for the purposes of conflicting - /// borrow diagnostics. + /// Returns the description of the root place for a conflicting borrow and the full + /// descriptions of the places that caused the conflict. + /// + /// In the simplest case, where there are no unions involved, if a mutable borrow of `x` is + /// attempted while a shared borrow is live, then this function will return: + /// + /// ("x", "", "") /// - /// If the borrow is of the field `b` of a union `u`, then the return value will be - /// `("u", " (via \`u.b\`)")`. Otherwise, for some variable `a`, the return value will be - /// `("a", "")`. + /// In the simple union case, if a mutable borrow of a union field `x.z` is attempted while + /// a shared borrow of another field `x.y`, then this function will return: + /// + /// ("x", "x.z", "x.y") + /// + /// In the more complex union case, where the union is a field of a struct, then if a mutable + /// borrow of a union field in a struct `x.u.z` is attempted while a shared borrow of + /// another field `x.u.y`, then this function will return: + /// + /// ("x.u", "x.u.z", "x.u.y") + /// + /// This is used when creating error messages like below: + /// + /// > cannot borrow `a.u` (via `a.u.z.c`) as immutable because it is also borrowed as + /// > mutable (via `a.u.s.b`) [E0502] pub(super) fn describe_place_for_conflicting_borrow( &self, - place: &Place<'tcx>, - ) -> (String, String) { - place.base_local() - .filter(|local| { - // Filter out non-unions. - self.mir.local_decls[*local].ty - .ty_adt_def() - .map(|adt| adt.is_union()) - .unwrap_or(false) + first_borrowed_place: &Place<'tcx>, + second_borrowed_place: &Place<'tcx>, + ) -> (String, String, String) { + // Define a small closure that we can use to check if the type of a place + // is a union. + let is_union = |place: &Place<'tcx>| -> bool { + place.ty(self.mir, self.infcx.tcx) + .to_ty(self.infcx.tcx) + .ty_adt_def() + .map(|adt| adt.is_union()) + .unwrap_or(false) + }; + + // Start with an empty tuple, so we can use the functions on `Option` to reduce some + // code duplication (particularly around returning an empty description in the failure + // case). + Some(()) + .filter(|_| { + // If we have a conflicting borrow of the same place, then we don't want to add + // an extraneous "via x.y" to our diagnostics, so filter out this case. + first_borrowed_place != second_borrowed_place }) - .and_then(|local| { - let desc_base = self.describe_place(&Place::Local(local)) - .unwrap_or_else(|| "_".to_owned()); - let desc_original = self.describe_place(place) - .unwrap_or_else(|| "_".to_owned()); - return Some((desc_base, format!(" (via `{}`)", desc_original))); + .and_then(|_| { + // We're going to want to traverse the first borrowed place to see if we can find + // field access to a union. If we find that, then we will keep the place of the + // union being accessed and the field that was being accessed so we can check the + // second borrowed place for the same union and a access to a different field. + let mut current = first_borrowed_place; + while let Place::Projection(box PlaceProjection { base, elem }) = current { + match elem { + ProjectionElem::Field(field, _) if is_union(base) => { + return Some((base, field)); + }, + _ => current = base, + } + } + None + }) + .and_then(|(target_base, target_field)| { + // With the place of a union and a field access into it, we traverse the second + // borrowed place and look for a access to a different field of the same union. + let mut current = second_borrowed_place; + while let Place::Projection(box PlaceProjection { base, elem }) = current { + match elem { + ProjectionElem::Field(field, _) if { + is_union(base) && field != target_field && base == target_base + } => { + let desc_base = self.describe_place(base) + .unwrap_or_else(|| "_".to_owned()); + let desc_first = self.describe_place(first_borrowed_place) + .unwrap_or_else(|| "_".to_owned()); + let desc_second = self.describe_place(second_borrowed_place) + .unwrap_or_else(|| "_".to_owned()); + return Some((desc_base, desc_first, desc_second)); + }, + _ => current = base, + } + } + None }) .unwrap_or_else(|| { - (self.describe_place(place).unwrap_or_else(|| "_".to_owned()), "".to_string()) + // If we didn't find a field access into a union, or both places match, then + // only return the description of the first place. + let desc_place = self.describe_place(first_borrowed_place) + .unwrap_or_else(|| "_".to_owned()); + (desc_place, "".to_string(), "".to_string()) }) } diff --git a/src/test/ui/nll/issue-57100.rs b/src/test/ui/nll/issue-57100.rs new file mode 100644 index 00000000000..f669fe00956 --- /dev/null +++ b/src/test/ui/nll/issue-57100.rs @@ -0,0 +1,69 @@ +#![allow(unused)] +#![feature(nll)] + +// ignore-tidy-linelength + +// This tests the error messages for borrows of union fields when the unions are embedded in other +// structs or unions. + +#[derive(Clone, Copy, Default)] +struct Leaf { + l1_u8: u8, + l2_u8: u8, +} + +#[derive(Clone, Copy)] +union First { + f1_leaf: Leaf, + f2_leaf: Leaf, + f3_union: Second, +} + +#[derive(Clone, Copy)] +union Second { + s1_leaf: Leaf, + s2_leaf: Leaf, +} + +struct Root { + r1_u8: u8, + r2_union: First, +} + +// Borrow a different field of the nested union. +fn nested_union() { + unsafe { + let mut r = Root { + r1_u8: 3, + r2_union: First { f3_union: Second { s2_leaf: Leaf { l1_u8: 8, l2_u8: 4 } } } + }; + + let mref = &mut r.r2_union.f3_union.s1_leaf.l1_u8; + // ^^^^^^^ + *mref = 22; + let nref = &r.r2_union.f3_union.s2_leaf.l1_u8; + // ^^^^^^^ + //~^^ ERROR cannot borrow `r.r2_union.f3_union` (via `r.r2_union.f3_union.s2_leaf.l1_u8`) as immutable because it is also borrowed as mutable (via `r.r2_union.f3_union.s1_leaf.l1_u8`) [E0502] + println!("{} {}", mref, nref) + } +} + +// Borrow a different field of the first union. +fn first_union() { + unsafe { + let mut r = Root { + r1_u8: 3, + r2_union: First { f3_union: Second { s2_leaf: Leaf { l1_u8: 8, l2_u8: 4 } } } + }; + + let mref = &mut r.r2_union.f2_leaf.l1_u8; + // ^^^^^^^ + *mref = 22; + let nref = &r.r2_union.f1_leaf.l1_u8; + // ^^^^^^^ + //~^^ ERROR cannot borrow `r.r2_union` (via `r.r2_union.f1_leaf.l1_u8`) as immutable because it is also borrowed as mutable (via `r.r2_union.f2_leaf.l1_u8`) [E0502] + println!("{} {}", mref, nref) + } +} + +fn main() {} diff --git a/src/test/ui/nll/issue-57100.stderr b/src/test/ui/nll/issue-57100.stderr new file mode 100644 index 00000000000..34dcdfc4947 --- /dev/null +++ b/src/test/ui/nll/issue-57100.stderr @@ -0,0 +1,27 @@ +error[E0502]: cannot borrow `r.r2_union.f3_union` (via `r.r2_union.f3_union.s2_leaf.l1_u8`) as immutable because it is also borrowed as mutable (via `r.r2_union.f3_union.s1_leaf.l1_u8`) + --> $DIR/issue-57100.rs:44:20 + | +LL | let mref = &mut r.r2_union.f3_union.s1_leaf.l1_u8; + | -------------------------------------- mutable borrow occurs here (via `r.r2_union.f3_union.s1_leaf.l1_u8`) +... +LL | let nref = &r.r2_union.f3_union.s2_leaf.l1_u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here (via `r.r2_union.f3_union.s2_leaf.l1_u8`) +... +LL | println!("{} {}", mref, nref) + | ---- mutable borrow later used here + +error[E0502]: cannot borrow `r.r2_union` (via `r.r2_union.f1_leaf.l1_u8`) as immutable because it is also borrowed as mutable (via `r.r2_union.f2_leaf.l1_u8`) + --> $DIR/issue-57100.rs:62:20 + | +LL | let mref = &mut r.r2_union.f2_leaf.l1_u8; + | ----------------------------- mutable borrow occurs here (via `r.r2_union.f2_leaf.l1_u8`) +... +LL | let nref = &r.r2_union.f1_leaf.l1_u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here (via `r.r2_union.f1_leaf.l1_u8`) +... +LL | println!("{} {}", mref, nref) + | ---- mutable borrow later used here + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0502`. |
