Skip to content

Commit d216ca0

Browse files
committed
Detect missing if let or let-else
During `let` binding parse error and encountering a block, detect if there is a likely missing `if` or `else`: ``` error: expected one of `.`, `;`, `?`, `else`, or an operator, found `{` --> $DIR/missing-if-let-or-let-else.rs:14:25 | LL | let Some(x) = foo() { | ^ expected one of `.`, `;`, `?`, `else`, or an operator | help: you might have meant to use `if let` | LL | if let Some(x) = foo() { | ++ help: alternatively, you might have meant to use `let else` | LL | let Some(x) = foo() else { | ++++ ```
1 parent c018ae5 commit d216ca0

File tree

4 files changed

+158
-0
lines changed

4 files changed

+158
-0
lines changed

compiler/rustc_parse/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#![feature(assert_matches)]
77
#![feature(box_patterns)]
88
#![feature(debug_closure_helpers)]
9+
#![feature(default_field_values)]
910
#![feature(if_let_guard)]
1011
#![feature(iter_intersperse)]
1112
#![recursion_limit = "256"]

compiler/rustc_parse/src/parser/stmt.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use ast::Label;
66
use rustc_ast as ast;
77
use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind};
88
use rustc_ast::util::classify::{self, TrailingBrace};
9+
use rustc_ast::visit::{Visitor, walk_expr};
910
use rustc_ast::{
1011
AttrStyle, AttrVec, Block, BlockCheckMode, DUMMY_NODE_ID, Expr, ExprKind, HasAttrs, Local,
1112
LocalKind, MacCall, MacCallStmt, MacStmtStyle, Recovered, Stmt, StmtKind,
@@ -783,6 +784,71 @@ impl<'a> Parser<'a> {
783784
Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span)))
784785
}
785786

787+
fn recover_missing_let_else(&mut self, err: &mut Diag<'_>, pat: &ast::Pat, stmt_span: Span) {
788+
if self.token.kind != token::OpenBrace {
789+
return;
790+
}
791+
match pat.kind {
792+
ast::PatKind::Ident(..) | ast::PatKind::Missing | ast::PatKind::Wild => {
793+
// Not if let or let else
794+
return;
795+
}
796+
_ => {}
797+
}
798+
let snapshot = self.create_snapshot_for_diagnostic();
799+
let block_span = self.token.span;
800+
let (if_let, let_else) = match self.parse_block() {
801+
Ok(block) => {
802+
let mut idents = vec![];
803+
pat.walk(&mut |pat: &ast::Pat| {
804+
if let ast::PatKind::Ident(_, ident, _) = pat.kind {
805+
idents.push(ident);
806+
}
807+
true
808+
});
809+
// Collect all bindings in pattern and see if they appear in the block. Likely meant
810+
// to write `if let`. See if the block has a return. Likely meant to write
811+
// `let else`.
812+
let mut visitor = IdentFinder { idents, .. };
813+
visitor.visit_block(&block);
814+
815+
(visitor.references_ident, visitor.has_return)
816+
}
817+
Err(e) => {
818+
e.cancel();
819+
self.restore_snapshot(snapshot);
820+
(false, false)
821+
}
822+
};
823+
824+
let mut alternatively = "";
825+
if if_let || !let_else {
826+
alternatively = "alternatively, ";
827+
err.span_suggestion_verbose(
828+
stmt_span.shrink_to_lo(),
829+
"you might have meant to use `if let`",
830+
"if ".to_string(),
831+
if if_let {
832+
Applicability::MachineApplicable
833+
} else {
834+
Applicability::MaybeIncorrect
835+
},
836+
);
837+
}
838+
if let_else || !if_let {
839+
err.span_suggestion_verbose(
840+
block_span.shrink_to_lo(),
841+
format!("{alternatively}you might have meant to use `let else`"),
842+
"else ".to_string(),
843+
if let_else {
844+
Applicability::MachineApplicable
845+
} else {
846+
Applicability::MaybeIncorrect
847+
},
848+
);
849+
}
850+
}
851+
786852
fn recover_missing_dot(&mut self, err: &mut Diag<'_>) {
787853
let Some((ident, _)) = self.token.ident() else {
788854
return;
@@ -977,6 +1043,7 @@ impl<'a> Parser<'a> {
9771043
self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err(
9781044
|mut e| {
9791045
self.recover_missing_dot(&mut e);
1046+
self.recover_missing_let_else(&mut e, &local.pat, stmt.span);
9801047
e
9811048
},
9821049
)?;
@@ -1065,3 +1132,30 @@ impl<'a> Parser<'a> {
10651132
self.mk_block(thin_vec![self.mk_stmt_err(span, guar)], BlockCheckMode::Default, span)
10661133
}
10671134
}
1135+
1136+
struct IdentFinder {
1137+
idents: Vec<Ident>,
1138+
/// If a block references one of the bindings introduced by the let pattern, we likely meant to
1139+
/// use `if let`.
1140+
/// This is pre-expansion, so if we encounter `let Some(x) = foo() { println!("{x}") }` we won't
1141+
/// find it.
1142+
references_ident: bool = false,
1143+
/// If a block has a `return`, then we know with high certainty that the
1144+
has_return: bool = false,
1145+
}
1146+
1147+
impl<'a> Visitor<'a> for IdentFinder {
1148+
fn visit_ident(&mut self, ident: &Ident) {
1149+
for i in &self.idents {
1150+
if ident.name == i.name {
1151+
self.references_ident = true;
1152+
}
1153+
}
1154+
}
1155+
fn visit_expr(&mut self, node: &'a Expr) {
1156+
if let ExprKind::Ret(..) = node.kind {
1157+
self.has_return = true;
1158+
}
1159+
walk_expr(self, node);
1160+
}
1161+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
fn a() {
2+
let Some(x) = foo() { //~ ERROR expected one of
3+
//~^ HELP you might have meant to use `if let`
4+
let y = x;
5+
}
6+
}
7+
fn b() {
8+
let Some(x) = foo() { //~ ERROR expected one of
9+
//~^ HELP you might have meant to use `let else`
10+
return;
11+
}
12+
}
13+
fn c() {
14+
let Some(x) = foo() { //~ ERROR expected one of
15+
//~^ HELP you might have meant to use `if let`
16+
//~| HELP alternatively, you might have meant to use `let else`
17+
// The parser check happens pre-macro-expansion, so we don't know for sure.
18+
println!("{x}");
19+
}
20+
}
21+
fn foo() -> Option<i32> {
22+
Some(42)
23+
}
24+
fn main() {}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
error: expected one of `.`, `;`, `?`, `else`, or an operator, found `{`
2+
--> $DIR/missing-if-let-or-let-else.rs:2:25
3+
|
4+
LL | let Some(x) = foo() {
5+
| ^ expected one of `.`, `;`, `?`, `else`, or an operator
6+
|
7+
help: you might have meant to use `if let`
8+
|
9+
LL | if let Some(x) = foo() {
10+
| ++
11+
12+
error: expected one of `.`, `;`, `?`, `else`, or an operator, found `{`
13+
--> $DIR/missing-if-let-or-let-else.rs:8:25
14+
|
15+
LL | let Some(x) = foo() {
16+
| ^ expected one of `.`, `;`, `?`, `else`, or an operator
17+
|
18+
help: you might have meant to use `let else`
19+
|
20+
LL | let Some(x) = foo() else {
21+
| ++++
22+
23+
error: expected one of `.`, `;`, `?`, `else`, or an operator, found `{`
24+
--> $DIR/missing-if-let-or-let-else.rs:14:25
25+
|
26+
LL | let Some(x) = foo() {
27+
| ^ expected one of `.`, `;`, `?`, `else`, or an operator
28+
|
29+
help: you might have meant to use `if let`
30+
|
31+
LL | if let Some(x) = foo() {
32+
| ++
33+
help: alternatively, you might have meant to use `let else`
34+
|
35+
LL | let Some(x) = foo() else {
36+
| ++++
37+
38+
error: aborting due to 3 previous errors
39+

0 commit comments

Comments
 (0)