diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index c0336cd57603c..288e128a0bc72 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -4690,3 +4690,15 @@ pub(crate) struct SuggestIntroduceTypeParameter { pub span: Span, pub parameters: String, } + +#[derive(Subdiagnostic)] +#[suggestion( + "you might have meant to write a diverging block on a refutable `let` statement by using `let-else` + for more information, visit ", + code = " else ", + applicability = "maybe-incorrect" +)] +pub(crate) struct MissingElseInLet { + #[primary_span] + pub span: Span, +} diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index e899bf1627681..51dc1aa1f9018 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -3865,7 +3865,7 @@ impl<'a> Parser<'a> { let mut base = ast::StructRest::None; let mut recovered_async = None; let in_if_guard = self.restrictions.contains(Restrictions::IN_IF_GUARD); - + let in_let = self.restrictions.contains(Restrictions::IN_LET); let async_block_err = |e: &mut Diag<'_>, span: Span| { diagnostics::AsyncBlockIn2015 { span }.add_to_diag(e); diagnostics::HelpUseLatestEdition::new().add_to_diag(e); @@ -3954,6 +3954,19 @@ impl<'a> Parser<'a> { return Err(e); } + if in_let { + let mut snapshot = self.create_snapshot_for_diagnostic(); + let might_be_stmt = snapshot.token.is_keyword(kw::Return); + snapshot.recover_stmt(); + snapshot.bump(); + if might_be_stmt && snapshot.eat(exp!(Semi)) { + e.subdiagnostic(diagnostics::MissingElseInLet { + span: pth.span.shrink_to_hi(), + }); + self.restore_snapshot(snapshot); + return Err(e); + } + } let guar = e.emit(); if pth == kw::Async { recovered_async = Some(guar); diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index ecd78fef6e142..7aa2368c99f27 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -125,6 +125,9 @@ bitflags::bitflags! { /// expression, but halts parsing the expression when reaching certain /// tokens like `=`. const IS_PAT = 1 << 5; + /// Used to detect a missing `else` in a let statement. + /// e.g. let Some(foo) = bar{return;}; + const IN_LET = 1 << 6; } } diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 27a047f598513..70da778133a18 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -3,13 +3,12 @@ use std::mem; use std::ops::Bound; use ast::Label; -use rustc_ast as ast; use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind}; use rustc_ast::util::classify::{self, TrailingBrace}; use rustc_ast::visit::{Visitor, walk_expr}; use rustc_ast::{ - AttrStyle, AttrVec, Block, BlockCheckMode, DUMMY_NODE_ID, Expr, ExprKind, HasAttrs, Local, - LocalKind, MacCall, MacCallStmt, MacStmtStyle, Recovered, Stmt, StmtKind, + self as ast, AttrStyle, AttrVec, Block, BlockCheckMode, DUMMY_NODE_ID, Expr, ExprKind, + HasAttrs, Local, LocalKind, MacCall, MacCallStmt, MacStmtStyle, Recovered, Stmt, StmtKind, }; use rustc_errors::{Applicability, Diag, PResult}; use rustc_span::{BytePos, ErrorGuaranteed, Ident, Span, kw, sym}; @@ -494,7 +493,13 @@ impl<'a> Parser<'a> { _ => self.eat(exp!(Eq)), }; - Ok(if eq_consumed || eq_optional { Some(self.parse_expr()?) } else { None }) + Ok(if eq_consumed || eq_optional { + self.current_closure.take(); + let attrs = self.parse_outer_attributes()?; + Some(self.parse_expr_res(Restrictions::IN_LET, attrs).map(|res| res.0)?) + } else { + None + }) } /// Parses a block. No inner attributes are allowed. diff --git a/tests/ui/parser/detect-missing-else-in-let-1.rs b/tests/ui/parser/detect-missing-else-in-let-1.rs new file mode 100644 index 0000000000000..9f9d11e558d4c --- /dev/null +++ b/tests/ui/parser/detect-missing-else-in-let-1.rs @@ -0,0 +1,8 @@ +// detect missing else in let statement. (issue #135857) +fn main() { + let foo = Some(1); + let Some(a) = foo{return;}; + //~^ HELP you might have meant to write a diverging block + //~| HELP escape `return` to use it as an identifier + //~| ERROR expected identifier, found keyword `return` +} diff --git a/tests/ui/parser/detect-missing-else-in-let-1.stderr b/tests/ui/parser/detect-missing-else-in-let-1.stderr new file mode 100644 index 0000000000000..2236bb1671464 --- /dev/null +++ b/tests/ui/parser/detect-missing-else-in-let-1.stderr @@ -0,0 +1,20 @@ +error: expected identifier, found keyword `return` + --> $DIR/detect-missing-else-in-let-1.rs:4:23 + | +LL | let Some(a) = foo{return;}; + | --- ^^^^^^ expected identifier, found keyword + | | + | while parsing this struct + | +help: escape `return` to use it as an identifier + | +LL | let Some(a) = foo{r#return;}; + | ++ +help: you might have meant to write a diverging block on a refutable `let` statement by using `let-else` + for more information, visit + | +LL | let Some(a) = foo else {return;}; + | ++++ + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/detect-missing-else-in-let-2.rs b/tests/ui/parser/detect-missing-else-in-let-2.rs new file mode 100644 index 0000000000000..87a5a750bf9f5 --- /dev/null +++ b/tests/ui/parser/detect-missing-else-in-let-2.rs @@ -0,0 +1,10 @@ +// the compiler currently will suggest the user to add `else` in this case +// it shouldn't because `bar` is an irrefutable pattern, the else block would never be valid/useful +// it is currently unmitigated +fn main(){ + let foo = 12; + let bar = foo{return;}; + //~^ HELP you might have meant to write a diverging block + //~| HELP escape `return` to use it as an identifier + //~| ERROR expected identifier, found keyword `return` +} diff --git a/tests/ui/parser/detect-missing-else-in-let-2.stderr b/tests/ui/parser/detect-missing-else-in-let-2.stderr new file mode 100644 index 0000000000000..5f21c9fe5fa83 --- /dev/null +++ b/tests/ui/parser/detect-missing-else-in-let-2.stderr @@ -0,0 +1,20 @@ +error: expected identifier, found keyword `return` + --> $DIR/detect-missing-else-in-let-2.rs:6:19 + | +LL | let bar = foo{return;}; + | --- ^^^^^^ expected identifier, found keyword + | | + | while parsing this struct + | +help: escape `return` to use it as an identifier + | +LL | let bar = foo{r#return;}; + | ++ +help: you might have meant to write a diverging block on a refutable `let` statement by using `let-else` + for more information, visit + | +LL | let bar = foo else {return;}; + | ++++ + +error: aborting due to 1 previous error +