about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFabian Zaiser <fabian.zaiser@gmail.com>2020-11-04 16:32:52 +0000
committerFabian Zaiser <fabian.zaiser@gmail.com>2020-11-07 13:17:19 +0000
commit3a7a997323436ecf255c39898667320935445f62 (patch)
tree817dc8428c13db1262c1857421d95ccb49a1d7b4
parent601c13c6fda6a7db423c974797e36c79a9a0c0ac (diff)
downloadrust-3a7a997323436ecf255c39898667320935445f62.tar.gz
rust-3a7a997323436ecf255c39898667320935445f62.zip
Implement destructuring assignment for tuples
Co-authored-by: varkor <github@varkor.com>
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs131
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs17
-rw-r--r--compiler/rustc_ast_lowering/src/pat.rs9
-rw-r--r--compiler/rustc_feature/src/active.rs3
-rw-r--r--compiler/rustc_hir/src/hir.rs6
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/check_match.rs1
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs37
-rw-r--r--compiler/rustc_typeck/src/check/pat.rs5
-rw-r--r--compiler/rustc_typeck/src/check/regionck.rs2
-rw-r--r--src/test/ui/bad/bad-expr-lhs.rs6
-rw-r--r--src/test/ui/bad/bad-expr-lhs.stderr47
-rw-r--r--src/test/ui/destructuring-assignment/default-match-bindings-forbidden.rs7
-rw-r--r--src/test/ui/destructuring-assignment/default-match-bindings-forbidden.stderr14
-rw-r--r--src/test/ui/destructuring-assignment/note-unsupported.rs13
-rw-r--r--src/test/ui/destructuring-assignment/note-unsupported.stderr48
-rw-r--r--src/test/ui/destructuring-assignment/tuple_destructure.rs37
-rw-r--r--src/test/ui/destructuring-assignment/tuple_destructure_fail.rs10
-rw-r--r--src/test/ui/destructuring-assignment/tuple_destructure_fail.stderr31
-rw-r--r--src/test/ui/destructuring-assignment/warn-unused-duplication.rs23
-rw-r--r--src/test/ui/destructuring-assignment/warn-unused-duplication.stderr15
-rw-r--r--src/test/ui/feature-gates/feature-gate-destructuring_assignment.rs4
-rw-r--r--src/test/ui/feature-gates/feature-gate-destructuring_assignment.stderr14
23 files changed, 395 insertions, 86 deletions
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index a6ac056b93b..1f2aba2b27e 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -9,6 +9,7 @@ use rustc_data_structures::thin_vec::ThinVec;
 use rustc_errors::struct_span_err;
 use rustc_hir as hir;
 use rustc_hir::def::Res;
+use rustc_session::parse::feature_err;
 use rustc_span::hygiene::ForLoopLoc;
 use rustc_span::source_map::{respan, DesugaringKind, Span, Spanned};
 use rustc_span::symbol::{sym, Ident, Symbol};
@@ -146,7 +147,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                     hir::ExprKind::Block(self.lower_block(blk, opt_label.is_some()), opt_label)
                 }
                 ExprKind::Assign(ref el, ref er, span) => {
-                    hir::ExprKind::Assign(self.lower_expr(el), self.lower_expr(er), span)
+                    self.lower_expr_assign(el, er, span, e.span)
                 }
                 ExprKind::AssignOp(op, ref el, ref er) => hir::ExprKind::AssignOp(
                     self.lower_binop(op),
@@ -840,6 +841,134 @@ impl<'hir> LoweringContext<'_, 'hir> {
         })
     }
 
+    /// Destructure the LHS of complex assignments.
+    /// For instance, lower `(a, b) = t` to `{ let (lhs1, lhs2) = t; a = lhs1; b = lhs2; }`.
+    fn lower_expr_assign(
+        &mut self,
+        lhs: &Expr,
+        rhs: &Expr,
+        eq_sign_span: Span,
+        whole_span: Span,
+    ) -> hir::ExprKind<'hir> {
+        // Return early in case of an ordinary assignment.
+        fn is_ordinary(lhs: &Expr) -> bool {
+            match &lhs.kind {
+                ExprKind::Tup(..) => false,
+                ExprKind::Paren(e) => {
+                    match e.kind {
+                        // We special-case `(..)` for consistency with patterns.
+                        ExprKind::Range(None, None, RangeLimits::HalfOpen) => false,
+                        _ => is_ordinary(e),
+                    }
+                }
+                _ => true,
+            }
+        }
+        if is_ordinary(lhs) {
+            return hir::ExprKind::Assign(self.lower_expr(lhs), self.lower_expr(rhs), eq_sign_span);
+        }
+        if !self.sess.features_untracked().destructuring_assignment {
+            feature_err(
+                &self.sess.parse_sess,
+                sym::destructuring_assignment,
+                eq_sign_span,
+                "destructuring assignments are unstable",
+            )
+            .span_label(lhs.span, "cannot assign to this expression")
+            .emit();
+        }
+
+        let mut assignments = vec![];
+
+        // The LHS becomes a pattern: `(lhs1, lhs2)`.
+        let pat = self.destructure_assign(lhs, eq_sign_span, &mut assignments);
+        let rhs = self.lower_expr(rhs);
+
+        // Introduce a `let` for destructuring: `let (lhs1, lhs2) = t`.
+        let destructure_let = self.stmt_let_pat(
+            ThinVec::new(),
+            whole_span,
+            Some(rhs),
+            pat,
+            hir::LocalSource::AssignDesugar(eq_sign_span),
+        );
+
+        // `a = lhs1; b = lhs2;`.
+        let stmts = self
+            .arena
+            .alloc_from_iter(std::iter::once(destructure_let).chain(assignments.into_iter()));
+
+        // Wrap everything in a block.
+        hir::ExprKind::Block(&self.block_all(whole_span, stmts, None), None)
+    }
+
+    /// Convert the LHS of a destructuring assignment to a pattern.
+    /// Each sub-assignment is recorded in `assignments`.
+    fn destructure_assign(
+        &mut self,
+        lhs: &Expr,
+        eq_sign_span: Span,
+        assignments: &mut Vec<hir::Stmt<'hir>>,
+    ) -> &'hir hir::Pat<'hir> {
+        match &lhs.kind {
+            // Tuples.
+            ExprKind::Tup(elements) => {
+                let (pats, rest) =
+                    self.destructure_sequence(elements, "tuple", eq_sign_span, assignments);
+                let tuple_pat = hir::PatKind::Tuple(pats, rest.map(|r| r.0));
+                return self.pat_without_dbm(lhs.span, tuple_pat);
+            }
+            ExprKind::Paren(e) => {
+                // We special-case `(..)` for consistency with patterns.
+                if let ExprKind::Range(None, None, RangeLimits::HalfOpen) = e.kind {
+                    let tuple_pat = hir::PatKind::Tuple(&[], Some(0));
+                    return self.pat_without_dbm(lhs.span, tuple_pat);
+                } else {
+                    return self.destructure_assign(e, eq_sign_span, assignments);
+                }
+            }
+            _ => {}
+        }
+        // Treat all other cases as normal lvalue.
+        let ident = Ident::new(sym::lhs, lhs.span);
+        let (pat, binding) = self.pat_ident(lhs.span, ident);
+        let ident = self.expr_ident(lhs.span, ident, binding);
+        let assign = hir::ExprKind::Assign(self.lower_expr(lhs), ident, eq_sign_span);
+        let expr = self.expr(lhs.span, assign, ThinVec::new());
+        assignments.push(self.stmt_expr(lhs.span, expr));
+        pat
+    }
+
+    /// Destructure a sequence of expressions occurring on the LHS of an assignment.
+    /// Such a sequence occurs in a tuple (struct)/slice.
+    /// Return a sequence of corresponding patterns, and the index and the span of `..` if it
+    /// exists.
+    /// Each sub-assignment is recorded in `assignments`.
+    fn destructure_sequence(
+        &mut self,
+        elements: &[AstP<Expr>],
+        ctx: &str,
+        eq_sign_span: Span,
+        assignments: &mut Vec<hir::Stmt<'hir>>,
+    ) -> (&'hir [&'hir hir::Pat<'hir>], Option<(usize, Span)>) {
+        let mut rest = None;
+        let elements =
+            self.arena.alloc_from_iter(elements.iter().enumerate().filter_map(|(i, e)| {
+                // Check for `..` pattern.
+                if let ExprKind::Range(None, None, RangeLimits::HalfOpen) = e.kind {
+                    if let Some((_, prev_span)) = rest {
+                        self.ban_extra_rest_pat(e.span, prev_span, ctx);
+                    } else {
+                        rest = Some((i, e.span));
+                    }
+                    None
+                } else {
+                    Some(self.destructure_assign(e, eq_sign_span, assignments))
+                }
+            }));
+        (elements, rest)
+    }
+
     /// Desugar `<start>..=<end>` into `std::ops::RangeInclusive::new(<start>, <end>)`.
     fn lower_expr_range_closed(&mut self, span: Span, e1: &Expr, e2: &Expr) -> hir::ExprKind<'hir> {
         let e1 = self.lower_expr_mut(e1);
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 599599f415f..af2f96d5e62 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -2531,6 +2531,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
                 hir_id,
                 kind: hir::PatKind::Binding(bm, hir_id, ident.with_span_pos(span), None),
                 span,
+                default_binding_modes: true,
             }),
             hir_id,
         )
@@ -2541,7 +2542,21 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
     }
 
     fn pat(&mut self, span: Span, kind: hir::PatKind<'hir>) -> &'hir hir::Pat<'hir> {
-        self.arena.alloc(hir::Pat { hir_id: self.next_id(), kind, span })
+        self.arena.alloc(hir::Pat {
+            hir_id: self.next_id(),
+            kind,
+            span,
+            default_binding_modes: true,
+        })
+    }
+
+    fn pat_without_dbm(&mut self, span: Span, kind: hir::PatKind<'hir>) -> &'hir hir::Pat<'hir> {
+        self.arena.alloc(hir::Pat {
+            hir_id: self.next_id(),
+            kind,
+            span,
+            default_binding_modes: false,
+        })
     }
 
     fn ty_path(
diff --git a/compiler/rustc_ast_lowering/src/pat.rs b/compiler/rustc_ast_lowering/src/pat.rs
index a1cbcde1f42..e4e7b24d29e 100644
--- a/compiler/rustc_ast_lowering/src/pat.rs
+++ b/compiler/rustc_ast_lowering/src/pat.rs
@@ -273,11 +273,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
 
     /// Construct a `Pat` with the `HirId` of `p.id` lowered.
     fn pat_with_node_id_of(&mut self, p: &Pat, kind: hir::PatKind<'hir>) -> &'hir hir::Pat<'hir> {
-        self.arena.alloc(hir::Pat { hir_id: self.lower_node_id(p.id), kind, span: p.span })
+        self.arena.alloc(hir::Pat {
+            hir_id: self.lower_node_id(p.id),
+            kind,
+            span: p.span,
+            default_binding_modes: true,
+        })
     }
 
     /// Emit a friendly error for extra `..` patterns in a tuple/tuple struct/slice pattern.
-    fn ban_extra_rest_pat(&self, sp: Span, prev_sp: Span, ctx: &str) {
+    crate fn ban_extra_rest_pat(&self, sp: Span, prev_sp: Span, ctx: &str) {
         self.diagnostic()
             .struct_span_err(sp, &format!("`..` can only be used once per {} pattern", ctx))
             .span_label(sp, &format!("can only be used once per {} pattern", ctx))
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index ad926a810e6..84114fc7735 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -610,6 +610,9 @@ declare_features! (
     /// Allows unsized fn parameters.
     (active, unsized_fn_params, "1.49.0", Some(48055), None),
 
+    /// Allows the use of destructuring assignments.
+    (active, destructuring_assignment, "1.49.0", Some(71126), None),
+
     // -------------------------------------------------------------------------
     // feature-group-end: actual feature gates
     // -------------------------------------------------------------------------
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index b9ec18688c5..6767041ecee 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -732,6 +732,9 @@ pub struct Pat<'hir> {
     pub hir_id: HirId,
     pub kind: PatKind<'hir>,
     pub span: Span,
+    // Whether to use default binding modes.
+    // At present, this is false only for destructuring assignment.
+    pub default_binding_modes: bool,
 }
 
 impl Pat<'_> {
@@ -1680,6 +1683,9 @@ pub enum LocalSource {
     AsyncFn,
     /// A desugared `<expr>.await`.
     AwaitDesugar,
+    /// A desugared `expr = expr`, where the LHS is a tuple, struct or array.
+    /// The span is that of the `=` sign.
+    AssignDesugar(Span),
 }
 
 /// Hints at the original code for a `match _ { .. }`.
diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
index 30b700a1d4f..04d456936eb 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
@@ -69,6 +69,7 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, 'tcx> {
             hir::LocalSource::ForLoopDesugar => ("`for` loop binding", None),
             hir::LocalSource::AsyncFn => ("async fn binding", None),
             hir::LocalSource::AwaitDesugar => ("`await` future binding", None),
+            hir::LocalSource::AssignDesugar(_) => ("destructuring assignment binding", None),
         };
         self.check_irrefutable(&loc.pat, msg, sp);
         self.check_patterns(&loc.pat);
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 1a6c45b6c80..2324dba80f5 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -434,6 +434,7 @@ symbols! {
         deref_mut,
         deref_target,
         derive,
+        destructuring_assignment,
         diagnostic,
         direct,
         discriminant_kind,
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index 324aa1a66a6..af19ad08c1d 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -718,39 +718,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         );
     }
 
-    fn is_destructuring_place_expr(&self, expr: &'tcx hir::Expr<'tcx>) -> bool {
-        match &expr.kind {
-            ExprKind::Array(comps) | ExprKind::Tup(comps) => {
-                comps.iter().all(|e| self.is_destructuring_place_expr(e))
-            }
-            ExprKind::Struct(_path, fields, rest) => {
-                rest.as_ref().map(|e| self.is_destructuring_place_expr(e)).unwrap_or(true)
-                    && fields.iter().all(|f| self.is_destructuring_place_expr(&f.expr))
-            }
-            _ => expr.is_syntactic_place_expr(),
-        }
-    }
-
     pub(crate) fn check_lhs_assignable(
         &self,
         lhs: &'tcx hir::Expr<'tcx>,
         err_code: &'static str,
         expr_span: &Span,
     ) {
-        if !lhs.is_syntactic_place_expr() {
-            // FIXME: Make this use SessionDiagnostic once error codes can be dynamically set.
-            let mut err = self.tcx.sess.struct_span_err_with_code(
-                *expr_span,
-                "invalid left-hand side of assignment",
-                DiagnosticId::Error(err_code.into()),
-            );
-            err.span_label(lhs.span, "cannot assign to this expression");
-            if self.is_destructuring_place_expr(lhs) {
-                err.note("destructuring assignments are not currently supported");
-                err.note("for more information, see https://github.com/rust-lang/rfcs/issues/372");
-            }
-            err.emit();
+        if lhs.is_syntactic_place_expr() {
+            return;
         }
+
+        // FIXME: Make this use SessionDiagnostic once error codes can be dynamically set.
+        let mut err = self.tcx.sess.struct_span_err_with_code(
+            *expr_span,
+            "invalid left-hand side of assignment",
+            DiagnosticId::Error(err_code.into()),
+        );
+        err.span_label(lhs.span, "cannot assign to this expression");
+        err.emit();
     }
 
     /// Type check assignment expression `expr` of form `lhs = rhs`.
diff --git a/compiler/rustc_typeck/src/check/pat.rs b/compiler/rustc_typeck/src/check/pat.rs
index 53bc2069b76..d0e6edfd9db 100644
--- a/compiler/rustc_typeck/src/check/pat.rs
+++ b/compiler/rustc_typeck/src/check/pat.rs
@@ -270,6 +270,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     ///
     /// When the pattern is a path pattern, `opt_path_res` must be `Some(res)`.
     fn calc_adjust_mode(&self, pat: &'tcx Pat<'tcx>, opt_path_res: Option<Res>) -> AdjustMode {
+        // When we perform destructuring assignment, we disable default match bindings, which are
+        // unintuitive in this context.
+        if !pat.default_binding_modes {
+            return AdjustMode::Reset;
+        }
         match &pat.kind {
             // Type checking these product-like types successfully always require
             // that the expected type be of those types and not reference types.
diff --git a/compiler/rustc_typeck/src/check/regionck.rs b/compiler/rustc_typeck/src/check/regionck.rs
index ba0f22513a1..7b31b9f3915 100644
--- a/compiler/rustc_typeck/src/check/regionck.rs
+++ b/compiler/rustc_typeck/src/check/regionck.rs
@@ -577,7 +577,7 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
     fn link_pattern(&self, discr_cmt: PlaceWithHirId<'tcx>, root_pat: &hir::Pat<'_>) {
         debug!("link_pattern(discr_cmt={:?}, root_pat={:?})", discr_cmt, root_pat);
         ignore_err!(self.with_mc(|mc| {
-            mc.cat_pattern(discr_cmt, root_pat, |sub_cmt, hir::Pat { kind, span, hir_id }| {
+            mc.cat_pattern(discr_cmt, root_pat, |sub_cmt, hir::Pat { kind, span, hir_id, .. }| {
                 // `ref x` pattern
                 if let PatKind::Binding(..) = kind {
                     if let Some(ty::BindByReference(mutbl)) =
diff --git a/src/test/ui/bad/bad-expr-lhs.rs b/src/test/ui/bad/bad-expr-lhs.rs
index d7cf1b77005..39536f12e3b 100644
--- a/src/test/ui/bad/bad-expr-lhs.rs
+++ b/src/test/ui/bad/bad-expr-lhs.rs
@@ -1,10 +1,12 @@
 fn main() {
     1 = 2; //~ ERROR invalid left-hand side of assignment
     1 += 2; //~ ERROR invalid left-hand side of assignment
-    (1, 2) = (3, 4); //~ ERROR invalid left-hand side of assignment
+    (1, 2) = (3, 4); //~ ERROR destructuring assignments are unstable
+    //~| ERROR invalid left-hand side of assignment
+    //~| ERROR invalid left-hand side of assignment
 
     let (a, b) = (1, 2);
-    (a, b) = (3, 4); //~ ERROR invalid left-hand side of assignment
+    (a, b) = (3, 4); //~ ERROR destructuring assignments are unstable
 
     None = Some(3); //~ ERROR invalid left-hand side of assignment
 }
diff --git a/src/test/ui/bad/bad-expr-lhs.stderr b/src/test/ui/bad/bad-expr-lhs.stderr
index a195e1054d0..d4b2193d09f 100644
--- a/src/test/ui/bad/bad-expr-lhs.stderr
+++ b/src/test/ui/bad/bad-expr-lhs.stderr
@@ -1,3 +1,25 @@
+error[E0658]: destructuring assignments are unstable
+  --> $DIR/bad-expr-lhs.rs:4:12
+   |
+LL |     (1, 2) = (3, 4);
+   |     ------ ^
+   |     |
+   |     cannot assign to this expression
+   |
+   = note: see issue #71126 <https://github.com/rust-lang/rust/issues/71126> for more information
+   = help: add `#![feature(destructuring_assignment)]` to the crate attributes to enable
+
+error[E0658]: destructuring assignments are unstable
+  --> $DIR/bad-expr-lhs.rs:9:12
+   |
+LL |     (a, b) = (3, 4);
+   |     ------ ^
+   |     |
+   |     cannot assign to this expression
+   |
+   = note: see issue #71126 <https://github.com/rust-lang/rust/issues/71126> for more information
+   = help: add `#![feature(destructuring_assignment)]` to the crate attributes to enable
+
 error[E0070]: invalid left-hand side of assignment
   --> $DIR/bad-expr-lhs.rs:2:7
    |
@@ -18,30 +40,27 @@ error[E0070]: invalid left-hand side of assignment
   --> $DIR/bad-expr-lhs.rs:4:12
    |
 LL |     (1, 2) = (3, 4);
-   |     ------ ^
-   |     |
-   |     cannot assign to this expression
+   |      -     ^
+   |      |
+   |      cannot assign to this expression
 
 error[E0070]: invalid left-hand side of assignment
-  --> $DIR/bad-expr-lhs.rs:7:12
-   |
-LL |     (a, b) = (3, 4);
-   |     ------ ^
-   |     |
-   |     cannot assign to this expression
+  --> $DIR/bad-expr-lhs.rs:4:12
    |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
+LL |     (1, 2) = (3, 4);
+   |         -  ^
+   |         |
+   |         cannot assign to this expression
 
 error[E0070]: invalid left-hand side of assignment
-  --> $DIR/bad-expr-lhs.rs:9:10
+  --> $DIR/bad-expr-lhs.rs:11:10
    |
 LL |     None = Some(3);
    |     ---- ^
    |     |
    |     cannot assign to this expression
 
-error: aborting due to 5 previous errors
+error: aborting due to 7 previous errors
 
-Some errors have detailed explanations: E0067, E0070.
+Some errors have detailed explanations: E0067, E0070, E0658.
 For more information about an error, try `rustc --explain E0067`.
diff --git a/src/test/ui/destructuring-assignment/default-match-bindings-forbidden.rs b/src/test/ui/destructuring-assignment/default-match-bindings-forbidden.rs
new file mode 100644
index 00000000000..adecd0ff291
--- /dev/null
+++ b/src/test/ui/destructuring-assignment/default-match-bindings-forbidden.rs
@@ -0,0 +1,7 @@
+#![feature(destructuring_assignment)]
+
+fn main() {
+    let mut x = &0;
+    let mut y = &0;
+    (x, y) = &(1, 2); //~ ERROR mismatched types
+}
diff --git a/src/test/ui/destructuring-assignment/default-match-bindings-forbidden.stderr b/src/test/ui/destructuring-assignment/default-match-bindings-forbidden.stderr
new file mode 100644
index 00000000000..e6161fdfa24
--- /dev/null
+++ b/src/test/ui/destructuring-assignment/default-match-bindings-forbidden.stderr
@@ -0,0 +1,14 @@
+error[E0308]: mismatched types
+  --> $DIR/default-match-bindings-forbidden.rs:6:5
+   |
+LL |     (x, y) = &(1, 2);
+   |     ^^^^^^   ------- this expression has type `&({integer}, {integer})`
+   |     |
+   |     expected reference, found tuple
+   |
+   = note: expected type `&({integer}, {integer})`
+             found tuple `(_, _)`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/destructuring-assignment/note-unsupported.rs b/src/test/ui/destructuring-assignment/note-unsupported.rs
index 876c9efea26..e0cb9dc9158 100644
--- a/src/test/ui/destructuring-assignment/note-unsupported.rs
+++ b/src/test/ui/destructuring-assignment/note-unsupported.rs
@@ -3,23 +3,24 @@ struct S { x: u8, y: u8 }
 fn main() {
     let (a, b) = (1, 2);
 
-    (a, b) = (3, 4); //~ ERROR invalid left-hand side of assignment
+    (a, b) = (3, 4); //~ ERROR destructuring assignments are unstable
     (a, b) += (3, 4); //~ ERROR invalid left-hand side of assignment
-    //~^ ERROR binary assignment operation `+=` cannot be applied
+    //~| ERROR binary assignment operation `+=` cannot be applied
 
     [a, b] = [3, 4]; //~ ERROR invalid left-hand side of assignment
     [a, b] += [3, 4]; //~ ERROR invalid left-hand side of assignment
-    //~^ ERROR binary assignment operation `+=` cannot be applied
+    //~| ERROR binary assignment operation `+=` cannot be applied
 
     let s = S { x: 3, y: 4 };
 
     S { x: a, y: b } = s; //~ ERROR invalid left-hand side of assignment
     S { x: a, y: b } += s; //~ ERROR invalid left-hand side of assignment
-    //~^ ERROR binary assignment operation `+=` cannot be applied
+    //~| ERROR binary assignment operation `+=` cannot be applied
 
-    S { x: a, ..s } = S { x: 3, y: 4 }; //~ ERROR invalid left-hand side of assignment
+    S { x: a, ..s } = S { x: 3, y: 4 };
+    //~^ ERROR invalid left-hand side of assignment
 
     let c = 3;
 
-    ((a, b), c) = ((3, 4), 5); //~ ERROR invalid left-hand side of assignment
+    ((a, b), c) = ((3, 4), 5); //~ ERROR destructuring assignments are unstable
 }
diff --git a/src/test/ui/destructuring-assignment/note-unsupported.stderr b/src/test/ui/destructuring-assignment/note-unsupported.stderr
index d4e25930d22..c5543fab825 100644
--- a/src/test/ui/destructuring-assignment/note-unsupported.stderr
+++ b/src/test/ui/destructuring-assignment/note-unsupported.stderr
@@ -1,4 +1,4 @@
-error[E0070]: invalid left-hand side of assignment
+error[E0658]: destructuring assignments are unstable
   --> $DIR/note-unsupported.rs:6:12
    |
 LL |     (a, b) = (3, 4);
@@ -6,8 +6,19 @@ LL |     (a, b) = (3, 4);
    |     |
    |     cannot assign to this expression
    |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
+   = note: see issue #71126 <https://github.com/rust-lang/rust/issues/71126> for more information
+   = help: add `#![feature(destructuring_assignment)]` to the crate attributes to enable
+
+error[E0658]: destructuring assignments are unstable
+  --> $DIR/note-unsupported.rs:25:17
+   |
+LL |     ((a, b), c) = ((3, 4), 5);
+   |     ----------- ^
+   |     |
+   |     cannot assign to this expression
+   |
+   = note: see issue #71126 <https://github.com/rust-lang/rust/issues/71126> for more information
+   = help: add `#![feature(destructuring_assignment)]` to the crate attributes to enable
 
 error[E0368]: binary assignment operation `+=` cannot be applied to type `({integer}, {integer})`
   --> $DIR/note-unsupported.rs:7:5
@@ -24,9 +35,6 @@ LL |     (a, b) += (3, 4);
    |     ------ ^^
    |     |
    |     cannot assign to this expression
-   |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
 
 error[E0070]: invalid left-hand side of assignment
   --> $DIR/note-unsupported.rs:10:12
@@ -35,9 +43,6 @@ LL |     [a, b] = [3, 4];
    |     ------ ^
    |     |
    |     cannot assign to this expression
-   |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
 
 error[E0368]: binary assignment operation `+=` cannot be applied to type `[{integer}; 2]`
   --> $DIR/note-unsupported.rs:11:5
@@ -54,9 +59,6 @@ LL |     [a, b] += [3, 4];
    |     ------ ^^
    |     |
    |     cannot assign to this expression
-   |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
 
 error[E0070]: invalid left-hand side of assignment
   --> $DIR/note-unsupported.rs:16:22
@@ -65,9 +67,6 @@ LL |     S { x: a, y: b } = s;
    |     ---------------- ^
    |     |
    |     cannot assign to this expression
-   |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
 
 error[E0368]: binary assignment operation `+=` cannot be applied to type `S`
   --> $DIR/note-unsupported.rs:17:5
@@ -86,9 +85,6 @@ LL |     S { x: a, y: b } += s;
    |     ---------------- ^^
    |     |
    |     cannot assign to this expression
-   |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
 
 error[E0070]: invalid left-hand side of assignment
   --> $DIR/note-unsupported.rs:20:21
@@ -97,22 +93,8 @@ LL |     S { x: a, ..s } = S { x: 3, y: 4 };
    |     --------------- ^
    |     |
    |     cannot assign to this expression
-   |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
-
-error[E0070]: invalid left-hand side of assignment
-  --> $DIR/note-unsupported.rs:24:17
-   |
-LL |     ((a, b), c) = ((3, 4), 5);
-   |     ----------- ^
-   |     |
-   |     cannot assign to this expression
-   |
-   = note: destructuring assignments are not currently supported
-   = note: for more information, see https://github.com/rust-lang/rfcs/issues/372
 
 error: aborting due to 11 previous errors
 
-Some errors have detailed explanations: E0067, E0070, E0368.
+Some errors have detailed explanations: E0067, E0070, E0368, E0658.
 For more information about an error, try `rustc --explain E0067`.
diff --git a/src/test/ui/destructuring-assignment/tuple_destructure.rs b/src/test/ui/destructuring-assignment/tuple_destructure.rs
new file mode 100644
index 00000000000..16aafc4693f
--- /dev/null
+++ b/src/test/ui/destructuring-assignment/tuple_destructure.rs
@@ -0,0 +1,37 @@
+// run-pass
+
+#![feature(destructuring_assignment)]
+
+fn main() {
+    let (mut a, mut b);
+    (a, b) = (0, 1);
+    assert_eq!((a, b), (0, 1));
+    (b, a) = (a, b);
+    assert_eq!((a, b), (1, 0));
+    (a, .., b) = (1, 2);
+    assert_eq!((a, b), (1, 2));
+    (.., a) = (1, 2);
+    assert_eq!((a, b), (2, 2));
+    (..) = (3, 4);
+    assert_eq!((a, b), (2, 2));
+    (b, ..) = (5, 6, 7);
+    assert_eq!(b, 5);
+
+    // Test for a non-Copy type (String):
+    let (mut c, mut d);
+    (c, d) = ("c".to_owned(), "d".to_owned());
+    assert_eq!(c, "c");
+    assert_eq!(d, "d");
+    (d, c) = (c, d);
+    assert_eq!(c, "d");
+    assert_eq!(d, "c");
+
+    // Test nesting/parentheses:
+    ((a, b)) = (0, 1);
+    assert_eq!((a, b), (0, 1));
+    (((a, b)), (c)) = ((2, 3), d);
+    assert_eq!((a, b), (2, 3));
+    assert_eq!(c, "c");
+    ((a, .., b), .., (..)) = ((4, 5), ());
+    assert_eq!((a, b), (4, 5));
+}
diff --git a/src/test/ui/destructuring-assignment/tuple_destructure_fail.rs b/src/test/ui/destructuring-assignment/tuple_destructure_fail.rs
new file mode 100644
index 00000000000..b76f4968e62
--- /dev/null
+++ b/src/test/ui/destructuring-assignment/tuple_destructure_fail.rs
@@ -0,0 +1,10 @@
+#![feature(destructuring_assignment)]
+
+const C: i32 = 1;
+
+fn main() {
+    let (mut a, mut b);
+    (a, .., b, ..) = (0, 1); //~ ERROR `..` can only be used once per tuple pattern
+    (a, a, b) = (1, 2); //~ ERROR mismatched types
+    (C, ..) = (0,1); //~ ERROR invalid left-hand side of assignment
+}
diff --git a/src/test/ui/destructuring-assignment/tuple_destructure_fail.stderr b/src/test/ui/destructuring-assignment/tuple_destructure_fail.stderr
new file mode 100644
index 00000000000..a60e1cb1eec
--- /dev/null
+++ b/src/test/ui/destructuring-assignment/tuple_destructure_fail.stderr
@@ -0,0 +1,31 @@
+error: `..` can only be used once per tuple pattern
+  --> $DIR/tuple_destructure_fail.rs:7:16
+   |
+LL |     (a, .., b, ..) = (0, 1);
+   |         --     ^^ can only be used once per tuple pattern
+   |         |
+   |         previously used here
+
+error[E0308]: mismatched types
+  --> $DIR/tuple_destructure_fail.rs:8:5
+   |
+LL |     (a, a, b) = (1, 2);
+   |     ^^^^^^^^^   ------ this expression has type `({integer}, {integer})`
+   |     |
+   |     expected a tuple with 2 elements, found one with 3 elements
+   |
+   = note: expected type `({integer}, {integer})`
+             found tuple `(_, _, _)`
+
+error[E0070]: invalid left-hand side of assignment
+  --> $DIR/tuple_destructure_fail.rs:9:13
+   |
+LL |     (C, ..) = (0,1);
+   |      -      ^
+   |      |
+   |      cannot assign to this expression
+
+error: aborting due to 3 previous errors
+
+Some errors have detailed explanations: E0070, E0308.
+For more information about an error, try `rustc --explain E0070`.
diff --git a/src/test/ui/destructuring-assignment/warn-unused-duplication.rs b/src/test/ui/destructuring-assignment/warn-unused-duplication.rs
new file mode 100644
index 00000000000..c1c5c2cd3ce
--- /dev/null
+++ b/src/test/ui/destructuring-assignment/warn-unused-duplication.rs
@@ -0,0 +1,23 @@
+// run-pass
+
+#![feature(destructuring_assignment)]
+
+#![warn(unused_assignments)]
+
+fn main() {
+    let mut a;
+    // Assignment occurs left-to-right.
+    // However, we emit warnings when this happens, so it is clear that this is happening.
+    (a, a) = (0, 1); //~ WARN value assigned to `a` is never read
+    assert_eq!(a, 1);
+
+    // We can't always tell when a variable is being assigned to twice, which is why we don't try
+    // to emit an error, which would be fallible.
+    let mut x = 1;
+    (*foo(&mut x), *foo(&mut x)) = (5, 6);
+    assert_eq!(x, 6);
+}
+
+fn foo<'a>(x: &'a mut u32) -> &'a mut u32 {
+    x
+}
diff --git a/src/test/ui/destructuring-assignment/warn-unused-duplication.stderr b/src/test/ui/destructuring-assignment/warn-unused-duplication.stderr
new file mode 100644
index 00000000000..b87ef6f1571
--- /dev/null
+++ b/src/test/ui/destructuring-assignment/warn-unused-duplication.stderr
@@ -0,0 +1,15 @@
+warning: value assigned to `a` is never read
+  --> $DIR/warn-unused-duplication.rs:11:6
+   |
+LL |     (a, a) = (0, 1);
+   |      ^
+   |
+note: the lint level is defined here
+  --> $DIR/warn-unused-duplication.rs:5:9
+   |
+LL | #![warn(unused_assignments)]
+   |         ^^^^^^^^^^^^^^^^^^
+   = help: maybe it is overwritten before being read?
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/feature-gates/feature-gate-destructuring_assignment.rs b/src/test/ui/feature-gates/feature-gate-destructuring_assignment.rs
new file mode 100644
index 00000000000..e7801f0e8ec
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-destructuring_assignment.rs
@@ -0,0 +1,4 @@
+fn main() {
+    let (a, b) = (0, 1);
+    (a, b) = (2, 3); //~ ERROR destructuring assignments are unstable
+}
diff --git a/src/test/ui/feature-gates/feature-gate-destructuring_assignment.stderr b/src/test/ui/feature-gates/feature-gate-destructuring_assignment.stderr
new file mode 100644
index 00000000000..62e71decb32
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-destructuring_assignment.stderr
@@ -0,0 +1,14 @@
+error[E0658]: destructuring assignments are unstable
+  --> $DIR/feature-gate-destructuring_assignment.rs:3:12
+   |
+LL |     (a, b) = (2, 3);
+   |     ------ ^
+   |     |
+   |     cannot assign to this expression
+   |
+   = note: see issue #71126 <https://github.com/rust-lang/rust/issues/71126> for more information
+   = help: add `#![feature(destructuring_assignment)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.