Skip to content

Commit 806b443

Browse files
committed
add check for typo in let chains
1 parent 869fb46 commit 806b443

File tree

5 files changed

+165
-19
lines changed

5 files changed

+165
-19
lines changed

compiler/rustc_hir_typeck/src/errors.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@
33
use std::borrow::Cow;
44

55
use rustc_abi::ExternAbi;
6-
use rustc_ast::Label;
6+
use rustc_ast::{AssignOpKind, Label};
77
use rustc_errors::codes::*;
88
use rustc_errors::{
99
Applicability, Diag, DiagArgValue, DiagCtxtHandle, DiagSymbolList, Diagnostic,
10-
EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic,
10+
EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic, struct_span_code_err,
1111
};
1212
use rustc_hir as hir;
1313
use rustc_hir::ExprKind;
1414
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
1515
use rustc_middle::ty::{self, Ty};
1616
use rustc_span::edition::{Edition, LATEST_STABLE_EDITION};
17+
use rustc_span::source_map::Spanned;
1718
use rustc_span::{Ident, Span, Symbol};
1819

19-
use crate::fluent_generated as fluent;
20+
use crate::{FnCtxt, fluent_generated as fluent};
2021

2122
#[derive(Diagnostic)]
2223
#[diag(hir_typeck_base_expression_double_dot, code = E0797)]
@@ -1133,6 +1134,39 @@ impl<G: EmissionGuarantee> Diagnostic<'_, G> for NakedFunctionsAsmBlock {
11331134
}
11341135
}
11351136

1137+
pub(crate) fn maybe_emit_plus_equals_diagnostic<'a>(
1138+
fnctxt: &FnCtxt<'a, '_>,
1139+
assign_op: Spanned<AssignOpKind>,
1140+
lhs_expr: &hir::Expr<'_>,
1141+
) -> Result<(), Diag<'a>> {
1142+
if assign_op.node == hir::AssignOpKind::AddAssign
1143+
&& let hir::ExprKind::Binary(bin_op, left, right) = &lhs_expr.kind
1144+
&& bin_op.node == hir::BinOpKind::And
1145+
&& crate::op::contains_let_in_chain(left)
1146+
&& let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = &right.kind
1147+
&& matches!(path.res, hir::def::Res::Local(_))
1148+
{
1149+
let mut err = struct_span_code_err!(
1150+
fnctxt.dcx(),
1151+
assign_op.span,
1152+
E0368,
1153+
"binary assignment operation `+=` cannot be used in a let chain",
1154+
);
1155+
1156+
err.span_label(assign_op.span, "cannot use `+=` in a let chain");
1157+
1158+
err.span_suggestion(
1159+
assign_op.span,
1160+
"you might have meant to compare with `==` instead of assigning with `+=`",
1161+
"==",
1162+
Applicability::MaybeIncorrect,
1163+
);
1164+
1165+
return Err(err);
1166+
}
1167+
Ok(())
1168+
}
1169+
11361170
#[derive(Diagnostic)]
11371171
#[diag(hir_typeck_naked_functions_must_naked_asm, code = E0787)]
11381172
pub(crate) struct NakedFunctionsMustNakedAsm {

compiler/rustc_hir_typeck/src/expr.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12491249
return;
12501250
}
12511251

1252+
// Skip suggestion if LHS contains a let-chain at this would likely be spurious
1253+
// cc: https://github.com/rust-lang/rust/issues/147664
1254+
if crate::op::contains_let_in_chain(lhs) {
1255+
return;
1256+
}
1257+
12521258
let mut err = self.dcx().struct_span_err(op_span, "invalid left-hand side of assignment");
12531259
err.code(code);
12541260
err.span_label(lhs.span, "cannot assign to this expression");

compiler/rustc_hir_typeck/src/op.rs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use {rustc_ast as ast, rustc_hir as hir};
2020

2121
use super::FnCtxt;
2222
use super::method::MethodCallee;
23-
use crate::Expectation;
2423
use crate::method::TreatNotYetDefinedOpaques;
24+
use crate::{Expectation, errors};
2525

2626
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
2727
/// Checks a `a <op>= b`
@@ -308,23 +308,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
308308
let mut path = None;
309309
let lhs_ty_str = self.tcx.short_string(lhs_ty, &mut path);
310310
let rhs_ty_str = self.tcx.short_string(rhs_ty, &mut path);
311+
311312
let (mut err, output_def_id) = match op {
313+
// Try and detect when `+=` was incorrectly
314+
// used instead of `==` in a let-chain
312315
Op::AssignOp(assign_op) => {
313-
let s = assign_op.node.as_str();
314-
let mut err = struct_span_code_err!(
315-
self.dcx(),
316-
expr.span,
317-
E0368,
318-
"binary assignment operation `{}` cannot be applied to type `{}`",
319-
s,
320-
lhs_ty_str,
321-
);
322-
err.span_label(
323-
lhs_expr.span,
324-
format!("cannot use `{}` on type `{}`", s, lhs_ty_str),
325-
);
326-
self.note_unmet_impls_on_type(&mut err, &errors, false);
327-
(err, None)
316+
if let Err(e) =
317+
errors::maybe_emit_plus_equals_diagnostic(&self, assign_op, lhs_expr)
318+
{
319+
(e, None)
320+
} else {
321+
let s = assign_op.node.as_str();
322+
let mut err = struct_span_code_err!(
323+
self.dcx(),
324+
expr.span,
325+
E0368,
326+
"binary assignment operation `{}` cannot be applied to type `{}`",
327+
s,
328+
lhs_ty_str,
329+
);
330+
err.span_label(
331+
lhs_expr.span,
332+
format!("cannot use `{}` on type `{}`", s, lhs_ty_str),
333+
);
334+
self.note_unmet_impls_on_type(&mut err, &errors, false);
335+
(err, None)
336+
}
328337
}
329338
Op::BinOp(bin_op) => {
330339
let message = match bin_op.node {
@@ -1084,6 +1093,17 @@ fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option<hir::de
10841093
}
10851094
}
10861095

1096+
/// Check if `expr` contains a `let` or `&&`, indicating presence of a let-chain
1097+
pub(crate) fn contains_let_in_chain(expr: &hir::Expr<'_>) -> bool {
1098+
match &expr.kind {
1099+
hir::ExprKind::Let(..) => true,
1100+
hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, left, right) => {
1101+
contains_let_in_chain(left) || contains_let_in_chain(right)
1102+
}
1103+
_ => false,
1104+
}
1105+
}
1106+
10871107
// Binary operator categories. These categories summarize the behavior
10881108
// with respect to the builtin operations supported.
10891109
#[derive(Clone, Copy)]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@ edition:2024
2+
3+
fn test_where_left_is_not_let() {
4+
let mut y = 2;
5+
if let x = 1 && true && y += 2 {};
6+
//~^ ERROR expected expression, found `let` statement
7+
//~| NOTE only supported directly in conditions of `if` and `while` expressions
8+
//~| ERROR mismatched types
9+
//~| NOTE expected `bool`, found integer
10+
//~| NOTE expected because this is `bool`
11+
//~| ERROR binary assignment operation `+=` cannot be used in a let chain
12+
//~| NOTE cannot use `+=` in a let chain
13+
//~| HELP you might have meant to compare with `==` instead of assigning with `+=`
14+
}
15+
16+
fn test_where_left_is_let() {
17+
let mut y = 2;
18+
if let x = 1 && y += 2 {};
19+
//~^ ERROR expected expression, found `let` statement
20+
//~| NOTE only supported directly in conditions of `if` and `while` expressions
21+
//~| ERROR mismatched types
22+
//~| NOTE expected `bool`, found integer
23+
//~| ERROR binary assignment operation `+=` cannot be used in a let chain
24+
//~| NOTE cannot use `+=` in a let chain
25+
//~| HELP you might have meant to compare with `==` instead of assigning with `+=`
26+
}
27+
28+
fn main() {}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
error: expected expression, found `let` statement
2+
--> $DIR/let-chains-assign-add-incorrect.rs:5:8
3+
|
4+
LL | if let x = 1 && true && y += 2 {};
5+
| ^^^^^^^^^
6+
|
7+
= note: only supported directly in conditions of `if` and `while` expressions
8+
9+
error: expected expression, found `let` statement
10+
--> $DIR/let-chains-assign-add-incorrect.rs:18:8
11+
|
12+
LL | if let x = 1 && y += 2 {};
13+
| ^^^^^^^^^
14+
|
15+
= note: only supported directly in conditions of `if` and `while` expressions
16+
17+
error[E0308]: mismatched types
18+
--> $DIR/let-chains-assign-add-incorrect.rs:5:29
19+
|
20+
LL | if let x = 1 && true && y += 2 {};
21+
| ----------------- ^ expected `bool`, found integer
22+
| |
23+
| expected because this is `bool`
24+
25+
error[E0368]: binary assignment operation `+=` cannot be used in a let chain
26+
--> $DIR/let-chains-assign-add-incorrect.rs:5:31
27+
|
28+
LL | if let x = 1 && true && y += 2 {};
29+
| ^^ cannot use `+=` in a let chain
30+
|
31+
help: you might have meant to compare with `==` instead of assigning with `+=`
32+
|
33+
LL - if let x = 1 && true && y += 2 {};
34+
LL + if let x = 1 && true && y == 2 {};
35+
|
36+
37+
error[E0308]: mismatched types
38+
--> $DIR/let-chains-assign-add-incorrect.rs:18:21
39+
|
40+
LL | if let x = 1 && y += 2 {};
41+
| ^ expected `bool`, found integer
42+
43+
error[E0368]: binary assignment operation `+=` cannot be used in a let chain
44+
--> $DIR/let-chains-assign-add-incorrect.rs:18:23
45+
|
46+
LL | if let x = 1 && y += 2 {};
47+
| ^^ cannot use `+=` in a let chain
48+
|
49+
help: you might have meant to compare with `==` instead of assigning with `+=`
50+
|
51+
LL - if let x = 1 && y += 2 {};
52+
LL + if let x = 1 && y == 2 {};
53+
|
54+
55+
error: aborting due to 6 previous errors
56+
57+
Some errors have detailed explanations: E0308, E0368.
58+
For more information about an error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)