Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions compiler/rustc_hir_typeck/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -1133,6 +1134,39 @@ impl<G: EmissionGuarantee> Diagnostic<'_, G> for NakedFunctionsAsmBlock {
}
}

pub(crate) fn maybe_emit_plus_equals_diagnostic<'a>(
fnctxt: &FnCtxt<'a, '_>,
assign_op: Spanned<AssignOpKind>,
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 {
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, do we prefer to use import or this explicit path are also fine?

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");
Expand Down
52 changes: 36 additions & 16 deletions compiler/rustc_hir_typeck/src/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <op>= b`
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1084,6 +1093,17 @@ fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option<hir::de
}
}

/// Check if `expr` contains a `let` or `&&`, indicating presence of a let-chain
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) => {
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)]
Expand Down
28 changes: 28 additions & 0 deletions tests/ui/parser/let-chains-assign-add-incorrect.rs
Original file line number Diff line number Diff line change
@@ -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() {}
58 changes: 58 additions & 0 deletions tests/ui/parser/let-chains-assign-add-incorrect.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading