Skip to content

Commit de84ad9

Browse files
fanziervarkor
andcommitted
Implement destructuring assignment for structs and slices
Co-authored-by: varkor <github@varkor.com>
1 parent cf9cf7c commit de84ad9

32 files changed

+618
-107
lines changed

compiler/rustc_ast/src/ast.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ pub struct Expr {
10611061

10621062
// `Expr` is used a lot. Make sure it doesn't unintentionally get bigger.
10631063
#[cfg(target_arch = "x86_64")]
1064-
rustc_data_structures::static_assert_size!(Expr, 112);
1064+
rustc_data_structures::static_assert_size!(Expr, 120);
10651065

10661066
impl Expr {
10671067
/// Returns `true` if this expression would be valid somewhere that expects a value;
@@ -1218,6 +1218,16 @@ pub enum RangeLimits {
12181218
Closed,
12191219
}
12201220

1221+
#[derive(Clone, Encodable, Decodable, Debug)]
1222+
pub enum StructRest {
1223+
/// `..x`.
1224+
Base(P<Expr>),
1225+
/// `..`.
1226+
Rest(Span),
1227+
/// No trailing `..` or expression.
1228+
None,
1229+
}
1230+
12211231
#[derive(Clone, Encodable, Decodable, Debug)]
12221232
pub enum ExprKind {
12231233
/// A `box x` expression.
@@ -1312,7 +1322,7 @@ pub enum ExprKind {
13121322
Field(P<Expr>, Ident),
13131323
/// An indexing operation (e.g., `foo[2]`).
13141324
Index(P<Expr>, P<Expr>),
1315-
/// A range (e.g., `1..2`, `1..`, `..2`, `1..=2`, `..=2`).
1325+
/// A range (e.g., `1..2`, `1..`, `..2`, `1..=2`, `..=2`; and `..` in destructuring assingment).
13161326
Range(Option<P<Expr>>, Option<P<Expr>>, RangeLimits),
13171327

13181328
/// Variable reference, possibly containing `::` and/or type
@@ -1340,9 +1350,8 @@ pub enum ExprKind {
13401350

13411351
/// A struct literal expression.
13421352
///
1343-
/// E.g., `Foo {x: 1, y: 2}`, or `Foo {x: 1, .. base}`,
1344-
/// where `base` is the `Option<Expr>`.
1345-
Struct(Path, Vec<Field>, Option<P<Expr>>),
1353+
/// E.g., `Foo {x: 1, y: 2}`, or `Foo {x: 1, .. rest}`.
1354+
Struct(Path, Vec<Field>, StructRest),
13461355

13471356
/// An array literal constructed from one repeated element.
13481357
///

compiler/rustc_ast/src/mut_visit.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,11 @@ pub fn noop_visit_expr<T: MutVisitor>(
12881288
ExprKind::Struct(path, fields, expr) => {
12891289
vis.visit_path(path);
12901290
fields.flat_map_in_place(|field| vis.flat_map_field(field));
1291-
visit_opt(expr, |expr| vis.visit_expr(expr));
1291+
match expr {
1292+
StructRest::Base(expr) => vis.visit_expr(expr),
1293+
StructRest::Rest(_span) => {}
1294+
StructRest::None => {}
1295+
}
12921296
}
12931297
ExprKind::Paren(expr) => {
12941298
vis.visit_expr(expr);

compiler/rustc_ast/src/visit.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,11 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
719719
ExprKind::Struct(ref path, ref fields, ref optional_base) => {
720720
visitor.visit_path(path, expression.id);
721721
walk_list!(visitor, visit_field, fields);
722-
walk_list!(visitor, visit_expr, optional_base);
722+
match optional_base {
723+
StructRest::Base(expr) => visitor.visit_expr(expr),
724+
StructRest::Rest(_span) => {}
725+
StructRest::None => {}
726+
}
723727
}
724728
ExprKind::Tup(ref subexpressions) => {
725729
walk_list!(visitor, visit_expr, subexpressions);

compiler/rustc_ast_lowering/src/expr.rs

+119-7
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,18 @@ impl<'hir> LoweringContext<'_, 'hir> {
187187
}
188188
ExprKind::InlineAsm(ref asm) => self.lower_expr_asm(e.span, asm),
189189
ExprKind::LlvmInlineAsm(ref asm) => self.lower_expr_llvm_asm(asm),
190-
ExprKind::Struct(ref path, ref fields, ref maybe_expr) => {
191-
let maybe_expr = maybe_expr.as_ref().map(|x| self.lower_expr(x));
190+
ExprKind::Struct(ref path, ref fields, ref rest) => {
191+
let rest = match rest {
192+
StructRest::Base(e) => Some(self.lower_expr(e)),
193+
StructRest::Rest(sp) => {
194+
self.sess
195+
.struct_span_err(*sp, "base expression required after `..`")
196+
.span_label(*sp, "add a base expression here")
197+
.emit();
198+
Some(&*self.arena.alloc(self.expr_err(*sp)))
199+
}
200+
StructRest::None => None,
201+
};
192202
hir::ExprKind::Struct(
193203
self.arena.alloc(self.lower_qpath(
194204
e.id,
@@ -198,7 +208,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
198208
ImplTraitContext::disallowed(),
199209
)),
200210
self.arena.alloc_from_iter(fields.iter().map(|x| self.lower_field(x))),
201-
maybe_expr,
211+
rest,
202212
)
203213
}
204214
ExprKind::Yield(ref opt_expr) => self.lower_expr_yield(e.span, opt_expr.as_deref()),
@@ -851,20 +861,22 @@ impl<'hir> LoweringContext<'_, 'hir> {
851861
whole_span: Span,
852862
) -> hir::ExprKind<'hir> {
853863
// Return early in case of an ordinary assignment.
854-
fn is_ordinary(lhs: &Expr) -> bool {
864+
fn is_ordinary(lower_ctx: &mut LoweringContext<'_, '_>, lhs: &Expr) -> bool {
855865
match &lhs.kind {
856-
ExprKind::Tup(..) => false,
866+
ExprKind::Array(..) | ExprKind::Struct(..) | ExprKind::Tup(..) => false,
867+
// Check for tuple struct constructor.
868+
ExprKind::Call(callee, ..) => lower_ctx.extract_tuple_struct_path(callee).is_none(),
857869
ExprKind::Paren(e) => {
858870
match e.kind {
859871
// We special-case `(..)` for consistency with patterns.
860872
ExprKind::Range(None, None, RangeLimits::HalfOpen) => false,
861-
_ => is_ordinary(e),
873+
_ => is_ordinary(lower_ctx, e),
862874
}
863875
}
864876
_ => true,
865877
}
866878
}
867-
if is_ordinary(lhs) {
879+
if is_ordinary(self, lhs) {
868880
return hir::ExprKind::Assign(self.lower_expr(lhs), self.lower_expr(rhs), eq_sign_span);
869881
}
870882
if !self.sess.features_untracked().destructuring_assignment {
@@ -902,6 +914,26 @@ impl<'hir> LoweringContext<'_, 'hir> {
902914
hir::ExprKind::Block(&self.block_all(whole_span, stmts, None), None)
903915
}
904916

917+
/// If the given expression is a path to a tuple struct, returns that path.
918+
/// It is not a complete check, but just tries to reject most paths early
919+
/// if they are not tuple structs.
920+
/// Type checking will take care of the full validation later.
921+
fn extract_tuple_struct_path<'a>(&mut self, expr: &'a Expr) -> Option<&'a Path> {
922+
// For tuple struct destructuring, it must be a non-qualified path (like in patterns).
923+
if let ExprKind::Path(None, path) = &expr.kind {
924+
// Does the path resolves to something disallowed in a tuple struct/variant pattern?
925+
if let Some(partial_res) = self.resolver.get_partial_res(expr.id) {
926+
if partial_res.unresolved_segments() == 0
927+
&& !partial_res.base_res().expected_in_tuple_struct_pat()
928+
{
929+
return None;
930+
}
931+
}
932+
return Some(path);
933+
}
934+
None
935+
}
936+
905937
/// Convert the LHS of a destructuring assignment to a pattern.
906938
/// Each sub-assignment is recorded in `assignments`.
907939
fn destructure_assign(
@@ -911,6 +943,86 @@ impl<'hir> LoweringContext<'_, 'hir> {
911943
assignments: &mut Vec<hir::Stmt<'hir>>,
912944
) -> &'hir hir::Pat<'hir> {
913945
match &lhs.kind {
946+
// Slice patterns.
947+
ExprKind::Array(elements) => {
948+
let (pats, rest) =
949+
self.destructure_sequence(elements, "slice", eq_sign_span, assignments);
950+
let slice_pat = if let Some((i, span)) = rest {
951+
let (before, after) = pats.split_at(i);
952+
hir::PatKind::Slice(
953+
before,
954+
Some(self.pat_without_dbm(span, hir::PatKind::Wild)),
955+
after,
956+
)
957+
} else {
958+
hir::PatKind::Slice(pats, None, &[])
959+
};
960+
return self.pat_without_dbm(lhs.span, slice_pat);
961+
}
962+
// Tuple structs.
963+
ExprKind::Call(callee, args) => {
964+
if let Some(path) = self.extract_tuple_struct_path(callee) {
965+
let (pats, rest) = self.destructure_sequence(
966+
args,
967+
"tuple struct or variant",
968+
eq_sign_span,
969+
assignments,
970+
);
971+
let qpath = self.lower_qpath(
972+
callee.id,
973+
&None,
974+
path,
975+
ParamMode::Optional,
976+
ImplTraitContext::disallowed(),
977+
);
978+
// Destructure like a tuple struct.
979+
let tuple_struct_pat =
980+
hir::PatKind::TupleStruct(qpath, pats, rest.map(|r| r.0));
981+
return self.pat_without_dbm(lhs.span, tuple_struct_pat);
982+
}
983+
}
984+
// Structs.
985+
ExprKind::Struct(path, fields, rest) => {
986+
let field_pats = self.arena.alloc_from_iter(fields.iter().map(|f| {
987+
let pat = self.destructure_assign(&f.expr, eq_sign_span, assignments);
988+
hir::FieldPat {
989+
hir_id: self.next_id(),
990+
ident: f.ident,
991+
pat,
992+
is_shorthand: f.is_shorthand,
993+
span: f.span,
994+
}
995+
}));
996+
let qpath = self.lower_qpath(
997+
lhs.id,
998+
&None,
999+
path,
1000+
ParamMode::Optional,
1001+
ImplTraitContext::disallowed(),
1002+
);
1003+
let fields_omitted = match rest {
1004+
StructRest::Base(e) => {
1005+
self.sess
1006+
.struct_span_err(
1007+
e.span,
1008+
"functional record updates are not allowed in destructuring \
1009+
assignments",
1010+
)
1011+
.span_suggestion(
1012+
e.span,
1013+
"consider removing the trailing pattern",
1014+
String::new(),
1015+
rustc_errors::Applicability::MachineApplicable,
1016+
)
1017+
.emit();
1018+
true
1019+
}
1020+
StructRest::Rest(_) => true,
1021+
StructRest::None => false,
1022+
};
1023+
let struct_pat = hir::PatKind::Struct(qpath, field_pats, fields_omitted);
1024+
return self.pat_without_dbm(lhs.span, struct_pat);
1025+
}
9141026
// Tuples.
9151027
ExprKind::Tup(elements) => {
9161028
let (pats, rest) =

compiler/rustc_ast_passes/src/feature_gate.rs

+1
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
630630
gate_all!(const_trait_impl, "const trait impls are experimental");
631631
gate_all!(half_open_range_patterns, "half-open range patterns are unstable");
632632
gate_all!(inline_const, "inline-const is experimental");
633+
gate_all!(destructuring_assignment, "destructuring assignments are unstable");
633634

634635
// All uses of `gate_all!` below this point were added in #65742,
635636
// and subsequently disabled (with the non-early gating readded).

compiler/rustc_ast_pretty/src/pprust/state.rs

+10-11
Original file line numberDiff line numberDiff line change
@@ -1729,7 +1729,7 @@ impl<'a> State<'a> {
17291729
&mut self,
17301730
path: &ast::Path,
17311731
fields: &[ast::Field],
1732-
wth: &Option<P<ast::Expr>>,
1732+
rest: &ast::StructRest,
17331733
attrs: &[ast::Attribute],
17341734
) {
17351735
self.print_path(path, true, 0);
@@ -1750,22 +1750,21 @@ impl<'a> State<'a> {
17501750
},
17511751
|f| f.span,
17521752
);
1753-
match *wth {
1754-
Some(ref expr) => {
1753+
match rest {
1754+
ast::StructRest::Base(_) | ast::StructRest::Rest(_) => {
17551755
self.ibox(INDENT_UNIT);
17561756
if !fields.is_empty() {
17571757
self.s.word(",");
17581758
self.s.space();
17591759
}
17601760
self.s.word("..");
1761-
self.print_expr(expr);
1762-
self.end();
1763-
}
1764-
_ => {
1765-
if !fields.is_empty() {
1766-
self.s.word(",")
1761+
if let ast::StructRest::Base(ref expr) = *rest {
1762+
self.print_expr(expr);
17671763
}
1764+
self.end();
17681765
}
1766+
ast::StructRest::None if !fields.is_empty() => self.s.word(","),
1767+
_ => {}
17691768
}
17701769
self.s.word("}");
17711770
}
@@ -1891,8 +1890,8 @@ impl<'a> State<'a> {
18911890
ast::ExprKind::Repeat(ref element, ref count) => {
18921891
self.print_expr_repeat(element, count, attrs);
18931892
}
1894-
ast::ExprKind::Struct(ref path, ref fields, ref wth) => {
1895-
self.print_expr_struct(path, &fields[..], wth, attrs);
1893+
ast::ExprKind::Struct(ref path, ref fields, ref rest) => {
1894+
self.print_expr_struct(path, &fields[..], rest, attrs);
18961895
}
18971896
ast::ExprKind::Tup(ref exprs) => {
18981897
self.print_expr_tup(&exprs[..], attrs);

compiler/rustc_expand/src/build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ impl<'a> ExtCtxt<'a> {
298298
path: ast::Path,
299299
fields: Vec<ast::Field>,
300300
) -> P<ast::Expr> {
301-
self.expr(span, ast::ExprKind::Struct(path, fields, None))
301+
self.expr(span, ast::ExprKind::Struct(path, fields, ast::StructRest::None))
302302
}
303303
pub fn expr_struct_ident(
304304
&self,

compiler/rustc_hir/src/def.rs

+5
Original file line numberDiff line numberDiff line change
@@ -484,4 +484,9 @@ impl<Id> Res<Id> {
484484
pub fn matches_ns(&self, ns: Namespace) -> bool {
485485
self.ns().map_or(true, |actual_ns| actual_ns == ns)
486486
}
487+
488+
/// Returns whether such a resolved path can occur in a tuple struct/variant pattern
489+
pub fn expected_in_tuple_struct_pat(&self) -> bool {
490+
matches!(self, Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) | Res::SelfCtor(..))
491+
}
487492
}

compiler/rustc_parse/src/parser/expr.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -2087,7 +2087,7 @@ impl<'a> Parser<'a> {
20872087
recover: bool,
20882088
) -> PResult<'a, P<Expr>> {
20892089
let mut fields = Vec::new();
2090-
let mut base = None;
2090+
let mut base = ast::StructRest::None;
20912091
let mut recover_async = false;
20922092

20932093
attrs.extend(self.parse_inner_attributes()?);
@@ -2102,8 +2102,14 @@ impl<'a> Parser<'a> {
21022102
while self.token != token::CloseDelim(token::Brace) {
21032103
if self.eat(&token::DotDot) {
21042104
let exp_span = self.prev_token.span;
2105+
// We permit `.. }` on the left-hand side of a destructuring assignment.
2106+
if self.check(&token::CloseDelim(token::Brace)) {
2107+
self.sess.gated_spans.gate(sym::destructuring_assignment, self.prev_token.span);
2108+
base = ast::StructRest::Rest(self.prev_token.span.shrink_to_hi());
2109+
break;
2110+
}
21052111
match self.parse_expr() {
2106-
Ok(e) => base = Some(e),
2112+
Ok(e) => base = ast::StructRest::Base(e),
21072113
Err(mut e) if recover => {
21082114
e.emit();
21092115
self.recover_stmt();

compiler/rustc_resolve/src/late.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,7 @@ impl<'a> PathSource<'a> {
298298
_,
299299
)
300300
| Res::SelfCtor(..)),
301-
PathSource::TupleStruct(..) => {
302-
matches!(res, Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) | Res::SelfCtor(..))
303-
}
301+
PathSource::TupleStruct(..) => res.expected_in_tuple_struct_pat(),
304302
PathSource::Struct => matches!(res, Res::Def(
305303
DefKind::Struct
306304
| DefKind::Union

compiler/rustc_save_analysis/src/dump_visitor.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,7 @@ impl<'tcx> DumpVisitor<'tcx> {
816816
path: &'tcx hir::QPath<'tcx>,
817817
fields: &'tcx [hir::Field<'tcx>],
818818
variant: &'tcx ty::VariantDef,
819-
base: Option<&'tcx hir::Expr<'tcx>>,
819+
rest: Option<&'tcx hir::Expr<'tcx>>,
820820
) {
821821
if let Some(struct_lit_data) = self.save_ctxt.get_expr_data(ex) {
822822
if let hir::QPath::Resolved(_, path) = path {
@@ -836,7 +836,9 @@ impl<'tcx> DumpVisitor<'tcx> {
836836
}
837837
}
838838

839-
walk_list!(self, visit_expr, base);
839+
if let Some(base) = rest {
840+
self.visit_expr(&base);
841+
}
840842
}
841843

842844
fn process_method_call(
@@ -1399,7 +1401,7 @@ impl<'tcx> Visitor<'tcx> for DumpVisitor<'tcx> {
13991401
debug!("visit_expr {:?}", ex.kind);
14001402
self.process_macro_use(ex.span);
14011403
match ex.kind {
1402-
hir::ExprKind::Struct(ref path, ref fields, ref base) => {
1404+
hir::ExprKind::Struct(ref path, ref fields, ref rest) => {
14031405
let hir_expr = self.save_ctxt.tcx.hir().expect_expr(ex.hir_id);
14041406
let adt = match self.save_ctxt.typeck_results().expr_ty_opt(&hir_expr) {
14051407
Some(ty) if ty.ty_adt_def().is_some() => ty.ty_adt_def().unwrap(),
@@ -1409,7 +1411,7 @@ impl<'tcx> Visitor<'tcx> for DumpVisitor<'tcx> {
14091411
}
14101412
};
14111413
let res = self.save_ctxt.get_path_res(hir_expr.hir_id);
1412-
self.process_struct_lit(ex, path, fields, adt.variant_of_res(res), *base)
1414+
self.process_struct_lit(ex, path, fields, adt.variant_of_res(res), *rest)
14131415
}
14141416
hir::ExprKind::MethodCall(ref seg, _, args, _) => {
14151417
self.process_method_call(ex, seg, args)

0 commit comments

Comments
 (0)