@@ -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
0 commit comments