diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 35b987cf50fa2..f64537e41cca6 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -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) diff --git a/tests/ui/rfcs/rfc-2497-if-let-chains/comma-let-chains-edition-gate.rs b/tests/ui/rfcs/rfc-2497-if-let-chains/comma-let-chains-edition-gate.rs new file mode 100644 index 0000000000000..924b315fb8d62 --- /dev/null +++ b/tests/ui/rfcs/rfc-2497-if-let-chains/comma-let-chains-edition-gate.rs @@ -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() {} + diff --git a/tests/ui/rfcs/rfc-2497-if-let-chains/comma-let-chains.rs b/tests/ui/rfcs/rfc-2497-if-let-chains/comma-let-chains.rs new file mode 100644 index 0000000000000..ccf39048f4458 --- /dev/null +++ b/tests/ui/rfcs/rfc-2497-if-let-chains/comma-let-chains.rs @@ -0,0 +1,33 @@ +//@ run-pass +//@ edition: 2024 + +fn main() { + let a = Some(1); + let b: Option = 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 = 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); +} +