Skip to content

Commit f66af28

Browse files
Rollup merge of #79016 - fanzier:underscore-expressions, r=petrochenkov
Make `_` an expression, to discard values in destructuring assignments This is the third and final step towards implementing destructuring assignment (RFC: rust-lang/rfcs#2909, tracking issue: #71126). This PR is the third and final part of #71156, which was split up to allow for easier review. With this PR, an underscore `_` is parsed as an expression but is allowed *only* on the left-hand side of a destructuring assignment. There it simply discards a value, similarly to the wildcard `_` in patterns. For instance, ```rust (a, _) = (1, 2) ``` will simply assign 1 to `a` and discard the 2. Note that for consistency, ``` _ = foo ``` is also allowed and equivalent to just `foo`. Thanks to ````@varkor```` who helped with the implementation, particularly around pre-expansion gating. r? ````@petrochenkov````
2 parents f32191f + 8cf3564 commit f66af28

27 files changed

+243
-45
lines changed

compiler/rustc_ast/src/ast.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,7 @@ impl Expr {
11921192
ExprKind::Field(..) => ExprPrecedence::Field,
11931193
ExprKind::Index(..) => ExprPrecedence::Index,
11941194
ExprKind::Range(..) => ExprPrecedence::Range,
1195+
ExprKind::Underscore => ExprPrecedence::Path,
11951196
ExprKind::Path(..) => ExprPrecedence::Path,
11961197
ExprKind::AddrOf(..) => ExprPrecedence::AddrOf,
11971198
ExprKind::Break(..) => ExprPrecedence::Break,
@@ -1324,6 +1325,8 @@ pub enum ExprKind {
13241325
Index(P<Expr>, P<Expr>),
13251326
/// A range (e.g., `1..2`, `1..`, `..2`, `1..=2`, `..=2`; and `..` in destructuring assingment).
13261327
Range(Option<P<Expr>>, Option<P<Expr>>, RangeLimits),
1328+
/// An underscore, used in destructuring assignment to ignore a value.
1329+
Underscore,
13271330

13281331
/// Variable reference, possibly containing `::` and/or type
13291332
/// parameters (e.g., `foo::bar::<baz>`).

compiler/rustc_ast/src/mut_visit.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
12321232
visit_opt(e1, |e1| vis.visit_expr(e1));
12331233
visit_opt(e2, |e2| vis.visit_expr(e2));
12341234
}
1235+
ExprKind::Underscore => {}
12351236
ExprKind::Path(qself, path) => {
12361237
vis.visit_qself(qself);
12371238
vis.visit_path(path);

compiler/rustc_ast/src/visit.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
806806
walk_list!(visitor, visit_expr, start);
807807
walk_list!(visitor, visit_expr, end);
808808
}
809+
ExprKind::Underscore => {}
809810
ExprKind::Path(ref maybe_qself, ref path) => {
810811
if let Some(ref qself) = *maybe_qself {
811812
visitor.visit_ty(&qself.ty);

compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
164164
ExprKind::Range(ref e1, ref e2, lims) => {
165165
self.lower_expr_range(e.span, e1.as_deref(), e2.as_deref(), lims)
166166
}
167+
ExprKind::Underscore => {
168+
self.sess
169+
.struct_span_err(
170+
e.span,
171+
"in expressions, `_` can only be used on the left-hand side of an assignment",
172+
)
173+
.span_label(e.span, "`_` not allowed here")
174+
.emit();
175+
hir::ExprKind::Err
176+
}
167177
ExprKind::Path(ref qself, ref path) => {
168178
let qpath = self.lower_qpath(
169179
e.id,
@@ -863,7 +873,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
863873
// Return early in case of an ordinary assignment.
864874
fn is_ordinary(lower_ctx: &mut LoweringContext<'_, '_>, lhs: &Expr) -> bool {
865875
match &lhs.kind {
866-
ExprKind::Array(..) | ExprKind::Struct(..) | ExprKind::Tup(..) => false,
876+
ExprKind::Array(..)
877+
| ExprKind::Struct(..)
878+
| ExprKind::Tup(..)
879+
| ExprKind::Underscore => false,
867880
// Check for tuple struct constructor.
868881
ExprKind::Call(callee, ..) => lower_ctx.extract_tuple_struct_path(callee).is_none(),
869882
ExprKind::Paren(e) => {
@@ -943,6 +956,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
943956
assignments: &mut Vec<hir::Stmt<'hir>>,
944957
) -> &'hir hir::Pat<'hir> {
945958
match &lhs.kind {
959+
// Underscore pattern.
960+
ExprKind::Underscore => {
961+
return self.pat_without_dbm(lhs.span, hir::PatKind::Wild);
962+
}
946963
// Slice patterns.
947964
ExprKind::Array(elements) => {
948965
let (pats, rest) =

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,11 @@ 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");
633+
if sess.parse_sess.span_diagnostic.err_count() == 0 {
634+
// Errors for `destructuring_assignment` can get quite noisy, especially where `_` is
635+
// involved, so we only emit errors where there are no other parsing errors.
636+
gate_all!(destructuring_assignment, "destructuring assignments are unstable");
637+
}
634638

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

compiler/rustc_ast_pretty/src/pprust/state.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,7 @@ impl<'a> State<'a> {
20672067
self.print_expr_maybe_paren(e, fake_prec);
20682068
}
20692069
}
2070+
ast::ExprKind::Underscore => self.s.word("_"),
20702071
ast::ExprKind::Path(None, ref path) => self.print_path(path, true, 0),
20712072
ast::ExprKind::Path(Some(ref qself), ref path) => self.print_qpath(path, qself, true),
20722073
ast::ExprKind::Break(opt_label, ref opt_expr) => {

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,9 @@ impl<'a> Parser<'a> {
10891089
self.parse_yield_expr(attrs)
10901090
} else if self.eat_keyword(kw::Let) {
10911091
self.parse_let_expr(attrs)
1092+
} else if self.eat_keyword(kw::Underscore) {
1093+
self.sess.gated_spans.gate(sym::destructuring_assignment, self.prev_token.span);
1094+
Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore, attrs))
10921095
} else if !self.unclosed_delims.is_empty() && self.check(&token::Semi) {
10931096
// Don't complain about bare semicolons after unclosed braces
10941097
// recovery in order to keep the error count down. Fixing the

src/test/ui/cross/cross-file-errors/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ mod underscore;
33

44
fn main() {
55
underscore!();
6-
//~^ ERROR expected expression, found reserved identifier `_`
6+
//~^ ERROR `_` can only be used on the left-hand side of an assignment
7+
//~| ERROR destructuring assignments are unstable
78
}
Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
1-
error: expected expression, found reserved identifier `_`
1+
error[E0658]: destructuring assignments are unstable
22
--> $DIR/underscore.rs:8:9
33
|
44
LL | _
5-
| ^ expected expression
5+
| ^
66
|
77
::: $DIR/main.rs:5:5
88
|
99
LL | underscore!();
1010
| -------------- in this macro invocation
1111
|
12+
= note: see issue #71126 <https://github.com/rust-lang/rust/issues/71126> for more information
13+
= help: add `#![feature(destructuring_assignment)]` to the crate attributes to enable
1214
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
1315

14-
error: aborting due to previous error
16+
error: in expressions, `_` can only be used on the left-hand side of an assignment
17+
--> $DIR/underscore.rs:8:9
18+
|
19+
LL | _
20+
| ^ `_` not allowed here
21+
|
22+
::: $DIR/main.rs:5:5
23+
|
24+
LL | underscore!();
25+
| -------------- in this macro invocation
26+
|
27+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
28+
29+
error: aborting due to 2 previous errors
1530

31+
For more information about this error, try `rustc --explain E0658`.

src/test/ui/destructuring-assignment/nested_destructure.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ fn main() {
1414
Struct { a: TupleStruct((a, b), c), b: [d] } =
1515
Struct { a: TupleStruct((0, 1), 2), b: [3] };
1616
assert_eq!((a, b, c, d), (0, 1, 2, 3));
17+
18+
// unnested underscore: just discard
19+
_ = 1;
1720
}

0 commit comments

Comments
 (0)