From 806b4436014fa3ab6a4c6876ccbe939ec24d927e Mon Sep 17 00:00:00 2001 From: Kivooeo Date: Tue, 21 Oct 2025 21:42:24 +0000 Subject: [PATCH 1/2] 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`. From d472d91a396866d58b23f84cfa555028d83eccf9 Mon Sep 17 00:00:00 2001 From: Kivooeo Date: Fri, 31 Oct 2025 18:24:10 +0000 Subject: [PATCH 2/2] address review --- compiler/rustc_hir_typeck/src/errors.rs | 11 +++-- compiler/rustc_hir_typeck/src/expr.rs | 3 +- .../let-chains-assign-add-incorrect.fixed | 36 ++++++++++++++ .../parser/let-chains-assign-add-incorrect.rs | 18 +++++-- .../let-chains-assign-add-incorrect.stderr | 47 ++++++++++--------- 5 files changed, 83 insertions(+), 32 deletions(-) create mode 100644 tests/ui/parser/let-chains-assign-add-incorrect.fixed diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 142495c7042c1..0861474e5b1fd 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -7,7 +7,7 @@ use rustc_ast::{AssignOpKind, Label}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, DiagSymbolList, Diagnostic, - EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic, struct_span_code_err, + EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic, }; use rustc_hir as hir; use rustc_hir::ExprKind; @@ -1146,13 +1146,16 @@ pub(crate) fn maybe_emit_plus_equals_diagnostic<'a>( && 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(), + let mut err = fnctxt.dcx().struct_span_err( assign_op.span, - E0368, "binary assignment operation `+=` cannot be used in a let chain", ); + err.span_label( + lhs_expr.span, + "you are add-assigning the right-hand side expression to the result of this let-chain", + ); + err.span_label(assign_op.span, "cannot use `+=` in a let chain"); err.span_suggestion( diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 1009c79278549..65a21cc210aca 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -48,6 +48,7 @@ use crate::errors::{ NoFieldOnVariant, ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive, TypeMismatchFruTypo, YieldExprOutsideOfCoroutine, }; +use crate::op::contains_let_in_chain; use crate::{ BreakableCtxt, CoroutineTypes, Diverges, FnCtxt, GatherLocalsVisitor, Needs, TupleArgumentsFlag, cast, fatally_break_rust, report_unexpected_variant_res, type_error_struct, @@ -1251,7 +1252,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // 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) { + if contains_let_in_chain(lhs) { return; } diff --git a/tests/ui/parser/let-chains-assign-add-incorrect.fixed b/tests/ui/parser/let-chains-assign-add-incorrect.fixed new file mode 100644 index 0000000000000..b1abce8bd2df5 --- /dev/null +++ b/tests/ui/parser/let-chains-assign-add-incorrect.fixed @@ -0,0 +1,36 @@ +//@ edition:2024 +//@ run-rustfix + +#![allow(irrefutable_let_patterns)] + +fn test_where_left_is_not_let() { + let y = 2; + if let _ = 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 you are add-assigning the right-hand side expression to the result of this let-chain + //~| 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 y = 2; + if let _ = 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 + //~| NOTE you are add-assigning the right-hand side expression to the result of this let-chain + //~| 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() { + test_where_left_is_let(); + test_where_left_is_not_let() +} diff --git a/tests/ui/parser/let-chains-assign-add-incorrect.rs b/tests/ui/parser/let-chains-assign-add-incorrect.rs index f3b497f5c1832..3b2e5e93d91ac 100644 --- a/tests/ui/parser/let-chains-assign-add-incorrect.rs +++ b/tests/ui/parser/let-chains-assign-add-incorrect.rs @@ -1,12 +1,16 @@ //@ edition:2024 +//@ run-rustfix + +#![allow(irrefutable_let_patterns)] fn test_where_left_is_not_let() { - let mut y = 2; - if let x = 1 && true && y += 2 {}; + let y = 2; + if let _ = 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 you are add-assigning the right-hand side expression to the result of this let-chain //~| NOTE expected because this is `bool` //~| ERROR binary assignment operation `+=` cannot be used in a let chain //~| NOTE cannot use `+=` in a let chain @@ -14,15 +18,19 @@ fn test_where_left_is_not_let() { } fn test_where_left_is_let() { - let mut y = 2; - if let x = 1 && y += 2 {}; + let y = 2; + if let _ = 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 + //~| NOTE you are add-assigning the right-hand side expression to the result of this let-chain //~| 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() {} +fn main() { + test_where_left_is_let(); + test_where_left_is_not_let() +} diff --git a/tests/ui/parser/let-chains-assign-add-incorrect.stderr b/tests/ui/parser/let-chains-assign-add-incorrect.stderr index 38811e0733a5a..dea8e6a1982b1 100644 --- a/tests/ui/parser/let-chains-assign-add-incorrect.stderr +++ b/tests/ui/parser/let-chains-assign-add-incorrect.stderr @@ -1,58 +1,61 @@ error: expected expression, found `let` statement - --> $DIR/let-chains-assign-add-incorrect.rs:5:8 + --> $DIR/let-chains-assign-add-incorrect.rs:8:8 | -LL | if let x = 1 && true && y += 2 {}; +LL | if let _ = 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 + --> $DIR/let-chains-assign-add-incorrect.rs:22:8 | -LL | if let x = 1 && y += 2 {}; +LL | if let _ = 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 + --> $DIR/let-chains-assign-add-incorrect.rs:8:29 | -LL | if let x = 1 && true && y += 2 {}; +LL | if let _ = 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 +error: binary assignment operation `+=` cannot be used in a let chain + --> $DIR/let-chains-assign-add-incorrect.rs:8:31 | -LL | if let x = 1 && true && y += 2 {}; - | ^^ cannot use `+=` in a let chain +LL | if let _ = 1 && true && y += 2 {}; + | ---------------------- ^^ cannot use `+=` in a let chain + | | + | you are add-assigning the right-hand side expression to the result of this 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 {}; +LL - if let _ = 1 && true && y += 2 {}; +LL + if let _ = 1 && true && y == 2 {}; | error[E0308]: mismatched types - --> $DIR/let-chains-assign-add-incorrect.rs:18:21 + --> $DIR/let-chains-assign-add-incorrect.rs:22:21 | -LL | if let x = 1 && y += 2 {}; +LL | if let _ = 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 +error: binary assignment operation `+=` cannot be used in a let chain + --> $DIR/let-chains-assign-add-incorrect.rs:22:23 | -LL | if let x = 1 && y += 2 {}; - | ^^ cannot use `+=` in a let chain +LL | if let _ = 1 && y += 2 {}; + | -------------- ^^ cannot use `+=` in a let chain + | | + | you are add-assigning the right-hand side expression to the result of this 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 {}; +LL - if let _ = 1 && y += 2 {}; +LL + if let _ = 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`. +For more information about this error, try `rustc --explain E0308`.