]> git.lizzy.rs Git - rust.git/commitdiff
Add struct field suggestions.
authorDavid Wood <david@davidtw.co>
Thu, 4 Oct 2018 23:17:43 +0000 (01:17 +0200)
committerDavid Wood <david@davidtw.co>
Mon, 8 Oct 2018 10:26:55 +0000 (12:26 +0200)
This commit adds suggestions to change the definitions of fields in
struct definitions from immutable references to mutable references.

src/librustc_mir/borrow_check/mutability_errors.rs
src/test/ui/did_you_mean/issue-38147-2.nll.stderr
src/test/ui/did_you_mean/issue-38147-3.nll.stderr

index ba625fb08c82c6865d9103cc000c06f9f40c2e45..fe24ec382ceb354bc912478faa1e30787a1366b1 100644 (file)
@@ -218,6 +218,33 @@ pub(super) fn report_mutability_error(
         debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
 
         match the_place_err {
+            // Suggest making an existing shared borrow in a struct definition a mutable borrow.
+            //
+            // This is applicable when we have a deref of a field access to a deref of a local -
+            // something like `*((*_1).0`. The local that we get will be a reference to the
+            // struct we've got a field access of (it must be a reference since there's a deref
+            // after the field access).
+            Place::Projection(box Projection {
+                base: Place::Projection(box Projection {
+                    base: Place::Projection(box Projection {
+                        base,
+                        elem: ProjectionElem::Deref,
+                    }),
+                    elem: ProjectionElem::Field(field, _),
+                }),
+                elem: ProjectionElem::Deref,
+            }) => {
+                err.span_label(span, format!("cannot {ACT}", ACT = act));
+
+                if let Some((span, message)) = annotate_struct_field(
+                    self.infcx.tcx,
+                    base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx),
+                    field,
+                ) {
+                    err.span_label(span, message);
+                }
+            },
+
             // Suggest removing a `&mut` from the use of a mutable reference.
             Place::Local(local)
                 if {
@@ -592,3 +619,56 @@ fn suggest_ampmut<'cx, 'gcx, 'tcx>(
 fn is_closure_or_generator(ty: ty::Ty) -> bool {
     ty.is_closure() || ty.is_generator()
 }
+
+/// Add a suggestion to a struct definition given a field access to a local.
+/// This function expects the local to be a reference to a struct in order to produce a suggestion.
+///
+/// ```text
+/// LL |     s: &'a String
+///    |        ---------- use `&'a mut String` here to make mutable
+/// ```
+fn annotate_struct_field(
+    tcx: TyCtxt<'cx, 'gcx, 'tcx>,
+    ty: ty::Ty<'tcx>,
+    field: &mir::Field,
+) -> Option<(Span, String)> {
+    // Expect our local to be a reference to a struct of some kind.
+    if let ty::TyKind::Ref(_, ty, _) = ty.sty {
+        if let ty::TyKind::Adt(def, _) = ty.sty {
+            let field = def.all_fields().nth(field.index())?;
+            let span = tcx.def_span(field.did);
+
+            // Use the HIR types to construct the diagnostic message.
+            let node_id = tcx.hir.as_local_node_id(field.did)?;
+            let node = tcx.hir.find(node_id)?;
+            // Now we're dealing with the actual struct that we're going to suggest a change to,
+            // we can expect a field that is an immutable reference to a type.
+            if let hir::Node::Field(field) = node {
+                if let hir::TyKind::Rptr(lifetime, hir::MutTy {
+                    mutbl: hir::Mutability::MutImmutable,
+                    ref ty
+                }) = field.ty.node {
+                    // Get the snippets in two parts - the named lifetime (if there is one) and
+                    // type being referenced, that way we can reconstruct the snippet without loss
+                    // of detail.
+                    let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
+                    let lifetime_snippet = if !lifetime.is_elided() {
+                        format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
+                    } else {
+                        String::new()
+                    };
+
+                    return Some((
+                        span,
+                        format!(
+                            "use `&{}mut {}` here to make mutable",
+                            lifetime_snippet, &*type_snippet,
+                        ),
+                    ));
+                }
+            }
+        }
+    }
+
+    None
+}
index 21fc4079d5b59d29626e162a04f1d6c2aba956a9..91ccef1a32f1f22f7e5e4b9f5bb8fa92b8c6aea7 100644 (file)
@@ -1,6 +1,9 @@
 error[E0596]: cannot borrow `*self.s` as mutable, as it is behind a `&` reference
   --> $DIR/issue-38147-2.rs:17:9
    |
+LL |     s: &'a String
+   |     ------------- use `&'a mut String` here to make mutable
+...
 LL |         self.s.push('x');
    |         ^^^^^^ cannot borrow as mutable
 
index d426c1f37fc1c667a3a44f339ed19d44b0fdbae1..ecc80a1d4310bae0c711cb86a6ad8a2865f7f702 100644 (file)
@@ -1,6 +1,9 @@
 error[E0596]: cannot borrow `*self.s` as mutable, as it is behind a `&` reference
   --> $DIR/issue-38147-3.rs:17:9
    |
+LL |     s: &'a String
+   |     ------------- use `&'a mut String` here to make mutable
+...
 LL |         self.s.push('x');
    |         ^^^^^^ cannot borrow as mutable