Rollup merge of #78836 - fanzier:struct-and-slice-destructuring, r=petrochenkov
Implement destructuring assignment for structs and slices This is the second step towards implementing destructuring assignment (RFC: rust-lang/rfcs#2909, tracking issue: #71126). This PR is the second part of #71156, which was split up to allow for easier review. Note that the first PR (#78748) is not merged yet, so it is included as the first commit in this one. I thought this would allow the review to start earlier because I have some time this weekend to respond to reviews. If ``@petrochenkov`` prefers to wait until the first PR is merged, I totally understand, of course. This PR implements destructuring assignment for (tuple) structs and slices. In order to do this, the following *parser change* was necessary: struct expressions are not required to have a base expression, i.e. `Struct { a: 1, .. }` becomes legal (in order to act like a struct pattern). Unfortunately, this PR slightly regresses the diagnostics implemented in #77283. However, it is only a missing help message in `src/test/ui/issues/issue-77218.rs`. Other instances of this diagnostic are not affected. Since I don't exactly understand how this help message works and how to fix it yet, I was hoping it's OK to regress this temporarily and fix it in a follow-up PR. Thanks to ``@varkor`` who helped with the implementation, particularly around the struct rest changes. r? ``@petrochenkov``
This commit is contained in:
commit
755dd14e00
32 changed files with 619 additions and 108 deletions
|
@ -187,8 +187,18 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
}
|
||||
ExprKind::InlineAsm(ref asm) => self.lower_expr_asm(e.span, asm),
|
||||
ExprKind::LlvmInlineAsm(ref asm) => self.lower_expr_llvm_asm(asm),
|
||||
ExprKind::Struct(ref path, ref fields, ref maybe_expr) => {
|
||||
let maybe_expr = maybe_expr.as_ref().map(|x| self.lower_expr(x));
|
||||
ExprKind::Struct(ref path, ref fields, ref rest) => {
|
||||
let rest = match rest {
|
||||
StructRest::Base(e) => Some(self.lower_expr(e)),
|
||||
StructRest::Rest(sp) => {
|
||||
self.sess
|
||||
.struct_span_err(*sp, "base expression required after `..`")
|
||||
.span_label(*sp, "add a base expression here")
|
||||
.emit();
|
||||
Some(&*self.arena.alloc(self.expr_err(*sp)))
|
||||
}
|
||||
StructRest::None => None,
|
||||
};
|
||||
hir::ExprKind::Struct(
|
||||
self.arena.alloc(self.lower_qpath(
|
||||
e.id,
|
||||
|
@ -198,7 +208,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
ImplTraitContext::disallowed(),
|
||||
)),
|
||||
self.arena.alloc_from_iter(fields.iter().map(|x| self.lower_field(x))),
|
||||
maybe_expr,
|
||||
rest,
|
||||
)
|
||||
}
|
||||
ExprKind::Yield(ref opt_expr) => self.lower_expr_yield(e.span, opt_expr.as_deref()),
|
||||
|
@ -851,20 +861,22 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
whole_span: Span,
|
||||
) -> hir::ExprKind<'hir> {
|
||||
// Return early in case of an ordinary assignment.
|
||||
fn is_ordinary(lhs: &Expr) -> bool {
|
||||
fn is_ordinary(lower_ctx: &mut LoweringContext<'_, '_>, lhs: &Expr) -> bool {
|
||||
match &lhs.kind {
|
||||
ExprKind::Tup(..) => false,
|
||||
ExprKind::Array(..) | ExprKind::Struct(..) | ExprKind::Tup(..) => false,
|
||||
// Check for tuple struct constructor.
|
||||
ExprKind::Call(callee, ..) => lower_ctx.extract_tuple_struct_path(callee).is_none(),
|
||||
ExprKind::Paren(e) => {
|
||||
match e.kind {
|
||||
// We special-case `(..)` for consistency with patterns.
|
||||
ExprKind::Range(None, None, RangeLimits::HalfOpen) => false,
|
||||
_ => is_ordinary(e),
|
||||
_ => is_ordinary(lower_ctx, e),
|
||||
}
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
if is_ordinary(lhs) {
|
||||
if is_ordinary(self, lhs) {
|
||||
return hir::ExprKind::Assign(self.lower_expr(lhs), self.lower_expr(rhs), eq_sign_span);
|
||||
}
|
||||
if !self.sess.features_untracked().destructuring_assignment {
|
||||
|
@ -902,6 +914,26 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
hir::ExprKind::Block(&self.block_all(whole_span, stmts, None), None)
|
||||
}
|
||||
|
||||
/// If the given expression is a path to a tuple struct, returns that path.
|
||||
/// It is not a complete check, but just tries to reject most paths early
|
||||
/// if they are not tuple structs.
|
||||
/// Type checking will take care of the full validation later.
|
||||
fn extract_tuple_struct_path<'a>(&mut self, expr: &'a Expr) -> Option<&'a Path> {
|
||||
// For tuple struct destructuring, it must be a non-qualified path (like in patterns).
|
||||
if let ExprKind::Path(None, path) = &expr.kind {
|
||||
// Does the path resolves to something disallowed in a tuple struct/variant pattern?
|
||||
if let Some(partial_res) = self.resolver.get_partial_res(expr.id) {
|
||||
if partial_res.unresolved_segments() == 0
|
||||
&& !partial_res.base_res().expected_in_tuple_struct_pat()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
return Some(path);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Convert the LHS of a destructuring assignment to a pattern.
|
||||
/// Each sub-assignment is recorded in `assignments`.
|
||||
fn destructure_assign(
|
||||
|
@ -911,6 +943,86 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
assignments: &mut Vec<hir::Stmt<'hir>>,
|
||||
) -> &'hir hir::Pat<'hir> {
|
||||
match &lhs.kind {
|
||||
// Slice patterns.
|
||||
ExprKind::Array(elements) => {
|
||||
let (pats, rest) =
|
||||
self.destructure_sequence(elements, "slice", eq_sign_span, assignments);
|
||||
let slice_pat = if let Some((i, span)) = rest {
|
||||
let (before, after) = pats.split_at(i);
|
||||
hir::PatKind::Slice(
|
||||
before,
|
||||
Some(self.pat_without_dbm(span, hir::PatKind::Wild)),
|
||||
after,
|
||||
)
|
||||
} else {
|
||||
hir::PatKind::Slice(pats, None, &[])
|
||||
};
|
||||
return self.pat_without_dbm(lhs.span, slice_pat);
|
||||
}
|
||||
// Tuple structs.
|
||||
ExprKind::Call(callee, args) => {
|
||||
if let Some(path) = self.extract_tuple_struct_path(callee) {
|
||||
let (pats, rest) = self.destructure_sequence(
|
||||
args,
|
||||
"tuple struct or variant",
|
||||
eq_sign_span,
|
||||
assignments,
|
||||
);
|
||||
let qpath = self.lower_qpath(
|
||||
callee.id,
|
||||
&None,
|
||||
path,
|
||||
ParamMode::Optional,
|
||||
ImplTraitContext::disallowed(),
|
||||
);
|
||||
// Destructure like a tuple struct.
|
||||
let tuple_struct_pat =
|
||||
hir::PatKind::TupleStruct(qpath, pats, rest.map(|r| r.0));
|
||||
return self.pat_without_dbm(lhs.span, tuple_struct_pat);
|
||||
}
|
||||
}
|
||||
// Structs.
|
||||
ExprKind::Struct(path, fields, rest) => {
|
||||
let field_pats = self.arena.alloc_from_iter(fields.iter().map(|f| {
|
||||
let pat = self.destructure_assign(&f.expr, eq_sign_span, assignments);
|
||||
hir::FieldPat {
|
||||
hir_id: self.next_id(),
|
||||
ident: f.ident,
|
||||
pat,
|
||||
is_shorthand: f.is_shorthand,
|
||||
span: f.span,
|
||||
}
|
||||
}));
|
||||
let qpath = self.lower_qpath(
|
||||
lhs.id,
|
||||
&None,
|
||||
path,
|
||||
ParamMode::Optional,
|
||||
ImplTraitContext::disallowed(),
|
||||
);
|
||||
let fields_omitted = match rest {
|
||||
StructRest::Base(e) => {
|
||||
self.sess
|
||||
.struct_span_err(
|
||||
e.span,
|
||||
"functional record updates are not allowed in destructuring \
|
||||
assignments",
|
||||
)
|
||||
.span_suggestion(
|
||||
e.span,
|
||||
"consider removing the trailing pattern",
|
||||
String::new(),
|
||||
rustc_errors::Applicability::MachineApplicable,
|
||||
)
|
||||
.emit();
|
||||
true
|
||||
}
|
||||
StructRest::Rest(_) => true,
|
||||
StructRest::None => false,
|
||||
};
|
||||
let struct_pat = hir::PatKind::Struct(qpath, field_pats, fields_omitted);
|
||||
return self.pat_without_dbm(lhs.span, struct_pat);
|
||||
}
|
||||
// Tuples.
|
||||
ExprKind::Tup(elements) => {
|
||||
let (pats, rest) =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue