From 806b4436014fa3ab6a4c6876ccbe939ec24d927e Mon Sep 17 00:00:00 2001 From: Kivooeo Date: Tue, 21 Oct 2025 21:42:24 +0000 Subject: [PATCH] add check for typo in let chains --- compiler/rustc_hir_typeck/src/errors.rs | 40 ++++++++++++- compiler/rustc_hir_typeck/src/expr.rs | 6 ++ compiler/rustc_hir_typeck/src/op.rs | 52 ++++++++++++----- .../parser/let-chains-assign-add-incorrect.rs | 28 +++++++++ .../let-chains-assign-add-incorrect.stderr | 58 +++++++++++++++++++ 5 files changed, 165 insertions(+), 19 deletions(-) create mode 100644 tests/ui/parser/let-chains-assign-add-incorrect.rs create mode 100644 tests/ui/parser/let-chains-assign-add-incorrect.stderr diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index d15d092b7d3da..142495c7042c1 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -3,20 +3,21 @@ use std::borrow::Cow; use rustc_abi::ExternAbi; -use rustc_ast::Label; +use rustc_ast::{AssignOpKind, Label}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, DiagSymbolList, Diagnostic, - EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic, + EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic, struct_span_code_err, }; use rustc_hir as hir; use rustc_hir::ExprKind; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::{self, Ty}; use rustc_span::edition::{Edition, LATEST_STABLE_EDITION}; +use rustc_span::source_map::Spanned; use rustc_span::{Ident, Span, Symbol}; -use crate::fluent_generated as fluent; +use crate::{FnCtxt, fluent_generated as fluent}; #[derive(Diagnostic)] #[diag(hir_typeck_base_expression_double_dot, code = E0797)] @@ -1133,6 +1134,39 @@ impl Diagnostic<'_, G> for NakedFunctionsAsmBlock { } } +pub(crate) fn maybe_emit_plus_equals_diagnostic<'a>( + fnctxt: &FnCtxt<'a, '_>, + assign_op: Spanned, + lhs_expr: &hir::Expr<'_>, +) -> Result<(), Diag<'a>> { + if assign_op.node == hir::AssignOpKind::AddAssign + && let hir::ExprKind::Binary(bin_op, left, right) = &lhs_expr.kind + && bin_op.node == hir::BinOpKind::And + && crate::op::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!( + fnctxt.dcx(), + assign_op.span, + E0368, + "binary assignment operation `+=` cannot be used 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, + ); + + return Err(err); + } + Ok(()) +} + #[derive(Diagnostic)] #[diag(hir_typeck_naked_functions_must_naked_asm, code = E0787)] pub(crate) struct NakedFunctionsMustNakedAsm { diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 2605aa18b91ee..1009c79278549 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; } + // Skip suggestion if LHS contains a let-chain at this would likely be spurious + // cc: https://github.com/rust-lang/rust/issues/147664 + if crate::op::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..a175b3557c47a 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -20,8 +20,8 @@ use {rustc_ast as ast, rustc_hir as hir}; use super::FnCtxt; use super::method::MethodCallee; -use crate::Expectation; use crate::method::TreatNotYetDefinedOpaques; +use crate::{Expectation, errors}; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Checks a `a = b` @@ -308,23 +308,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut path = None; 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 { + // Try and detect when `+=` was incorrectly + // used instead of `==` in a let-chain Op::AssignOp(assign_op) => { - let s = assign_op.node.as_str(); - let mut err = struct_span_code_err!( - self.dcx(), - expr.span, - E0368, - "binary assignment operation `{}` cannot be applied to type `{}`", - s, - lhs_ty_str, - ); - err.span_label( - lhs_expr.span, - format!("cannot use `{}` on type `{}`", s, lhs_ty_str), - ); - self.note_unmet_impls_on_type(&mut err, &errors, false); - (err, None) + if let Err(e) = + errors::maybe_emit_plus_equals_diagnostic(&self, assign_op, lhs_expr) + { + (e, None) + } else { + let s = assign_op.node.as_str(); + let mut err = struct_span_code_err!( + self.dcx(), + expr.span, + E0368, + "binary assignment operation `{}` cannot be applied to type `{}`", + s, + lhs_ty_str, + ); + err.span_label( + lhs_expr.span, + format!("cannot use `{}` on type `{}`", s, lhs_ty_str), + ); + self.note_unmet_impls_on_type(&mut err, &errors, false); + (err, None) + } } Op::BinOp(bin_op) => { let message = match bin_op.node { @@ -1084,6 +1093,17 @@ fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option) -> bool { + match &expr.kind { + hir::ExprKind::Let(..) => true, + hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, left, right) => { + contains_let_in_chain(left) || contains_let_in_chain(right) + } + _ => false, + } +} + // Binary operator categories. These categories summarize the behavior // with respect to the builtin operations supported. #[derive(Clone, Copy)] 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..f3b497f5c1832 --- /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 used 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 used 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..38811e0733a5a --- /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 used 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 used 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`.