Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2742,6 +2742,36 @@ impl<'a> Parser<'a> {
let (mut cond, _) =
self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, attrs)?;

// Support a more natural "let chain" syntax using commas:
//
// if let Some(x) = a, let None = b { ... }
//
// This is parsed as an `&&`-chain in the AST (same as `..., && let ...`), which keeps
// semantics unchanged (short-circuiting, temporary lifetimes/drop order) and reuses the
// existing let-chain validation and lowering logic.
//
// We intentionally only treat `, let ...` at the top level of a condition as chain
// separators. This avoids ambiguity with tuple expressions and keeps the change local to
// `if`/`while` condition parsing.
while self.token == token::Comma
&& self.look_ahead(1, |t| t.is_keyword(kw::Let))
{
let comma_span = self.token.span;
self.bump(); // Eat `,`

let next_attrs = self.parse_outer_attributes()?;
let (next, _) = self.parse_expr_res(
Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET,
next_attrs,
)?;

let span = self.mk_expr_sp(&cond, cond.span, comma_span, next.span);
cond = self.mk_expr(
span,
ExprKind::Binary(Spanned { span: comma_span, node: BinOpKind::And }, cond, next),
);
}

CondChecker::new(self, let_chains_policy).visit_expr(&mut cond);

Ok(cond)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//@ edition: 2021

#![allow(irrefutable_let_patterns)]

fn _if() {
if let 0 = 1, let 1 = 2 {}
//~^ ERROR let chains are only allowed in Rust 2024 or later
//~| ERROR let chains are only allowed in Rust 2024 or later

if true, let 0 = 1 {}
//~^ ERROR let chains are only allowed in Rust 2024 or later
}

fn _while() {
while let 0 = 1, let 1 = 2 {}
//~^ ERROR let chains are only allowed in Rust 2024 or later
//~| ERROR let chains are only allowed in Rust 2024 or later

while true, let 0 = 1 {}
//~^ ERROR let chains are only allowed in Rust 2024 or later
}

fn main() {}

33 changes: 33 additions & 0 deletions tests/ui/rfcs/rfc-2497-if-let-chains/comma-let-chains.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//@ run-pass
//@ edition: 2024

fn main() {
let a = Some(1);
let b: Option<i32> = None;

// Basic: multiple `let` conditions chained with `, let ...`.
let mut hit = false;
if let Some(x) = a, let None = b {
hit = true;
assert_eq!(x, 1);
}
assert!(hit);

// Short-circuit: the second `let` must not be evaluated if the first fails.
let mut side_effect = 0;
let c: Option<i32> = None;
let d = Some(2);
if let Some(_x) = c, let Some(_y) = { side_effect += 1; d } {
unreachable!("first `let` fails, so the second must not run");
}
assert_eq!(side_effect, 0);

// Mix a boolean with `, let ...` in a `while` condition.
let mut counter = 0;
while counter < 1, let Some(y) = a {
assert_eq!(y, 1);
counter += 1;
}
assert_eq!(counter, 1);
}