diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 2605aa18b91ee..73bab376ee91d 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1249,6 +1249,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } + // ignore suggestion to not make it broad in case of unallowed operations in let chains + // cc: https://github.com/rust-lang/rust/issues/147664 + if FnCtxt::contains_let_in_chain(lhs) { + return; + } + let mut err = self.dcx().struct_span_err(op_span, "invalid left-hand side of assignment"); err.code(code); err.span_label(lhs.span, "cannot assign to this expression"); diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index d18aed00a6f6d..591a98774c734 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -197,6 +197,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + pub(crate) fn contains_let_in_chain(expr: &hir::Expr<'_>) -> bool { + match &expr.kind { + hir::ExprKind::Let(..) => true, + hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, left, right) => { + Self::contains_let_in_chain(left) || Self::contains_let_in_chain(right) + } + _ => false, + } + } + fn check_overloaded_binop( &self, expr: &'tcx hir::Expr<'tcx>, @@ -309,6 +319,37 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let lhs_ty_str = self.tcx.short_string(lhs_ty, &mut path); let rhs_ty_str = self.tcx.short_string(rhs_ty, &mut path); let (mut err, output_def_id) = match op { + // this branch here is need to predict typo in let chains where + // user may write `+=` instead of `==`, which is the same button + // on most of keyboards + // it recursively get through let chain via `contains_let_in_chain` + Op::AssignOp(assign_op) + if assign_op.node == hir::AssignOpKind::AddAssign + && let hir::ExprKind::Binary(bin_op, left, right) = &lhs_expr.kind + && bin_op.node == hir::BinOpKind::And + && Self::contains_let_in_chain(left) + && let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = + &right.kind + && matches!(path.res, hir::def::Res::Local(_)) => + { + let mut err = struct_span_code_err!( + self.dcx(), + assign_op.span, + E0368, + "binary assignment operation `+=` cannot be applied in a let chain", + ); + + err.span_label(assign_op.span, "cannot use `+=` in a let chain"); + + err.span_suggestion( + assign_op.span, + "you might have meant to compare with `==` instead of assigning with `+=`", + "==", + Applicability::MaybeIncorrect, + ); + + (err, None) + } Op::AssignOp(assign_op) => { let s = assign_op.node.as_str(); let mut err = struct_span_code_err!( diff --git a/tests/ui/parser/let-chains-assign-add-incorrect.rs b/tests/ui/parser/let-chains-assign-add-incorrect.rs new file mode 100644 index 0000000000000..e2b957b671b16 --- /dev/null +++ b/tests/ui/parser/let-chains-assign-add-incorrect.rs @@ -0,0 +1,28 @@ +//@ edition:2024 + +fn test_where_left_is_not_let() { + let mut y = 2; + if let x = 1 && true && y += 2 {}; + //~^ ERROR expected expression, found `let` statement + //~| NOTE only supported directly in conditions of `if` and `while` expressions + //~| ERROR mismatched types + //~| NOTE expected `bool`, found integer + //~| NOTE expected because this is `bool` + //~| ERROR binary assignment operation `+=` cannot be applied in a let chain + //~| NOTE cannot use `+=` in a let chain + //~| HELP you might have meant to compare with `==` instead of assigning with `+=` +} + +fn test_where_left_is_let() { + let mut y = 2; + if let x = 1 && y += 2 {}; + //~^ ERROR expected expression, found `let` statement + //~| NOTE only supported directly in conditions of `if` and `while` expressions + //~| ERROR mismatched types + //~| NOTE expected `bool`, found integer + //~| ERROR binary assignment operation `+=` cannot be applied in a let chain + //~| NOTE cannot use `+=` in a let chain + //~| HELP you might have meant to compare with `==` instead of assigning with `+=` +} + +fn main() {} diff --git a/tests/ui/parser/let-chains-assign-add-incorrect.stderr b/tests/ui/parser/let-chains-assign-add-incorrect.stderr new file mode 100644 index 0000000000000..5cd5db7665b4b --- /dev/null +++ b/tests/ui/parser/let-chains-assign-add-incorrect.stderr @@ -0,0 +1,58 @@ +error: expected expression, found `let` statement + --> $DIR/let-chains-assign-add-incorrect.rs:5:8 + | +LL | if let x = 1 && true && y += 2 {}; + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions + +error: expected expression, found `let` statement + --> $DIR/let-chains-assign-add-incorrect.rs:18:8 + | +LL | if let x = 1 && y += 2 {}; + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions + +error[E0308]: mismatched types + --> $DIR/let-chains-assign-add-incorrect.rs:5:29 + | +LL | if let x = 1 && true && y += 2 {}; + | ----------------- ^ expected `bool`, found integer + | | + | expected because this is `bool` + +error[E0368]: binary assignment operation `+=` cannot be applied in a let chain + --> $DIR/let-chains-assign-add-incorrect.rs:5:31 + | +LL | if let x = 1 && true && y += 2 {}; + | ^^ cannot use `+=` in a let chain + | +help: you might have meant to compare with `==` instead of assigning with `+=` + | +LL - if let x = 1 && true && y += 2 {}; +LL + if let x = 1 && true && y == 2 {}; + | + +error[E0308]: mismatched types + --> $DIR/let-chains-assign-add-incorrect.rs:18:21 + | +LL | if let x = 1 && y += 2 {}; + | ^ expected `bool`, found integer + +error[E0368]: binary assignment operation `+=` cannot be applied in a let chain + --> $DIR/let-chains-assign-add-incorrect.rs:18:23 + | +LL | if let x = 1 && y += 2 {}; + | ^^ cannot use `+=` in a let chain + | +help: you might have meant to compare with `==` instead of assigning with `+=` + | +LL - if let x = 1 && y += 2 {}; +LL + if let x = 1 && y == 2 {}; + | + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0308, E0368. +For more information about an error, try `rustc --explain E0308`.