diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index bd6e4c30fc3..575a00cdd0e 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1302,7 +1302,9 @@ pub enum ExprKind { Type(P, P), /// A `let pat = expr` expression that is only semantically allowed in the condition /// of `if` / `while` expressions. (e.g., `if let 0 = x { .. }`). - Let(P, P), + /// + /// `Span` represents the whole `let pat = expr` statement. + Let(P, P, Span), /// An `if` block, with an optional `else` block. /// /// `if expr { block } else { expr }` diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 87950b44083..c8245831187 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -1237,7 +1237,7 @@ pub fn noop_visit_expr( vis.visit_ty(ty); } ExprKind::AddrOf(_, _, ohs) => vis.visit_expr(ohs), - ExprKind::Let(pat, scrutinee) => { + ExprKind::Let(pat, scrutinee, _) => { vis.visit_pat(pat); vis.visit_expr(scrutinee); } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 1ebfcf36711..a377763983a 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -779,9 +779,9 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { visitor.visit_expr(subexpression); visitor.visit_ty(typ) } - ExprKind::Let(ref pat, ref scrutinee) => { + ExprKind::Let(ref pat, ref expr, _) => { visitor.visit_pat(pat); - visitor.visit_expr(scrutinee); + visitor.visit_expr(expr); } ExprKind::If(ref head_expression, ref if_block, ref optional_else) => { visitor.visit_expr(head_expression); diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index bacf5662bc0..bf7589e84ad 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -86,32 +86,12 @@ impl<'hir> LoweringContext<'_, 'hir> { let ohs = self.lower_expr(ohs); hir::ExprKind::AddrOf(k, m, ohs) } - ExprKind::Let(ref pat, ref scrutinee) => { - self.lower_expr_let(e.span, pat, scrutinee) + ExprKind::Let(ref pat, ref scrutinee, span) => { + hir::ExprKind::Let(self.lower_pat(pat), self.lower_expr(scrutinee), span) + } + ExprKind::If(ref cond, ref then, ref else_opt) => { + self.lower_expr_if(cond, then, else_opt.as_deref()) } - ExprKind::If(ref cond, ref then, ref else_opt) => match cond.kind { - ExprKind::Let(ref pat, ref scrutinee) => { - self.lower_expr_if_let(e.span, pat, scrutinee, then, else_opt.as_deref()) - } - ExprKind::Paren(ref paren) => match paren.peel_parens().kind { - ExprKind::Let(ref pat, ref scrutinee) => { - // A user has written `if (let Some(x) = foo) {`, we want to avoid - // confusing them with mentions of nightly features. - // If this logic is changed, you will also likely need to touch - // `unused::UnusedParens::check_expr`. - self.if_let_expr_with_parens(cond, &paren.peel_parens()); - self.lower_expr_if_let( - e.span, - pat, - scrutinee, - then, - else_opt.as_deref(), - ) - } - _ => self.lower_expr_if(cond, then, else_opt.as_deref()), - }, - _ => self.lower_expr_if(cond, then, else_opt.as_deref()), - }, ExprKind::While(ref cond, ref body, opt_label) => self .with_loop_scope(e.id, |this| { this.lower_expr_while_in_loop_scope(e.span, cond, body, opt_label) @@ -368,115 +348,51 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Call(f, self.lower_exprs(&real_args)) } - fn if_let_expr_with_parens(&mut self, cond: &Expr, paren: &Expr) { - let start = cond.span.until(paren.span); - let end = paren.span.shrink_to_hi().until(cond.span.shrink_to_hi()); - self.sess - .struct_span_err( - vec![start, end], - "invalid parentheses around `let` expression in `if let`", - ) - .multipart_suggestion( - "`if let` needs to be written without parentheses", - vec![(start, String::new()), (end, String::new())], - rustc_errors::Applicability::MachineApplicable, - ) - .emit(); - // Ideally, we'd remove the feature gating of a `let` expression since we are already - // complaining about it here, but `feature_gate::check_crate` has already run by now: - // self.sess.parse_sess.gated_spans.ungate_last(sym::let_chains, paren.span); - } - - /// Emit an error and lower `ast::ExprKind::Let(pat, scrutinee)` into: - /// ```rust - /// match scrutinee { pats => true, _ => false } - /// ``` - fn lower_expr_let(&mut self, span: Span, pat: &Pat, scrutinee: &Expr) -> hir::ExprKind<'hir> { - // If we got here, the `let` expression is not allowed. - - if self.sess.opts.unstable_features.is_nightly_build() { - self.sess - .struct_span_err(span, "`let` expressions are not supported here") - .note( - "only supported directly without parentheses in conditions of `if`- and \ - `while`-expressions, as well as in `let` chains within parentheses", - ) - .emit(); - } else { - self.sess - .struct_span_err(span, "expected expression, found statement (`let`)") - .note("variable declaration using `let` is a statement") - .emit(); - } - - // For better recovery, we emit: - // ``` - // match scrutinee { pat => 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 `pat` and `scrutinee`. - // 3. `pat` 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 pat = self.lower_pat(pat); - let expr = self.expr_bool(span, true); - self.arm(pat, expr) - }; - let else_arm = { - let pat = self.pat_wild(span); - let expr = self.expr_bool(span, false); - self.arm(pat, expr) - }; - hir::ExprKind::Match( - scrutinee, - arena_vec![self; then_arm, else_arm], - hir::MatchSource::Normal, - ) - } - fn lower_expr_if( &mut self, cond: &Expr, then: &Block, else_opt: Option<&Expr>, ) -> hir::ExprKind<'hir> { - let cond = self.lower_expr(cond); - let then = self.arena.alloc(self.lower_block_expr(then)); - let els = else_opt.map(|els| self.lower_expr(els)); - hir::ExprKind::If(cond, then, els) - } - - fn lower_expr_if_let( - &mut self, - span: Span, - pat: &Pat, - scrutinee: &Expr, - then: &Block, - else_opt: Option<&Expr>, - ) -> hir::ExprKind<'hir> { - // FIXME(#53667): handle lowering of && and parens. - - // `_ => else_block` where `else_block` is `{}` if there's `None`: - let else_pat = self.pat_wild(span); - let (else_expr, contains_else_clause) = match else_opt { - None => (self.expr_block_empty(span.shrink_to_hi()), false), - Some(els) => (self.lower_expr(els), true), - }; - let else_arm = self.arm(else_pat, else_expr); - - // Handle then + scrutinee: - let scrutinee = self.lower_expr(scrutinee); - let then_pat = self.lower_pat(pat); - + let lowered_cond = self.lower_expr(cond); + let new_cond = self.manage_let_cond(lowered_cond); let then_expr = self.lower_block_expr(then); - let then_arm = self.arm(then_pat, self.arena.alloc(then_expr)); - - let desugar = hir::MatchSource::IfLetDesugar { contains_else_clause }; - hir::ExprKind::Match(scrutinee, arena_vec![self; then_arm, else_arm], desugar) + if let Some(rslt) = else_opt { + hir::ExprKind::If(new_cond, self.arena.alloc(then_expr), Some(self.lower_expr(rslt))) + } else { + hir::ExprKind::If(new_cond, self.arena.alloc(then_expr), None) + } } + // If `cond` kind is `let`, returns `let`. Otherwise, wraps and returns `cond` + // in a temporary block. + fn manage_let_cond(&mut self, cond: &'hir hir::Expr<'hir>) -> &'hir hir::Expr<'hir> { + match cond.kind { + hir::ExprKind::Let(..) => cond, + _ => { + let span_block = + self.mark_span_with_reason(DesugaringKind::CondTemporary, cond.span, None); + self.expr_drop_temps(span_block, cond, AttrVec::new()) + } + } + } + + // We desugar: `'label: while $cond $body` into: + // + // ``` + // 'label: loop { + // if { let _t = $cond; _t } { + // $body + // } + // else { + // break; + // } + // } + // ``` + // + // Wrap in a construct equivalent to `{ let _t = $cond; _t }` + // to preserve drop semantics since `while $cond { ... }` does not + // let temporaries live outside of `cond`. fn lower_expr_while_in_loop_scope( &mut self, span: Span, @@ -484,72 +400,17 @@ impl<'hir> LoweringContext<'_, 'hir> { body: &Block, opt_label: Option