Rollup merge of #118191 - estebank:let-chain-typo, r=compiler-errors
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:
commit
aab61d0b9a
@ -492,9 +492,13 @@ parse_match_arm_body_without_braces = `match` arm body without braces
|
|||||||
} with a body
|
} with a body
|
||||||
.suggestion_use_comma_not_semicolon = replace `;` with `,` to end a `match` arm expression
|
.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`
|
parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of `fn`
|
||||||
.suggestion = replace `fn` with `impl` here
|
.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 =
|
parse_maybe_recover_from_bad_qpath_stage_2 =
|
||||||
missing angle brackets in associated item path
|
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
|
.suggestion = types that don't start with an identifier need to be surrounded with angle brackets in qualified paths
|
||||||
|
@ -415,6 +415,32 @@ pub(crate) struct ExpectedExpressionFoundLet {
|
|||||||
pub span: Span,
|
pub span: Span,
|
||||||
#[subdiagnostic]
|
#[subdiagnostic]
|
||||||
pub reason: ForbiddenLetReason,
|
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)]
|
#[derive(Diagnostic)]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// ignore-tidy-filelength
|
||||||
use super::diagnostics::SnapshotParser;
|
use super::diagnostics::SnapshotParser;
|
||||||
use super::pat::{CommaRecoveryMode, Expected, RecoverColon, RecoverComma};
|
use super::pat::{CommaRecoveryMode, Expected, RecoverColon, RecoverComma};
|
||||||
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
|
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
|
||||||
@ -2477,7 +2478,7 @@ impl<'a> Parser<'a> {
|
|||||||
let mut cond =
|
let mut cond =
|
||||||
self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, None)?;
|
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 {
|
if let ExprKind::Let(_, _, _, None) = cond.kind {
|
||||||
// Remove the last feature gating of a `let` expression since it's stable.
|
// 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 {
|
let err = errors::ExpectedExpressionFoundLet {
|
||||||
span: self.token.span,
|
span: self.token.span,
|
||||||
reason: ForbiddenLetReason::OtherForbidden,
|
reason: ForbiddenLetReason::OtherForbidden,
|
||||||
|
missing_let: None,
|
||||||
|
comparison: None,
|
||||||
};
|
};
|
||||||
if self.prev_token.kind == token::BinOp(token::Or) {
|
if self.prev_token.kind == token::BinOp(token::Or) {
|
||||||
// This was part of a closure, the that part of the parser recover.
|
// 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 if_span = this.prev_token.span;
|
||||||
let mut cond = this.parse_match_guard_condition()?;
|
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);
|
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
|
||||||
if has_let_expr {
|
if has_let_expr {
|
||||||
@ -3552,6 +3555,14 @@ pub(crate) enum ForbiddenLetReason {
|
|||||||
struct CondChecker<'a> {
|
struct CondChecker<'a> {
|
||||||
parser: &'a Parser<'a>,
|
parser: &'a Parser<'a>,
|
||||||
forbid_let_reason: Option<ForbiddenLetReason>,
|
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<'_> {
|
impl MutVisitor for CondChecker<'_> {
|
||||||
@ -3562,11 +3573,13 @@ impl MutVisitor for CondChecker<'_> {
|
|||||||
match e.kind {
|
match e.kind {
|
||||||
ExprKind::Let(_, _, _, ref mut is_recovered @ None) => {
|
ExprKind::Let(_, _, _, ref mut is_recovered @ None) => {
|
||||||
if let Some(reason) = self.forbid_let_reason {
|
if let Some(reason) = self.forbid_let_reason {
|
||||||
*is_recovered = Some(
|
*is_recovered =
|
||||||
self.parser
|
Some(self.parser.sess.emit_err(errors::ExpectedExpressionFoundLet {
|
||||||
.sess
|
span,
|
||||||
.emit_err(errors::ExpectedExpressionFoundLet { span, reason }),
|
reason,
|
||||||
);
|
missing_let: self.missing_let,
|
||||||
|
comparison: self.comparison,
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
self.parser.sess.gated_spans.gate(sym::let_chains, span);
|
self.parser.sess.gated_spans.gate(sym::let_chains, span);
|
||||||
}
|
}
|
||||||
@ -3590,9 +3603,28 @@ impl MutVisitor for CondChecker<'_> {
|
|||||||
noop_visit_expr(e, self);
|
noop_visit_expr(e, self);
|
||||||
self.forbid_let_reason = forbid_let_reason;
|
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::Unary(_, _)
|
||||||
| ExprKind::Await(_, _)
|
| ExprKind::Await(_, _)
|
||||||
| ExprKind::Assign(_, _, _)
|
|
||||||
| ExprKind::AssignOp(_, _, _)
|
| ExprKind::AssignOp(_, _, _)
|
||||||
| ExprKind::Range(_, _, _)
|
| ExprKind::Range(_, _, _)
|
||||||
| ExprKind::Try(_)
|
| ExprKind::Try(_)
|
||||||
|
@ -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() {
|
fn a() {
|
||||||
if let x = 1 && i = 2 {}
|
if let x = 1 && i = 2 {}
|
||||||
//~^ ERROR cannot find value `i` in this scope
|
//~^ ERROR cannot find value `i` in this scope
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
error: expected expression, found `let` statement
|
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 {}
|
LL | if let x = 1 && i = 2 {}
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
|
|
||||||
= note: only supported directly in conditions of `if` and `while` expressions
|
= 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
|
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 {}
|
LL | if let x = 1 && i = 2 {}
|
||||||
| ^ not found in this scope
|
| ^ not found in this scope
|
||||||
|
|
||||||
error[E0425]: cannot find value `i` 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() {
|
LL | fn a() {
|
||||||
| ------ similarly named function `a` defined here
|
| ------ similarly named function `a` defined here
|
||||||
@ -22,7 +30,7 @@ LL | if (i + j) = i {}
|
|||||||
| ^ help: a function with a similar name exists: `a`
|
| ^ help: a function with a similar name exists: `a`
|
||||||
|
|
||||||
error[E0425]: cannot find value `j` in this scope
|
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() {
|
LL | fn a() {
|
||||||
| ------ similarly named function `a` defined here
|
| ------ similarly named function `a` defined here
|
||||||
@ -31,7 +39,7 @@ LL | if (i + j) = i {}
|
|||||||
| ^ help: a function with a similar name exists: `a`
|
| ^ help: a function with a similar name exists: `a`
|
||||||
|
|
||||||
error[E0425]: cannot find value `i` in this scope
|
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() {
|
LL | fn a() {
|
||||||
| ------ similarly named function `a` defined here
|
| ------ similarly named function `a` defined here
|
||||||
@ -40,7 +48,7 @@ LL | if (i + j) = i {}
|
|||||||
| ^ help: a function with a similar name exists: `a`
|
| ^ help: a function with a similar name exists: `a`
|
||||||
|
|
||||||
error[E0425]: cannot find value `x` in this scope
|
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() {
|
LL | fn a() {
|
||||||
| ------ similarly named function `a` defined here
|
| ------ similarly named function `a` defined here
|
||||||
@ -49,7 +57,7 @@ LL | if x[0] = 1 {}
|
|||||||
| ^ help: a function with a similar name exists: `a`
|
| ^ help: a function with a similar name exists: `a`
|
||||||
|
|
||||||
error[E0308]: mismatched types
|
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 {}
|
LL | if let x = 1 && i = 2 {}
|
||||||
| ^^^^^^^^^^^^^^^^^^ expected `bool`, found `()`
|
| ^^^^^^^^^^^^^^^^^^ expected `bool`, found `()`
|
||||||
|
@ -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
|
= 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
|
error: expected expression, found `let` statement
|
||||||
--> $DIR/invalid-let-in-a-valid-let-context.rs:24:23
|
--> $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
|
= 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
|
error: expected expression, found `let` statement
|
||||||
--> $DIR/invalid-let-in-a-valid-let-context.rs:42:21
|
--> $DIR/invalid-let-in-a-valid-let-context.rs:42:21
|
||||||
|
Loading…
x
Reference in New Issue
Block a user