Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recover from common if let syntax mistakes/typos #103636

Merged
merged 3 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_error_messages/locales/en-US/infer.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,4 @@ infer_msl_introduces_static = introduces a `'static` lifetime requirement
infer_msl_unmet_req = because this has an unmet lifetime requirement
infer_msl_trait_note = this has an implicit `'static` lifetime requirement
infer_msl_trait_sugg = consider relaxing the implicit `'static` requirement
infer_suggest_add_let_for_letchains = consider adding `let`
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
12 changes: 12 additions & 0 deletions compiler/rustc_infer/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ pub enum SourceKindMultiSuggestion<'a> {
},
}

#[derive(Subdiagnostic)]
#[suggestion(
infer_suggest_add_let_for_letchains,
style = "verbose",
applicability = "machine-applicable",
code = "let "
)]
pub(crate) struct SuggAddLetForLetChains {
#[primary_span]
pub span: Span,
}

impl<'a> SourceKindMultiSuggestion<'a> {
pub fn new_fully_qualified(
span: Span,
Expand Down
70 changes: 70 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,15 @@ use crate::traits::{
StatementAsExpression,
};

use crate::errors::SuggAddLetForLetChains;
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 +2337,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 +2366,67 @@ 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(
chenyukang marked this conversation as resolved.
Show resolved Hide resolved
&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.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()});
}
}
}

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(applicability = "maybe-incorrect", code = "=", style = "verbose")]
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
5 changes: 5 additions & 0 deletions src/test/ui/did_you_mean/issue-103909.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ error[E0308]: mismatched types
|
LL | if Err(err) = File::open("hello.txt") {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found `()`
|
help: consider adding `let`
|
LL | if let Err(err) = File::open("hello.txt") {
| +++

error: aborting due to 2 previous errors

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
5 changes: 5 additions & 0 deletions src/tools/clippy/tests/ui/crashes/ice-6250.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ error[E0308]: mismatched types
|
LL | Some(reference) = cache.data.get(key) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found `()`
|
help: consider adding `let`
|
LL | let Some(reference) = cache.data.get(key) {
| +++

error: aborting due to 3 previous errors

Expand Down