diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs index c87ab68693726..36b29ae6e6076 100644 --- a/src/librustc/hir/lowering.rs +++ b/src/librustc/hir/lowering.rs @@ -4344,53 +4344,147 @@ impl<'a> LoweringContext<'a> { let ohs = P(self.lower_expr(ohs)); hir::ExprKind::AddrOf(m, ohs) } - // More complicated than you might expect because the else branch - // might be `if let`. + ExprKind::Let(ref pats, ref scrutinee) => { + // If we got here, the `let` expression is not allowed. + self.sess + .struct_span_err(e.span, "`let` expressions are not supported here") + .note("only supported directly in conditions of `if`- and `while`-expressions") + .note("as well as when nested within `&&` and parenthesis in those conditions") + .emit(); + + // For better recovery, we emit: + // ``` + // match scrutinee { pats => true, _ => false } + // ``` + // While this doesn't fully match the user's intent, it has key advantages: + // 1. We can avoid using `abort_if_errors`. + // 2. We can typeck both `pats` and `scrutinee`. + // 3. `pats` is allowed to be refutable. + // 4. The return type of the block is `bool` which seems like what the user wanted. + let scrutinee = self.lower_expr(scrutinee); + let then_arm = { + let pats = pats.iter().map(|pat| self.lower_pat(pat)).collect(); + let expr = self.expr_bool(e.span, true); + self.arm(pats, P(expr)) + }; + let else_arm = { + let pats = hir_vec![self.pat_wild(e.span)]; + let expr = self.expr_bool(e.span, false); + self.arm(pats, P(expr)) + }; + hir::ExprKind::Match( + P(scrutinee), + vec![then_arm, else_arm].into(), + hir::MatchSource::Normal, + ) + } + // FIXME(#53667): handle lowering of && and parens. ExprKind::If(ref cond, ref then, ref else_opt) => { - // `true => then`: - let then_pat = self.pat_bool(e.span, true); - let then_blk = self.lower_block(then, false); - let then_expr = self.expr_block(then_blk, ThinVec::new()); - let then_arm = self.arm(hir_vec![then_pat], P(then_expr)); - // `_ => else_block` where `else_block` is `{}` if there's `None`: let else_pat = self.pat_wild(e.span); - let else_expr = match else_opt { - None => self.expr_block_empty(e.span), - Some(els) => match els.node { - ExprKind::IfLet(..) => { - // Wrap the `if let` expr in a block. - let els = self.lower_expr(els); - let blk = self.block_all(els.span, hir_vec![], Some(P(els))); - self.expr_block(P(blk), ThinVec::new()) - } - _ => self.lower_expr(els), - } + let (else_expr, contains_else_clause) = match else_opt { + None => (self.expr_block_empty(e.span), false), + Some(els) => (self.lower_expr(els), true), }; let else_arm = self.arm(hir_vec![else_pat], P(else_expr)); - // Lower condition: - let span_block = self.mark_span_with_reason(IfTemporary, cond.span, None); - let cond = self.lower_expr(cond); - // Wrap in a construct equivalent to `{ let _t = $cond; _t }` to preserve drop - // semantics since `if cond { ... }` don't let temporaries live outside of `cond`. - let cond = self.expr_drop_temps(span_block, P(cond), ThinVec::new()); + // Handle then + scrutinee: + let then_blk = self.lower_block(then, false); + let then_expr = self.expr_block(then_blk, ThinVec::new()); + let (then_pats, scrutinee, desugar) = match cond.node { + // ` => ` + ExprKind::Let(ref pats, ref scrutinee) => { + let scrutinee = self.lower_expr(scrutinee); + let pats = pats.iter().map(|pat| self.lower_pat(pat)).collect(); + let desugar = hir::MatchSource::IfLetDesugar { contains_else_clause }; + (pats, scrutinee, desugar) + } + // `true => then`: + _ => { + // Lower condition: + let cond = self.lower_expr(cond); + // Wrap in a construct equivalent to `{ let _t = $cond; _t }` + // to preserve drop semantics since `if cond { ... }` + // don't let temporaries live outside of `cond`. + let span_block = self.mark_span_with_reason(IfTemporary, cond.span, None); + // Wrap in a construct equivalent to `{ let _t = $cond; _t }` + // to preserve drop semantics since `if cond { ... }` does not + // let temporaries live outside of `cond`. + let cond = self.expr_drop_temps(span_block, P(cond), ThinVec::new()); + + let desugar = hir::MatchSource::IfDesugar { contains_else_clause }; + let pats = hir_vec![self.pat_bool(e.span, true)]; + (pats, cond, desugar) + } + }; + let then_arm = self.arm(then_pats, P(then_expr)); - hir::ExprKind::Match( - P(cond), - vec![then_arm, else_arm].into(), - hir::MatchSource::IfDesugar { - contains_else_clause: else_opt.is_some() - }, - ) + hir::ExprKind::Match(P(scrutinee), vec![then_arm, else_arm].into(), desugar) + } + // FIXME(#53667): handle lowering of && and parens. + ExprKind::While(ref cond, ref body, opt_label) => { + // Desugar `ExprWhileLet` + // from: `[opt_ident]: while let = ` + if let ExprKind::Let(ref pats, ref sub_expr) = cond.node { + // to: + // + // [opt_ident]: loop { + // match { + // => , + // _ => break + // } + // } + + // Note that the block AND the condition are evaluated in the loop scope. + // This is done to allow `break` from inside the condition of the loop. + let (body, break_expr, sub_expr) = self.with_loop_scope(e.id, |this| { + ( + this.lower_block(body, false), + this.expr_break(e.span, ThinVec::new()), + this.with_loop_condition_scope(|this| P(this.lower_expr(sub_expr))), + ) + }); + + // ` => ` + let pat_arm = { + let body_expr = P(self.expr_block(body, ThinVec::new())); + let pats = pats.iter().map(|pat| self.lower_pat(pat)).collect(); + self.arm(pats, body_expr) + }; + + // `_ => break` + let break_arm = { + let pat_under = self.pat_wild(e.span); + self.arm(hir_vec![pat_under], break_expr) + }; + + // `match { ... }` + let arms = hir_vec![pat_arm, break_arm]; + let match_expr = self.expr( + sub_expr.span, + hir::ExprKind::Match(sub_expr, arms, hir::MatchSource::WhileLetDesugar), + ThinVec::new(), + ); + + // `[opt_ident]: loop { ... }` + let loop_block = P(self.block_expr(P(match_expr))); + let loop_expr = hir::ExprKind::Loop( + loop_block, + self.lower_label(opt_label), + hir::LoopSource::WhileLet, + ); + // Add attributes to the outer returned expr node. + loop_expr + } else { + self.with_loop_scope(e.id, |this| { + hir::ExprKind::While( + this.with_loop_condition_scope(|this| P(this.lower_expr(cond))), + this.lower_block(body, false), + this.lower_label(opt_label), + ) + }) + } } - ExprKind::While(ref cond, ref body, opt_label) => self.with_loop_scope(e.id, |this| { - hir::ExprKind::While( - this.with_loop_condition_scope(|this| P(this.lower_expr(cond))), - this.lower_block(body, false), - this.lower_label(opt_label), - ) - }), ExprKind::Loop(ref body, opt_label) => self.with_loop_scope(e.id, |this| { hir::ExprKind::Loop( this.lower_block(body, false), @@ -4703,105 +4797,6 @@ impl<'a> LoweringContext<'a> { ExprKind::Err => hir::ExprKind::Err, - // Desugar `ExprIfLet` - // from: `if let = []` - ExprKind::IfLet(ref pats, ref sub_expr, ref body, ref else_opt) => { - // to: - // - // match { - // => , - // _ => [ | ()] - // } - - let mut arms = vec![]; - - // ` => ` - { - let body = self.lower_block(body, false); - let body_expr = P(self.expr_block(body, ThinVec::new())); - let pats = pats.iter().map(|pat| self.lower_pat(pat)).collect(); - arms.push(self.arm(pats, body_expr)); - } - - // _ => [|{}] - { - let wildcard_arm: Option<&Expr> = else_opt.as_ref().map(|p| &**p); - let wildcard_pattern = self.pat_wild(e.span); - let body = if let Some(else_expr) = wildcard_arm { - self.lower_expr(else_expr) - } else { - self.expr_block_empty(e.span) - }; - arms.push(self.arm(hir_vec![wildcard_pattern], P(body))); - } - - let contains_else_clause = else_opt.is_some(); - - let sub_expr = P(self.lower_expr(sub_expr)); - - hir::ExprKind::Match( - sub_expr, - arms.into(), - hir::MatchSource::IfLetDesugar { - contains_else_clause, - }, - ) - } - - // Desugar `ExprWhileLet` - // from: `[opt_ident]: while let = ` - ExprKind::WhileLet(ref pats, ref sub_expr, ref body, opt_label) => { - // to: - // - // [opt_ident]: loop { - // match { - // => , - // _ => break - // } - // } - - // Note that the block AND the condition are evaluated in the loop scope. - // This is done to allow `break` from inside the condition of the loop. - let (body, break_expr, sub_expr) = self.with_loop_scope(e.id, |this| { - ( - this.lower_block(body, false), - this.expr_break(e.span, ThinVec::new()), - this.with_loop_condition_scope(|this| P(this.lower_expr(sub_expr))), - ) - }); - - // ` => ` - let pat_arm = { - let body_expr = P(self.expr_block(body, ThinVec::new())); - let pats = pats.iter().map(|pat| self.lower_pat(pat)).collect(); - self.arm(pats, body_expr) - }; - - // `_ => break` - let break_arm = { - let pat_under = self.pat_wild(e.span); - self.arm(hir_vec![pat_under], break_expr) - }; - - // `match { ... }` - let arms = hir_vec![pat_arm, break_arm]; - let match_expr = self.expr( - sub_expr.span, - hir::ExprKind::Match(sub_expr, arms, hir::MatchSource::WhileLetDesugar), - ThinVec::new(), - ); - - // `[opt_ident]: loop { ... }` - let loop_block = P(self.block_expr(P(match_expr))); - let loop_expr = hir::ExprKind::Loop( - loop_block, - self.lower_label(opt_label), - hir::LoopSource::WhileLet, - ); - // Add attributes to the outer returned expr node. - loop_expr - } - // Desugar `ExprForLoop` // from: `[opt_ident]: for in ` ExprKind::ForLoop(ref pat, ref head, ref body, opt_label) => { @@ -5463,10 +5458,15 @@ impl<'a> LoweringContext<'a> { ) } + /// Constructs a `true` or `false` literal expression. + fn expr_bool(&mut self, span: Span, val: bool) -> hir::Expr { + let lit = Spanned { span, node: LitKind::Bool(val) }; + self.expr(span, hir::ExprKind::Lit(lit), ThinVec::new()) + } + /// Constructs a `true` or `false` literal pattern. fn pat_bool(&mut self, span: Span, val: bool) -> P { - let lit = Spanned { span, node: LitKind::Bool(val) }; - let expr = self.expr(span, hir::ExprKind::Lit(lit), ThinVec::new()); + let expr = self.expr_bool(span, val); self.pat(span, hir::PatKind::Lit(P(expr))) } diff --git a/src/librustc_lint/unused.rs b/src/librustc_lint/unused.rs index f84ce2f015edf..b5c5fc0608b95 100644 --- a/src/librustc_lint/unused.rs +++ b/src/librustc_lint/unused.rs @@ -324,20 +324,28 @@ impl UnusedParens { value: &ast::Expr, msg: &str, followed_by_block: bool) { - if let ast::ExprKind::Paren(ref inner) = value.node { - let necessary = followed_by_block && match inner.node { - ast::ExprKind::Ret(_) | ast::ExprKind::Break(..) => true, - _ => parser::contains_exterior_struct_lit(&inner), - }; - if !necessary { - let expr_text = if let Ok(snippet) = cx.sess().source_map() - .span_to_snippet(value.span) { - snippet - } else { - pprust::expr_to_string(value) - }; - Self::remove_outer_parens(cx, value.span, &expr_text, msg); + match value.node { + ast::ExprKind::Paren(ref inner) => { + let necessary = followed_by_block && match inner.node { + ast::ExprKind::Ret(_) | ast::ExprKind::Break(..) => true, + _ => parser::contains_exterior_struct_lit(&inner), + }; + if !necessary { + let expr_text = if let Ok(snippet) = cx.sess().source_map() + .span_to_snippet(value.span) { + snippet + } else { + pprust::expr_to_string(value) + }; + Self::remove_outer_parens(cx, value.span, &expr_text, msg); + } + } + ast::ExprKind::Let(_, ref expr) => { + // FIXME(#60336): Properly handle `let true = (false && true)` + // actually needing the parenthesis. + self.check_unused_parens_expr(cx, expr, "`let` head expression", followed_by_block); } + _ => {} } } @@ -399,8 +407,6 @@ impl EarlyLintPass for UnusedParens { let (value, msg, followed_by_block) = match e.node { If(ref cond, ..) => (cond, "`if` condition", true), While(ref cond, ..) => (cond, "`while` condition", true), - IfLet(_, ref cond, ..) => (cond, "`if let` head expression", true), - WhileLet(_, ref cond, ..) => (cond, "`while let` head expression", true), ForLoop(_, ref cond, ..) => (cond, "`for` head expression", true), Match(ref head, _) => (head, "`match` head expression", true), Ret(Some(ref value)) => (value, "`return` value", false), diff --git a/src/librustc_passes/ast_validation.rs b/src/librustc_passes/ast_validation.rs index 00b6db0ed9a12..2da9c5adf9baf 100644 --- a/src/librustc_passes/ast_validation.rs +++ b/src/librustc_passes/ast_validation.rs @@ -17,13 +17,11 @@ use syntax::attr; use syntax::feature_gate::is_builtin_attr; use syntax::source_map::Spanned; use syntax::symbol::{kw, sym}; -use syntax::ptr::P; use syntax::visit::{self, Visitor}; use syntax::{span_err, struct_span_err, walk_list}; use syntax_ext::proc_macro_decls::is_proc_macro_attr; use syntax_pos::{Span, MultiSpan}; use errors::{Applicability, FatalError}; -use log::debug; #[derive(Copy, Clone, Debug)] struct OuterImplTrait { @@ -319,54 +317,6 @@ impl<'a> AstValidator<'a> { } } - /// With eRFC 2497, we need to check whether an expression is ambiguous and warn or error - /// depending on the edition, this function handles that. - fn while_if_let_ambiguity(&self, expr: &P) { - if let Some((span, op_kind)) = self.while_if_let_expr_ambiguity(&expr) { - let mut err = self.err_handler().struct_span_err( - span, &format!("ambiguous use of `{}`", op_kind.to_string()) - ); - - err.note( - "this will be a error until the `let_chains` feature is stabilized" - ); - err.note( - "see rust-lang/rust#53668 for more information" - ); - - if let Ok(snippet) = self.session.source_map().span_to_snippet(span) { - err.span_suggestion( - span, "consider adding parentheses", format!("({})", snippet), - Applicability::MachineApplicable, - ); - } - - err.emit(); - } - } - - /// With eRFC 2497 adding if-let chains, there is a requirement that the parsing of - /// `&&` and `||` in a if-let statement be unambiguous. This function returns a span and - /// a `BinOpKind` (either `&&` or `||` depending on what was ambiguous) if it is determined - /// that the current expression parsed is ambiguous and will break in future. - fn while_if_let_expr_ambiguity(&self, expr: &P) -> Option<(Span, BinOpKind)> { - debug!("while_if_let_expr_ambiguity: expr.node: {:?}", expr.node); - match &expr.node { - ExprKind::Binary(op, _, _) if op.node == BinOpKind::And || op.node == BinOpKind::Or => { - Some((expr.span, op.node)) - }, - ExprKind::Range(ref lhs, ref rhs, _) => { - let lhs_ambiguous = lhs.as_ref() - .and_then(|lhs| self.while_if_let_expr_ambiguity(lhs)); - let rhs_ambiguous = rhs.as_ref() - .and_then(|rhs| self.while_if_let_expr_ambiguity(rhs)); - - lhs_ambiguous.or(rhs_ambiguous) - } - _ => None, - } - } - fn check_fn_decl(&self, fn_decl: &FnDecl) { fn_decl .inputs @@ -493,19 +443,17 @@ fn validate_generics_order<'a>( impl<'a> Visitor<'a> for AstValidator<'a> { fn visit_expr(&mut self, expr: &'a Expr) { - match expr.node { - ExprKind::Closure(_, _, _, ref fn_decl, _, _) => { + match &expr.node { + ExprKind::Closure(_, _, _, fn_decl, _, _) => { self.check_fn_decl(fn_decl); } - ExprKind::IfLet(_, ref expr, _, _) | ExprKind::WhileLet(_, ref expr, _, _) => - self.while_if_let_ambiguity(&expr), ExprKind::InlineAsm(..) if !self.session.target.target.options.allow_asm => { span_err!(self.session, expr.span, E0472, "asm! is unsupported on this target"); } _ => {} } - visit::walk_expr(self, expr) + visit::walk_expr(self, expr); } fn visit_ty(&mut self, ty: &'a Ty) { diff --git a/src/librustc_passes/lib.rs b/src/librustc_passes/lib.rs index bf2f7634e55a1..5f3d7159be6ce 100644 --- a/src/librustc_passes/lib.rs +++ b/src/librustc_passes/lib.rs @@ -8,6 +8,7 @@ #![feature(in_band_lifetimes)] #![feature(nll)] +#![feature(bind_by_move_pattern_guards)] #![feature(rustc_diagnostic_macros)] #![recursion_limit="256"] diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index 0fbd0666ad1c1..a9e4ffd5e7f02 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -485,8 +485,6 @@ type BindingMap = FxHashMap; #[derive(Copy, Clone, PartialEq, Eq, Debug)] enum PatternSource { Match, - IfLet, - WhileLet, Let, For, FnParam, @@ -496,8 +494,6 @@ impl PatternSource { fn descr(self) -> &'static str { match self { PatternSource::Match => "match binding", - PatternSource::IfLet => "if let binding", - PatternSource::WhileLet => "while let binding", PatternSource::Let => "let binding", PatternSource::For => "for binding", PatternSource::FnParam => "function parameter", @@ -3057,13 +3053,7 @@ impl<'a> Resolver<'a> { fn resolve_arm(&mut self, arm: &Arm) { self.ribs[ValueNS].push(Rib::new(NormalRibKind)); - let mut bindings_list = FxHashMap::default(); - for pattern in &arm.pats { - self.resolve_pattern(&pattern, PatternSource::Match, &mut bindings_list); - } - - // This has to happen *after* we determine which pat_idents are variants. - self.check_consistent_bindings(&arm.pats); + self.resolve_pats(&arm.pats, PatternSource::Match); if let Some(ast::Guard::If(ref expr)) = arm.guard { self.visit_expr(expr) @@ -3073,6 +3063,16 @@ impl<'a> Resolver<'a> { self.ribs[ValueNS].pop(); } + /// Arising from `source`, resolve a sequence of patterns (top level or-patterns). + fn resolve_pats(&mut self, pats: &[P], source: PatternSource) { + let mut bindings_list = FxHashMap::default(); + for pat in pats { + self.resolve_pattern(pat, source, &mut bindings_list); + } + // This has to happen *after* we determine which pat_idents are variants + self.check_consistent_bindings(pats); + } + fn resolve_block(&mut self, block: &Block) { debug!("(resolving block) entering block"); // Move down in the graph, if there's an anonymous module rooted here. @@ -3151,8 +3151,7 @@ impl<'a> Resolver<'a> { ); } Some(..) if pat_src == PatternSource::Match || - pat_src == PatternSource::IfLet || - pat_src == PatternSource::WhileLet => { + pat_src == PatternSource::Let => { // `Variant1(a) | Variant2(a)`, ok // Reuse definition from the first `a`. res = self.ribs[ValueNS].last_mut().unwrap().bindings[&ident]; @@ -4345,41 +4344,26 @@ impl<'a> Resolver<'a> { visit::walk_expr(self, expr); } - ExprKind::IfLet(ref pats, ref subexpression, ref if_block, ref optional_else) => { - self.visit_expr(subexpression); + ExprKind::Let(ref pats, ref scrutinee) => { + self.visit_expr(scrutinee); + self.resolve_pats(pats, PatternSource::Let); + } + ExprKind::If(ref cond, ref then, ref opt_else) => { self.ribs[ValueNS].push(Rib::new(NormalRibKind)); - let mut bindings_list = FxHashMap::default(); - for pat in pats { - self.resolve_pattern(pat, PatternSource::IfLet, &mut bindings_list); - } - // This has to happen *after* we determine which pat_idents are variants - self.check_consistent_bindings(pats); - self.visit_block(if_block); + self.visit_expr(cond); + self.visit_block(then); self.ribs[ValueNS].pop(); - optional_else.as_ref().map(|expr| self.visit_expr(expr)); + opt_else.as_ref().map(|expr| self.visit_expr(expr)); } ExprKind::Loop(ref block, label) => self.resolve_labeled_block(label, expr.id, &block), ExprKind::While(ref subexpression, ref block, label) => { self.with_resolved_label(label, expr.id, |this| { - this.visit_expr(subexpression); - this.visit_block(block); - }); - } - - ExprKind::WhileLet(ref pats, ref subexpression, ref block, label) => { - self.with_resolved_label(label, expr.id, |this| { - this.visit_expr(subexpression); this.ribs[ValueNS].push(Rib::new(NormalRibKind)); - let mut bindings_list = FxHashMap::default(); - for pat in pats { - this.resolve_pattern(pat, PatternSource::WhileLet, &mut bindings_list); - } - // This has to happen *after* we determine which pat_idents are variants. - this.check_consistent_bindings(pats); + this.visit_expr(subexpression); this.visit_block(block); this.ribs[ValueNS].pop(); }); diff --git a/src/librustc_save_analysis/dump_visitor.rs b/src/librustc_save_analysis/dump_visitor.rs index f67241ef23efc..09406f7306dd7 100644 --- a/src/librustc_save_analysis/dump_visitor.rs +++ b/src/librustc_save_analysis/dump_visitor.rs @@ -1580,17 +1580,9 @@ impl<'l, 'tcx, 'll, O: DumpOutput + 'll> Visitor<'l> for DumpVisitor<'l, 'tcx, ' self.visit_expr(subexpression); visit::walk_block(self, block); } - ast::ExprKind::WhileLet(ref pats, ref subexpression, ref block, _) => { + ast::ExprKind::Let(ref pats, ref scrutinee) => { self.process_var_decl_multi(pats); - debug!("for loop, walk sub-expr: {:?}", subexpression.node); - self.visit_expr(subexpression); - visit::walk_block(self, block); - } - ast::ExprKind::IfLet(ref pats, ref subexpression, ref block, ref opt_else) => { - self.process_var_decl_multi(pats); - self.visit_expr(subexpression); - visit::walk_block(self, block); - opt_else.as_ref().map(|el| self.visit_expr(el)); + self.visit_expr(scrutinee); } ast::ExprKind::Repeat(ref element, ref count) => { self.visit_expr(element); diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index b1a62ac81d033..21704206cbf86 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -1032,10 +1032,9 @@ impl Expr { ExprKind::Unary(..) => ExprPrecedence::Unary, ExprKind::Lit(_) => ExprPrecedence::Lit, ExprKind::Type(..) | ExprKind::Cast(..) => ExprPrecedence::Cast, + ExprKind::Let(..) => ExprPrecedence::Let, ExprKind::If(..) => ExprPrecedence::If, - ExprKind::IfLet(..) => ExprPrecedence::IfLet, ExprKind::While(..) => ExprPrecedence::While, - ExprKind::WhileLet(..) => ExprPrecedence::WhileLet, ExprKind::ForLoop(..) => ExprPrecedence::ForLoop, ExprKind::Loop(..) => ExprPrecedence::Loop, ExprKind::Match(..) => ExprPrecedence::Match, @@ -1116,26 +1115,20 @@ pub enum ExprKind { Cast(P, P), /// A type ascription (e.g., `42: usize`). Type(P, P), + /// A `let pats = expr` expression that is only semantically allowed in the condition + /// of `if` / `while` expressions. (e.g., `if let 0 = x { .. }`). + /// + /// The `Vec>` is for or-patterns at the top level. + /// FIXME(54883): Change this to just `P`. + Let(Vec>, P), /// An `if` block, with an optional `else` block. /// /// `if expr { block } else { expr }` If(P, P, Option>), - /// An `if let` expression with an optional else block - /// - /// `if let pat = expr { block } else { expr }` - /// - /// This is desugared to a `match` expression. - IfLet(Vec>, P, P, Option>), - /// A while loop, with an optional label + /// A while loop, with an optional label. /// /// `'label: while expr { block }` While(P, P, Option