Skip to content

Commit 667b15b

Browse files
committed
fix rust-lang#103587, Recover from common if let syntax mistakes/typos
1 parent 57d3c58 commit 667b15b

File tree

7 files changed

+165
-4
lines changed

7 files changed

+165
-4
lines changed

compiler/rustc_error_messages/locales/en-US/parser.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ parser_if_expression_missing_condition = missing condition for `if` expression
125125
126126
parser_expected_expression_found_let = expected expression, found `let` statement
127127
128+
parser_expect_eq_instead_of_eqeq = expected `=`, found `==`
129+
.suggestion = consider using `=` here
130+
128131
parser_expected_else_block = expected `{"{"}`, found {$first_tok}
129132
.label = expected an `if` or a block after this `else`
130133
.suggestion = add an `if` if this is the condition of a chained `else if` statement

compiler/rustc_infer/src/infer/error_reporting/mod.rs

+74
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-filelength
12
//! Error Reporting Code for the inference engine
23
//!
34
//! Because of the way inference, and in particular region inference,
@@ -58,12 +59,14 @@ use crate::traits::{
5859
StatementAsExpression,
5960
};
6061

62+
use hir::intravisit::{walk_expr, walk_stmt};
6163
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
6264
use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed, IntoDiagnosticArg};
6365
use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan};
6466
use rustc_hir as hir;
6567
use rustc_hir::def::DefKind;
6668
use rustc_hir::def_id::{DefId, LocalDefId};
69+
use rustc_hir::intravisit::Visitor;
6770
use rustc_hir::lang_items::LangItem;
6871
use rustc_hir::Node;
6972
use rustc_middle::dep_graph::DepContext;
@@ -2333,6 +2336,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
23332336
}
23342337
}
23352338
}
2339+
// For code `if Some(..) = expr `, the type mismatch may be expected `bool` but found `()`,
2340+
// we try to suggest to add the missing `let` for `if let Some(..) = expr`
2341+
(ty::Bool, ty::Tuple(list)) => if list.len() == 0 {
2342+
self.suggest_let_for_letchains(&mut err, &trace.cause, span);
2343+
}
23362344
_ => {}
23372345
}
23382346
}
@@ -2357,6 +2365,72 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
23572365
diag
23582366
}
23592367

2368+
/// Try to find code with pattern `if Some(..) = expr`
2369+
/// use a `visitor` to mark the `if` which its span contains given error span,
2370+
/// and then try to find a assignment in the `cond` part, which span is equal with error span
2371+
fn suggest_let_for_letchains(
2372+
&self,
2373+
err: &mut Diagnostic,
2374+
cause: &ObligationCause<'_>,
2375+
span: Span,
2376+
) {
2377+
let hir = self.tcx.hir();
2378+
let fn_hir_id = hir.get_parent_node(cause.body_id);
2379+
if let Some(node) = self.tcx.hir().find(fn_hir_id) &&
2380+
let hir::Node::Item(hir::Item {
2381+
kind: hir::ItemKind::Fn(_sig, _, body_id), ..
2382+
}) = node {
2383+
let body = hir.body(*body_id);
2384+
2385+
/// Find the if expression with given span
2386+
struct IfVisitor {
2387+
pub result: bool,
2388+
pub found_if: bool,
2389+
pub err_span: Span,
2390+
}
2391+
2392+
impl<'v> Visitor<'v> for IfVisitor {
2393+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
2394+
if self.result { return; }
2395+
match ex.kind {
2396+
hir::ExprKind::If(cond, _, _) => {
2397+
self.found_if = true;
2398+
walk_expr(self, cond);
2399+
self.found_if = false;
2400+
}
2401+
_ => walk_expr(self, ex),
2402+
}
2403+
}
2404+
2405+
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
2406+
if let hir::StmtKind::Local(hir::Local {
2407+
span, pat: hir::Pat{..}, ty: None, init: Some(_), ..
2408+
}) = &ex.kind
2409+
&& self.found_if
2410+
&& span.eq(&self.err_span) {
2411+
self.result = true;
2412+
}
2413+
walk_stmt(self, ex);
2414+
}
2415+
2416+
fn visit_body(&mut self, body: &'v hir::Body<'v>) {
2417+
hir::intravisit::walk_body(self, body);
2418+
}
2419+
}
2420+
2421+
let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
2422+
visitor.visit_body(&body);
2423+
if visitor.result {
2424+
err.span_suggestion_verbose(
2425+
span.shrink_to_lo(),
2426+
"consider adding `let`",
2427+
"let ".to_string(),
2428+
Applicability::MachineApplicable,
2429+
);
2430+
}
2431+
}
2432+
}
2433+
23602434
fn emit_tuple_wrap_err(
23612435
&self,
23622436
err: &mut Diagnostic,

compiler/rustc_parse/src/errors.rs

+9
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,15 @@ pub(crate) struct ExpectedExpressionFoundLet {
420420
pub span: Span,
421421
}
422422

423+
#[derive(Diagnostic)]
424+
#[diag(parser_expect_eq_instead_of_eqeq)]
425+
pub(crate) struct ExpectedEqForLetExpr {
426+
#[primary_span]
427+
pub span: Span,
428+
#[suggestion_verbose(applicability = "maybe-incorrect", code = "=")]
429+
pub sugg_span: Span,
430+
}
431+
423432
#[derive(Diagnostic)]
424433
#[diag(parser_expected_else_block)]
425434
pub(crate) struct ExpectedElseBlock {

compiler/rustc_parse/src/parser/expr.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use crate::errors::{
99
ArrayBracketsInsteadOfSpaces, ArrayBracketsInsteadOfSpacesSugg, AsyncMoveOrderIncorrect,
1010
BinaryFloatLiteralNotSupported, BracesForStructLiteral, CatchAfterTry, CommaAfterBaseStruct,
1111
ComparisonInterpretedAsGeneric, ComparisonOrShiftInterpretedAsGenericSugg,
12-
DoCatchSyntaxRemoved, DotDotDot, EqFieldInit, ExpectedElseBlock, ExpectedExpressionFoundLet,
13-
FieldExpressionWithGeneric, FloatLiteralRequiresIntegerPart, FoundExprWouldBeStmt,
14-
HexadecimalFloatLiteralNotSupported, IfExpressionMissingCondition,
12+
DoCatchSyntaxRemoved, DotDotDot, EqFieldInit, ExpectedElseBlock, ExpectedEqForLetExpr,
13+
ExpectedExpressionFoundLet, FieldExpressionWithGeneric, FloatLiteralRequiresIntegerPart,
14+
FoundExprWouldBeStmt, HexadecimalFloatLiteralNotSupported, IfExpressionMissingCondition,
1515
IfExpressionMissingThenBlock, IfExpressionMissingThenBlockSub, IntLiteralTooLarge,
1616
InvalidBlockMacroSegment, InvalidComparisonOperator, InvalidComparisonOperatorSub,
1717
InvalidFloatLiteralSuffix, InvalidFloatLiteralWidth, InvalidIntLiteralWidth,
@@ -2334,7 +2334,15 @@ impl<'a> Parser<'a> {
23342334
RecoverColon::Yes,
23352335
CommaRecoveryMode::LikelyTuple,
23362336
)?;
2337-
self.expect(&token::Eq)?;
2337+
if self.token == token::EqEq {
2338+
self.sess.emit_err(ExpectedEqForLetExpr {
2339+
span: self.token.span,
2340+
sugg_span: self.token.span,
2341+
});
2342+
self.bump();
2343+
} else {
2344+
self.expect(&token::Eq)?;
2345+
}
23382346
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
23392347
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
23402348
})?;

src/test/ui/inference/issue-103587.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
fn main() {
2+
let x = Some(123);
3+
4+
if let Some(_) == x {}
5+
//~^ ERROR expected `=`, found `==`
6+
7+
if Some(_) = x {}
8+
//~^ ERROR mismatched types
9+
10+
if None = x { }
11+
//~^ ERROR mismatched types
12+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: expected `=`, found `==`
2+
--> $DIR/issue-103587.rs:4:20
3+
|
4+
LL | if let Some(_) == x {}
5+
| ^^
6+
|
7+
help: consider using `=` here
8+
|
9+
LL | if let Some(_) = x {}
10+
| ~
11+
12+
error[E0308]: mismatched types
13+
--> $DIR/issue-103587.rs:7:8
14+
|
15+
LL | if Some(_) = x {}
16+
| ^^^^^^^^^^^ expected `bool`, found `()`
17+
|
18+
help: consider adding `let`
19+
|
20+
LL | if let Some(_) = x {}
21+
| +++
22+
23+
error[E0308]: mismatched types
24+
--> $DIR/issue-103587.rs:10:8
25+
|
26+
LL | if None = x { }
27+
| ^^^^^^^^ expected `bool`, found `()`
28+
|
29+
help: you might have meant to use pattern matching
30+
|
31+
LL | if let None = x { }
32+
| +++
33+
help: you might have meant to compare for equality
34+
|
35+
LL | if None == x { }
36+
| +
37+
38+
error: aborting due to 3 previous errors
39+
40+
For more information about this error, try `rustc --explain E0308`.

src/test/ui/suggestions/if-let-typo.stderr

+15
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,22 @@ error[E0308]: mismatched types
2525
|
2626
LL | if Some(x) = foo {}
2727
| ^^^^^^^^^^^^^ expected `bool`, found `()`
28+
|
29+
help: consider adding `let`
30+
|
31+
LL | if let Some(x) = foo {}
32+
| +++
2833

2934
error[E0308]: mismatched types
3035
--> $DIR/if-let-typo.rs:6:8
3136
|
3237
LL | if Some(foo) = bar {}
3338
| ^^^^^^^^^^^^^^^ expected `bool`, found `()`
39+
|
40+
help: consider adding `let`
41+
|
42+
LL | if let Some(foo) = bar {}
43+
| +++
3444

3545
error[E0308]: mismatched types
3646
--> $DIR/if-let-typo.rs:7:8
@@ -51,6 +61,11 @@ error[E0308]: mismatched types
5161
|
5262
LL | if Some(3) = foo {}
5363
| ^^^^^^^^^^^^^ expected `bool`, found `()`
64+
|
65+
help: consider adding `let`
66+
|
67+
LL | if let Some(3) = foo {}
68+
| +++
5469

5570
error: aborting due to 7 previous errors
5671

0 commit comments

Comments
 (0)