Skip to content

Commit

Permalink
fix #103587, Recover from common if let syntax mistakes/typos
Browse files Browse the repository at this point in the history
  • Loading branch information
chenyukang committed Nov 8, 2022
1 parent 57d3c58 commit 667b15b
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 4 deletions.
3 changes: 3 additions & 0 deletions compiler/rustc_error_messages/locales/en-US/parser.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ parser_if_expression_missing_condition = missing condition for `if` expression
parser_expected_expression_found_let = expected expression, found `let` statement
parser_expect_eq_instead_of_eqeq = expected `=`, found `==`
.suggestion = consider using `=` here
parser_expected_else_block = expected `{"{"}`, found {$first_tok}
.label = expected an `if` or a block after this `else`
.suggestion = add an `if` if this is the condition of a chained `else if` statement
Expand Down
74 changes: 74 additions & 0 deletions compiler/rustc_infer/src/infer/error_reporting/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ignore-tidy-filelength
//! Error Reporting Code for the inference engine
//!
//! Because of the way inference, and in particular region inference,
Expand Down Expand Up @@ -58,12 +59,14 @@ use crate::traits::{
StatementAsExpression,
};

use hir::intravisit::{walk_expr, walk_stmt};
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed, IntoDiagnosticArg};
use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::Visitor;
use rustc_hir::lang_items::LangItem;
use rustc_hir::Node;
use rustc_middle::dep_graph::DepContext;
Expand Down Expand Up @@ -2333,6 +2336,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
}
}
}
// For code `if Some(..) = expr `, the type mismatch may be expected `bool` but found `()`,
// we try to suggest to add the missing `let` for `if let Some(..) = expr`
(ty::Bool, ty::Tuple(list)) => if list.len() == 0 {
self.suggest_let_for_letchains(&mut err, &trace.cause, span);
}
_ => {}
}
}
Expand All @@ -2357,6 +2365,72 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
diag
}

/// Try to find code with pattern `if Some(..) = expr`
/// use a `visitor` to mark the `if` which its span contains given error span,
/// and then try to find a assignment in the `cond` part, which span is equal with error span
fn suggest_let_for_letchains(
&self,
err: &mut Diagnostic,
cause: &ObligationCause<'_>,
span: Span,
) {
let hir = self.tcx.hir();
let fn_hir_id = hir.get_parent_node(cause.body_id);
if let Some(node) = self.tcx.hir().find(fn_hir_id) &&
let hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(_sig, _, body_id), ..
}) = node {
let body = hir.body(*body_id);

/// Find the if expression with given span
struct IfVisitor {
pub result: bool,
pub found_if: bool,
pub err_span: Span,
}

impl<'v> Visitor<'v> for IfVisitor {
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
if self.result { return; }
match ex.kind {
hir::ExprKind::If(cond, _, _) => {
self.found_if = true;
walk_expr(self, cond);
self.found_if = false;
}
_ => walk_expr(self, ex),
}
}

fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
if let hir::StmtKind::Local(hir::Local {
span, pat: hir::Pat{..}, ty: None, init: Some(_), ..
}) = &ex.kind
&& self.found_if
&& span.eq(&self.err_span) {
self.result = true;
}
walk_stmt(self, ex);
}

fn visit_body(&mut self, body: &'v hir::Body<'v>) {
hir::intravisit::walk_body(self, body);
}
}

let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
visitor.visit_body(&body);
if visitor.result {
err.span_suggestion_verbose(
span.shrink_to_lo(),
"consider adding `let`",
"let ".to_string(),
Applicability::MachineApplicable,
);
}
}
}

fn emit_tuple_wrap_err(
&self,
err: &mut Diagnostic,
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,15 @@ pub(crate) struct ExpectedExpressionFoundLet {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(parser_expect_eq_instead_of_eqeq)]
pub(crate) struct ExpectedEqForLetExpr {
#[primary_span]
pub span: Span,
#[suggestion_verbose(applicability = "maybe-incorrect", code = "=")]
pub sugg_span: Span,
}

#[derive(Diagnostic)]
#[diag(parser_expected_else_block)]
pub(crate) struct ExpectedElseBlock {
Expand Down
16 changes: 12 additions & 4 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use crate::errors::{
ArrayBracketsInsteadOfSpaces, ArrayBracketsInsteadOfSpacesSugg, AsyncMoveOrderIncorrect,
BinaryFloatLiteralNotSupported, BracesForStructLiteral, CatchAfterTry, CommaAfterBaseStruct,
ComparisonInterpretedAsGeneric, ComparisonOrShiftInterpretedAsGenericSugg,
DoCatchSyntaxRemoved, DotDotDot, EqFieldInit, ExpectedElseBlock, ExpectedExpressionFoundLet,
FieldExpressionWithGeneric, FloatLiteralRequiresIntegerPart, FoundExprWouldBeStmt,
HexadecimalFloatLiteralNotSupported, IfExpressionMissingCondition,
DoCatchSyntaxRemoved, DotDotDot, EqFieldInit, ExpectedElseBlock, ExpectedEqForLetExpr,
ExpectedExpressionFoundLet, FieldExpressionWithGeneric, FloatLiteralRequiresIntegerPart,
FoundExprWouldBeStmt, HexadecimalFloatLiteralNotSupported, IfExpressionMissingCondition,
IfExpressionMissingThenBlock, IfExpressionMissingThenBlockSub, IntLiteralTooLarge,
InvalidBlockMacroSegment, InvalidComparisonOperator, InvalidComparisonOperatorSub,
InvalidFloatLiteralSuffix, InvalidFloatLiteralWidth, InvalidIntLiteralWidth,
Expand Down Expand Up @@ -2334,7 +2334,15 @@ impl<'a> Parser<'a> {
RecoverColon::Yes,
CommaRecoveryMode::LikelyTuple,
)?;
self.expect(&token::Eq)?;
if self.token == token::EqEq {
self.sess.emit_err(ExpectedEqForLetExpr {
span: self.token.span,
sugg_span: self.token.span,
});
self.bump();
} else {
self.expect(&token::Eq)?;
}
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
})?;
Expand Down
12 changes: 12 additions & 0 deletions src/test/ui/inference/issue-103587.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fn main() {
let x = Some(123);

if let Some(_) == x {}
//~^ ERROR expected `=`, found `==`

if Some(_) = x {}
//~^ ERROR mismatched types

if None = x { }
//~^ ERROR mismatched types
}
40 changes: 40 additions & 0 deletions src/test/ui/inference/issue-103587.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
error: expected `=`, found `==`
--> $DIR/issue-103587.rs:4:20
|
LL | if let Some(_) == x {}
| ^^
|
help: consider using `=` here
|
LL | if let Some(_) = x {}
| ~

error[E0308]: mismatched types
--> $DIR/issue-103587.rs:7:8
|
LL | if Some(_) = x {}
| ^^^^^^^^^^^ expected `bool`, found `()`
|
help: consider adding `let`
|
LL | if let Some(_) = x {}
| +++

error[E0308]: mismatched types
--> $DIR/issue-103587.rs:10:8
|
LL | if None = x { }
| ^^^^^^^^ expected `bool`, found `()`
|
help: you might have meant to use pattern matching
|
LL | if let None = x { }
| +++
help: you might have meant to compare for equality
|
LL | if None == x { }
| +

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0308`.
15 changes: 15 additions & 0 deletions src/test/ui/suggestions/if-let-typo.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,22 @@ error[E0308]: mismatched types
|
LL | if Some(x) = foo {}
| ^^^^^^^^^^^^^ expected `bool`, found `()`
|
help: consider adding `let`
|
LL | if let Some(x) = foo {}
| +++

error[E0308]: mismatched types
--> $DIR/if-let-typo.rs:6:8
|
LL | if Some(foo) = bar {}
| ^^^^^^^^^^^^^^^ expected `bool`, found `()`
|
help: consider adding `let`
|
LL | if let Some(foo) = bar {}
| +++

error[E0308]: mismatched types
--> $DIR/if-let-typo.rs:7:8
Expand All @@ -51,6 +61,11 @@ error[E0308]: mismatched types
|
LL | if Some(3) = foo {}
| ^^^^^^^^^^^^^ expected `bool`, found `()`
|
help: consider adding `let`
|
LL | if let Some(3) = foo {}
| +++

error: aborting due to 7 previous errors

Expand Down

0 comments on commit 667b15b

Please sign in to comment.