Origin::Mir,
);
- self.add_closure_invoked_twice_with_moved_variable_suggestion(
+ self.add_moved_or_invoked_closure_note(
context.loc,
used_place,
&mut err,
"closure"
};
- let desc_place = self.describe_place(place).unwrap_or_else(|| "_".to_owned());
- let tcx = self.infcx.tcx;
-
- let first_borrow_desc;
+ let (desc_place, msg_place, msg_borrow, union_type_name) =
+ self.describe_place_for_conflicting_borrow(place, &issued_borrow.borrowed_place);
let explanation = self.explain_why_borrow_contains_point(context, issued_borrow, None);
let second_borrow_desc = if explanation.is_explained() {
};
// FIXME: supply non-"" `opt_via` when appropriate
+ let tcx = self.infcx.tcx;
+ let first_borrow_desc;
let mut err = match (
gen_borrow_kind,
"immutable",
tcx.cannot_reborrow_already_borrowed(
span,
&desc_place,
- "",
+ &msg_place,
lft,
issued_span,
"it",
rgt,
- "",
+ &msg_borrow,
None,
Origin::Mir,
)
tcx.cannot_reborrow_already_borrowed(
span,
&desc_place,
- "",
+ &msg_place,
lft,
issued_span,
"it",
rgt,
- "",
+ &msg_borrow,
None,
Origin::Mir,
)
tcx.cannot_mutably_borrow_multiply(
span,
&desc_place,
- "",
+ &msg_place,
issued_span,
- "",
+ &msg_borrow,
None,
Origin::Mir,
)
);
}
+ if union_type_name != "" {
+ err.note(&format!(
+ "`{}` is a field of the union `{}`, so it overlaps the field `{}`",
+ msg_place, union_type_name, msg_borrow,
+ ));
+ }
+
explanation
.add_explanation_to_diagnostic(self.infcx.tcx, self.mir, &mut err, first_borrow_desc);
err.buffer(&mut self.errors_buffer);
}
+ /// 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", "", "")
+ ///
+ /// 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,
+ first_borrowed_place: &Place<'tcx>,
+ second_borrowed_place: &Place<'tcx>,
+ ) -> (String, 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(|_| {
+ // 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());
+
+ // Also compute the name of the union type, eg. `Foo` so we
+ // can add a helpful note with it.
+ let ty = base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx);
+
+ return Some((desc_base, desc_first, desc_second, ty.to_string()));
+ },
+ _ => current = base,
+ }
+ }
+ None
+ })
+ .unwrap_or_else(|| {
+ // 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(), "".to_string())
+ })
+ }
+
/// Reports StorageDeadOrDrop of `place` conflicts with `borrow`.
///
/// This means that some data referenced by `borrow` needs to live
functions can only return borrows to data passed as arguments",
);
err.note(
- "to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch04-02-\
+ "to learn more, visit <https://doc.rust-lang.org/book/ch04-02-\
references-and-borrowing.html#dangling-references>",
);
} else {
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- /// Adds a suggestion when a closure is invoked twice with a moved variable.
+ /// Adds a suggestion when a closure is invoked twice with a moved variable or when a closure
+ /// is moved after being invoked.
///
/// ```text
/// note: closure cannot be invoked more than once because it moves the variable `dict` out of
/// LL | for (key, value) in dict {
/// | ^^^^
/// ```
- pub(super) fn add_closure_invoked_twice_with_moved_variable_suggestion(
+ pub(super) fn add_moved_or_invoked_closure_note(
&self,
location: Location,
place: &Place<'tcx>,
diag: &mut DiagnosticBuilder<'_>,
) {
+ debug!("add_moved_or_invoked_closure_note: location={:?} place={:?}", location, place);
let mut target = place.local();
- debug!(
- "add_closure_invoked_twice_with_moved_variable_suggestion: location={:?} place={:?} \
- target={:?}",
- location, place, target,
- );
for stmt in &self.mir[location.block].statements[location.statement_index..] {
- debug!(
- "add_closure_invoked_twice_with_moved_variable_suggestion: stmt={:?} \
- target={:?}",
- stmt, target,
- );
+ debug!("add_moved_or_invoked_closure_note: stmt={:?} target={:?}", stmt, target);
if let StatementKind::Assign(into, box Rvalue::Use(from)) = &stmt.kind {
- debug!(
- "add_closure_invoked_twice_with_moved_variable_suggestion: into={:?} \
- from={:?}",
- into, from,
- );
+ debug!("add_fnonce_closure_note: into={:?} from={:?}", into, from);
match from {
Operand::Copy(ref place) |
Operand::Move(ref place) if target == place.local() =>
}
}
-
+ // Check if we are attempting to call a closure after it has been invoked.
let terminator = self.mir[location.block].terminator();
- debug!(
- "add_closure_invoked_twice_with_moved_variable_suggestion: terminator={:?}",
- terminator,
- );
+ debug!("add_moved_or_invoked_closure_note: terminator={:?}", terminator);
if let TerminatorKind::Call {
func: Operand::Constant(box Constant {
- literal: ty::Const { ty: &ty::TyS { sty: ty::TyKind::FnDef(id, _), .. }, .. },
+ literal: ty::LazyConst::Evaluated(ty::Const {
+ ty: &ty::TyS { sty: ty::TyKind::FnDef(id, _), .. },
+ ..
+ }),
..
}),
args,
..
} = &terminator.kind {
- debug!("add_closure_invoked_twice_with_moved_variable_suggestion: id={:?}", id);
+ debug!("add_moved_or_invoked_closure_note: id={:?}", id);
if self.infcx.tcx.parent(id) == self.infcx.tcx.lang_items().fn_once_trait() {
let closure = match args.first() {
Some(Operand::Copy(ref place)) |
place.local().unwrap(),
_ => return,
};
- debug!(
- "add_closure_invoked_twice_with_moved_variable_suggestion: closure={:?}",
- closure,
- );
- if let ty::TyKind::Closure(did, _substs) = self.mir.local_decls[closure].ty.sty {
- let node_id = match self.infcx.tcx.hir().as_local_node_id(did) {
- Some(node_id) => node_id,
- _ => return,
- };
+ debug!("add_moved_or_invoked_closure_note: closure={:?}", closure);
+ if let ty::TyKind::Closure(did, _) = self.mir.local_decls[closure].ty.sty {
+ let node_id = self.infcx.tcx.hir().as_local_node_id(did).unwrap();
let hir_id = self.infcx.tcx.hir().node_to_hir_id(node_id);
- if let Some((
- span, name
- )) = self.infcx.tcx.typeck_tables_of(did).closure_kind_origins().get(hir_id) {
+ if let Some((span, name)) = self.infcx.tcx.typeck_tables_of(did)
+ .closure_kind_origins()
+ .get(hir_id)
+ {
diag.span_note(
*span,
&format!(
- "closure cannot be invoked more than once because it \
- moves the variable `{}` out of its environment",
- name,
+ "closure cannot be invoked more than once because it moves the \
+ variable `{}` out of its environment",
+ name,
),
);
+ return;
}
}
}
}
+
+ // Check if we are just moving a closure after it has been invoked.
+ if let Some(target) = target {
+ if let ty::TyKind::Closure(did, _) = self.mir.local_decls[target].ty.sty {
+ let node_id = self.infcx.tcx.hir().as_local_node_id(did).unwrap();
+ let hir_id = self.infcx.tcx.hir().node_to_hir_id(node_id);
+
+ if let Some((span, name)) = self.infcx.tcx.typeck_tables_of(did)
+ .closure_kind_origins()
+ .get(hir_id)
+ {
+ diag.span_note(
+ *span,
+ &format!(
+ "closure cannot be moved more than once as it is not `Copy` due to \
+ moving the variable `{}` out of its environment",
+ name
+ ),
+ );
+ }
+ }
+ }
}
/// End-user visible description of `place` if one can be found. If the
"argument and return type have the same lifetime due to lifetime elision rules",
);
diag.note(
- "to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch10-03-\
+ "to learn more, visit <https://doc.rust-lang.org/book/ch10-03-\
lifetime-syntax.html#lifetime-elision>",
);