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
41 changes: 39 additions & 2 deletions compiler/rustc_hir_typeck/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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,
Expand All @@ -14,9 +14,10 @@ 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,42 @@ 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 = fnctxt.dcx().struct_span_err(
assign_op.span,
"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(
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
7 changes: 7 additions & 0 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1249,6 +1250,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 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");
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
36 changes: 36 additions & 0 deletions tests/ui/parser/let-chains-assign-add-incorrect.fixed
Original file line number Diff line number Diff line change
@@ -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()
}
36 changes: 36 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,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()
}
61 changes: 61 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,61 @@
error: expected expression, found `let` statement
--> $DIR/let-chains-assign-add-incorrect.rs:8:8
|
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:22:8
|
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:8:29
|
LL | if let _ = 1 && true && y += 2 {};
| ----------------- ^ expected `bool`, found integer
| |
| expected because this is `bool`

error: binary assignment operation `+=` cannot be used in a let chain
--> $DIR/let-chains-assign-add-incorrect.rs:8:31
|
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 _ = 1 && true && y += 2 {};
LL + if let _ = 1 && true && y == 2 {};
|

error[E0308]: mismatched types
--> $DIR/let-chains-assign-add-incorrect.rs:22:21
|
LL | if let _ = 1 && y += 2 {};
| ^ expected `bool`, found integer

error: binary assignment operation `+=` cannot be used in a let chain
--> $DIR/let-chains-assign-add-incorrect.rs:22:23
|
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 _ = 1 && y += 2 {};
LL + if let _ = 1 && y == 2 {};
|

error: aborting due to 6 previous errors

For more information about this error, try `rustc --explain E0308`.
Loading