Skip to content

Commit abaa78b

Browse files
authored
Rollup merge of #78748 - fanzier:tuple-assignment, r=petrochenkov
Implement destructuring assignment for tuples This is the first step towards implementing destructuring assignment (RFC: rust-lang/rfcs#2909, tracking issue: #71126). This PR is the first part of #71156, which was split up to allow for easier review. Quick summary: This change allows destructuring the LHS of an assignment if it's a (possibly nested) tuple. It is implemented via a desugaring (AST -> HIR lowering) as follows: ```rust (a,b) = (1,2) ``` ... becomes ... ```rust { let (lhs0,lhs1) = (1,2); a = lhs0; b = lhs1; } ``` Thanks to `@varkor` who helped with the implementation, particularly around default binding modes. r? `@petrochenkov`
2 parents b4589a8 + 3a7a997 commit abaa78b

23 files changed

+395
-86
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+130-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_data_structures::thin_vec::ThinVec;
99
use rustc_errors::struct_span_err;
1010
use rustc_hir as hir;
1111
use rustc_hir::def::Res;
12+
use rustc_session::parse::feature_err;
1213
use rustc_span::hygiene::ForLoopLoc;
1314
use rustc_span::source_map::{respan, DesugaringKind, Span, Spanned};
1415
use rustc_span::symbol::{sym, Ident, Symbol};
@@ -146,7 +147,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
146147
hir::ExprKind::Block(self.lower_block(blk, opt_label.is_some()), opt_label)
147148
}
148149
ExprKind::Assign(ref el, ref er, span) => {
149-
hir::ExprKind::Assign(self.lower_expr(el), self.lower_expr(er), span)
150+
self.lower_expr_assign(el, er, span, e.span)
150151
}
151152
ExprKind::AssignOp(op, ref el, ref er) => hir::ExprKind::AssignOp(
152153
self.lower_binop(op),
@@ -840,6 +841,134 @@ impl<'hir> LoweringContext<'_, 'hir> {
840841
})
841842
}
842843

844+
/// Destructure the LHS of complex assignments.
845+
/// For instance, lower `(a, b) = t` to `{ let (lhs1, lhs2) = t; a = lhs1; b = lhs2; }`.
846+
fn lower_expr_assign(
847+
&mut self,
848+
lhs: &Expr,
849+
rhs: &Expr,
850+
eq_sign_span: Span,
851+
whole_span: Span,
852+
) -> hir::ExprKind<'hir> {
853+
// Return early in case of an ordinary assignment.
854+
fn is_ordinary(lhs: &Expr) -> bool {
855+
match &lhs.kind {
856+
ExprKind::Tup(..) => false,
857+
ExprKind::Paren(e) => {
858+
match e.kind {
859+
// We special-case `(..)` for consistency with patterns.
860+
ExprKind::Range(None, None, RangeLimits::HalfOpen) => false,
861+
_ => is_ordinary(e),
862+
}
863+
}
864+
_ => true,
865+
}
866+
}
867+
if is_ordinary(lhs) {
868+
return hir::ExprKind::Assign(self.lower_expr(lhs), self.lower_expr(rhs), eq_sign_span);
869+
}
870+
if !self.sess.features_untracked().destructuring_assignment {
871+
feature_err(
872+
&self.sess.parse_sess,
873+
sym::destructuring_assignment,
874+
eq_sign_span,
875+
"destructuring assignments are unstable",
876+
)
877+
.span_label(lhs.span, "cannot assign to this expression")
878+
.emit();
879+
}
880+
881+
let mut assignments = vec![];
882+
883+
// The LHS becomes a pattern: `(lhs1, lhs2)`.
884+
let pat = self.destructure_assign(lhs, eq_sign_span, &mut assignments);
885+
let rhs = self.lower_expr(rhs);
886+
887+
// Introduce a `let` for destructuring: `let (lhs1, lhs2) = t`.
888+
let destructure_let = self.stmt_let_pat(
889+
ThinVec::new(),
890+
whole_span,
891+
Some(rhs),
892+
pat,
893+
hir::LocalSource::AssignDesugar(eq_sign_span),
894+
);
895+
896+
// `a = lhs1; b = lhs2;`.
897+
let stmts = self
898+
.arena
899+
.alloc_from_iter(std::iter::once(destructure_let).chain(assignments.into_iter()));
900+
901+
// Wrap everything in a block.
902+
hir::ExprKind::Block(&self.block_all(whole_span, stmts, None), None)
903+
}
904+
905+
/// Convert the LHS of a destructuring assignment to a pattern.
906+
/// Each sub-assignment is recorded in `assignments`.
907+
fn destructure_assign(
908+
&mut self,
909+
lhs: &Expr,
910+
eq_sign_span: Span,
911+
assignments: &mut Vec<hir::Stmt<'hir>>,
912+
) -> &'hir hir::Pat<'hir> {
913+
match &lhs.kind {
914+
// Tuples.
915+
ExprKind::Tup(elements) => {
916+
let (pats, rest) =
917+
self.destructure_sequence(elements, "tuple", eq_sign_span, assignments);
918+
let tuple_pat = hir::PatKind::Tuple(pats, rest.map(|r| r.0));
919+
return self.pat_without_dbm(lhs.span, tuple_pat);
920+
}
921+
ExprKind::Paren(e) => {
922+
// We special-case `(..)` for consistency with patterns.
923+
if let ExprKind::Range(None, None, RangeLimits::HalfOpen) = e.kind {
924+
let tuple_pat = hir::PatKind::Tuple(&[], Some(0));
925+
return self.pat_without_dbm(lhs.span, tuple_pat);
926+
} else {
927+
return self.destructure_assign(e, eq_sign_span, assignments);
928+
}
929+
}
930+
_ => {}
931+
}
932+
// Treat all other cases as normal lvalue.
933+
let ident = Ident::new(sym::lhs, lhs.span);
934+
let (pat, binding) = self.pat_ident(lhs.span, ident);
935+
let ident = self.expr_ident(lhs.span, ident, binding);
936+
let assign = hir::ExprKind::Assign(self.lower_expr(lhs), ident, eq_sign_span);
937+
let expr = self.expr(lhs.span, assign, ThinVec::new());
938+
assignments.push(self.stmt_expr(lhs.span, expr));
939+
pat
940+
}
941+
942+
/// Destructure a sequence of expressions occurring on the LHS of an assignment.
943+
/// Such a sequence occurs in a tuple (struct)/slice.
944+
/// Return a sequence of corresponding patterns, and the index and the span of `..` if it
945+
/// exists.
946+
/// Each sub-assignment is recorded in `assignments`.
947+
fn destructure_sequence(
948+
&mut self,
949+
elements: &[AstP<Expr>],
950+
ctx: &str,
951+
eq_sign_span: Span,
952+
assignments: &mut Vec<hir::Stmt<'hir>>,
953+
) -> (&'hir [&'hir hir::Pat<'hir>], Option<(usize, Span)>) {
954+
let mut rest = None;
955+
let elements =
956+
self.arena.alloc_from_iter(elements.iter().enumerate().filter_map(|(i, e)| {
957+
// Check for `..` pattern.
958+
if let ExprKind::Range(None, None, RangeLimits::HalfOpen) = e.kind {
959+
if let Some((_, prev_span)) = rest {
960+
self.ban_extra_rest_pat(e.span, prev_span, ctx);
961+
} else {
962+
rest = Some((i, e.span));
963+
}
964+
None
965+
} else {
966+
Some(self.destructure_assign(e, eq_sign_span, assignments))
967+
}
968+
}));
969+
(elements, rest)
970+
}
971+
843972
/// Desugar `<start>..=<end>` into `std::ops::RangeInclusive::new(<start>, <end>)`.
844973
fn lower_expr_range_closed(&mut self, span: Span, e1: &Expr, e2: &Expr) -> hir::ExprKind<'hir> {
845974
let e1 = self.lower_expr_mut(e1);

compiler/rustc_ast_lowering/src/lib.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -2531,6 +2531,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
25312531
hir_id,
25322532
kind: hir::PatKind::Binding(bm, hir_id, ident.with_span_pos(span), None),
25332533
span,
2534+
default_binding_modes: true,
25342535
}),
25352536
hir_id,
25362537
)
@@ -2541,7 +2542,21 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
25412542
}
25422543

25432544
fn pat(&mut self, span: Span, kind: hir::PatKind<'hir>) -> &'hir hir::Pat<'hir> {
2544-
self.arena.alloc(hir::Pat { hir_id: self.next_id(), kind, span })
2545+
self.arena.alloc(hir::Pat {
2546+
hir_id: self.next_id(),
2547+
kind,
2548+
span,
2549+
default_binding_modes: true,
2550+
})
2551+
}
2552+
2553+
fn pat_without_dbm(&mut self, span: Span, kind: hir::PatKind<'hir>) -> &'hir hir::Pat<'hir> {
2554+
self.arena.alloc(hir::Pat {
2555+
hir_id: self.next_id(),
2556+
kind,
2557+
span,
2558+
default_binding_modes: false,
2559+
})
25452560
}
25462561

25472562
fn ty_path(

compiler/rustc_ast_lowering/src/pat.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
273273

274274
/// Construct a `Pat` with the `HirId` of `p.id` lowered.
275275
fn pat_with_node_id_of(&mut self, p: &Pat, kind: hir::PatKind<'hir>) -> &'hir hir::Pat<'hir> {
276-
self.arena.alloc(hir::Pat { hir_id: self.lower_node_id(p.id), kind, span: p.span })
276+
self.arena.alloc(hir::Pat {
277+
hir_id: self.lower_node_id(p.id),
278+
kind,
279+
span: p.span,
280+
default_binding_modes: true,
281+
})
277282
}
278283

279284
/// Emit a friendly error for extra `..` patterns in a tuple/tuple struct/slice pattern.
280-
fn ban_extra_rest_pat(&self, sp: Span, prev_sp: Span, ctx: &str) {
285+
crate fn ban_extra_rest_pat(&self, sp: Span, prev_sp: Span, ctx: &str) {
281286
self.diagnostic()
282287
.struct_span_err(sp, &format!("`..` can only be used once per {} pattern", ctx))
283288
.span_label(sp, &format!("can only be used once per {} pattern", ctx))

compiler/rustc_feature/src/active.rs

+3
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,9 @@ declare_features! (
610610
/// Allows unsized fn parameters.
611611
(active, unsized_fn_params, "1.49.0", Some(48055), None),
612612

613+
/// Allows the use of destructuring assignments.
614+
(active, destructuring_assignment, "1.49.0", Some(71126), None),
615+
613616
// -------------------------------------------------------------------------
614617
// feature-group-end: actual feature gates
615618
// -------------------------------------------------------------------------

compiler/rustc_hir/src/hir.rs

+6
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,9 @@ pub struct Pat<'hir> {
732732
pub hir_id: HirId,
733733
pub kind: PatKind<'hir>,
734734
pub span: Span,
735+
// Whether to use default binding modes.
736+
// At present, this is false only for destructuring assignment.
737+
pub default_binding_modes: bool,
735738
}
736739

737740
impl Pat<'_> {
@@ -1680,6 +1683,9 @@ pub enum LocalSource {
16801683
AsyncFn,
16811684
/// A desugared `<expr>.await`.
16821685
AwaitDesugar,
1686+
/// A desugared `expr = expr`, where the LHS is a tuple, struct or array.
1687+
/// The span is that of the `=` sign.
1688+
AssignDesugar(Span),
16831689
}
16841690

16851691
/// Hints at the original code for a `match _ { .. }`.

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, 'tcx> {
6969
hir::LocalSource::ForLoopDesugar => ("`for` loop binding", None),
7070
hir::LocalSource::AsyncFn => ("async fn binding", None),
7171
hir::LocalSource::AwaitDesugar => ("`await` future binding", None),
72+
hir::LocalSource::AssignDesugar(_) => ("destructuring assignment binding", None),
7273
};
7374
self.check_irrefutable(&loc.pat, msg, sp);
7475
self.check_patterns(&loc.pat);

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ symbols! {
434434
deref_mut,
435435
deref_target,
436436
derive,
437+
destructuring_assignment,
437438
diagnostic,
438439
direct,
439440
discriminant_kind,

compiler/rustc_typeck/src/check/expr.rs

+11-26
Original file line numberDiff line numberDiff line change
@@ -718,39 +718,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
718718
);
719719
}
720720

721-
fn is_destructuring_place_expr(&self, expr: &'tcx hir::Expr<'tcx>) -> bool {
722-
match &expr.kind {
723-
ExprKind::Array(comps) | ExprKind::Tup(comps) => {
724-
comps.iter().all(|e| self.is_destructuring_place_expr(e))
725-
}
726-
ExprKind::Struct(_path, fields, rest) => {
727-
rest.as_ref().map(|e| self.is_destructuring_place_expr(e)).unwrap_or(true)
728-
&& fields.iter().all(|f| self.is_destructuring_place_expr(&f.expr))
729-
}
730-
_ => expr.is_syntactic_place_expr(),
731-
}
732-
}
733-
734721
pub(crate) fn check_lhs_assignable(
735722
&self,
736723
lhs: &'tcx hir::Expr<'tcx>,
737724
err_code: &'static str,
738725
expr_span: &Span,
739726
) {
740-
if !lhs.is_syntactic_place_expr() {
741-
// FIXME: Make this use SessionDiagnostic once error codes can be dynamically set.
742-
let mut err = self.tcx.sess.struct_span_err_with_code(
743-
*expr_span,
744-
"invalid left-hand side of assignment",
745-
DiagnosticId::Error(err_code.into()),
746-
);
747-
err.span_label(lhs.span, "cannot assign to this expression");
748-
if self.is_destructuring_place_expr(lhs) {
749-
err.note("destructuring assignments are not currently supported");
750-
err.note("for more information, see https://github.com/rust-lang/rfcs/issues/372");
751-
}
752-
err.emit();
727+
if lhs.is_syntactic_place_expr() {
728+
return;
753729
}
730+
731+
// FIXME: Make this use SessionDiagnostic once error codes can be dynamically set.
732+
let mut err = self.tcx.sess.struct_span_err_with_code(
733+
*expr_span,
734+
"invalid left-hand side of assignment",
735+
DiagnosticId::Error(err_code.into()),
736+
);
737+
err.span_label(lhs.span, "cannot assign to this expression");
738+
err.emit();
754739
}
755740

756741
/// Type check assignment expression `expr` of form `lhs = rhs`.

compiler/rustc_typeck/src/check/pat.rs

+5
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
270270
///
271271
/// When the pattern is a path pattern, `opt_path_res` must be `Some(res)`.
272272
fn calc_adjust_mode(&self, pat: &'tcx Pat<'tcx>, opt_path_res: Option<Res>) -> AdjustMode {
273+
// When we perform destructuring assignment, we disable default match bindings, which are
274+
// unintuitive in this context.
275+
if !pat.default_binding_modes {
276+
return AdjustMode::Reset;
277+
}
273278
match &pat.kind {
274279
// Type checking these product-like types successfully always require
275280
// that the expected type be of those types and not reference types.

compiler/rustc_typeck/src/check/regionck.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
577577
fn link_pattern(&self, discr_cmt: PlaceWithHirId<'tcx>, root_pat: &hir::Pat<'_>) {
578578
debug!("link_pattern(discr_cmt={:?}, root_pat={:?})", discr_cmt, root_pat);
579579
ignore_err!(self.with_mc(|mc| {
580-
mc.cat_pattern(discr_cmt, root_pat, |sub_cmt, hir::Pat { kind, span, hir_id }| {
580+
mc.cat_pattern(discr_cmt, root_pat, |sub_cmt, hir::Pat { kind, span, hir_id, .. }| {
581581
// `ref x` pattern
582582
if let PatKind::Binding(..) = kind {
583583
if let Some(ty::BindByReference(mutbl)) =

src/test/ui/bad/bad-expr-lhs.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
fn main() {
22
1 = 2; //~ ERROR invalid left-hand side of assignment
33
1 += 2; //~ ERROR invalid left-hand side of assignment
4-
(1, 2) = (3, 4); //~ ERROR invalid left-hand side of assignment
4+
(1, 2) = (3, 4); //~ ERROR destructuring assignments are unstable
5+
//~| ERROR invalid left-hand side of assignment
6+
//~| ERROR invalid left-hand side of assignment
57

68
let (a, b) = (1, 2);
7-
(a, b) = (3, 4); //~ ERROR invalid left-hand side of assignment
9+
(a, b) = (3, 4); //~ ERROR destructuring assignments are unstable
810

911
None = Some(3); //~ ERROR invalid left-hand side of assignment
1012
}

0 commit comments

Comments
 (0)