diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index e1ea464dedb1e..0632d937c4c17 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1005,13 +1005,42 @@ pub struct Local { pub id: NodeId, pub pat: P, pub ty: Option>, - /// Initializer expression to set the value, if any. - pub init: Option>, + pub kind: LocalKind, pub span: Span, pub attrs: AttrVec, pub tokens: Option, } +#[derive(Clone, Encodable, Decodable, Debug)] +pub enum LocalKind { + /// Local declaration. + /// Example: `let x;` + Decl, + /// Local declaration with an initializer. + /// Example: `let x = y;` + Init(P), + /// Local declaration with an initializer and an `else` clause. + /// Example: `let Some(x) = y else { return };` + InitElse(P, P), +} + +impl LocalKind { + pub fn init(&self) -> Option<&Expr> { + match self { + Self::Decl => None, + Self::Init(i) | Self::InitElse(i, _) => Some(i), + } + } + + pub fn init_else_opt(&self) -> Option<(&Expr, Option<&Block>)> { + match self { + Self::Decl => None, + Self::Init(init) => Some((init, None)), + Self::InitElse(init, els) => Some((init, Some(els))), + } + } +} + /// An arm of a 'match'. /// /// E.g., `0..=10 => { println!("match!") }` as in diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index fb4db6005aca5..368a23e34290d 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -571,11 +571,20 @@ pub fn noop_visit_parenthesized_parameter_data( } pub fn noop_visit_local(local: &mut P, vis: &mut T) { - let Local { id, pat, ty, init, span, attrs, tokens } = local.deref_mut(); + let Local { id, pat, ty, kind, span, attrs, tokens } = local.deref_mut(); vis.visit_id(id); vis.visit_pat(pat); visit_opt(ty, |ty| vis.visit_ty(ty)); - visit_opt(init, |init| vis.visit_expr(init)); + match kind { + LocalKind::Decl => {} + LocalKind::Init(init) => { + vis.visit_expr(init); + } + LocalKind::InitElse(init, els) => { + vis.visit_expr(init); + vis.visit_block(els); + } + } vis.visit_span(span); visit_thin_attrs(attrs, vis); visit_lazy_tts(tokens, vis); diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index 90786520fe802..6ea3db6d30372 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -23,3 +23,30 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool { | ast::ExprKind::TryBlock(..) ) } + +/// If an expression ends with `}`, returns the innermost expression ending in the `}` +pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> { + use ast::ExprKind::*; + + loop { + match &expr.kind { + AddrOf(_, _, e) + | Assign(_, e, _) + | AssignOp(_, _, e) + | Binary(_, _, e) + | Box(e) + | Break(_, Some(e)) + | Closure(.., e, _) + | Let(_, e, _) + | Range(_, Some(e), _) + | Ret(Some(e)) + | Unary(_, e) + | Yield(Some(e)) => { + expr = e; + } + Async(..) | Block(..) | ForLoop(..) | If(..) | Loop(..) | Match(..) | Struct(..) + | TryBlock(..) | While(..) => break Some(expr), + _ => break None, + } + } +} diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index dd8927496e019..c30f711b39707 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -242,7 +242,10 @@ pub fn walk_local<'a, V: Visitor<'a>>(visitor: &mut V, local: &'a Local) { } visitor.visit_pat(&local.pat); walk_list!(visitor, visit_ty, &local.ty); - walk_list!(visitor, visit_expr, &local.init); + if let Some((init, els)) = local.kind.init_else_opt() { + visitor.visit_expr(init); + walk_list!(visitor, visit_block, els); + } } pub fn walk_label<'a, V: Visitor<'a>>(visitor: &mut V, label: &'a Label) { diff --git a/compiler/rustc_ast_lowering/src/block.rs b/compiler/rustc_ast_lowering/src/block.rs new file mode 100644 index 0000000000000..ca804ec6758b7 --- /dev/null +++ b/compiler/rustc_ast_lowering/src/block.rs @@ -0,0 +1,185 @@ +use crate::{ImplTraitContext, ImplTraitPosition, LoweringContext}; +use rustc_ast::{AttrVec, Block, BlockCheckMode, Expr, Local, LocalKind, Stmt, StmtKind}; +use rustc_hir as hir; +use rustc_session::parse::feature_err; +use rustc_span::symbol::Ident; +use rustc_span::{sym, DesugaringKind}; + +use smallvec::SmallVec; + +impl<'a, 'hir> LoweringContext<'a, 'hir> { + pub(super) fn lower_block( + &mut self, + b: &Block, + targeted_by_break: bool, + ) -> &'hir hir::Block<'hir> { + self.arena.alloc(self.lower_block_noalloc(b, targeted_by_break)) + } + + pub(super) fn lower_block_noalloc( + &mut self, + b: &Block, + targeted_by_break: bool, + ) -> hir::Block<'hir> { + let (stmts, expr) = self.lower_stmts(&b.stmts); + let rules = self.lower_block_check_mode(&b.rules); + let hir_id = self.lower_node_id(b.id); + hir::Block { hir_id, stmts, expr, rules, span: self.lower_span(b.span), targeted_by_break } + } + + fn lower_stmts( + &mut self, + mut ast_stmts: &[Stmt], + ) -> (&'hir [hir::Stmt<'hir>], Option<&'hir hir::Expr<'hir>>) { + let mut stmts = SmallVec::<[hir::Stmt<'hir>; 8]>::new(); + let mut expr = None; + while let [s, tail @ ..] = ast_stmts { + match s.kind { + StmtKind::Local(ref local) => { + let hir_id = self.lower_node_id(s.id); + match &local.kind { + LocalKind::InitElse(init, els) => { + let (s, e) = self.lower_let_else(hir_id, local, init, els, tail); + stmts.push(s); + expr = Some(e); + // remaining statements are in let-else expression + break; + } + _ => { + let local = self.lower_local(local); + self.alias_attrs(hir_id, local.hir_id); + let kind = hir::StmtKind::Local(local); + let span = self.lower_span(s.span); + stmts.push(hir::Stmt { hir_id, kind, span }); + } + } + } + StmtKind::Item(ref it) => { + stmts.extend(self.lower_item_id(it).into_iter().enumerate().map( + |(i, item_id)| { + let hir_id = match i { + 0 => self.lower_node_id(s.id), + _ => self.next_id(), + }; + let kind = hir::StmtKind::Item(item_id); + let span = self.lower_span(s.span); + hir::Stmt { hir_id, kind, span } + }, + )); + } + StmtKind::Expr(ref e) => { + let e = self.lower_expr(e); + if tail.is_empty() { + expr = Some(e); + } else { + let hir_id = self.lower_node_id(s.id); + self.alias_attrs(hir_id, e.hir_id); + let kind = hir::StmtKind::Expr(e); + let span = self.lower_span(s.span); + stmts.push(hir::Stmt { hir_id, kind, span }); + } + } + StmtKind::Semi(ref e) => { + let e = self.lower_expr(e); + let hir_id = self.lower_node_id(s.id); + self.alias_attrs(hir_id, e.hir_id); + let kind = hir::StmtKind::Semi(e); + let span = self.lower_span(s.span); + stmts.push(hir::Stmt { hir_id, kind, span }); + } + StmtKind::Empty => {} + StmtKind::MacCall(..) => panic!("shouldn't exist here"), + } + ast_stmts = &ast_stmts[1..]; + } + (self.arena.alloc_from_iter(stmts), expr) + } + + fn lower_local(&mut self, l: &Local) -> &'hir hir::Local<'hir> { + let ty = l + .ty + .as_ref() + .map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding))); + let init = l.kind.init().map(|init| self.lower_expr(init)); + let hir_id = self.lower_node_id(l.id); + let pat = self.lower_pat(&l.pat); + let span = self.lower_span(l.span); + let source = hir::LocalSource::Normal; + self.lower_attrs(hir_id, &l.attrs); + self.arena.alloc(hir::Local { hir_id, ty, pat, init, span, source }) + } + + fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode { + match *b { + BlockCheckMode::Default => hir::BlockCheckMode::DefaultBlock, + BlockCheckMode::Unsafe(u) => { + hir::BlockCheckMode::UnsafeBlock(self.lower_unsafe_source(u)) + } + } + } + + fn lower_let_else( + &mut self, + stmt_hir_id: hir::HirId, + local: &Local, + init: &Expr, + els: &Block, + tail: &[Stmt], + ) -> (hir::Stmt<'hir>, &'hir hir::Expr<'hir>) { + let ty = local + .ty + .as_ref() + .map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding))); + let span = self.lower_span(local.span); + let span = self.mark_span_with_reason(DesugaringKind::LetElse, span, None); + let init = Some(self.lower_expr(init)); + let val = Ident::with_dummy_span(sym::val); + let (pat, val_id) = + self.pat_ident_binding_mode(span, val, hir::BindingAnnotation::Unannotated); + let local_hir_id = self.lower_node_id(local.id); + self.lower_attrs(local_hir_id, &local.attrs); + // first statement which basically exists for the type annotation + let stmt = { + let local = self.arena.alloc(hir::Local { + hir_id: local_hir_id, + ty, + pat, + init, + span, + source: hir::LocalSource::Normal, + }); + let kind = hir::StmtKind::Local(local); + hir::Stmt { hir_id: stmt_hir_id, kind, span } + }; + let let_expr = { + let scrutinee = self.expr_ident(span, val, val_id); + let let_kind = hir::ExprKind::Let(self.lower_pat(&local.pat), scrutinee, span); + self.arena.alloc(self.expr(span, let_kind, AttrVec::new())) + }; + let then_expr = { + let (stmts, expr) = self.lower_stmts(tail); + let block = self.block_all(span, stmts, expr); + self.arena.alloc(self.expr_block(block, AttrVec::new())) + }; + let else_expr = { + let block = self.lower_block(els, false); + self.arena.alloc(self.expr_block(block, AttrVec::new())) + }; + self.alias_attrs(else_expr.hir_id, local_hir_id); + let if_expr = self.arena.alloc(hir::Expr { + hir_id: self.next_id(), + span, + kind: hir::ExprKind::If(let_expr, then_expr, Some(else_expr)), + }); + if !self.sess.features_untracked().let_else { + feature_err( + &self.sess.parse_sess, + sym::let_else, + local.span, + "`let...else` statements are unstable", + ) + .emit(); + } + (stmt, if_expr) + } +} diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 0133acfee1008..deb7e742e5cc3 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -64,7 +64,7 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{Span, DUMMY_SP}; use rustc_target::spec::abi::Abi; -use smallvec::{smallvec, SmallVec}; +use smallvec::SmallVec; use std::collections::BTreeMap; use std::mem; use tracing::{debug, trace}; @@ -77,6 +77,7 @@ macro_rules! arena_vec { } mod asm; +mod block; mod expr; mod item; mod pat; @@ -1793,24 +1794,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { ) } - fn lower_local(&mut self, l: &Local) -> hir::Local<'hir> { - let ty = l - .ty - .as_ref() - .map(|t| self.lower_ty(t, ImplTraitContext::Disallowed(ImplTraitPosition::Binding))); - let init = l.init.as_ref().map(|e| self.lower_expr(e)); - let hir_id = self.lower_node_id(l.id); - self.lower_attrs(hir_id, &l.attrs); - hir::Local { - hir_id, - ty, - pat: self.lower_pat(&l.pat), - init, - span: self.lower_span(l.span), - source: hir::LocalSource::Normal, - } - } - fn lower_fn_params_to_names(&mut self, decl: &FnDecl) -> &'hir [Ident] { // Skip the `...` (`CVarArgs`) trailing arguments from the AST, // as they are not explicit in HIR/Ty function signatures. @@ -2396,23 +2379,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { bounds.iter().map(move |bound| self.lower_param_bound(bound, itctx.reborrow())) } - fn lower_block(&mut self, b: &Block, targeted_by_break: bool) -> &'hir hir::Block<'hir> { - self.arena.alloc(self.lower_block_noalloc(b, targeted_by_break)) - } - - fn lower_block_noalloc(&mut self, b: &Block, targeted_by_break: bool) -> hir::Block<'hir> { - let (stmts, expr) = match &*b.stmts { - [stmts @ .., Stmt { kind: StmtKind::Expr(e), .. }] => (stmts, Some(&*e)), - stmts => (stmts, None), - }; - let stmts = self.arena.alloc_from_iter(stmts.iter().flat_map(|stmt| self.lower_stmt(stmt))); - let expr = expr.map(|e| self.lower_expr(e)); - let rules = self.lower_block_check_mode(&b.rules); - let hir_id = self.lower_node_id(b.id); - - hir::Block { hir_id, stmts, expr, rules, span: self.lower_span(b.span), targeted_by_break } - } - /// Lowers a block directly to an expression, presuming that it /// has no attributes and is not targeted by a `break`. fn lower_block_expr(&mut self, b: &Block) -> hir::Expr<'hir> { @@ -2427,65 +2393,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { }) } - fn lower_stmt(&mut self, s: &Stmt) -> SmallVec<[hir::Stmt<'hir>; 1]> { - let (hir_id, kind) = match s.kind { - StmtKind::Local(ref l) => { - let l = self.lower_local(l); - let hir_id = self.lower_node_id(s.id); - self.alias_attrs(hir_id, l.hir_id); - return smallvec![hir::Stmt { - hir_id, - kind: hir::StmtKind::Local(self.arena.alloc(l)), - span: self.lower_span(s.span), - }]; - } - StmtKind::Item(ref it) => { - // Can only use the ID once. - let mut id = Some(s.id); - return self - .lower_item_id(it) - .into_iter() - .map(|item_id| { - let hir_id = id - .take() - .map(|id| self.lower_node_id(id)) - .unwrap_or_else(|| self.next_id()); - - hir::Stmt { - hir_id, - kind: hir::StmtKind::Item(item_id), - span: self.lower_span(s.span), - } - }) - .collect(); - } - StmtKind::Expr(ref e) => { - let e = self.lower_expr(e); - let hir_id = self.lower_node_id(s.id); - self.alias_attrs(hir_id, e.hir_id); - (hir_id, hir::StmtKind::Expr(e)) - } - StmtKind::Semi(ref e) => { - let e = self.lower_expr(e); - let hir_id = self.lower_node_id(s.id); - self.alias_attrs(hir_id, e.hir_id); - (hir_id, hir::StmtKind::Semi(e)) - } - StmtKind::Empty => return smallvec![], - StmtKind::MacCall(..) => panic!("shouldn't exist here"), - }; - smallvec![hir::Stmt { hir_id, kind, span: self.lower_span(s.span) }] - } - - fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode { - match *b { - BlockCheckMode::Default => hir::BlockCheckMode::DefaultBlock, - BlockCheckMode::Unsafe(u) => { - hir::BlockCheckMode::UnsafeBlock(self.lower_unsafe_source(u)) - } - } - } - fn lower_unsafe_source(&mut self, u: UnsafeSource) -> hir::UnsafeSource { match u { CompilerGenerated => hir::UnsafeSource::CompilerGenerated, diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 949e7a1fbb815..3cf04be160c64 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -1518,13 +1518,19 @@ impl<'a> State<'a> { self.ibox(INDENT_UNIT); self.print_local_decl(loc); self.end(); - if let Some(ref init) = loc.init { + if let Some((init, els)) = loc.kind.init_else_opt() { self.nbsp(); self.word_space("="); self.print_expr(init); + if let Some(els) = els { + self.cbox(INDENT_UNIT); + self.ibox(INDENT_UNIT); + self.s.word(" else "); + self.print_block(els); + } } self.s.word(";"); - self.end(); + self.end(); // `let` ibox } ast::StmtKind::Item(ref item) => self.print_item(item), ast::StmtKind::Expr(ref expr) => { diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index cc6dac52d7663..14506f296bf95 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -3,7 +3,7 @@ use crate::deriving::generic::*; use crate::deriving::path_std; use rustc_ast::ptr::P; -use rustc_ast::{self as ast, Expr, MetaItem}; +use rustc_ast::{self as ast, Expr, LocalKind, MetaItem}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::{sym, Ident}; use rustc_span::{Span, DUMMY_SP}; @@ -135,8 +135,8 @@ fn stmt_let_underscore(cx: &mut ExtCtxt<'_>, sp: Span, expr: P) -> as let local = P(ast::Local { pat: cx.pat_wild(sp), ty: None, - init: Some(expr), id: ast::DUMMY_NODE_ID, + kind: LocalKind::Init(expr), span: sp, attrs: ast::AttrVec::new(), tokens: None, diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 824df2757ea90..1d83ecbfd404b 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -2,7 +2,7 @@ use crate::base::ExtCtxt; use rustc_ast::attr; use rustc_ast::ptr::P; -use rustc_ast::{self as ast, AttrVec, BlockCheckMode, Expr, PatKind, UnOp}; +use rustc_ast::{self as ast, AttrVec, BlockCheckMode, Expr, LocalKind, PatKind, UnOp}; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, sym, Ident, Symbol}; @@ -153,8 +153,8 @@ impl<'a> ExtCtxt<'a> { let local = P(ast::Local { pat, ty: None, - init: Some(ex), id: ast::DUMMY_NODE_ID, + kind: LocalKind::Init(ex), span: sp, attrs: AttrVec::new(), tokens: None, @@ -167,8 +167,8 @@ impl<'a> ExtCtxt<'a> { let local = P(ast::Local { pat: self.pat_wild(span), ty: Some(ty), - init: None, id: ast::DUMMY_NODE_ID, + kind: LocalKind::Decl, span, attrs: AttrVec::new(), tokens: None, diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index 1ff2c75966ab6..a3807a2bb9fde 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -676,6 +676,9 @@ declare_features! ( /// Allows additional const parameter types, such as `&'static str` or user defined types (incomplete, adt_const_params, "1.56.0", Some(44580), None), + /// Allows `let...else` statements. + (active, let_else, "1.56.0", Some(87335), None), + // ------------------------------------------------------------------------- // feature-group-end: actual feature gates // ------------------------------------------------------------------------- diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 299dcf5f17a87..d54933841fd4e 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -781,6 +781,10 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { ); } } + ObligationCauseCode::LetElse => { + err.help("try adding a diverging expression, such as `return` or `panic!(..)`"); + err.help("...or use `match` instead of `let...else`"); + } _ => (), } } @@ -2592,6 +2596,7 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> { } IfExpression { .. } => Error0308("`if` and `else` have incompatible types"), IfExpressionWithNoElse => Error0317("`if` may be missing an `else` clause"), + LetElse => Error0308("`else` clause of `let...else` does not diverge"), MainFunctionType => Error0580("`main` function has wrong type"), StartFunctionType => Error0308("`#[start]` function has wrong type"), IntrinsicType => Error0308("intrinsic has wrong type"), diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index be137884b4b27..f04ac8dd9426f 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1,7 +1,7 @@ use crate::Lint; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; use rustc_ast as ast; -use rustc_ast::util::parser; +use rustc_ast::util::{classify, parser}; use rustc_ast::{ExprKind, StmtKind}; use rustc_ast_pretty::pprust; use rustc_errors::{pluralize, Applicability}; @@ -382,6 +382,7 @@ enum UnusedDelimsCtx { FunctionArg, MethodArg, AssignedValue, + AssignedValueLetElse, IfCond, WhileCond, ForIterExpr, @@ -398,7 +399,9 @@ impl From for &'static str { match ctx { UnusedDelimsCtx::FunctionArg => "function argument", UnusedDelimsCtx::MethodArg => "method argument", - UnusedDelimsCtx::AssignedValue => "assigned value", + UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => { + "assigned value" + } UnusedDelimsCtx::IfCond => "`if` condition", UnusedDelimsCtx::WhileCond => "`while` condition", UnusedDelimsCtx::ForIterExpr => "`for` iterator expression", @@ -441,14 +444,26 @@ trait UnusedDelimLint { right_pos: Option, ); - fn is_expr_delims_necessary(inner: &ast::Expr, followed_by_block: bool) -> bool { + fn is_expr_delims_necessary( + inner: &ast::Expr, + followed_by_block: bool, + followed_by_else: bool, + ) -> bool { + if followed_by_else { + match inner.kind { + ast::ExprKind::Binary(op, ..) if op.node.lazy() => return true, + _ if classify::expr_trailing_brace(inner).is_some() => return true, + _ => {} + } + } + // Prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }` let lhs_needs_parens = { let mut innermost = inner; loop { if let ExprKind::Binary(_, lhs, _rhs) = &innermost.kind { innermost = lhs; - if !rustc_ast::util::classify::expr_requires_semi_to_be_stmt(innermost) { + if !classify::expr_requires_semi_to_be_stmt(innermost) { break true; } } else { @@ -618,15 +633,12 @@ trait UnusedDelimLint { fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { match s.kind { StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => { - if let Some(ref value) = local.init { - self.check_unused_delims_expr( - cx, - &value, - UnusedDelimsCtx::AssignedValue, - false, - None, - None, - ); + if let Some((init, els)) = local.kind.init_else_opt() { + let ctx = match els { + None => UnusedDelimsCtx::AssignedValue, + Some(_) => UnusedDelimsCtx::AssignedValueLetElse, + }; + self.check_unused_delims_expr(cx, init, ctx, false, None, None); } } StmtKind::Expr(ref expr) => { @@ -702,7 +714,8 @@ impl UnusedDelimLint for UnusedParens { ) { match value.kind { ast::ExprKind::Paren(ref inner) => { - if !Self::is_expr_delims_necessary(inner, followed_by_block) + let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse; + if !Self::is_expr_delims_necessary(inner, followed_by_block, followed_by_else) && value.attrs.is_empty() && !value.span.from_expansion() && (ctx != UnusedDelimsCtx::LetScrutineeExpr @@ -941,7 +954,7 @@ impl UnusedDelimLint for UnusedBraces { // FIXME(const_generics): handle paths when #67075 is fixed. if let [stmt] = inner.stmts.as_slice() { if let ast::StmtKind::Expr(ref expr) = stmt.kind { - if !Self::is_expr_delims_necessary(expr, followed_by_block) + if !Self::is_expr_delims_necessary(expr, followed_by_block, false) && (ctx != UnusedDelimsCtx::AnonConst || matches!(expr.kind, ast::ExprKind::Lit(_))) && !cx.sess().source_map().is_multiline(value.span) diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index 676cb7fe41d97..74edb17fe32f1 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -305,6 +305,9 @@ pub enum ObligationCauseCode<'tcx> { /// Intrinsic has wrong type IntrinsicType, + /// A let else block does not diverge + LetElse, + /// Method receiver MethodReceiver, diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index 50cbe0f71f552..b34c1e07be71c 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -17,7 +17,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::lint::builtin::BINDINGS_WITH_VARIANT_NAME; use rustc_session::lint::builtin::{IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS}; use rustc_session::Session; -use rustc_span::Span; +use rustc_span::{DesugaringKind, ExpnKind, Span}; use std::slice; crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) { @@ -118,31 +118,6 @@ impl<'tcx> MatchVisitor<'_, 'tcx> { check_for_bindings_named_same_as_variants(self, pat); } - fn let_source(&mut self, pat: &'tcx hir::Pat<'tcx>, _expr: &hir::Expr<'_>) -> LetSource { - let hir = self.tcx.hir(); - let parent = hir.get_parent_node(pat.hir_id); - let parent_parent = hir.get_parent_node(parent); - let parent_parent_node = hir.get(parent_parent); - - let parent_parent_parent = hir.get_parent_node(parent_parent); - let parent_parent_parent_parent = hir.get_parent_node(parent_parent_parent); - let parent_parent_parent_parent_node = hir.get(parent_parent_parent_parent); - - if let hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Loop(_, _, hir::LoopSource::While, _), - .. - }) = parent_parent_parent_parent_node - { - LetSource::WhileLet - } else if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::If { .. }, .. }) = - parent_parent_node - { - LetSource::IfLet - } else { - LetSource::GenericLet - } - } - fn lower_pattern<'p>( &self, cx: &mut MatchCheckCtxt<'p, 'tcx>, @@ -172,10 +147,9 @@ impl<'tcx> MatchVisitor<'_, 'tcx> { fn check_let(&mut self, pat: &'tcx hir::Pat<'tcx>, expr: &hir::Expr<'_>, span: Span) { self.check_patterns(pat); - let ls = self.let_source(pat, expr); let mut cx = self.new_cx(expr.hir_id); let tpat = self.lower_pattern(&mut cx, pat, &mut false).0; - check_let_reachability(&mut cx, ls, pat.hir_id, &tpat, span); + check_let_reachability(&mut cx, pat.hir_id, &tpat, span); } fn check_match( @@ -192,13 +166,7 @@ impl<'tcx> MatchVisitor<'_, 'tcx> { if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard { self.check_patterns(pat); let tpat = self.lower_pattern(&mut cx, pat, &mut false).0; - check_let_reachability( - &mut cx, - LetSource::IfLetGuard, - pat.hir_id, - &tpat, - tpat.span, - ); + check_let_reachability(&mut cx, pat.hir_id, &tpat, tpat.span); } } @@ -397,7 +365,7 @@ fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option< }); } -fn irrefutable_let_pattern(id: HirId, ls: LetSource, span: Span, tcx: TyCtxt<'_>) { +fn irrefutable_let_pattern(tcx: TyCtxt<'_>, id: HirId, span: Span) { macro_rules! emit_diag { ( $lint:expr, @@ -412,7 +380,12 @@ fn irrefutable_let_pattern(id: HirId, ls: LetSource, span: Span, tcx: TyCtxt<'_> }}; } - tcx.struct_span_lint_hir(IRREFUTABLE_LET_PATTERNS, id, span, |lint| match ls { + let source = let_source(tcx, id); + let span = match source { + LetSource::LetElse(span) => span, + _ => span, + }; + tcx.struct_span_lint_hir(IRREFUTABLE_LET_PATTERNS, id, span, |lint| match source { LetSource::GenericLet => { emit_diag!(lint, "`let`", "`let` is useless", "removing `let`"); } @@ -432,6 +405,14 @@ fn irrefutable_let_pattern(id: HirId, ls: LetSource, span: Span, tcx: TyCtxt<'_> "removing the guard and adding a `let` inside the match arm" ); } + LetSource::LetElse(..) => { + emit_diag!( + lint, + "`let...else`", + "`else` clause is useless", + "removing the `else` clause" + ); + } LetSource::WhileLet => { emit_diag!( lint, @@ -445,7 +426,6 @@ fn irrefutable_let_pattern(id: HirId, ls: LetSource, span: Span, tcx: TyCtxt<'_> fn check_let_reachability<'p, 'tcx>( cx: &mut MatchCheckCtxt<'p, 'tcx>, - ls: LetSource, pat_id: HirId, pat: &'p super::Pat<'tcx>, span: Span, @@ -454,13 +434,13 @@ fn check_let_reachability<'p, 'tcx>( let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty); report_arm_reachability(&cx, &report, |arm_index, arm_span, arm_hir_id, _| { - match ls { + match let_source(cx.tcx, pat_id) { LetSource::IfLet | LetSource::WhileLet => { match arm_index { // The arm with the user-specified pattern. 0 => unreachable_pattern(cx.tcx, arm_span, arm_hir_id, None), // The arm with the wildcard pattern. - 1 => irrefutable_let_pattern(pat_id, ls, arm_span, cx.tcx), + 1 => irrefutable_let_pattern(cx.tcx, pat_id, arm_span), _ => bug!(), } } @@ -473,7 +453,7 @@ fn check_let_reachability<'p, 'tcx>( if report.non_exhaustiveness_witnesses.is_empty() { // The match is exhaustive, i.e. the `if let` pattern is irrefutable. - irrefutable_let_pattern(pat_id, ls, span, cx.tcx); + irrefutable_let_pattern(cx.tcx, pat_id, span); } } @@ -787,5 +767,46 @@ pub enum LetSource { GenericLet, IfLet, IfLetGuard, + LetElse(Span), WhileLet, } + +fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource { + let hir = tcx.hir(); + let parent = hir.get_parent_node(pat_id); + match hir.get(parent) { + hir::Node::Arm(hir::Arm { + guard: Some(hir::Guard::IfLet(&hir::Pat { hir_id, .. }, _)), + .. + }) if hir_id == pat_id => { + return LetSource::IfLetGuard; + } + hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Let(..), span, .. }) => { + let expn_data = span.ctxt().outer_expn_data(); + if let ExpnKind::Desugaring(DesugaringKind::LetElse) = expn_data.kind { + return LetSource::LetElse(expn_data.call_site); + } + } + _ => {} + } + let parent_parent = hir.get_parent_node(parent); + let parent_parent_node = hir.get(parent_parent); + + let parent_parent_parent = hir.get_parent_node(parent_parent); + let parent_parent_parent_parent = hir.get_parent_node(parent_parent_parent); + let parent_parent_parent_parent_node = hir.get(parent_parent_parent_parent); + + if let hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Loop(_, _, hir::LoopSource::While, _), + .. + }) = parent_parent_parent_parent_node + { + LetSource::WhileLet + } else if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::If { .. }, .. }) = + parent_parent_node + { + LetSource::IfLet + } else { + LetSource::GenericLet + } +} diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 85515bd2a6314..068bd36af5524 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -11,8 +11,9 @@ use rustc_ast as ast; use rustc_ast::ptr::P; use rustc_ast::token::{self, TokenKind}; use rustc_ast::util::classify; -use rustc_ast::AstLike; -use rustc_ast::{AttrStyle, AttrVec, Attribute, MacCall, MacCallStmt, MacStmtStyle}; +use rustc_ast::{ + AstLike, AttrStyle, AttrVec, Attribute, LocalKind, MacCall, MacCallStmt, MacStmtStyle, +}; use rustc_ast::{Block, BlockCheckMode, Expr, ExprKind, Local, Stmt}; use rustc_ast::{StmtKind, DUMMY_NODE_ID}; use rustc_errors::{Applicability, PResult}; @@ -292,8 +293,65 @@ impl<'a> Parser<'a> { return Err(err); } }; + let kind = match init { + None => LocalKind::Decl, + Some(init) => { + if self.eat_keyword(kw::Else) { + let els = self.parse_block()?; + self.check_let_else_init_bool_expr(&init); + self.check_let_else_init_trailing_brace(&init); + LocalKind::InitElse(init, els) + } else { + LocalKind::Init(init) + } + } + }; let hi = if self.token == token::Semi { self.token.span } else { self.prev_token.span }; - Ok(P(ast::Local { ty, pat, init, id: DUMMY_NODE_ID, span: lo.to(hi), attrs, tokens: None })) + Ok(P(ast::Local { ty, pat, kind, id: DUMMY_NODE_ID, span: lo.to(hi), attrs, tokens: None })) + } + + fn check_let_else_init_bool_expr(&self, init: &ast::Expr) { + if let ast::ExprKind::Binary(op, ..) = init.kind { + if op.node.lazy() { + let suggs = vec![ + (init.span.shrink_to_lo(), "(".to_string()), + (init.span.shrink_to_hi(), ")".to_string()), + ]; + self.struct_span_err( + init.span, + &format!( + "a `{}` expression cannot be directly assigned in `let...else`", + op.node.to_string() + ), + ) + .multipart_suggestion( + "wrap the expression in parenthesis", + suggs, + Applicability::MachineApplicable, + ) + .emit(); + } + } + } + + fn check_let_else_init_trailing_brace(&self, init: &ast::Expr) { + if let Some(trailing) = classify::expr_trailing_brace(init) { + let err_span = trailing.span.with_lo(trailing.span.hi() - BytePos(1)); + let suggs = vec![ + (trailing.span.shrink_to_lo(), "(".to_string()), + (trailing.span.shrink_to_hi(), ")".to_string()), + ]; + self.struct_span_err( + err_span, + "right curly brace `}` before `else` in a `let...else` statement not allowed", + ) + .multipart_suggestion( + "try wrapping the expression in parenthesis", + suggs, + Applicability::MachineApplicable, + ) + .emit(); + } } /// Parses the RHS of a local variable declaration (e.g., `= 14;`). @@ -495,13 +553,13 @@ impl<'a> Parser<'a> { StmtKind::Expr(_) | StmtKind::MacCall(_) => {} StmtKind::Local(ref mut local) if let Err(e) = self.expect_semi() => { // We might be at the `,` in `let x = foo;`. Try to recover. - match &mut local.init { - Some(ref mut expr) => { - self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?; - // We found `foo`, have we fully recovered? - self.expect_semi()?; - } - None => return Err(e), + match &mut local.kind { + LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => { + self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?; + // We found `foo`, have we fully recovered? + self.expect_semi()?; + } + LocalKind::Decl => return Err(e), } eat_semi = false; } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 0b552aa07f517..5c7b4b028227e 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -454,7 +454,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> { _ => Some(( local.pat.span, local.ty.as_ref().map(|ty| ty.span), - local.init.as_ref().map(|init| init.span), + local.kind.init().map(|init| init.span), )), }; let original = replace(&mut self.diagnostic_metadata.current_let_binding, local_spans); @@ -1426,7 +1426,14 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { walk_list!(self, visit_ty, &local.ty); // Resolve the initializer. - walk_list!(self, visit_expr, &local.init); + if let Some((init, els)) = local.kind.init_else_opt() { + self.visit_expr(init); + + // Resolve the `else` block + if let Some(els) = els { + self.visit_block(els); + } + } // Resolve the pattern. self.resolve_pattern_top(&local.pat, PatternSource::Let); diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index e44a2e96598ce..c22093c5a4288 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1097,6 +1097,7 @@ pub enum DesugaringKind { Async, Await, ForLoop(ForLoopLoc), + LetElse, } /// A location in the desugaring of a `for` loop @@ -1117,6 +1118,7 @@ impl DesugaringKind { DesugaringKind::TryBlock => "`try` block", DesugaringKind::OpaqueTy => "`impl Trait`", DesugaringKind::ForLoop(_) => "`for` loop", + DesugaringKind::LetElse => "`let...else`", } } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 19c02ba45c447..899c51e12ff1f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -744,6 +744,7 @@ symbols! { le, len, let_chains, + let_else, lhs, lib, libc, diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index 40841a6e32d00..db3432b01422f 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -1928,7 +1928,11 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> { | ObligationCauseCode::OpaqueType | ObligationCauseCode::MiscObligation | ObligationCauseCode::WellFormed(..) - | ObligationCauseCode::MatchImpl(..) => {} + | ObligationCauseCode::MatchImpl(..) + | ObligationCauseCode::ReturnType + | ObligationCauseCode::ReturnValue(_) + | ObligationCauseCode::BlockTailExpression(_) + | ObligationCauseCode::LetElse => {} ObligationCauseCode::SliceOrArrayElem => { err.note("slice and array elements must have `Sized` type"); } @@ -2338,9 +2342,6 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> { predicate )); } - ObligationCauseCode::ReturnType - | ObligationCauseCode::ReturnValue(_) - | ObligationCauseCode::BlockTailExpression(_) => (), ObligationCauseCode::TrivialBound => { err.help("see issue #48214"); if tcx.sess.opts.unstable_features.is_nightly_build() { diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 51c646e500ca3..10f5b000aca1c 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -849,7 +849,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { coerce.coerce(self, &self.misc(sp), then_expr, then_ty); if let Some(else_expr) = opt_else_expr { - let else_ty = self.check_expr_with_expectation(else_expr, expected); + let else_ty = if sp.desugaring_kind() == Some(DesugaringKind::LetElse) { + // todo introduce `check_expr_with_expectation(.., Expectation::LetElse)` + // for errors that point to the offending expression rather than the entire block. + // We could use `check_expr_eq_type(.., tcx.types.never)`, but then there is no + // way to detect that the expected type originated from let-else and provide + // a customized error. + let else_ty = self.check_expr(else_expr); + let cause = self.cause(else_expr.span, ObligationCauseCode::LetElse); + + if let Some(mut err) = + self.demand_eqtype_with_origin(&cause, self.tcx.types.never, else_ty) + { + err.emit(); + self.tcx.ty_error() + } else { + else_ty + } + } else { + self.check_expr_with_expectation(else_expr, expected) + }; let else_diverges = self.diverges.get(); let opt_suggest_box_span = diff --git a/src/test/ui/did_you_mean/issue-40396.stderr b/src/test/ui/did_you_mean/issue-40396.stderr index f4bc5aef82dc3..d2938435ece31 100644 --- a/src/test/ui/did_you_mean/issue-40396.stderr +++ b/src/test/ui/did_you_mean/issue-40396.stderr @@ -31,11 +31,11 @@ help: use `::<...>` instead of `<...>` to specify type or const arguments LL | (0..13).collect::(); | ++ -error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,` +error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,` --> $DIR/issue-40396.rs:11:43 | LL | let x = std::collections::HashMap::new(); - | ^ expected one of 7 possible tokens + | ^ expected one of 8 possible tokens | help: use `::<...>` instead of `<...>` to specify type or const arguments | diff --git a/src/test/ui/feature-gates/feature-gate-let_else.rs b/src/test/ui/feature-gates/feature-gate-let_else.rs new file mode 100644 index 0000000000000..3f04a9dabfd1f --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-let_else.rs @@ -0,0 +1,5 @@ +fn main() { + let Some(x) = Some(1) else { //~ ERROR `let...else` statements are unstable + return; + }; +} diff --git a/src/test/ui/feature-gates/feature-gate-let_else.stderr b/src/test/ui/feature-gates/feature-gate-let_else.stderr new file mode 100644 index 0000000000000..8625260415467 --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-let_else.stderr @@ -0,0 +1,14 @@ +error[E0658]: `let...else` statements are unstable + --> $DIR/feature-gate-let_else.rs:2:5 + | +LL | / let Some(x) = Some(1) else { +LL | | return; +LL | | }; + | |______^ + | + = note: see issue #87335 for more information + = help: add `#![feature(let_else)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/let-else/let-else-bool-binop-init.fixed b/src/test/ui/let-else/let-else-bool-binop-init.fixed new file mode 100644 index 0000000000000..e47f7f23d7e07 --- /dev/null +++ b/src/test/ui/let-else/let-else-bool-binop-init.fixed @@ -0,0 +1,8 @@ +// run-rustfix + +#![feature(let_else)] + +fn main() { + let true = (true && false) else { return }; //~ ERROR a `&&` expression cannot be directly assigned in `let...else` + let true = (true || false) else { return }; //~ ERROR a `||` expression cannot be directly assigned in `let...else` +} diff --git a/src/test/ui/let-else/let-else-bool-binop-init.rs b/src/test/ui/let-else/let-else-bool-binop-init.rs new file mode 100644 index 0000000000000..e443fb0d6a376 --- /dev/null +++ b/src/test/ui/let-else/let-else-bool-binop-init.rs @@ -0,0 +1,8 @@ +// run-rustfix + +#![feature(let_else)] + +fn main() { + let true = true && false else { return }; //~ ERROR a `&&` expression cannot be directly assigned in `let...else` + let true = true || false else { return }; //~ ERROR a `||` expression cannot be directly assigned in `let...else` +} diff --git a/src/test/ui/let-else/let-else-bool-binop-init.stderr b/src/test/ui/let-else/let-else-bool-binop-init.stderr new file mode 100644 index 0000000000000..6551e24cc83d0 --- /dev/null +++ b/src/test/ui/let-else/let-else-bool-binop-init.stderr @@ -0,0 +1,24 @@ +error: a `&&` expression cannot be directly assigned in `let...else` + --> $DIR/let-else-bool-binop-init.rs:6:16 + | +LL | let true = true && false else { return }; + | ^^^^^^^^^^^^^ + | +help: wrap the expression in parenthesis + | +LL | let true = (true && false) else { return }; + | + + + +error: a `||` expression cannot be directly assigned in `let...else` + --> $DIR/let-else-bool-binop-init.rs:7:16 + | +LL | let true = true || false else { return }; + | ^^^^^^^^^^^^^ + | +help: wrap the expression in parenthesis + | +LL | let true = (true || false) else { return }; + | + + + +error: aborting due to 2 previous errors + diff --git a/src/test/ui/let-else/let-else-brace-before-else.fixed b/src/test/ui/let-else/let-else-brace-before-else.fixed new file mode 100644 index 0000000000000..fb4fd77791e03 --- /dev/null +++ b/src/test/ui/let-else/let-else-brace-before-else.fixed @@ -0,0 +1,26 @@ +// run-rustfix + +#![feature(let_else)] + +fn main() { + let Some(1) = ({ Some(1) }) else { + //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed + return; + }; + let Some(1) = (loop { break Some(1) }) else { + //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed + return; + }; + let 2 = 1 + (match 1 { n => n }) else { + //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed + return; + }; + let Some(1) = (unsafe { unsafe_fn() }) else { + //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed + return; + }; +} + +unsafe fn unsafe_fn() -> T { + unimplemented!(); +} diff --git a/src/test/ui/let-else/let-else-brace-before-else.rs b/src/test/ui/let-else/let-else-brace-before-else.rs new file mode 100644 index 0000000000000..c4c5a1ca28bbe --- /dev/null +++ b/src/test/ui/let-else/let-else-brace-before-else.rs @@ -0,0 +1,26 @@ +// run-rustfix + +#![feature(let_else)] + +fn main() { + let Some(1) = { Some(1) } else { + //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed + return; + }; + let Some(1) = loop { break Some(1) } else { + //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed + return; + }; + let 2 = 1 + match 1 { n => n } else { + //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed + return; + }; + let Some(1) = unsafe { unsafe_fn() } else { + //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed + return; + }; +} + +unsafe fn unsafe_fn() -> T { + unimplemented!(); +} diff --git a/src/test/ui/let-else/let-else-brace-before-else.stderr b/src/test/ui/let-else/let-else-brace-before-else.stderr new file mode 100644 index 0000000000000..eac029c848b20 --- /dev/null +++ b/src/test/ui/let-else/let-else-brace-before-else.stderr @@ -0,0 +1,46 @@ +error: right curly brace `}` before `else` in a `let...else` statement not allowed + --> $DIR/let-else-brace-before-else.rs:6:29 + | +LL | let Some(1) = { Some(1) } else { + | ^ + | +help: try wrapping the expression in parenthesis + | +LL | let Some(1) = ({ Some(1) }) else { + | + + + +error: right curly brace `}` before `else` in a `let...else` statement not allowed + --> $DIR/let-else-brace-before-else.rs:10:40 + | +LL | let Some(1) = loop { break Some(1) } else { + | ^ + | +help: try wrapping the expression in parenthesis + | +LL | let Some(1) = (loop { break Some(1) }) else { + | + + + +error: right curly brace `}` before `else` in a `let...else` statement not allowed + --> $DIR/let-else-brace-before-else.rs:14:34 + | +LL | let 2 = 1 + match 1 { n => n } else { + | ^ + | +help: try wrapping the expression in parenthesis + | +LL | let 2 = 1 + (match 1 { n => n }) else { + | + + + +error: right curly brace `}` before `else` in a `let...else` statement not allowed + --> $DIR/let-else-brace-before-else.rs:18:40 + | +LL | let Some(1) = unsafe { unsafe_fn() } else { + | ^ + | +help: try wrapping the expression in parenthesis + | +LL | let Some(1) = (unsafe { unsafe_fn() }) else { + | + + + +error: aborting due to 4 previous errors + diff --git a/src/test/ui/let-else/let-else-check.rs b/src/test/ui/let-else/let-else-check.rs new file mode 100644 index 0000000000000..ab763447ef7e1 --- /dev/null +++ b/src/test/ui/let-else/let-else-check.rs @@ -0,0 +1,14 @@ +#![feature(let_else)] + +#![deny(unused_variables)] + +fn main() { + // type annotation, attributes + #[allow(unused_variables)] + let Some(_): Option = Some(Default::default()) else { + let x = 1; // OK + return; + }; + + let x = 1; //~ ERROR unused variable: `x` +} diff --git a/src/test/ui/let-else/let-else-check.stderr b/src/test/ui/let-else/let-else-check.stderr new file mode 100644 index 0000000000000..50e54d320b006 --- /dev/null +++ b/src/test/ui/let-else/let-else-check.stderr @@ -0,0 +1,14 @@ +error: unused variable: `x` + --> $DIR/let-else-check.rs:13:9 + | +LL | let x = 1; + | ^ help: if this is intentional, prefix it with an underscore: `_x` + | +note: the lint level is defined here + --> $DIR/let-else-check.rs:3:9 + | +LL | #![deny(unused_variables)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/test/ui/let-else/let-else-irrefutable.rs b/src/test/ui/let-else/let-else-irrefutable.rs new file mode 100644 index 0000000000000..b1e09a1248ff7 --- /dev/null +++ b/src/test/ui/let-else/let-else-irrefutable.rs @@ -0,0 +1,7 @@ +// check-pass + +#![feature(let_else)] + +fn main() { + let x = 1 else { return }; //~ WARN irrefutable `let...else` pattern +} diff --git a/src/test/ui/let-else/let-else-irrefutable.stderr b/src/test/ui/let-else/let-else-irrefutable.stderr new file mode 100644 index 0000000000000..e030c50d45dcf --- /dev/null +++ b/src/test/ui/let-else/let-else-irrefutable.stderr @@ -0,0 +1,12 @@ +warning: irrefutable `let...else` pattern + --> $DIR/let-else-irrefutable.rs:6:5 + | +LL | let x = 1 else { return }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(irrefutable_let_patterns)]` on by default + = note: this pattern will always match, so the `else` clause is useless + = help: consider removing the `else` clause + +warning: 1 warning emitted + diff --git a/src/test/ui/let-else/let-else-missing-semicolon.rs b/src/test/ui/let-else/let-else-missing-semicolon.rs new file mode 100644 index 0000000000000..ed9d79f1ebde9 --- /dev/null +++ b/src/test/ui/let-else/let-else-missing-semicolon.rs @@ -0,0 +1,11 @@ +#![feature(let_else)] + +fn main() { + let Some(x) = Some(1) else { + return; + } //~ ERROR expected `;`, found keyword `let` + let _ = ""; + let Some(x) = Some(1) else { + panic!(); + } //~ ERROR expected `;`, found `}` +} diff --git a/src/test/ui/let-else/let-else-missing-semicolon.stderr b/src/test/ui/let-else/let-else-missing-semicolon.stderr new file mode 100644 index 0000000000000..1818a0b126348 --- /dev/null +++ b/src/test/ui/let-else/let-else-missing-semicolon.stderr @@ -0,0 +1,18 @@ +error: expected `;`, found keyword `let` + --> $DIR/let-else-missing-semicolon.rs:6:6 + | +LL | } + | ^ help: add `;` here +LL | let _ = ""; + | --- unexpected token + +error: expected `;`, found `}` + --> $DIR/let-else-missing-semicolon.rs:10:6 + | +LL | } + | ^ help: add `;` here +LL | } + | - unexpected token + +error: aborting due to 2 previous errors + diff --git a/src/test/ui/let-else/let-else-non-diverging.rs b/src/test/ui/let-else/let-else-non-diverging.rs new file mode 100644 index 0000000000000..a1cee335aeeba --- /dev/null +++ b/src/test/ui/let-else/let-else-non-diverging.rs @@ -0,0 +1,13 @@ +#![feature(let_else)] + +fn main() { + let Some(x) = Some(1) else { //~ ERROR does not diverge + Some(2) + }; + let Some(x) = Some(1) else { //~ ERROR does not diverge + if 1 == 1 { + panic!(); + } + }; + let Some(x) = Some(1) else { Some(2) }; //~ ERROR does not diverge +} diff --git a/src/test/ui/let-else/let-else-non-diverging.stderr b/src/test/ui/let-else/let-else-non-diverging.stderr new file mode 100644 index 0000000000000..fd5a18ce7ea51 --- /dev/null +++ b/src/test/ui/let-else/let-else-non-diverging.stderr @@ -0,0 +1,44 @@ +error[E0308]: `else` clause of `let...else` does not diverge + --> $DIR/let-else-non-diverging.rs:12:32 + | +LL | let Some(x) = Some(1) else { Some(2) }; + | ^^^^^^^^^^^ expected `!`, found enum `Option` + | + = note: expected type `!` + found type `Option<{integer}>` + = help: try adding a diverging expression, such as `return` or `panic!(..)` + = help: ...or use `match` instead of `let...else` + +error[E0308]: `else` clause of `let...else` does not diverge + --> $DIR/let-else-non-diverging.rs:7:32 + | +LL | let Some(x) = Some(1) else { + | ________________________________^ +LL | | if 1 == 1 { +LL | | panic!(); +LL | | } +LL | | }; + | |_____^ expected `!`, found `()` + | + = note: expected type `!` + found type `()` + = help: try adding a diverging expression, such as `return` or `panic!(..)` + = help: ...or use `match` instead of `let...else` + +error[E0308]: `else` clause of `let...else` does not diverge + --> $DIR/let-else-non-diverging.rs:4:32 + | +LL | let Some(x) = Some(1) else { + | ________________________________^ +LL | | Some(2) +LL | | }; + | |_____^ expected `!`, found enum `Option` + | + = note: expected type `!` + found type `Option<{integer}>` + = help: try adding a diverging expression, such as `return` or `panic!(..)` + = help: ...or use `match` instead of `let...else` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/let-else/let-else-run-pass.rs b/src/test/ui/let-else/let-else-run-pass.rs new file mode 100644 index 0000000000000..5d96623236dab --- /dev/null +++ b/src/test/ui/let-else/let-else-run-pass.rs @@ -0,0 +1,35 @@ +// run-pass + +#![feature(let_else)] + +fn main() { + #[allow(dead_code)] + enum MyEnum { + A(String), + B { f: String }, + C, + } + // ref binding to non-copy value and or-pattern + let (MyEnum::A(ref x) | MyEnum::B { f: ref x }) = (MyEnum::B { f: String::new() }) else { + panic!(); + }; + assert_eq!(x, ""); + + // nested let-else + let mut x = 1; + loop { + let 4 = x else { + let 3 = x else { + x += 1; + continue; + }; + break; + }; + panic!(); + } + assert_eq!(x, 3); + + // else return + let Some(1) = Some(2) else { return }; + panic!(); +} diff --git a/src/test/ui/let-else/let-else-scope.rs b/src/test/ui/let-else/let-else-scope.rs new file mode 100644 index 0000000000000..f17682db4c3bd --- /dev/null +++ b/src/test/ui/let-else/let-else-scope.rs @@ -0,0 +1,7 @@ +#![feature(let_else)] + +fn main() { + let Some(x) = Some(2) else { + panic!("{}", x); //~ ERROR cannot find value `x` in this scope + }; +} diff --git a/src/test/ui/let-else/let-else-scope.stderr b/src/test/ui/let-else/let-else-scope.stderr new file mode 100644 index 0000000000000..4b3936eac4bd5 --- /dev/null +++ b/src/test/ui/let-else/let-else-scope.stderr @@ -0,0 +1,9 @@ +error[E0425]: cannot find value `x` in this scope + --> $DIR/let-else-scope.rs:5:22 + | +LL | panic!("{}", x); + | ^ not found in this scope + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0425`. diff --git a/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr b/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr index 6dfe7aad6ea64..cec6980c008c4 100644 --- a/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr +++ b/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr @@ -12,11 +12,11 @@ error: expected expression, found `]` LL | #[cfg(FALSE)] fn e() { let _ = [#[attr]]; } | ^ expected expression -error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `#` +error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `#` --> $DIR/attr-stmt-expr-attr-bad.rs:9:35 | LL | #[cfg(FALSE)] fn e() { let _ = foo#[attr](); } - | ^ expected one of 7 possible tokens + | ^ expected one of 8 possible tokens error: an inner attribute is not permitted in this context --> $DIR/attr-stmt-expr-attr-bad.rs:11:36 @@ -70,11 +70,11 @@ LL | #[cfg(FALSE)] fn e() { let _ = -#![attr] 0; } | = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them. -error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `#` +error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `#` --> $DIR/attr-stmt-expr-attr-bad.rs:23:34 | LL | #[cfg(FALSE)] fn e() { let _ = x #![attr] as Y; } - | ^ expected one of 7 possible tokens + | ^ expected one of 8 possible tokens error: an inner attribute is not permitted in this context --> $DIR/attr-stmt-expr-attr-bad.rs:25:35 @@ -372,11 +372,11 @@ error: unexpected token: `#` LL | #[cfg(FALSE)] fn e() { let _ = x.#![attr]foo(); } | ^ -error: expected one of `.`, `;`, `?`, or an operator, found `#` +error: expected one of `.`, `;`, `?`, `else`, or an operator, found `#` --> $DIR/attr-stmt-expr-attr-bad.rs:100:34 | LL | #[cfg(FALSE)] fn e() { let _ = x.#![attr]foo(); } - | ^ expected one of `.`, `;`, `?`, or an operator + | ^ expected one of `.`, `;`, `?`, `else`, or an operator error: unexpected token: `#` --> $DIR/attr-stmt-expr-attr-bad.rs:103:34 @@ -384,11 +384,11 @@ error: unexpected token: `#` LL | #[cfg(FALSE)] fn e() { let _ = x.#[attr]foo(); } | ^ -error: expected one of `.`, `;`, `?`, or an operator, found `#` +error: expected one of `.`, `;`, `?`, `else`, or an operator, found `#` --> $DIR/attr-stmt-expr-attr-bad.rs:103:34 | LL | #[cfg(FALSE)] fn e() { let _ = x.#[attr]foo(); } - | ^ expected one of `.`, `;`, `?`, or an operator + | ^ expected one of `.`, `;`, `?`, `else`, or an operator error: expected statement after outer attribute --> $DIR/attr-stmt-expr-attr-bad.rs:108:37 diff --git a/src/test/ui/parser/issue-72253.rs b/src/test/ui/parser/issue-72253.rs index 6f9af73b039e4..1446a796fa0d5 100644 --- a/src/test/ui/parser/issue-72253.rs +++ b/src/test/ui/parser/issue-72253.rs @@ -1,6 +1,6 @@ fn main() { let a = std::process::Command::new("echo") .arg("1") - ,arg("2") //~ ERROR expected one of `.`, `;`, `?`, or an operator, found `,` + ,arg("2") //~ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `,` .output(); } diff --git a/src/test/ui/parser/issue-72253.stderr b/src/test/ui/parser/issue-72253.stderr index 3819fd92a9e21..477fa09f495e2 100644 --- a/src/test/ui/parser/issue-72253.stderr +++ b/src/test/ui/parser/issue-72253.stderr @@ -1,8 +1,8 @@ -error: expected one of `.`, `;`, `?`, or an operator, found `,` +error: expected one of `.`, `;`, `?`, `else`, or an operator, found `,` --> $DIR/issue-72253.rs:4:9 | LL | .arg("1") - | - expected one of `.`, `;`, `?`, or an operator + | - expected one of `.`, `;`, `?`, `else`, or an operator LL | ,arg("2") | ^ unexpected token diff --git a/src/test/ui/parser/issue-84117.rs b/src/test/ui/parser/issue-84117.rs index 0f20073591598..919585877cf9c 100644 --- a/src/test/ui/parser/issue-84117.rs +++ b/src/test/ui/parser/issue-84117.rs @@ -2,8 +2,8 @@ fn main() { let outer_local:e_outer<&str, { let inner_local:e_inner<&str, } //~^ ERROR expected one of `>`, a const expression //~| ERROR expected one of `>`, a const expression, lifetime, or type, found `}` - //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,` - //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,` - //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,` + //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,` + //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,` + //~| ERROR expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,` } //~^ ERROR expected one of `,`, `:`, `=`, or `>`, found `}` diff --git a/src/test/ui/parser/issue-84117.stderr b/src/test/ui/parser/issue-84117.stderr index d667a4977d0d3..5b9cc53baa5eb 100644 --- a/src/test/ui/parser/issue-84117.stderr +++ b/src/test/ui/parser/issue-84117.stderr @@ -7,11 +7,11 @@ LL | let outer_local:e_outer<&str, { let inner_local:e_inner<&str, } | | help: use `=` if you meant to assign | while parsing the type for `inner_local` -error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,` +error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,` --> $DIR/issue-84117.rs:2:65 | LL | let outer_local:e_outer<&str, { let inner_local:e_inner<&str, } - | ^ expected one of 7 possible tokens + | ^ expected one of 8 possible tokens error: expected one of `,`, `:`, `=`, or `>`, found `}` --> $DIR/issue-84117.rs:8:1 @@ -33,17 +33,17 @@ LL | let outer_local:e_outer<&str, { let inner_local:e_inner<&str, } | | help: use `=` if you meant to assign | while parsing the type for `inner_local` -error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,` +error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,` --> $DIR/issue-84117.rs:2:65 | LL | let outer_local:e_outer<&str, { let inner_local:e_inner<&str, } - | ^ expected one of 7 possible tokens + | ^ expected one of 8 possible tokens -error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `,` +error: expected one of `!`, `.`, `::`, `;`, `?`, `else`, `{`, or an operator, found `,` --> $DIR/issue-84117.rs:2:33 | LL | let outer_local:e_outer<&str, { let inner_local:e_inner<&str, } - | ^ expected one of 7 possible tokens + | ^ expected one of 8 possible tokens error: aborting due to 6 previous errors diff --git a/src/test/ui/parser/macro/issue-37234.stderr b/src/test/ui/parser/macro/issue-37234.stderr index f0ec79e53574a..8d9636d401c1e 100644 --- a/src/test/ui/parser/macro/issue-37234.stderr +++ b/src/test/ui/parser/macro/issue-37234.stderr @@ -1,8 +1,8 @@ -error: expected one of `.`, `;`, `?`, or an operator, found `""` +error: expected one of `.`, `;`, `?`, `else`, or an operator, found `""` --> $DIR/issue-37234.rs:3:19 | LL | let x = 5 ""; - | ^^ expected one of `.`, `;`, `?`, or an operator + | ^^ expected one of `.`, `;`, `?`, `else`, or an operator ... LL | failed!(); | ---------- in this macro invocation diff --git a/src/test/ui/parser/missing-semicolon.rs b/src/test/ui/parser/missing-semicolon.rs index a24dfa761a60f..f68d177c01f1f 100644 --- a/src/test/ui/parser/missing-semicolon.rs +++ b/src/test/ui/parser/missing-semicolon.rs @@ -1,6 +1,6 @@ macro_rules! m { ($($e1:expr),*; $($e2:expr),*) => { - $( let x = $e1 )*; //~ ERROR expected one of `.`, `;`, `?`, or + $( let x = $e1 )*; //~ ERROR expected one of `.`, `;`, `?`, `else`, or $( println!("{}", $e2) )*; } } diff --git a/src/test/ui/parser/missing-semicolon.stderr b/src/test/ui/parser/missing-semicolon.stderr index 68f0f440c46b9..72f76b6fe3f54 100644 --- a/src/test/ui/parser/missing-semicolon.stderr +++ b/src/test/ui/parser/missing-semicolon.stderr @@ -1,8 +1,8 @@ -error: expected one of `.`, `;`, `?`, or an operator, found keyword `let` +error: expected one of `.`, `;`, `?`, `else`, or an operator, found keyword `let` --> $DIR/missing-semicolon.rs:3:12 | LL | $( let x = $e1 )*; - | ^^^ expected one of `.`, `;`, `?`, or an operator + | ^^^ expected one of `.`, `;`, `?`, `else`, or an operator ... LL | fn main() { m!(0, 0; 0, 0); } | --------------- in this macro invocation diff --git a/src/test/ui/parser/range-3.rs b/src/test/ui/parser/range-3.rs index 931839fb282d1..2c917a24e908a 100644 --- a/src/test/ui/parser/range-3.rs +++ b/src/test/ui/parser/range-3.rs @@ -2,5 +2,5 @@ pub fn main() { let r = 1..2..3; - //~^ ERROR expected one of `.`, `;`, `?`, or an operator, found `..` + //~^ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `..` } diff --git a/src/test/ui/parser/range-3.stderr b/src/test/ui/parser/range-3.stderr index f866ea59983b6..340167f1804e3 100644 --- a/src/test/ui/parser/range-3.stderr +++ b/src/test/ui/parser/range-3.stderr @@ -1,8 +1,8 @@ -error: expected one of `.`, `;`, `?`, or an operator, found `..` +error: expected one of `.`, `;`, `?`, `else`, or an operator, found `..` --> $DIR/range-3.rs:4:17 | LL | let r = 1..2..3; - | ^^ expected one of `.`, `;`, `?`, or an operator + | ^^ expected one of `.`, `;`, `?`, `else`, or an operator error: aborting due to previous error diff --git a/src/test/ui/parser/range-4.rs b/src/test/ui/parser/range-4.rs index 20af956720579..c970c96de8490 100644 --- a/src/test/ui/parser/range-4.rs +++ b/src/test/ui/parser/range-4.rs @@ -2,5 +2,5 @@ pub fn main() { let r = ..1..2; - //~^ ERROR expected one of `.`, `;`, `?`, or an operator, found `..` + //~^ ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `..` } diff --git a/src/test/ui/parser/range-4.stderr b/src/test/ui/parser/range-4.stderr index dcb85170c1d3b..720d489389bfb 100644 --- a/src/test/ui/parser/range-4.stderr +++ b/src/test/ui/parser/range-4.stderr @@ -1,8 +1,8 @@ -error: expected one of `.`, `;`, `?`, or an operator, found `..` +error: expected one of `.`, `;`, `?`, `else`, or an operator, found `..` --> $DIR/range-4.rs:4:16 | LL | let r = ..1..2; - | ^^ expected one of `.`, `;`, `?`, or an operator + | ^^ expected one of `.`, `;`, `?`, `else`, or an operator error: aborting due to previous error diff --git a/src/test/ui/pattern/usefulness/top-level-alternation.rs b/src/test/ui/pattern/usefulness/top-level-alternation.rs index 4b47b978930f3..076de846129e6 100644 --- a/src/test/ui/pattern/usefulness/top-level-alternation.rs +++ b/src/test/ui/pattern/usefulness/top-level-alternation.rs @@ -1,3 +1,5 @@ +#![feature(let_else)] + #![deny(unreachable_patterns)] fn main() { @@ -53,4 +55,5 @@ fn main() { 1..=2 => {}, //~ ERROR unreachable pattern _ => {}, } + let (0 | 0) = 0 else { return }; //~ ERROR unreachable pattern } diff --git a/src/test/ui/pattern/usefulness/top-level-alternation.stderr b/src/test/ui/pattern/usefulness/top-level-alternation.stderr index 76bc4f8d0910a..dd5936fdcc429 100644 --- a/src/test/ui/pattern/usefulness/top-level-alternation.stderr +++ b/src/test/ui/pattern/usefulness/top-level-alternation.stderr @@ -1,68 +1,74 @@ error: unreachable pattern - --> $DIR/top-level-alternation.rs:4:23 + --> $DIR/top-level-alternation.rs:6:23 | LL | while let 0..=2 | 1 = 0 {} | ^ | note: the lint level is defined here - --> $DIR/top-level-alternation.rs:1:9 + --> $DIR/top-level-alternation.rs:3:9 | LL | #![deny(unreachable_patterns)] | ^^^^^^^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:5:20 + --> $DIR/top-level-alternation.rs:7:20 | LL | if let 0..=2 | 1 = 0 {} | ^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:9:15 + --> $DIR/top-level-alternation.rs:11:15 | LL | | 0 => {} | ^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:14:15 + --> $DIR/top-level-alternation.rs:16:15 | LL | | Some(0) => {} | ^^^^^^^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:19:9 + --> $DIR/top-level-alternation.rs:21:9 | LL | (0, 0) => {} | ^^^^^^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:39:9 + --> $DIR/top-level-alternation.rs:41:9 | LL | _ => {} | ^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:43:9 + --> $DIR/top-level-alternation.rs:45:9 | LL | Some(_) => {} | ^^^^^^^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:44:9 + --> $DIR/top-level-alternation.rs:46:9 | LL | None => {} | ^^^^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:49:9 + --> $DIR/top-level-alternation.rs:51:9 | LL | None | Some(_) => {} | ^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/top-level-alternation.rs:53:9 + --> $DIR/top-level-alternation.rs:55:9 | LL | 1..=2 => {}, | ^^^^^ -error: aborting due to 10 previous errors +error: unreachable pattern + --> $DIR/top-level-alternation.rs:58:14 + | +LL | let (0 | 0) = 0 else { return }; + | ^ + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs index ac21eb5275f0f..2ffc00b449d08 100644 --- a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs +++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs @@ -316,8 +316,11 @@ impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> { impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> { fn visit_local(&mut self, local: &'tcx Local) { - if let Some(ref init) = local.init { - self.apply(|this| walk_expr(this, &**init)); + if let Some((init, els)) = &local.kind.init_else_opt() { + self.apply(|this| walk_expr(this, init)); + if let Some(els) = els { + self.apply(|this| walk_block(this, els)); + } } // add the pattern after the expression because the bindings aren't available // yet in the init diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs index 7ea07a15aea51..133f6c29f7d21 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs @@ -221,7 +221,7 @@ pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool { (Local(l), Local(r)) => { eq_pat(&l.pat, &r.pat) && both(&l.ty, &r.ty, |l, r| eq_ty(l, r)) - && eq_expr_opt(&l.init, &r.init) + && eq_local_kind(&l.kind, &r.kind) && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) }, (Item(l), Item(r)) => eq_item(l, r, eq_item_kind), @@ -234,6 +234,16 @@ pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool { } } +pub fn eq_local_kind(l: &LocalKind, r: &LocalKind) -> bool { + use LocalKind::*; + match (l, r) { + (Decl, Decl) => true, + (Init(l), Init(r)) => eq_expr(l, r), + (InitElse(li, le), InitElse(ri, re)) => eq_expr(li, ri) && eq_block(le, re), + _ => false, + } +} + pub fn eq_item(l: &Item, r: &Item, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool { eq_id(l.ident, r.ident) && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index 0542358c6e7c5..2483d0570d9ea 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -48,7 +48,7 @@ impl Rewrite for ast::Local { skip_out_of_file_lines_range!(context, self.span); - if contains_skip(&self.attrs) { + if contains_skip(&self.attrs) || matches!(self.kind, ast::LocalKind::InitElse(..)) { return None; } @@ -97,7 +97,7 @@ impl Rewrite for ast::Local { infix.push_str(&rewrite); } - if self.init.is_some() { + if self.kind.init().is_some() { infix.push_str(" ="); } @@ -106,11 +106,12 @@ impl Rewrite for ast::Local { result.push_str(&infix); - if let Some(ref ex) = self.init { + if let Some((init, _els)) = self.kind.init_else_opt() { // 1 = trailing semicolon; let nested_shape = shape.sub_width(1)?; - result = rewrite_assign_rhs(context, result, &**ex, nested_shape)?; + result = rewrite_assign_rhs(context, result, init, nested_shape)?; + // todo else } result.push(';'); diff --git a/src/tools/rustfmt/tests/source/let_else.rs b/src/tools/rustfmt/tests/source/let_else.rs new file mode 100644 index 0000000000000..a6e816fb524b7 --- /dev/null +++ b/src/tools/rustfmt/tests/source/let_else.rs @@ -0,0 +1,3 @@ +fn main() { + let Some(1) = Some(1) else { return }; +} diff --git a/src/tools/rustfmt/tests/target/let_else.rs b/src/tools/rustfmt/tests/target/let_else.rs new file mode 100644 index 0000000000000..a6e816fb524b7 --- /dev/null +++ b/src/tools/rustfmt/tests/target/let_else.rs @@ -0,0 +1,3 @@ +fn main() { + let Some(1) = Some(1) else { return }; +}