Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 64a99db

Browse files
committed
Auto merge of #145582 - estebank:issue-107806, r=chenyukang
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 { | ++++ ``` Fix #107806.
2 parents cd60c60 + 3af81cf commit 64a99db

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-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: 96 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,100 @@ 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+
810+
struct IdentFinder {
811+
idents: Vec<Ident>,
812+
/// If a block references one of the bindings introduced by the let pattern,
813+
/// we likely meant to use `if let`.
814+
/// This is pre-expansion, so if we encounter
815+
/// `let Some(x) = foo() { println!("{x}") }` we won't find it.
816+
references_ident: bool = false,
817+
/// If a block has a `return`, then we know with high certainty that it was
818+
/// meant to be let-else.
819+
has_return: bool = false,
820+
}
821+
822+
impl<'a> Visitor<'a> for IdentFinder {
823+
fn visit_ident(&mut self, ident: &Ident) {
824+
for i in &self.idents {
825+
if ident.name == i.name {
826+
self.references_ident = true;
827+
}
828+
}
829+
}
830+
fn visit_expr(&mut self, node: &'a Expr) {
831+
if let ExprKind::Ret(..) = node.kind {
832+
self.has_return = true;
833+
}
834+
walk_expr(self, node);
835+
}
836+
}
837+
838+
// Collect all bindings in pattern and see if they appear in the block. Likely meant
839+
// to write `if let`. See if the block has a return. Likely meant to write
840+
// `let else`.
841+
let mut visitor = IdentFinder { idents, .. };
842+
visitor.visit_block(&block);
843+
844+
(visitor.references_ident, visitor.has_return)
845+
}
846+
Err(e) => {
847+
e.cancel();
848+
self.restore_snapshot(snapshot);
849+
(false, false)
850+
}
851+
};
852+
853+
let mut alternatively = "";
854+
if if_let || !let_else {
855+
alternatively = "alternatively, ";
856+
err.span_suggestion_verbose(
857+
stmt_span.shrink_to_lo(),
858+
"you might have meant to use `if let`",
859+
"if ".to_string(),
860+
if if_let {
861+
Applicability::MachineApplicable
862+
} else {
863+
Applicability::MaybeIncorrect
864+
},
865+
);
866+
}
867+
if let_else || !if_let {
868+
err.span_suggestion_verbose(
869+
block_span.shrink_to_lo(),
870+
format!("{alternatively}you might have meant to use `let else`"),
871+
"else ".to_string(),
872+
if let_else {
873+
Applicability::MachineApplicable
874+
} else {
875+
Applicability::MaybeIncorrect
876+
},
877+
);
878+
}
879+
}
880+
786881
fn recover_missing_dot(&mut self, err: &mut Diag<'_>) {
787882
let Some((ident, _)) = self.token.ident() else {
788883
return;
@@ -977,6 +1072,7 @@ impl<'a> Parser<'a> {
9771072
self.check_mistyped_turbofish_with_multiple_type_params(e, expr).map_err(
9781073
|mut e| {
9791074
self.recover_missing_dot(&mut e);
1075+
self.recover_missing_let_else(&mut e, &local.pat, stmt.span);
9801076
e
9811077
},
9821078
)?;
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)

AltStyle によって変換されたページ (->オリジナル) /