1
Fork 0

Auto merge of #87688 - camsteffen:let-else, r=cjgillot

Introduce `let...else`

Tracking issue: #87335

The trickiest part for me was enforcing the diverging else block with clear diagnostics. Perhaps the obvious solution is to expand to `let _: ! = ..`, but I decided against this because, when a "mismatched type" error is found in typeck, there is no way to trace where in the HIR the expected type originated, AFAICT. In order to pass down this information, I believe we should introduce `Expectation::LetElseNever(HirId)` or maybe add `HirId` to `Expectation::HasType`, but I left that as a future enhancement. For now, I simply assert that the block is `!` with a custom `ObligationCauseCode`, and I think this is clear enough, at least to start. The downside here is that the error points at the entire block rather than the specific expression with the wrong type. I left a todo to this effect.

Overall, I believe this PR is feature-complete with regard to the RFC.
This commit is contained in:
bors 2021-09-01 01:02:42 +00:00
commit c2a408840a
59 changed files with 901 additions and 232 deletions

View file

@ -1005,13 +1005,42 @@ pub struct Local {
pub id: NodeId,
pub pat: P<Pat>,
pub ty: Option<P<Ty>>,
/// Initializer expression to set the value, if any.
pub init: Option<P<Expr>>,
pub kind: LocalKind,
pub span: Span,
pub attrs: AttrVec,
pub tokens: Option<LazyTokenStream>,
}
#[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<Expr>),
/// Local declaration with an initializer and an `else` clause.
/// Example: `let Some(x) = y else { return };`
InitElse(P<Expr>, P<Block>),
}
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

View file

@ -571,11 +571,20 @@ pub fn noop_visit_parenthesized_parameter_data<T: MutVisitor>(
}
pub fn noop_visit_local<T: MutVisitor>(local: &mut P<Local>, 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);

View file

@ -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,
}
}
}

View file

@ -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) {

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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) => {

View file

@ -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<ast::Expr>) -> 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,

View file

@ -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,

View file

@ -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
// -------------------------------------------------------------------------

View file

@ -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"),

View file

@ -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<UnusedDelimsCtx> 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<BytePos>,
);
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)

View file

@ -305,6 +305,9 @@ pub enum ObligationCauseCode<'tcx> {
/// Intrinsic has wrong type
IntrinsicType,
/// A let else block does not diverge
LetElse,
/// Method receiver
MethodReceiver,

View file

@ -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
}
}

View file

@ -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<bar, baz>;`. Try to recover.
match &mut local.init {
Some(ref mut expr) => {
match &mut local.kind {
LocalKind::Init(expr) | LocalKind::InitElse(expr, _) => {
self.check_mistyped_turbofish_with_multiple_type_params(e, expr)?;
// We found `foo<bar, baz>`, have we fully recovered?
self.expect_semi()?;
}
None => return Err(e),
LocalKind::Decl => return Err(e),
}
eat_semi = false;
}

View file

@ -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);

View file

@ -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`",
}
}
}

View file

@ -744,6 +744,7 @@ symbols! {
le,
len,
let_chains,
let_else,
lhs,
lib,
libc,

View file

@ -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() {

View file

@ -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 =

View file

@ -31,11 +31,11 @@ help: use `::<...>` instead of `<...>` to specify type or const arguments
LL | (0..13).collect::<Vec<i32>();
| ++
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<i128, i128>::new();
| ^ expected one of 7 possible tokens
| ^ expected one of 8 possible tokens
|
help: use `::<...>` instead of `<...>` to specify type or const arguments
|

View file

@ -0,0 +1,5 @@
fn main() {
let Some(x) = Some(1) else { //~ ERROR `let...else` statements are unstable
return;
};
}

View file

@ -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 <https://github.com/rust-lang/rust/issues/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`.

View file

@ -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`
}

View file

@ -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`
}

View file

@ -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

View file

@ -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>() -> T {
unimplemented!();
}

View file

@ -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>() -> T {
unimplemented!();
}

View file

@ -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

View file

@ -0,0 +1,14 @@
#![feature(let_else)]
#![deny(unused_variables)]
fn main() {
// type annotation, attributes
#[allow(unused_variables)]
let Some(_): Option<u32> = Some(Default::default()) else {
let x = 1; // OK
return;
};
let x = 1; //~ ERROR unused variable: `x`
}

View file

@ -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

View file

@ -0,0 +1,7 @@
// check-pass
#![feature(let_else)]
fn main() {
let x = 1 else { return }; //~ WARN irrefutable `let...else` pattern
}

View file

@ -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

View file

@ -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 `}`
}

View file

@ -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

View file

@ -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
}

View file

@ -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`.

View file

@ -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!();
}

View file

@ -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
};
}

View file

@ -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`.

View file

@ -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

View file

@ -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();
}

View file

@ -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

View file

@ -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 `}`

View file

@ -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

View file

@ -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

View file

@ -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) )*;
}
}

View file

@ -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

View file

@ -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 `..`
}

View file

@ -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

View file

@ -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 `..`
}

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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<K>(l: &Item<K>, r: &Item<K>, 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))

View file

@ -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(';');

View file

@ -0,0 +1,3 @@
fn main() {
let Some(1) = Some(1) else { return };
}

View file

@ -0,0 +1,3 @@
fn main() {
let Some(1) = Some(1) else { return };
}