Skip to content

Commit ca1e379

Browse files
authored
Rollup merge of rust-lang#57102 - davidtwco:issue-57100, r=nikomatsakis
NLL: Add union justifications to conflicting borrows. Fixes rust-lang#57100. This PR adds justifications to error messages for conflicting borrows of union fields. Where previously an error message would say ``cannot borrow `u.b` as mutable..``, it now says ``cannot borrow `u` (via `u.b`) as mutable..``. r? @pnkfelix
2 parents b1200a2 + c2b477c commit ca1e379

16 files changed

+366
-153
lines changed

src/librustc_borrowck/borrowck/check_loans.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -557,12 +557,8 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
557557
if new_loan.loan_path.has_fork(&old_loan.loan_path) && common.is_some() {
558558
let nl = self.bccx.loan_path_to_string(&common.unwrap());
559559
let ol = nl.clone();
560-
let new_loan_msg = format!(" (via `{}`)",
561-
self.bccx.loan_path_to_string(
562-
&new_loan.loan_path));
563-
let old_loan_msg = format!(" (via `{}`)",
564-
self.bccx.loan_path_to_string(
565-
&old_loan.loan_path));
560+
let new_loan_msg = self.bccx.loan_path_to_string(&new_loan.loan_path);
561+
let old_loan_msg = self.bccx.loan_path_to_string(&old_loan.loan_path);
566562
(nl, ol, new_loan_msg, old_loan_msg)
567563
} else {
568564
(self.bccx.loan_path_to_string(&new_loan.loan_path),

src/librustc_mir/borrow_check/error_reporting.rs

+116-10
Original file line numberDiff line numberDiff line change
@@ -327,10 +327,8 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
327327
"closure"
328328
};
329329

330-
let desc_place = self.describe_place(place).unwrap_or_else(|| "_".to_owned());
331-
let tcx = self.infcx.tcx;
332-
333-
let first_borrow_desc;
330+
let (desc_place, msg_place, msg_borrow, union_type_name) =
331+
self.describe_place_for_conflicting_borrow(place, &issued_borrow.borrowed_place);
334332

335333
let explanation = self.explain_why_borrow_contains_point(context, issued_borrow, None);
336334
let second_borrow_desc = if explanation.is_explained() {
@@ -340,6 +338,8 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
340338
};
341339

342340
// FIXME: supply non-"" `opt_via` when appropriate
341+
let tcx = self.infcx.tcx;
342+
let first_borrow_desc;
343343
let mut err = match (
344344
gen_borrow_kind,
345345
"immutable",
@@ -353,12 +353,12 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
353353
tcx.cannot_reborrow_already_borrowed(
354354
span,
355355
&desc_place,
356-
"",
356+
&msg_place,
357357
lft,
358358
issued_span,
359359
"it",
360360
rgt,
361-
"",
361+
&msg_borrow,
362362
None,
363363
Origin::Mir,
364364
)
@@ -368,12 +368,12 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
368368
tcx.cannot_reborrow_already_borrowed(
369369
span,
370370
&desc_place,
371-
"",
371+
&msg_place,
372372
lft,
373373
issued_span,
374374
"it",
375375
rgt,
376-
"",
376+
&msg_borrow,
377377
None,
378378
Origin::Mir,
379379
)
@@ -384,9 +384,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
384384
tcx.cannot_mutably_borrow_multiply(
385385
span,
386386
&desc_place,
387-
"",
387+
&msg_place,
388388
issued_span,
389-
"",
389+
&msg_borrow,
390390
None,
391391
Origin::Mir,
392392
)
@@ -510,12 +510,118 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
510510
);
511511
}
512512

513+
if union_type_name != "" {
514+
err.note(&format!(
515+
"`{}` is a field of the union `{}`, so it overlaps the field `{}`",
516+
msg_place, union_type_name, msg_borrow,
517+
));
518+
}
519+
513520
explanation
514521
.add_explanation_to_diagnostic(self.infcx.tcx, self.mir, &mut err, first_borrow_desc);
515522

516523
err.buffer(&mut self.errors_buffer);
517524
}
518525

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

src/librustc_mir/util/borrowck_errors.rs

+32-11
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,15 @@ pub trait BorrowckErrors<'cx>: Sized + Copy {
138138
old_load_end_span: Option<Span>,
139139
o: Origin,
140140
) -> DiagnosticBuilder<'cx> {
141+
let via = |msg: &str|
142+
if msg.is_empty() { msg.to_string() } else { format!(" (via `{}`)", msg) };
141143
let mut err = struct_span_err!(
142144
self,
143145
new_loan_span,
144146
E0499,
145147
"cannot borrow `{}`{} as mutable more than once at a time{OGN}",
146148
desc,
147-
opt_via,
149+
via(opt_via),
148150
OGN = o
149151
);
150152
if old_loan_span == new_loan_span {
@@ -164,11 +166,11 @@ pub trait BorrowckErrors<'cx>: Sized + Copy {
164166
} else {
165167
err.span_label(
166168
old_loan_span,
167-
format!("first mutable borrow occurs here{}", old_opt_via),
169+
format!("first mutable borrow occurs here{}", via(old_opt_via)),
168170
);
169171
err.span_label(
170172
new_loan_span,
171-
format!("second mutable borrow occurs here{}", opt_via),
173+
format!("second mutable borrow occurs here{}", via(opt_via)),
172174
);
173175
if let Some(old_load_end_span) = old_load_end_span {
174176
err.span_label(old_load_end_span, "first borrow ends here");
@@ -292,27 +294,46 @@ pub trait BorrowckErrors<'cx>: Sized + Copy {
292294
old_load_end_span: Option<Span>,
293295
o: Origin,
294296
) -> DiagnosticBuilder<'cx> {
297+
let via = |msg: &str|
298+
if msg.is_empty() { msg.to_string() } else { format!(" (via `{}`)", msg) };
295299
let mut err = struct_span_err!(
296300
self,
297301
span,
298302
E0502,
299-
"cannot borrow `{}`{} as {} because {} is also borrowed as {}{}{OGN}",
303+
"cannot borrow `{}`{} as {} because {} is also borrowed \
304+
as {}{}{OGN}",
300305
desc_new,
301-
msg_new,
306+
via(msg_new),
302307
kind_new,
303308
noun_old,
304309
kind_old,
305-
msg_old,
310+
via(msg_old),
306311
OGN = o
307312
);
308-
err.span_label(span, format!("{} borrow occurs here{}", kind_new, msg_new));
309-
err.span_label(
310-
old_span,
311-
format!("{} borrow occurs here{}", kind_old, msg_old),
312-
);
313+
314+
if msg_new == "" {
315+
// If `msg_new` is empty, then this isn't a borrow of a union field.
316+
err.span_label(span, format!("{} borrow occurs here", kind_new));
317+
err.span_label(old_span, format!("{} borrow occurs here", kind_old));
318+
} else {
319+
// If `msg_new` isn't empty, then this a borrow of a union field.
320+
err.span_label(
321+
span,
322+
format!(
323+
"{} borrow of `{}` -- which overlaps with `{}` -- occurs here",
324+
kind_new, msg_new, msg_old,
325+
)
326+
);
327+
err.span_label(
328+
old_span,
329+
format!("{} borrow occurs here{}", kind_old, via(msg_old)),
330+
);
331+
}
332+
313333
if let Some(old_load_end_span) = old_load_end_span {
314334
err.span_label(old_load_end_span, format!("{} borrow ends here", kind_old));
315335
}
336+
316337
self.cancel_if_wrong_origin(err, o)
317338
}
318339

src/test/ui/borrowck/borrowck-borrow-from-owned-ptr.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ error[E0502]: cannot borrow `foo` (via `foo.bar2`) as immutable because `foo` is
138138
LL | let bar1 = &mut foo.bar1;
139139
| -------- mutable borrow occurs here (via `foo.bar1`)
140140
LL | let _foo1 = &foo.bar2; //~ ERROR cannot borrow
141-
| ^^^^^^^^ immutable borrow occurs here (via `foo.bar2`)
141+
| ^^^^^^^^ immutable borrow of `foo.bar2` -- which overlaps with `foo.bar1` -- occurs here
142142
LL | *bar1;
143143
LL | }
144144
| - mutable borrow ends here

src/test/ui/borrowck/borrowck-box-insensitivity.ast.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ error[E0502]: cannot borrow `a` (via `a.y`) as immutable because `a` is also bor
6161
LL | let _x = &mut a.x;
6262
| --- mutable borrow occurs here (via `a.x`)
6363
LL | let _y = &a.y; //[ast]~ ERROR cannot borrow
64-
| ^^^ immutable borrow occurs here (via `a.y`)
64+
| ^^^ immutable borrow of `a.y` -- which overlaps with `a.x` -- occurs here
6565
...
6666
LL | }
6767
| - mutable borrow ends here
@@ -72,7 +72,7 @@ error[E0502]: cannot borrow `a` (via `a.y`) as mutable because `a` is also borro
7272
LL | let _x = &a.x;
7373
| --- immutable borrow occurs here (via `a.x`)
7474
LL | let _y = &mut a.y; //[ast]~ ERROR cannot borrow
75-
| ^^^ mutable borrow occurs here (via `a.y`)
75+
| ^^^ mutable borrow of `a.y` -- which overlaps with `a.x` -- occurs here
7676
...
7777
LL | }
7878
| - immutable borrow ends here

src/test/ui/borrowck/borrowck-box-insensitivity.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ fn borrow_after_mut_borrow() {
8383
let mut a: Box<_> = box A { x: box 0, y: 1 };
8484
let _x = &mut a.x;
8585
let _y = &a.y; //[ast]~ ERROR cannot borrow
86-
//[ast]~^ immutable borrow occurs here (via `a.y`)
86+
//[ast]~^ immutable borrow of `a.y` -- which overlaps with `a.x` -- occurs here
8787
use_mut(_x);
8888
}
8989
fn mut_borrow_after_borrow() {
9090
let mut a: Box<_> = box A { x: box 0, y: 1 };
9191
let _x = &a.x;
9292
let _y = &mut a.y; //[ast]~ ERROR cannot borrow
93-
//[ast]~^ mutable borrow occurs here (via `a.y`)
93+
//[ast]~^ mutable borrow of `a.y` -- which overlaps with `a.x` -- occurs here
9494
use_imm(_x);
9595
}
9696
fn copy_after_move_nested() {

0 commit comments

Comments
 (0)