diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 2c2d30d872e20..f3bac07328ac9 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1302,9 +1302,7 @@ 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 { .. }`). - /// - /// `Span` represents the whole `let pat = expr` statement. - Let(P, P, Span), + Let(P, P), /// 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 fb4db6005aca5..c62a23cc7050b 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -1234,7 +1234,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 dd8927496e019..112312ea87551 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -776,9 +776,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 expr, _) => { + ExprKind::Let(ref pat, ref scrutinee) => { visitor.visit_pat(pat); - visitor.visit_expr(expr); + visitor.visit_expr(scrutinee); } 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 bf7589e84adc4..bacf5662bc005 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -86,12 +86,32 @@ impl<'hir> LoweringContext<'_, 'hir> { let ohs = self.lower_expr(ohs); hir::ExprKind::AddrOf(k, m, ohs) } - 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::Let(ref pat, ref scrutinee) => { + self.lower_expr_let(e.span, pat, scrutinee) } + 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) @@ -348,51 +368,115 @@ 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 lowered_cond = self.lower_expr(cond); - let new_cond = self.manage_let_cond(lowered_cond); - let then_expr = self.lower_block_expr(then); - 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) - } + 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) } - // 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()) - } - } + 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 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) } - // 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, @@ -400,17 +484,72 @@ impl<'hir> LoweringContext<'_, 'hir> { body: &Block, opt_label: Option