Suggest `let` or `==` on typo'd let-chain

When encountering a bare assignment in a let-chain, suggest turning the
assignment into a `let` expression or an equality check.

```
error: expected expression, found `let` statement
  --> $DIR/bad-if-let-suggestion.rs:5:8
   |
LL |     if let x = 1 && i = 2 {}
   |        ^^^^^^^^^
   |
   = note: only supported directly in conditions of `if` and `while` expressions
help: you might have meant to continue the let-chain
   |
LL |     if let x = 1 && let i = 2 {}
   |                     +++
help: you might have meant to compare for equality
   |
LL |     if let x = 1 && i == 2 {}
   |                        +
```
This commit is contained in:
Esteban Küber 2023-11-22 23:27:15 +00:00
parent aa330518f4
commit 55e4e3e393
6 changed files with 93 additions and 18 deletions

View File

@ -492,9 +492,13 @@ parse_match_arm_body_without_braces = `match` arm body without braces
} with a body
.suggestion_use_comma_not_semicolon = replace `;` with `,` to end a `match` arm expression
parse_maybe_comparison = you might have meant to compare for equality
parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of `fn`
.suggestion = replace `fn` with `impl` here
parse_maybe_missing_let = you might have meant to continue the let-chain
parse_maybe_recover_from_bad_qpath_stage_2 =
missing angle brackets in associated item path
.suggestion = types that don't start with an identifier need to be surrounded with angle brackets in qualified paths

View File

@ -407,6 +407,32 @@ pub(crate) struct ExpectedExpressionFoundLet {
pub span: Span,
#[subdiagnostic]
pub reason: ForbiddenLetReason,
#[subdiagnostic]
pub missing_let: Option<MaybeMissingLet>,
#[subdiagnostic]
pub comparison: Option<MaybeComparison>,
}
#[derive(Subdiagnostic, Clone, Copy)]
#[multipart_suggestion(
parse_maybe_missing_let,
applicability = "maybe-incorrect",
style = "verbose"
)]
pub(crate) struct MaybeMissingLet {
#[suggestion_part(code = "let ")]
pub span: Span,
}
#[derive(Subdiagnostic, Clone, Copy)]
#[multipart_suggestion(
parse_maybe_comparison,
applicability = "maybe-incorrect",
style = "verbose"
)]
pub(crate) struct MaybeComparison {
#[suggestion_part(code = "=")]
pub span: Span,
}
#[derive(Diagnostic)]

View File

@ -1,3 +1,4 @@
// ignore-tidy-filelength
use super::diagnostics::SnapshotParser;
use super::pat::{CommaRecoveryMode, Expected, RecoverColon, RecoverComma};
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
@ -2477,7 +2478,7 @@ impl<'a> Parser<'a> {
let mut cond =
self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, None)?;
CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond);
CondChecker::new(self).visit_expr(&mut cond);
if let ExprKind::Let(_, _, _, None) = cond.kind {
// Remove the last feature gating of a `let` expression since it's stable.
@ -2493,6 +2494,8 @@ impl<'a> Parser<'a> {
let err = errors::ExpectedExpressionFoundLet {
span: self.token.span,
reason: ForbiddenLetReason::OtherForbidden,
missing_let: None,
comparison: None,
};
if self.prev_token.kind == token::BinOp(token::Or) {
// This was part of a closure, the that part of the parser recover.
@ -2876,7 +2879,7 @@ impl<'a> Parser<'a> {
let if_span = this.prev_token.span;
let mut cond = this.parse_match_guard_condition()?;
CondChecker { parser: this, forbid_let_reason: None }.visit_expr(&mut cond);
CondChecker::new(this).visit_expr(&mut cond);
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
if has_let_expr {
@ -3552,6 +3555,14 @@ pub(crate) enum ForbiddenLetReason {
struct CondChecker<'a> {
parser: &'a Parser<'a>,
forbid_let_reason: Option<ForbiddenLetReason>,
missing_let: Option<errors::MaybeMissingLet>,
comparison: Option<errors::MaybeComparison>,
}
impl<'a> CondChecker<'a> {
fn new(parser: &'a Parser<'a>) -> Self {
CondChecker { parser, forbid_let_reason: None, missing_let: None, comparison: None }
}
}
impl MutVisitor for CondChecker<'_> {
@ -3562,11 +3573,13 @@ impl MutVisitor for CondChecker<'_> {
match e.kind {
ExprKind::Let(_, _, _, ref mut is_recovered @ None) => {
if let Some(reason) = self.forbid_let_reason {
*is_recovered = Some(
self.parser
.sess
.emit_err(errors::ExpectedExpressionFoundLet { span, reason }),
);
*is_recovered =
Some(self.parser.sess.emit_err(errors::ExpectedExpressionFoundLet {
span,
reason,
missing_let: self.missing_let,
comparison: self.comparison,
}));
} else {
self.parser.sess.gated_spans.gate(sym::let_chains, span);
}
@ -3590,9 +3603,28 @@ impl MutVisitor for CondChecker<'_> {
noop_visit_expr(e, self);
self.forbid_let_reason = forbid_let_reason;
}
ExprKind::Assign(ref lhs, _, span) => {
let forbid_let_reason = self.forbid_let_reason;
self.forbid_let_reason = Some(OtherForbidden);
let missing_let = self.missing_let;
if let ExprKind::Binary(_, _, rhs) = &lhs.kind
&& let ExprKind::Path(_, _)
| ExprKind::Struct(_)
| ExprKind::Call(_, _)
| ExprKind::Array(_) = rhs.kind
{
self.missing_let =
Some(errors::MaybeMissingLet { span: rhs.span.shrink_to_lo() });
}
let comparison = self.comparison;
self.comparison = Some(errors::MaybeComparison { span: span.shrink_to_hi() });
noop_visit_expr(e, self);
self.forbid_let_reason = forbid_let_reason;
self.missing_let = missing_let;
self.comparison = comparison;
}
ExprKind::Unary(_, _)
| ExprKind::Await(_, _)
| ExprKind::Assign(_, _, _)
| ExprKind::AssignOp(_, _, _)
| ExprKind::Range(_, _, _)
| ExprKind::Try(_)

View File

@ -1,6 +1,3 @@
// FIXME(compiler-errors): This really should suggest `let` on the RHS of the
// `&&` operator, but that's kinda hard to do because of precedence.
// Instead, for now we just make sure not to suggest `if let let`.
fn a() {
if let x = 1 && i = 2 {}
//~^ ERROR cannot find value `i` in this scope

View File

@ -1,19 +1,27 @@
error: expected expression, found `let` statement
--> $DIR/bad-if-let-suggestion.rs:5:8
--> $DIR/bad-if-let-suggestion.rs:2:8
|
LL | if let x = 1 && i = 2 {}
| ^^^^^^^^^
|
= note: only supported directly in conditions of `if` and `while` expressions
help: you might have meant to continue the let-chain
|
LL | if let x = 1 && let i = 2 {}
| +++
help: you might have meant to compare for equality
|
LL | if let x = 1 && i == 2 {}
| +
error[E0425]: cannot find value `i` in this scope
--> $DIR/bad-if-let-suggestion.rs:5:21
--> $DIR/bad-if-let-suggestion.rs:2:21
|
LL | if let x = 1 && i = 2 {}
| ^ not found in this scope
error[E0425]: cannot find value `i` in this scope
--> $DIR/bad-if-let-suggestion.rs:12:9
--> $DIR/bad-if-let-suggestion.rs:9:9
|
LL | fn a() {
| ------ similarly named function `a` defined here
@ -22,7 +30,7 @@ LL | if (i + j) = i {}
| ^ help: a function with a similar name exists: `a`
error[E0425]: cannot find value `j` in this scope
--> $DIR/bad-if-let-suggestion.rs:12:13
--> $DIR/bad-if-let-suggestion.rs:9:13
|
LL | fn a() {
| ------ similarly named function `a` defined here
@ -31,7 +39,7 @@ LL | if (i + j) = i {}
| ^ help: a function with a similar name exists: `a`
error[E0425]: cannot find value `i` in this scope
--> $DIR/bad-if-let-suggestion.rs:12:18
--> $DIR/bad-if-let-suggestion.rs:9:18
|
LL | fn a() {
| ------ similarly named function `a` defined here
@ -40,7 +48,7 @@ LL | if (i + j) = i {}
| ^ help: a function with a similar name exists: `a`
error[E0425]: cannot find value `x` in this scope
--> $DIR/bad-if-let-suggestion.rs:19:8
--> $DIR/bad-if-let-suggestion.rs:16:8
|
LL | fn a() {
| ------ similarly named function `a` defined here
@ -49,7 +57,7 @@ LL | if x[0] = 1 {}
| ^ help: a function with a similar name exists: `a`
error[E0308]: mismatched types
--> $DIR/bad-if-let-suggestion.rs:5:8
--> $DIR/bad-if-let-suggestion.rs:2:8
|
LL | if let x = 1 && i = 2 {}
| ^^^^^^^^^^^^^^^^^^ expected `bool`, found `()`

View File

@ -29,6 +29,10 @@ LL | if let Some(elem) = _opt && [1, 2, 3][let _ = &&let Some(x) = Some(
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: only supported directly in conditions of `if` and `while` expressions
help: you might have meant to compare for equality
|
LL | if let Some(elem) = _opt && [1, 2, 3][let _ = &&let Some(x) = Some(42)] == 1 {
| +
error: expected expression, found `let` statement
--> $DIR/invalid-let-in-a-valid-let-context.rs:24:23
@ -53,6 +57,10 @@ LL | if let Some(elem) = _opt && [1, 2, 3][let _ = ()] = 1 {
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: only supported directly in conditions of `if` and `while` expressions
help: you might have meant to compare for equality
|
LL | if let Some(elem) = _opt && [1, 2, 3][let _ = ()] == 1 {
| +
error: expected expression, found `let` statement
--> $DIR/invalid-let-in-a-valid-let-context.rs:42:21