Skip to content

Commit 388dffe

Browse files
committed
Make conflicting borrow description more robust.
This commit improves the logic for place descriptions in conflicting borrow errors so that borrows of union fields have better messages even when the unions are embedded in other unions or structs.
1 parent 69bded2 commit 388dffe

File tree

3 files changed

+187
-31
lines changed

3 files changed

+187
-31
lines changed

src/librustc_mir/borrow_check/error_reporting.rs

+91-31
Original file line numberDiff line numberDiff line change
@@ -329,16 +329,12 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
329329
"closure"
330330
};
331331

332-
let (desc_place, msg_place, msg_borrow) = if issued_borrow.borrowed_place == *place {
333-
let desc_place = self.describe_place(place).unwrap_or_else(|| "_".to_owned());
334-
(desc_place, "".to_string(), "".to_string())
335-
} else {
336-
let (desc_place, msg_place) = self.describe_place_for_conflicting_borrow(place);
337-
let (_, msg_borrow) = self.describe_place_for_conflicting_borrow(
338-
&issued_borrow.borrowed_place
339-
);
340-
(desc_place, msg_place, msg_borrow)
341-
};
332+
let (desc_place, msg_place, msg_borrow) = self.describe_place_for_conflicting_borrow(
333+
place, &issued_borrow.borrowed_place,
334+
);
335+
let via = |msg: String| if msg.is_empty() { msg } else { format!(" (via `{}`)", msg) };
336+
let msg_place = via(msg_place);
337+
let msg_borrow = via(msg_borrow);
342338

343339
let explanation = self.explain_why_borrow_contains_point(context, issued_borrow, None);
344340
let second_borrow_desc = if explanation.is_explained() {
@@ -526,33 +522,97 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
526522
err.buffer(&mut self.errors_buffer);
527523
}
528524

529-
/// Returns a description of a place and an associated message for the purposes of conflicting
530-
/// borrow diagnostics.
525+
/// Returns the description of the root place for a conflicting borrow and the full
526+
/// descriptions of the places that caused the conflict.
527+
///
528+
/// In the simplest case, where there are no unions involved, if a mutable borrow of `x` is
529+
/// attempted while a shared borrow is live, then this function will return:
530+
///
531+
/// ("x", "", "")
531532
///
532-
/// If the borrow is of the field `b` of a union `u`, then the return value will be
533-
/// `("u", " (via \`u.b\`)")`. Otherwise, for some variable `a`, the return value will be
534-
/// `("a", "")`.
533+
/// In the simple union case, if a mutable borrow of a union field `x.z` is attempted while
534+
/// a shared borrow of another field `x.y`, then this function will return:
535+
///
536+
/// ("x", "x.z", "x.y")
537+
///
538+
/// In the more complex union case, where the union is a field of a struct, then if a mutable
539+
/// borrow of a union field in a struct `x.u.z` is attempted while a shared borrow of
540+
/// another field `x.u.y`, then this function will return:
541+
///
542+
/// ("x.u", "x.u.z", "x.u.y")
543+
///
544+
/// This is used when creating error messages like below:
545+
///
546+
/// > cannot borrow `a.u` (via `a.u.z.c`) as immutable because it is also borrowed as
547+
/// > mutable (via `a.u.s.b`) [E0502]
535548
pub(super) fn describe_place_for_conflicting_borrow(
536549
&self,
537-
place: &Place<'tcx>,
538-
) -> (String, String) {
539-
place.base_local()
540-
.filter(|local| {
541-
// Filter out non-unions.
542-
self.mir.local_decls[*local].ty
543-
.ty_adt_def()
544-
.map(|adt| adt.is_union())
545-
.unwrap_or(false)
550+
first_borrowed_place: &Place<'tcx>,
551+
second_borrowed_place: &Place<'tcx>,
552+
) -> (String, String, String) {
553+
// Define a small closure that we can use to check if the type of a place
554+
// is a union.
555+
let is_union = |place: &Place<'tcx>| -> bool {
556+
place.ty(self.mir, self.infcx.tcx)
557+
.to_ty(self.infcx.tcx)
558+
.ty_adt_def()
559+
.map(|adt| adt.is_union())
560+
.unwrap_or(false)
561+
};
562+
563+
// Start with an empty tuple, so we can use the functions on `Option` to reduce some
564+
// code duplication (particularly around returning an empty description in the failure
565+
// case).
566+
Some(())
567+
.filter(|_| {
568+
// If we have a conflicting borrow of the same place, then we don't want to add
569+
// an extraneous "via x.y" to our diagnostics, so filter out this case.
570+
first_borrowed_place != second_borrowed_place
546571
})
547-
.and_then(|local| {
548-
let desc_base = self.describe_place(&Place::Local(local))
549-
.unwrap_or_else(|| "_".to_owned());
550-
let desc_original = self.describe_place(place)
551-
.unwrap_or_else(|| "_".to_owned());
552-
return Some((desc_base, format!(" (via `{}`)", desc_original)));
572+
.and_then(|_| {
573+
// We're going to want to traverse the first borrowed place to see if we can find
574+
// field access to a union. If we find that, then we will keep the place of the
575+
// union being accessed and the field that was being accessed so we can check the
576+
// second borrowed place for the same union and a access to a different field.
577+
let mut current = first_borrowed_place;
578+
while let Place::Projection(box PlaceProjection { base, elem }) = current {
579+
match elem {
580+
ProjectionElem::Field(field, _) if is_union(base) => {
581+
return Some((base, field));
582+
},
583+
_ => current = base,
584+
}
585+
}
586+
None
587+
})
588+
.and_then(|(target_base, target_field)| {
589+
// With the place of a union and a field access into it, we traverse the second
590+
// borrowed place and look for a access to a different field of the same union.
591+
let mut current = second_borrowed_place;
592+
while let Place::Projection(box PlaceProjection { base, elem }) = current {
593+
match elem {
594+
ProjectionElem::Field(field, _) if {
595+
is_union(base) && field != target_field && base == target_base
596+
} => {
597+
let desc_base = self.describe_place(base)
598+
.unwrap_or_else(|| "_".to_owned());
599+
let desc_first = self.describe_place(first_borrowed_place)
600+
.unwrap_or_else(|| "_".to_owned());
601+
let desc_second = self.describe_place(second_borrowed_place)
602+
.unwrap_or_else(|| "_".to_owned());
603+
return Some((desc_base, desc_first, desc_second));
604+
},
605+
_ => current = base,
606+
}
607+
}
608+
None
553609
})
554610
.unwrap_or_else(|| {
555-
(self.describe_place(place).unwrap_or_else(|| "_".to_owned()), "".to_string())
611+
// If we didn't find a field access into a union, or both places match, then
612+
// only return the description of the first place.
613+
let desc_place = self.describe_place(first_borrowed_place)
614+
.unwrap_or_else(|| "_".to_owned());
615+
(desc_place, "".to_string(), "".to_string())
556616
})
557617
}
558618

src/test/ui/nll/issue-57100.rs

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#![allow(unused)]
2+
#![feature(nll)]
3+
4+
// ignore-tidy-linelength
5+
6+
// This tests the error messages for borrows of union fields when the unions are embedded in other
7+
// structs or unions.
8+
9+
#[derive(Clone, Copy, Default)]
10+
struct Leaf {
11+
l1_u8: u8,
12+
l2_u8: u8,
13+
}
14+
15+
#[derive(Clone, Copy)]
16+
union First {
17+
f1_leaf: Leaf,
18+
f2_leaf: Leaf,
19+
f3_union: Second,
20+
}
21+
22+
#[derive(Clone, Copy)]
23+
union Second {
24+
s1_leaf: Leaf,
25+
s2_leaf: Leaf,
26+
}
27+
28+
struct Root {
29+
r1_u8: u8,
30+
r2_union: First,
31+
}
32+
33+
// Borrow a different field of the nested union.
34+
fn nested_union() {
35+
unsafe {
36+
let mut r = Root {
37+
r1_u8: 3,
38+
r2_union: First { f3_union: Second { s2_leaf: Leaf { l1_u8: 8, l2_u8: 4 } } }
39+
};
40+
41+
let mref = &mut r.r2_union.f3_union.s1_leaf.l1_u8;
42+
// ^^^^^^^
43+
*mref = 22;
44+
let nref = &r.r2_union.f3_union.s2_leaf.l1_u8;
45+
// ^^^^^^^
46+
//~^^ 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]
47+
println!("{} {}", mref, nref)
48+
}
49+
}
50+
51+
// Borrow a different field of the first union.
52+
fn first_union() {
53+
unsafe {
54+
let mut r = Root {
55+
r1_u8: 3,
56+
r2_union: First { f3_union: Second { s2_leaf: Leaf { l1_u8: 8, l2_u8: 4 } } }
57+
};
58+
59+
let mref = &mut r.r2_union.f2_leaf.l1_u8;
60+
// ^^^^^^^
61+
*mref = 22;
62+
let nref = &r.r2_union.f1_leaf.l1_u8;
63+
// ^^^^^^^
64+
//~^^ 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]
65+
println!("{} {}", mref, nref)
66+
}
67+
}
68+
69+
fn main() {}

src/test/ui/nll/issue-57100.stderr

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
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`)
2+
--> $DIR/issue-57100.rs:44:20
3+
|
4+
LL | let mref = &mut r.r2_union.f3_union.s1_leaf.l1_u8;
5+
| -------------------------------------- mutable borrow occurs here (via `r.r2_union.f3_union.s1_leaf.l1_u8`)
6+
...
7+
LL | let nref = &r.r2_union.f3_union.s2_leaf.l1_u8;
8+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here (via `r.r2_union.f3_union.s2_leaf.l1_u8`)
9+
...
10+
LL | println!("{} {}", mref, nref)
11+
| ---- mutable borrow later used here
12+
13+
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`)
14+
--> $DIR/issue-57100.rs:62:20
15+
|
16+
LL | let mref = &mut r.r2_union.f2_leaf.l1_u8;
17+
| ----------------------------- mutable borrow occurs here (via `r.r2_union.f2_leaf.l1_u8`)
18+
...
19+
LL | let nref = &r.r2_union.f1_leaf.l1_u8;
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here (via `r.r2_union.f1_leaf.l1_u8`)
21+
...
22+
LL | println!("{} {}", mref, nref)
23+
| ---- mutable borrow later used here
24+
25+
error: aborting due to 2 previous errors
26+
27+
For more information about this error, try `rustc --explain E0502`.

0 commit comments

Comments
 (0)